From ca5057ea04df852dc3cefa8e3af7521acbe545d8 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 3 Oct 2022 18:55:43 +0200 Subject: [PATCH] Add eval and argument implementation Signed-off-by: trivernis --- Cargo.toml | 2 +- src/argument.rs | 35 +++++++++++++++++++++ src/context/mod.rs | 47 ++++++++++++++++++++++++++-- src/error.rs | 3 ++ src/into_expression.rs | 70 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 ++++- tests/test_eval.rs | 33 +++++++++++++++++--- 7 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 src/argument.rs create mode 100644 src/into_expression.rs diff --git a/Cargo.toml b/Cargo.toml index 03f8e2f..061b43e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,5 @@ nu-engine = "0.69.1" nu-parser = "0.69.1" nu-protocol = "0.69.1" paste = "1.0.9" -rusty-value = { version = "0.2.0", features = ["derive"] } +rusty-value = { version = "0.2.1", features = ["derive"] } thiserror = "1.0.37" diff --git a/src/argument.rs b/src/argument.rs new file mode 100644 index 0000000..11128d5 --- /dev/null +++ b/src/argument.rs @@ -0,0 +1,35 @@ +use nu_protocol::{ast::Expression, Span, Spanned}; + +use crate::{into_expression::IntoExpression, NewEmpty}; + +/// A struct representing the argument to a function +pub enum Argument { + Named((String, Option)), + Positional(Expression), +} + +impl Argument { + /// Creates a new named argument. No value means passing the argument as a flag (like --verbose) + pub fn named(name: S, value: Option) -> Self { + Self::Named((name.to_string(), value.map(|v| v.into_expression()))) + } + + /// Creates a new positional argument + pub fn positional(value: E) -> Self { + Self::Positional(value.into_expression()) + } + + pub(crate) fn into_nu_argument(self) -> nu_protocol::ast::Argument { + match self { + Argument::Named((name, value)) => nu_protocol::ast::Argument::Named(( + Spanned { + item: name, + span: Span::empty(), + }, + None, + value, + )), + Argument::Positional(value) => nu_protocol::ast::Argument::Positional(value), + } + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index b8746c0..091052e 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,14 +4,16 @@ mod command_group_config; pub use builder::*; pub use command_group_config::CommandGroupConfig; use nu_protocol::{ - ast::Block, + ast::{Block, Call}, engine::{EngineState, Stack}, - PipelineData, + PipelineData, Span, }; use crate::{ + argument::Argument, error::{CrateError, CrateResult}, utils::parse_nu_script, + NewEmpty, }; /// Represents the evaluation context of nu scripts and commands @@ -52,6 +54,47 @@ impl Context { self.eval_block(&block, input) } + /// Returns if the given function exists in the context + pub fn has_fn>(&mut self, name: S) -> bool { + self.engine_state + .find_decl(name.as_ref().as_bytes(), &vec![]) + .is_some() + } + + /// Calls a function by the given name + /// Errs if the function doesn't exist + pub fn call_fn, I: IntoIterator>( + &mut self, + name: S, + args: I, + ) -> CrateResult { + let args = args + .into_iter() + .map(|a: Argument| a.into_nu_argument()) + .collect::>(); + + let decl_id = self + .engine_state + .find_decl(name.as_ref().as_bytes(), &vec![]) + .ok_or_else(|| CrateError::FunctionNotFound(name.as_ref().to_string()))?; + let call = Call { + decl_id, + head: Span::empty(), + arguments: args, + redirect_stdout: true, + redirect_stderr: true, + }; + + let data = nu_engine::eval_call( + &self.engine_state, + &mut self.stack, + &call, + PipelineData::empty(), + )?; + + Ok(data) + } + /// Prints the data of the given pipeline to stdout pub fn print_pipeline(&mut self, pipeline: PipelineData) -> CrateResult<()> { pipeline.print(&self.engine_state, &mut self.stack, false, false)?; diff --git a/src/error.rs b/src/error.rs index b725767..be7931a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,4 +11,7 @@ pub enum CrateError { #[error("Parse Error {0}")] NuParseError(#[from] ParseError), + + #[error("Could not find the function {0}")] + FunctionNotFound(String), } diff --git a/src/into_expression.rs b/src/into_expression.rs new file mode 100644 index 0000000..3ce342f --- /dev/null +++ b/src/into_expression.rs @@ -0,0 +1,70 @@ +use nu_protocol::{ + ast::{Expr, Expression}, + Span, Value, +}; + +use crate::{IntoValue, NewEmpty}; + +pub trait IntoExpression { + fn into_expression(self) -> Expression; +} + +trait ValueIntoExpression { + fn into_expression(self) -> Expression; + fn into_expr(self) -> Expr; +} + +impl IntoExpression for V { + #[inline] + fn into_expression(self) -> Expression { + self.into_value().into_expression() + } +} + +impl ValueIntoExpression for Value { + fn into_expression(self) -> Expression { + let ty = self.get_type(); + + Expression { + expr: self.into_expr(), + span: Span::empty(), + ty, + custom_completion: None, + } + } + + fn into_expr(self) -> Expr { + match self { + Value::Bool { val, .. } => Expr::Bool(val), + Value::Int { val, .. } => Expr::Int(val), + Value::Float { val, .. } => Expr::Float(val), + Value::Filesize { val, .. } => Expr::Int(val), + Value::Duration { val, .. } => Expr::Int(val), + Value::Date { val, .. } => Expr::DateTime(val), + Value::String { val, .. } => Expr::String(val), + Value::Record { + mut cols, mut vals, .. + } => { + let mut entries = Vec::new(); + + for i in 0..cols.len() { + let col = cols.remove(i).into_expression(); + let val = vals.remove(i).into_expression(); + entries.push((col, val)); + } + + Expr::Record(entries) + } + Value::List { vals, .. } => { + let vals = vals.into_iter().map(|v| v.into_expression()).collect(); + Expr::List(vals) + } + Value::Block { val, .. } => Expr::Block(val), + Value::Nothing { .. } => Expr::Nothing, + Value::Error { error } => Expr::String(error.to_string()), + Value::Binary { val, .. } => Expr::Binary(val), + Value::CellPath { val, .. } => Expr::CellPath(val), + _ => Expr::Nothing, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a5c02f2..d953c1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,14 @@ -pub mod context; +pub(crate) mod argument; +pub(crate) mod context; pub(crate) mod error; +pub(crate) mod into_expression; pub(crate) mod into_value; pub(crate) mod utils; +pub use argument::Argument; +pub use context::{CommandGroupConfig, Context, ContextBuilder}; pub use into_value::*; +pub use nu_protocol::PipelineData; pub use rusty_value; pub use utils::NewEmpty; diff --git a/tests/test_eval.rs b/tests/test_eval.rs index e608bd0..d5bd023 100644 --- a/tests/test_eval.rs +++ b/tests/test_eval.rs @@ -1,18 +1,41 @@ -use embed_nu::{ - context::{CommandGroupConfig, Context}, - NewEmpty, -}; +use embed_nu::{Argument, CommandGroupConfig, Context, NewEmpty}; use nu_protocol::PipelineData; #[test] fn it_evals_strings() { let mut ctx = get_context(); let pipeline = ctx - .eval_raw(r#"echo "Hello World""#, PipelineData::empty()) + .eval_raw( + r#"echo "Hello World from this eval""#, + PipelineData::empty(), + ) .unwrap(); ctx.print_pipeline(pipeline).unwrap() } +#[test] +fn it_executes_functions() { + let mut ctx = get_context(); + ctx.eval_raw( + r#" + + def hello [] { + echo "Hello World from this script"; + echo # dummy echo so I don't have to print the output + } + + "#, + PipelineData::empty(), + ) + .unwrap(); + ctx.call_fn("hello", []).unwrap(); + assert!(ctx.has_fn("world") == false); + + let arg = Argument::positional("Hello from rust"); + let pipeline = ctx.call_fn("echo", [arg]).unwrap(); + ctx.print_pipeline(pipeline).unwrap(); +} + fn get_context() -> Context { Context::builder() .with_command_groups(CommandGroupConfig::default().all_groups(true))