diff --git a/Cargo.lock b/Cargo.lock index ce91cc8..67ca81a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,16 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "derivative" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "difference" version = "2.0.0" @@ -198,6 +208,7 @@ dependencies = [ "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -274,6 +285,14 @@ dependencies = [ "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "1.0.6" @@ -287,6 +306,14 @@ name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "1.0.2" @@ -390,6 +417,16 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "1.0.8" @@ -454,6 +491,11 @@ name = "unicode-width" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -534,6 +576,7 @@ dependencies = [ "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" +"checksum derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" "checksum dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" @@ -553,8 +596,10 @@ dependencies = [ "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" @@ -568,6 +613,7 @@ dependencies = [ "checksum snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41207ca11f96a62cd34e6b7fdf73d322b25ae3848eb9d38302169724bb32cf27" "checksum snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5e338c8b0577457c9dda8e794b6ad7231c96e25b1b0dd5842d52249020c1c0" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" "checksum target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10000465bb0cc031c87a44668991b284fd84c0e6bd945f62d4af04e9e52a222a" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" @@ -575,6 +621,7 @@ dependencies = [ "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index ac1f660..42510f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ ansi_term = "0.12" assert_matches = "1" atty = "0.2" clap = "2.33" +derivative = "1" dotenv = "0.15" edit-distance = "2" env_logger = "0.7" diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index e48e090..ea9aab4 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -101,20 +101,47 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { }) } } - Expression::Call { - function, - arguments: call_arguments, - } => { - let call_arguments = call_arguments - .iter() - .map(|argument| self.evaluate_expression(argument, arguments)) - .collect::, RuntimeError>>()?; + Expression::Call { thunk } => { let context = FunctionContext { invocation_directory: &self.config.invocation_directory, working_directory: &self.working_directory, dotenv: self.dotenv, }; - Function::evaluate(*function, &context, &call_arguments) + + use Thunk::*; + match thunk { + Nullary { name, function, .. } => { + function(&context).map_err(|message| RuntimeError::FunctionCall { + function: *name, + message, + }) + } + Unary { + name, + function, + arg, + .. + } => function(&context, &self.evaluate_expression(arg, arguments)?).map_err(|message| { + RuntimeError::FunctionCall { + function: *name, + message, + } + }), + Binary { + name, + function, + args: [a, b], + .. + } => function( + &context, + &self.evaluate_expression(a, arguments)?, + &self.evaluate_expression(b, arguments)?, + ) + .map_err(|message| RuntimeError::FunctionCall { + function: *name, + message, + }), + } } Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.to_string()), Expression::Backtick { contents, token } => { diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index ae572f7..b172e35 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -61,32 +61,35 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { Expression::Variable { name } => { let variable = name.lexeme(); if self.evaluated.contains(variable) { - return Ok(()); + Ok(()) } else if self.seen.contains(variable) { let token = self.assignments[variable].name.token(); self.stack.push(variable); - return Err(token.error(CircularVariableDependency { + Err(token.error(CircularVariableDependency { variable, circle: self.stack.clone(), - })); + })) } else if self.assignments.contains_key(variable) { - self.resolve_assignment(variable)?; + self.resolve_assignment(variable) } else { - return Err(name.token().error(UndefinedVariable { variable })); + Err(name.token().error(UndefinedVariable { variable })) } } - Expression::Call { - function, - arguments, - } => Function::resolve(&function.token(), arguments.len())?, + Expression::Call { thunk } => match thunk { + Thunk::Nullary { .. } => Ok(()), + Thunk::Unary { arg, .. } => self.resolve_expression(arg), + Thunk::Binary { args: [a, b], .. } => { + self.resolve_expression(a)?; + self.resolve_expression(b) + } + }, Expression::Concatination { lhs, rhs } => { self.resolve_expression(lhs)?; - self.resolve_expression(rhs)?; + self.resolve_expression(rhs) } - Expression::StringLiteral { .. } | Expression::Backtick { .. } => {} - Expression::Group { contents } => self.resolve_expression(contents)?, + Expression::StringLiteral { .. } | Expression::Backtick { .. } => Ok(()), + Expression::Group { contents } => self.resolve_expression(contents), } - Ok(()) } } @@ -125,12 +128,32 @@ mod tests { } analysis_error! { - name: unknown_function, - input: "a = foo()", - offset: 4, + name: unknown_function_parameter, + input: "x := env_var(yy)", + offset: 13, line: 0, - column: 4, - width: 3, - kind: UnknownFunction{function: "foo"}, + column: 13, + width: 2, + kind: UndefinedVariable{variable: "yy"}, + } + + analysis_error! { + name: unknown_function_parameter_binary_first, + input: "x := env_var_or_default(yy, 'foo')", + offset: 24, + line: 0, + column: 24, + width: 2, + kind: UndefinedVariable{variable: "yy"}, + } + + analysis_error! { + name: unknown_function_parameter_binary_second, + input: "x := env_var_or_default('foo', yy)", + offset: 31, + line: 0, + column: 31, + width: 2, + kind: UndefinedVariable{variable: "yy"}, } } diff --git a/src/common.rs b/src/common.rs index 37b9bb7..0074a20 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,7 +4,7 @@ pub(crate) use std::{ cmp, collections::{BTreeMap, BTreeSet}, env, - fmt::{self, Display, Formatter}, + fmt::{self, Debug, Display, Formatter}, fs, io::{self, Write}, iter::{self, FromIterator}, @@ -26,6 +26,7 @@ pub(crate) use crate::testing; pub(crate) use crate::{node::Node, tree::Tree}; // dependencies +pub(crate) use derivative::Derivative; pub(crate) use edit_distance::edit_distance; pub(crate) use libc::EXIT_FAILURE; pub(crate) use log::warn; @@ -52,15 +53,15 @@ pub(crate) use crate::{ compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind, compiler::Compiler, config::Config, config_error::ConfigError, count::Count, dependency::Dependency, enclosure::Enclosure, expression::Expression, fragment::Fragment, - function::Function, function_context::FunctionContext, functions::Functions, - interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, - justfile::Justfile, lexer::Lexer, line::Line, list::List, load_error::LoadError, module::Module, - name::Name, output_error::OutputError, parameter::Parameter, parser::Parser, platform::Platform, - position::Position, positional::Positional, recipe::Recipe, recipe_context::RecipeContext, + function::Function, function_context::FunctionContext, interrupt_guard::InterruptGuard, + interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, lexer::Lexer, line::Line, + list::List, load_error::LoadError, module::Module, name::Name, output_error::OutputError, + parameter::Parameter, parser::Parser, platform::Platform, position::Position, + positional::Positional, recipe::Recipe, recipe_context::RecipeContext, recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search::Search, search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, state::State, - string_literal::StringLiteral, subcommand::Subcommand, table::Table, token::Token, + string_literal::StringLiteral, subcommand::Subcommand, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind, use_color::UseColor, variables::Variables, verbosity::Verbosity, warning::Warning, }; diff --git a/src/expression.rs b/src/expression.rs index ac07fe1..3904dc3 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -14,10 +14,7 @@ pub(crate) enum Expression<'src> { token: Token<'src>, }, /// `name(arguments)` - Call { - function: Name<'src>, - arguments: Vec>, - }, + Call { thunk: Thunk<'src> }, /// `lhs + rhs` Concatination { lhs: Box>, @@ -35,35 +32,17 @@ impl<'src> Expression<'src> { pub(crate) fn variables<'expression>(&'expression self) -> Variables<'expression, 'src> { Variables::new(self) } - - pub(crate) fn functions<'expression>(&'expression self) -> Functions<'expression, 'src> { - Functions::new(self) - } } impl<'src> Display for Expression<'src> { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { match self { - Expression::Backtick { contents, .. } => write!(f, "`{}`", contents)?, - Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs)?, - Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal)?, - Expression::Variable { name } => write!(f, "{}", name.lexeme())?, - Expression::Call { - function, - arguments, - } => { - write!(f, "{}(", function.lexeme())?; - for (i, argument) in arguments.iter().enumerate() { - if i > 0 { - write!(f, ", {}", argument)?; - } else { - write!(f, "{}", argument)?; - } - } - write!(f, ")")?; - } - Expression::Group { contents } => write!(f, "({})", contents)?, + Expression::Backtick { contents, .. } => write!(f, "`{}`", contents), + Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs), + Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal), + Expression::Variable { name } => write!(f, "{}", name.lexeme()), + Expression::Call { thunk } => write!(f, "{}", thunk), + Expression::Group { contents } => write!(f, "({})", contents), } - Ok(()) } } diff --git a/src/function.rs b/src/function.rs index 564383f..5fe186f 100644 --- a/src/function.rs +++ b/src/function.rs @@ -2,8 +2,14 @@ use crate::common::*; use target; +pub(crate) enum Function { + Nullary(fn(&FunctionContext) -> Result), + Unary(fn(&FunctionContext, &str) -> Result), + Binary(fn(&FunctionContext, &str, &str) -> Result), +} + lazy_static! { - static ref FUNCTIONS: BTreeMap<&'static str, Function> = vec![ + pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![ ("arch", Function::Nullary(arch)), ("os", Function::Nullary(os)), ("os_family", Function::Nullary(os_family)), @@ -18,14 +24,8 @@ lazy_static! { .collect(); } -pub(crate) enum Function { - Nullary(fn(&FunctionContext) -> Result), - Unary(fn(&FunctionContext, &str) -> Result), - Binary(fn(&FunctionContext, &str, &str) -> Result), -} - impl Function { - fn argc(&self) -> usize { + pub(crate) fn argc(&self) -> usize { use self::Function::*; match *self { Nullary(_) => 0, @@ -33,85 +33,26 @@ impl Function { Binary(_) => 2, } } - - pub(crate) fn resolve<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> { - let name = token.lexeme(); - if let Some(function) = FUNCTIONS.get(&name) { - use self::Function::*; - match (function, argc) { - (&Nullary(_), 0) | (&Unary(_), 1) | (&Binary(_), 2) => Ok(()), - _ => Err( - token.error(CompilationErrorKind::FunctionArgumentCountMismatch { - function: name, - found: argc, - expected: function.argc(), - }), - ), - } - } else { - Err(token.error(CompilationErrorKind::UnknownFunction { - function: token.lexeme(), - })) - } - } - - pub(crate) fn evaluate<'a>( - function_name: Name<'a>, - context: &FunctionContext, - arguments: &[String], - ) -> RunResult<'a, String> { - let name = function_name.lexeme(); - if let Some(function) = FUNCTIONS.get(name) { - use self::Function::*; - let argc = arguments.len(); - match (function, argc) { - (&Nullary(f), 0) => f(context).map_err(|message| RuntimeError::FunctionCall { - function: function_name, - message, - }), - (&Unary(f), 1) => f(context, &arguments[0]).map_err(|message| RuntimeError::FunctionCall { - function: function_name, - message, - }), - (&Binary(f), 2) => { - f(context, &arguments[0], &arguments[1]).map_err(|message| RuntimeError::FunctionCall { - function: function_name, - message, - }) - } - _ => Err(RuntimeError::Internal { - message: format!( - "attempted to evaluate function `{}` with {} arguments", - name, argc - ), - }), - } - } else { - Err(RuntimeError::Internal { - message: format!("attempted to evaluate unknown function: `{}`", name), - }) - } - } } -pub(crate) fn arch(_context: &FunctionContext) -> Result { +fn arch(_context: &FunctionContext) -> Result { Ok(target::arch().to_string()) } -pub(crate) fn os(_context: &FunctionContext) -> Result { +fn os(_context: &FunctionContext) -> Result { Ok(target::os().to_string()) } -pub(crate) fn os_family(_context: &FunctionContext) -> Result { +fn os_family(_context: &FunctionContext) -> Result { Ok(target::os_family().to_string()) } -pub(crate) fn invocation_directory(context: &FunctionContext) -> Result { +fn invocation_directory(context: &FunctionContext) -> Result { Platform::to_shell_path(context.working_directory, context.invocation_directory) .map_err(|e| format!("Error getting shell path: {}", e)) } -pub(crate) fn env_var(context: &FunctionContext, key: &str) -> Result { +fn env_var(context: &FunctionContext, key: &str) -> Result { use std::env::VarError::*; if let Some(value) = context.dotenv.get(key) { @@ -128,7 +69,7 @@ pub(crate) fn env_var(context: &FunctionContext, key: &str) -> Result { - stack: Vec<&'expression Expression<'src>>, -} - -impl<'expression, 'src> Functions<'expression, 'src> { - pub(crate) fn new(root: &'expression Expression<'src>) -> Functions<'expression, 'src> { - Functions { stack: vec![root] } - } -} - -impl<'expression, 'src> Iterator for Functions<'expression, 'src> { - type Item = (Token<'src>, usize); - - fn next(&mut self) -> Option { - match self.stack.pop() { - None - | Some(Expression::StringLiteral { .. }) - | Some(Expression::Backtick { .. }) - | Some(Expression::Variable { .. }) => None, - Some(Expression::Call { - function, - arguments, - .. - }) => Some((function.token(), arguments.len())), - Some(Expression::Concatination { lhs, rhs }) => { - self.stack.push(lhs); - self.stack.push(rhs); - self.next() - } - Some(Expression::Group { contents }) => { - self.stack.push(contents); - self.next() - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index d3040d2..8513971 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ mod expression; mod fragment; mod function; mod function_context; -mod functions; mod interrupt_guard; mod interrupt_handler; mod item; @@ -81,6 +80,7 @@ mod state; mod string_literal; mod subcommand; mod table; +mod thunk; mod token; mod token_kind; mod use_color; diff --git a/src/node.rs b/src/node.rs index 26fb1c0..e7a63ca 100644 --- a/src/node.rs +++ b/src/node.rs @@ -50,12 +50,27 @@ impl<'src> Node<'src> for Expression<'src> { fn tree(&self) -> Tree<'src> { match self { Expression::Concatination { lhs, rhs } => Tree::atom("+").push(lhs.tree()).push(rhs.tree()), - Expression::Call { - function, - arguments, - } => Tree::atom("call") - .push(function.lexeme()) - .extend(arguments.iter().map(|argument| argument.tree())), + Expression::Call { thunk } => { + let mut tree = Tree::atom("call"); + + use Thunk::*; + match thunk { + Nullary { name, .. } => tree.push_mut(name.lexeme()), + Unary { name, arg, .. } => { + tree.push_mut(name.lexeme()); + tree.push_mut(arg.tree()); + } + Binary { + name, args: [a, b], .. + } => { + tree.push_mut(name.lexeme()); + tree.push_mut(a.tree()); + tree.push_mut(b.tree()); + } + } + + tree + } Expression::Variable { name } => Tree::atom(name.lexeme()), Expression::StringLiteral { string_literal: StringLiteral { cooked, .. }, diff --git a/src/parser.rs b/src/parser.rs index 925daaa..5f35005 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -379,8 +379,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { if self.next_is(ParenL) { let arguments = self.parse_sequence()?; Ok(Expression::Call { - function: name, - arguments, + thunk: Thunk::resolve(name, arguments)?, }) } else { Ok(Expression::Variable { name }) @@ -837,20 +836,20 @@ mod tests { test! { name: call_one_arg, - text: "x := foo(y)", - tree: (justfile (assignment x (call foo y))), + text: "x := env_var(y)", + tree: (justfile (assignment x (call env_var y))), } test! { name: call_multiple_args, - text: "x := foo(y, z)", - tree: (justfile (assignment x (call foo y z))), + text: "x := env_var_or_default(y, z)", + tree: (justfile (assignment x (call env_var_or_default y z))), } test! { name: call_trailing_comma, - text: "x := foo(y,)", - tree: (justfile (assignment x (call foo y))), + text: "x := env_var(y,)", + tree: (justfile (assignment x (call env_var y))), } test! { @@ -1718,4 +1717,76 @@ mod tests { setting: "shall", }, } + + error! { + name: unknown_function, + input: "a = foo()", + offset: 4, + line: 0, + column: 4, + width: 3, + kind: UnknownFunction{function: "foo"}, + } + + error! { + name: unknown_function_in_interpolation, + input: "a:\n echo {{bar()}}", + offset: 11, + line: 1, + column: 8, + width: 3, + kind: UnknownFunction{function: "bar"}, + } + + error! { + name: unknown_function_in_default, + input: "a f=baz():", + offset: 4, + line: 0, + column: 4, + width: 3, + kind: UnknownFunction{function: "baz"}, + } + + error! { + name: function_argument_count_nullary, + input: "x := arch('foo')", + offset: 5, + line: 0, + column: 5, + width: 4, + kind: FunctionArgumentCountMismatch { + function: "arch", + found: 1, + expected: 0, + }, + } + + error! { + name: function_argument_count_unary, + input: "x := env_var()", + offset: 5, + line: 0, + column: 5, + width: 7, + kind: FunctionArgumentCountMismatch { + function: "env_var", + found: 0, + expected: 1, + }, + } + + error! { + name: function_argument_count_binary, + input: "x := env_var_or_default('foo')", + offset: 5, + line: 0, + column: 5, + width: 18, + kind: FunctionArgumentCountMismatch { + function: "env_var_or_default", + found: 1, + expected: 2, + }, + } } diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs index 8a3fd99..3fcde9a 100644 --- a/src/recipe_resolver.rs +++ b/src/recipe_resolver.rs @@ -26,9 +26,6 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { for recipe in resolver.resolved_recipes.values() { for parameter in &recipe.parameters { if let Some(expression) = ¶meter.default { - for (function, argc) in expression.functions() { - Function::resolve(&function, argc)?; - } for variable in expression.variables() { resolver.resolve_variable(&variable, &[])?; } @@ -38,9 +35,6 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { for line in &recipe.body { for fragment in &line.fragments { if let Fragment::Interpolation { expression, .. } = fragment { - for (function, argc) in expression.functions() { - Function::resolve(&function, argc)?; - } for variable in expression.variables() { resolver.resolve_variable(&variable, &recipe.parameters)?; } @@ -186,26 +180,6 @@ mod tests { kind: UndefinedVariable{variable: "lol"}, } - analysis_error! { - name: unknown_function_in_interpolation, - input: "a:\n echo {{bar()}}", - offset: 11, - line: 1, - column: 8, - width: 3, - kind: UnknownFunction{function: "bar"}, - } - - analysis_error! { - name: unknown_function_in_default, - input: "a f=baz():", - offset: 4, - line: 0, - column: 4, - width: 3, - kind: UnknownFunction{function: "baz"}, - } - analysis_error! { name: unknown_variable_in_default, input: "a f=foo:", diff --git a/src/thunk.rs b/src/thunk.rs new file mode 100644 index 0000000..5580eeb --- /dev/null +++ b/src/thunk.rs @@ -0,0 +1,77 @@ +use crate::common::*; + +#[derive(Derivative)] +#[derivative(Debug, PartialEq = "feature_allow_slow_enum")] +pub(crate) enum Thunk<'src> { + Nullary { + name: Name<'src>, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + function: fn(&FunctionContext) -> Result, + }, + Unary { + name: Name<'src>, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + function: fn(&FunctionContext, &str) -> Result, + arg: Box>, + }, + Binary { + name: Name<'src>, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + function: fn(&FunctionContext, &str, &str) -> Result, + args: [Box>; 2], + }, +} + +impl<'src> Thunk<'src> { + pub(crate) fn resolve( + name: Name<'src>, + mut arguments: Vec>, + ) -> CompilationResult<'src, Thunk<'src>> { + if let Some(function) = crate::function::TABLE.get(&name.lexeme()) { + match (function, arguments.len()) { + (Function::Nullary(function), 0) => Ok(Thunk::Nullary { + function: *function, + name, + }), + (Function::Unary(function), 1) => Ok(Thunk::Unary { + function: *function, + arg: Box::new(arguments.pop().unwrap()), + name, + }), + (Function::Binary(function), 2) => { + let b = Box::new(arguments.pop().unwrap()); + let a = Box::new(arguments.pop().unwrap()); + Ok(Thunk::Binary { + function: *function, + args: [a, b], + name, + }) + } + _ => Err( + name.error(CompilationErrorKind::FunctionArgumentCountMismatch { + function: name.lexeme(), + found: arguments.len(), + expected: function.argc(), + }), + ), + } + } else { + Err(name.error(CompilationErrorKind::UnknownFunction { + function: name.lexeme(), + })) + } + } +} + +impl Display for Thunk<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use Thunk::*; + match self { + Nullary { name, .. } => write!(f, "{}()", name.lexeme()), + Unary { name, arg, .. } => write!(f, "{}({})", name.lexeme(), arg), + Binary { + name, args: [a, b], .. + } => write!(f, "{}({}, {})", name.lexeme(), a, b), + } + } +}