diff --git a/src/abstract_tree/built_in_value.rs b/src/abstract_tree/built_in_value.rs index 169e702..cc5f66d 100644 --- a/src/abstract_tree/built_in_value.rs +++ b/src/abstract_tree/built_in_value.rs @@ -103,8 +103,8 @@ impl BuiltInValue { BuiltInFunction::RandomInteger, ] { let key = built_in_function.name().to_string(); - let value = Value::Function(Function::BuiltIn(built_in_function)); let r#type = built_in_function.r#type(); + let value = Value::Function(Function::BuiltIn(built_in_function)); random_context.insert(key, (value, r#type)); } diff --git a/src/abstract_tree/function_call.rs b/src/abstract_tree/function_call.rs index 2ecd926..c926c52 100644 --- a/src/abstract_tree/function_call.rs +++ b/src/abstract_tree/function_call.rs @@ -68,19 +68,36 @@ impl AbstractTree for FunctionCall { } }; - if self.arguments.len() != parameter_types.len() { - return Err(Error::ExpectedFunctionArgumentAmount { - expected: parameter_types.len(), + let required_argument_count = + parameter_types.iter().fold( + 0, + |acc, r#type| { + if r#type.is_option() { + acc + } else { + acc + 1 + } + }, + ); + + if self.arguments.len() < required_argument_count { + return Err(Error::ExpectedFunctionArgumentMinimum { + minumum: required_argument_count, actual: self.arguments.len(), - } - .at_source_position(source, self.syntax_position)); + }); } for (index, expression) in self.arguments.iter().enumerate() { if let Some(r#type) = parameter_types.get(index) { - r#type - .check(&expression.expected_type(context)?) - .map_err(|error| error.at_source_position(source, self.syntax_position))?; + let expected_type = expression.expected_type(context)?; + + if let Type::Option(optional_type) = r#type { + optional_type.check(&expected_type)?; + } else { + r#type + .check(&expression.expected_type(context)?) + .map_err(|error| error.at_source_position(source, self.syntax_position))?; + } } } diff --git a/src/abstract_tree/type.rs b/src/abstract_tree/type.rs index eaf9618..ef4793e 100644 --- a/src/abstract_tree/type.rs +++ b/src/abstract_tree/type.rs @@ -150,6 +150,10 @@ impl Type { pub fn is_map(&self) -> bool { matches!(self, Type::Map(_)) } + + pub fn is_option(&self) -> bool { + matches!(self, Type::Option(_)) + } } impl AbstractTree for Type { diff --git a/src/built_in_functions/mod.rs b/src/built_in_functions/mod.rs index 1699a1e..a8d9afc 100644 --- a/src/built_in_functions/mod.rs +++ b/src/built_in_functions/mod.rs @@ -3,6 +3,7 @@ mod string; use std::{ fmt::{self, Display, Formatter}, fs::read_to_string, + process::Command, }; use rand::{random, thread_rng, Rng}; @@ -12,9 +13,10 @@ use crate::{Error, Format, Map, Result, Type, Value}; pub use string::{string_functions, StringFunction}; -#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum BuiltInFunction { AssertEqual, + Binary(String), FsRead, JsonParse, Length, @@ -30,6 +32,7 @@ impl BuiltInFunction { pub fn name(&self) -> &'static str { match self { BuiltInFunction::AssertEqual => "assert_equal", + BuiltInFunction::Binary(_) => "binary", BuiltInFunction::FsRead => "read", BuiltInFunction::JsonParse => "parse", BuiltInFunction::Length => "length", @@ -45,6 +48,10 @@ impl BuiltInFunction { pub fn r#type(&self) -> Type { match self { BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), + BuiltInFunction::Binary(_) => Type::function( + vec![Type::Option(Box::new(Type::String))], + Type::Option(Box::new(Type::Integer)), + ), BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String), BuiltInFunction::JsonParse => Type::function(vec![Type::String], Type::Any), BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer), @@ -67,6 +74,36 @@ impl BuiltInFunction { Ok(Value::Boolean(left == right)) } + BuiltInFunction::Binary(binary_name) => { + let input = if let Some(value) = arguments.first() { + value.clone() + } else { + Value::none() + }; + let mut command = Command::new(binary_name); + + if let Ok(Some(value)) = input.as_option() { + let input_string = value.as_string()?; + + if !input_string.is_empty() { + command.args(input_string.split_whitespace()); + } + } + + if let Ok(input_string) = input.as_string() { + if !input_string.is_empty() { + command.args(input_string.split_whitespace()); + } + } + + let output = command.spawn()?.wait()?; + let status_code = match output.code() { + Some(code) => Value::some(Value::Integer(code as i64)), + None => Value::none(), + }; + + Ok(status_code) + } BuiltInFunction::FsRead => { Error::expect_argument_amount(self.name(), 1, arguments.len())?; diff --git a/src/error.rs b/src/error.rs index 3c85f26..986e03c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,8 +83,7 @@ pub enum Error { /// A function was called with the wrong amount of arguments. ExpectedFunctionArgumentMinimum { - source: String, - minumum_expected: usize, + minumum: usize, actual: usize, }, @@ -338,14 +337,10 @@ impl fmt::Display for Error { ExpectedFunctionArgumentAmount { expected, actual } => { write!(f, "Expected {expected} arguments, but got {actual}.",) } - ExpectedFunctionArgumentMinimum { - source, - minumum_expected, - actual, - } => { + ExpectedFunctionArgumentMinimum { minumum, actual } => { write!( f, - "{source} expected at least {minumum_expected} arguments, but got {actual}." + "Expected at least {minumum} arguments, but got {actual}." ) } ExpectedString { actual } => { diff --git a/src/main.rs b/src/main.rs index e66cb81..f7403cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use reedline::{ use std::{fs::read_to_string, path::PathBuf}; -use dust_lang::{built_in_values, Interpreter, Map, Result, Value}; +use dust_lang::{built_in_values, Function, Interpreter, Map, Result, Value}; /// Command-line arguments to be parsed. #[derive(Parser, Debug)] @@ -171,6 +171,31 @@ impl Highlighter for DustHighlighter { } fn run_shell(context: Map) -> Result<()> { + for (key, value) in std::env::vars() { + if key == "PATH" { + for path in value.split([' ', ':']) { + let path_dir = if let Ok(path_dir) = PathBuf::from(path).read_dir() { + path_dir + } else { + continue; + }; + + for entry in path_dir { + let entry = entry?; + + if entry.file_type()?.is_file() { + context.set( + entry.file_name().to_string_lossy().to_string(), + Value::Function(Function::BuiltIn(dust_lang::BuiltInFunction::Binary( + entry.file_name().to_string_lossy().to_string(), + ))), + )?; + } + } + } + } + } + let mut interpreter = Interpreter::new(context.clone()); let prompt = DefaultPrompt::default(); let mut keybindings = default_emacs_keybindings();