diff --git a/examples/option.ds b/examples/option.ds new file mode 100644 index 0000000..363ce7e --- /dev/null +++ b/examples/option.ds @@ -0,0 +1,6 @@ +create_user = (fn email , name ) { + { + email = email + username = (either_or username email) + } +} diff --git a/src/abstract_tree/function_call.rs b/src/abstract_tree/function_call.rs index a68686a..8471d88 100644 --- a/src/abstract_tree/function_call.rs +++ b/src/abstract_tree/function_call.rs @@ -28,6 +28,7 @@ impl AbstractTree for FunctionCall { let function_expression = Expression::from_syntax_node(source, expression_node, context)?; let function_type = function_expression.expected_type(context)?; + let mut minimum_parameter_count = 0; let mut arguments = Vec::new(); for index in 2..node.child_count() - 1 { @@ -43,6 +44,11 @@ impl AbstractTree for FunctionCall { } = &function_type { if let Some(r#type) = parameter_types.get(argument_index) { + if let Type::Option(_) = r#type { + } else { + minimum_parameter_count += 1; + } + r#type .check(&expression_type) .map_err(|error| error.at_node(child, source))?; @@ -54,13 +60,13 @@ impl AbstractTree for FunctionCall { } if let Type::Function { - parameter_types, .. + parameter_types: _, .. } = &function_type { - if arguments.len() != parameter_types.len() { - return Err(Error::ExpectedFunctionArgumentAmount { + if arguments.len() < minimum_parameter_count { + return Err(Error::ExpectedFunctionArgumentMinimum { source: source[expression_node.byte_range()].to_string(), - expected: parameter_types.len(), + minumum_expected: minimum_parameter_count, actual: arguments.len(), }); } diff --git a/src/built_in_functions/commands.rs b/src/built_in_functions/commands.rs index cbd4d4e..d0b850a 100644 --- a/src/built_in_functions/commands.rs +++ b/src/built_in_functions/commands.rs @@ -41,14 +41,18 @@ impl BuiltInFunction for Sh { } fn run(&self, arguments: &[Value], _context: &Map) -> Result { - Error::expect_argument_amount(self, 1, arguments.len())?; - let command_text = arguments.first().unwrap().as_string()?; let mut command = Command::new("sh"); command.arg("-c"); command.arg(command_text); + let extra_command_text = arguments.get(1).unwrap_or_default().as_option()?; + + if let Some(text) = extra_command_text { + command.args(["--", text.as_string()?]); + } + let output = command.spawn()?.wait_with_output()?.stdout; Ok(Value::String(String::from_utf8(output)?)) @@ -56,7 +60,7 @@ impl BuiltInFunction for Sh { fn r#type(&self) -> crate::Type { Type::Function { - parameter_types: vec![Type::String], + parameter_types: vec![Type::String, Type::Option(Box::new(Type::String))], return_type: Box::new(Type::String), } } diff --git a/src/error.rs b/src/error.rs index 2333a4b..8a45078 100644 --- a/src/error.rs +++ b/src/error.rs @@ -74,9 +74,9 @@ pub enum Error { }, /// A function was called with the wrong amount of arguments. - ExpectedArgumentMinimum { - function_name: &'static str, - minimum: usize, + ExpectedFunctionArgumentMinimum { + source: String, + minumum_expected: usize, actual: usize, }, @@ -136,6 +136,10 @@ pub enum Error { actual: Value, }, + ExpectedOption { + actual: Value, + }, + /// A string, list, map or table value was expected. ExpectedCollection { actual: Value, @@ -205,22 +209,6 @@ impl Error { } } - pub fn expect_argument_minimum( - function: &F, - minimum: usize, - actual: usize, - ) -> Result<()> { - if actual < minimum { - Ok(()) - } else { - Err(Error::ExpectedArgumentMinimum { - function_name: function.name(), - minimum, - actual, - }) - } - } - pub fn is_type_check_error(&self, other: &Error) -> bool { match self { Error::WithContext { error, .. } => { @@ -335,14 +323,16 @@ impl fmt::Display for Error { f, "{source} expected {expected} arguments, but got {actual}.", ), - ExpectedArgumentMinimum { - function_name, - minimum, + ExpectedFunctionArgumentMinimum { + source, + minumum_expected, actual, - } => write!( - f, - "{function_name} expected a minimum of {minimum} arguments, but got {actual}.", - ), + } => { + write!( + f, + "{source} expected at least {minumum_expected} arguments, but got {actual}." + ) + } ExpectedString { actual } => { write!(f, "Expected a string but got {actual}.") } @@ -379,6 +369,7 @@ impl fmt::Display for Error { ExpectedFunction { actual } => { write!(f, "Expected function, but got {actual}.") } + ExpectedOption { actual } => write!(f, "Expected option, but got {actual}."), ExpectedCollection { actual } => { write!( f, diff --git a/src/value/function.rs b/src/value/function.rs index a1f5192..f9ae590 100644 --- a/src/value/function.rs +++ b/src/value/function.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::{AbstractTree, Block, Error, Identifier, Map, Result, Type, Value}; +use crate::{AbstractTree, Block, Identifier, Map, Result, Type, Value}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct Function { @@ -48,14 +48,6 @@ impl Function { } pub fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result { - if self.parameters.len() != arguments.len() { - return Err(Error::ExpectedFunctionArgumentAmount { - source: "unknown".to_string(), - expected: self.parameters.len(), - actual: arguments.len(), - }); - } - let context = Map::clone_from(outer_context)?; let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter()); diff --git a/src/value/mod.rs b/src/value/mod.rs index 54445c4..fd2416c 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -220,6 +220,16 @@ impl Value { } } + /// Returns `Option`, or returns `Err` if `self` is not a `Value::Option`. + pub fn as_option(&self) -> Result<&Option>> { + match self { + Value::Option(option) => Ok(option), + value => Err(Error::ExpectedOption { + actual: value.clone(), + }), + } + } + /// Returns `()`, or returns `Err` if `self` is not a `Value::Option(None)`. pub fn as_none(&self) -> Result<()> { match self {