Resolve functions (#550)

Modifies parsing to return strongly-typed `Thunk`s, which contain both
the function implementation, as well as the correct number of arguments.

This moves unknown function and function argument count mismatch errors
to parse time.
This commit is contained in:
Casey Rodarmor 2019-11-21 12:14:10 -06:00 committed by GitHub
parent ba93c5e6af
commit d2decbfdb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 333 additions and 215 deletions

47
Cargo.lock generated
View File

@ -113,6 +113,16 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "difference" name = "difference"
version = "2.0.0" version = "2.0.0"
@ -198,6 +208,7 @@ dependencies = [
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "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)", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.6" version = "1.0.6"
@ -287,6 +306,14 @@ name = "quick-error"
version = "1.2.2" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "quote" name = "quote"
version = "1.0.2" version = "1.0.2"
@ -390,6 +417,16 @@ name = "strsim"
version = "0.8.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "syn" name = "syn"
version = "1.0.8" version = "1.0.8"
@ -454,6 +491,11 @@ name = "unicode-width"
version = "0.1.6" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.0" 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 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 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 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 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 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" "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 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 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 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 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 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 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 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" "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 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 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 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 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 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" "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 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 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-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 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 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" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@ -19,6 +19,7 @@ ansi_term = "0.12"
assert_matches = "1" assert_matches = "1"
atty = "0.2" atty = "0.2"
clap = "2.33" clap = "2.33"
derivative = "1"
dotenv = "0.15" dotenv = "0.15"
edit-distance = "2" edit-distance = "2"
env_logger = "0.7" env_logger = "0.7"

View File

@ -101,20 +101,47 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
}) })
} }
} }
Expression::Call { Expression::Call { thunk } => {
function,
arguments: call_arguments,
} => {
let call_arguments = call_arguments
.iter()
.map(|argument| self.evaluate_expression(argument, arguments))
.collect::<Result<Vec<String>, RuntimeError>>()?;
let context = FunctionContext { let context = FunctionContext {
invocation_directory: &self.config.invocation_directory, invocation_directory: &self.config.invocation_directory,
working_directory: &self.working_directory, working_directory: &self.working_directory,
dotenv: self.dotenv, 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::StringLiteral { string_literal } => Ok(string_literal.cooked.to_string()),
Expression::Backtick { contents, token } => { Expression::Backtick { contents, token } => {

View File

@ -61,32 +61,35 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
Expression::Variable { name } => { Expression::Variable { name } => {
let variable = name.lexeme(); let variable = name.lexeme();
if self.evaluated.contains(variable) { if self.evaluated.contains(variable) {
return Ok(()); Ok(())
} else if self.seen.contains(variable) { } else if self.seen.contains(variable) {
let token = self.assignments[variable].name.token(); let token = self.assignments[variable].name.token();
self.stack.push(variable); self.stack.push(variable);
return Err(token.error(CircularVariableDependency { Err(token.error(CircularVariableDependency {
variable, variable,
circle: self.stack.clone(), circle: self.stack.clone(),
})); }))
} else if self.assignments.contains_key(variable) { } else if self.assignments.contains_key(variable) {
self.resolve_assignment(variable)?; self.resolve_assignment(variable)
} else { } else {
return Err(name.token().error(UndefinedVariable { variable })); Err(name.token().error(UndefinedVariable { variable }))
} }
} }
Expression::Call { Expression::Call { thunk } => match thunk {
function, Thunk::Nullary { .. } => Ok(()),
arguments, Thunk::Unary { arg, .. } => self.resolve_expression(arg),
} => Function::resolve(&function.token(), arguments.len())?, Thunk::Binary { args: [a, b], .. } => {
self.resolve_expression(a)?;
self.resolve_expression(b)
}
},
Expression::Concatination { lhs, rhs } => { Expression::Concatination { lhs, rhs } => {
self.resolve_expression(lhs)?; self.resolve_expression(lhs)?;
self.resolve_expression(rhs)?; self.resolve_expression(rhs)
} }
Expression::StringLiteral { .. } | Expression::Backtick { .. } => {} Expression::StringLiteral { .. } | Expression::Backtick { .. } => Ok(()),
Expression::Group { contents } => self.resolve_expression(contents)?, Expression::Group { contents } => self.resolve_expression(contents),
} }
Ok(())
} }
} }
@ -125,12 +128,32 @@ mod tests {
} }
analysis_error! { analysis_error! {
name: unknown_function, name: unknown_function_parameter,
input: "a = foo()", input: "x := env_var(yy)",
offset: 4, offset: 13,
line: 0, line: 0,
column: 4, column: 13,
width: 3, width: 2,
kind: UnknownFunction{function: "foo"}, 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"},
} }
} }

View File

@ -4,7 +4,7 @@ pub(crate) use std::{
cmp, cmp,
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
env, env,
fmt::{self, Display, Formatter}, fmt::{self, Debug, Display, Formatter},
fs, fs,
io::{self, Write}, io::{self, Write},
iter::{self, FromIterator}, iter::{self, FromIterator},
@ -26,6 +26,7 @@ pub(crate) use crate::testing;
pub(crate) use crate::{node::Node, tree::Tree}; pub(crate) use crate::{node::Node, tree::Tree};
// dependencies // dependencies
pub(crate) use derivative::Derivative;
pub(crate) use edit_distance::edit_distance; pub(crate) use edit_distance::edit_distance;
pub(crate) use libc::EXIT_FAILURE; pub(crate) use libc::EXIT_FAILURE;
pub(crate) use log::warn; pub(crate) use log::warn;
@ -52,15 +53,15 @@ pub(crate) use crate::{
compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind, compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind,
compiler::Compiler, config::Config, config_error::ConfigError, count::Count, compiler::Compiler, config::Config, config_error::ConfigError, count::Count,
dependency::Dependency, enclosure::Enclosure, expression::Expression, fragment::Fragment, dependency::Dependency, enclosure::Enclosure, expression::Expression, fragment::Fragment,
function::Function, function_context::FunctionContext, functions::Functions, function::Function, function_context::FunctionContext, interrupt_guard::InterruptGuard,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, lexer::Lexer, line::Line,
justfile::Justfile, lexer::Lexer, line::Line, list::List, load_error::LoadError, module::Module, list::List, load_error::LoadError, module::Module, name::Name, output_error::OutputError,
name::Name, output_error::OutputError, parameter::Parameter, parser::Parser, platform::Platform, parameter::Parameter, parser::Parser, platform::Platform, position::Position,
position::Position, positional::Positional, recipe::Recipe, recipe_context::RecipeContext, positional::Positional, recipe::Recipe, recipe_context::RecipeContext,
recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search::Search, recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search::Search,
search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, state::State, 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, token_kind::TokenKind, use_color::UseColor, variables::Variables, verbosity::Verbosity,
warning::Warning, warning::Warning,
}; };

View File

@ -14,10 +14,7 @@ pub(crate) enum Expression<'src> {
token: Token<'src>, token: Token<'src>,
}, },
/// `name(arguments)` /// `name(arguments)`
Call { Call { thunk: Thunk<'src> },
function: Name<'src>,
arguments: Vec<Expression<'src>>,
},
/// `lhs + rhs` /// `lhs + rhs`
Concatination { Concatination {
lhs: Box<Expression<'src>>, lhs: Box<Expression<'src>>,
@ -35,35 +32,17 @@ impl<'src> Expression<'src> {
pub(crate) fn variables<'expression>(&'expression self) -> Variables<'expression, 'src> { pub(crate) fn variables<'expression>(&'expression self) -> Variables<'expression, 'src> {
Variables::new(self) Variables::new(self)
} }
pub(crate) fn functions<'expression>(&'expression self) -> Functions<'expression, 'src> {
Functions::new(self)
}
} }
impl<'src> Display for Expression<'src> { impl<'src> Display for Expression<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
match self { match self {
Expression::Backtick { contents, .. } => write!(f, "`{}`", contents)?, Expression::Backtick { contents, .. } => write!(f, "`{}`", contents),
Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs)?, Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs),
Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal)?, Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal),
Expression::Variable { name } => write!(f, "{}", name.lexeme())?, Expression::Variable { name } => write!(f, "{}", name.lexeme()),
Expression::Call { Expression::Call { thunk } => write!(f, "{}", thunk),
function, Expression::Group { contents } => write!(f, "({})", contents),
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)?,
} }
Ok(())
} }
} }

View File

@ -2,8 +2,14 @@ use crate::common::*;
use target; use target;
pub(crate) enum Function {
Nullary(fn(&FunctionContext) -> Result<String, String>),
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
}
lazy_static! { lazy_static! {
static ref FUNCTIONS: BTreeMap<&'static str, Function> = vec![ pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![
("arch", Function::Nullary(arch)), ("arch", Function::Nullary(arch)),
("os", Function::Nullary(os)), ("os", Function::Nullary(os)),
("os_family", Function::Nullary(os_family)), ("os_family", Function::Nullary(os_family)),
@ -18,14 +24,8 @@ lazy_static! {
.collect(); .collect();
} }
pub(crate) enum Function {
Nullary(fn(&FunctionContext) -> Result<String, String>),
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
}
impl Function { impl Function {
fn argc(&self) -> usize { pub(crate) fn argc(&self) -> usize {
use self::Function::*; use self::Function::*;
match *self { match *self {
Nullary(_) => 0, Nullary(_) => 0,
@ -33,85 +33,26 @@ impl Function {
Binary(_) => 2, 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<String, String> { fn arch(_context: &FunctionContext) -> Result<String, String> {
Ok(target::arch().to_string()) Ok(target::arch().to_string())
} }
pub(crate) fn os(_context: &FunctionContext) -> Result<String, String> { fn os(_context: &FunctionContext) -> Result<String, String> {
Ok(target::os().to_string()) Ok(target::os().to_string())
} }
pub(crate) fn os_family(_context: &FunctionContext) -> Result<String, String> { fn os_family(_context: &FunctionContext) -> Result<String, String> {
Ok(target::os_family().to_string()) Ok(target::os_family().to_string())
} }
pub(crate) fn invocation_directory(context: &FunctionContext) -> Result<String, String> { fn invocation_directory(context: &FunctionContext) -> Result<String, String> {
Platform::to_shell_path(context.working_directory, context.invocation_directory) Platform::to_shell_path(context.working_directory, context.invocation_directory)
.map_err(|e| format!("Error getting shell path: {}", e)) .map_err(|e| format!("Error getting shell path: {}", e))
} }
pub(crate) fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> { fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*; use std::env::VarError::*;
if let Some(value) = context.dotenv.get(key) { if let Some(value) = context.dotenv.get(key) {
@ -128,7 +69,7 @@ pub(crate) fn env_var(context: &FunctionContext, key: &str) -> Result<String, St
} }
} }
pub(crate) fn env_var_or_default( fn env_var_or_default(
context: &FunctionContext, context: &FunctionContext,
key: &str, key: &str,
default: &str, default: &str,

View File

@ -1,38 +0,0 @@
use crate::common::*;
pub(crate) struct Functions<'expression, 'src> {
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<Self::Item> {
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()
}
}
}
}

View File

@ -40,7 +40,6 @@ mod expression;
mod fragment; mod fragment;
mod function; mod function;
mod function_context; mod function_context;
mod functions;
mod interrupt_guard; mod interrupt_guard;
mod interrupt_handler; mod interrupt_handler;
mod item; mod item;
@ -81,6 +80,7 @@ mod state;
mod string_literal; mod string_literal;
mod subcommand; mod subcommand;
mod table; mod table;
mod thunk;
mod token; mod token;
mod token_kind; mod token_kind;
mod use_color; mod use_color;

View File

@ -50,12 +50,27 @@ impl<'src> Node<'src> for Expression<'src> {
fn tree(&self) -> Tree<'src> { fn tree(&self) -> Tree<'src> {
match self { match self {
Expression::Concatination { lhs, rhs } => Tree::atom("+").push(lhs.tree()).push(rhs.tree()), Expression::Concatination { lhs, rhs } => Tree::atom("+").push(lhs.tree()).push(rhs.tree()),
Expression::Call { Expression::Call { thunk } => {
function, let mut tree = Tree::atom("call");
arguments,
} => Tree::atom("call") use Thunk::*;
.push(function.lexeme()) match thunk {
.extend(arguments.iter().map(|argument| argument.tree())), 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::Variable { name } => Tree::atom(name.lexeme()),
Expression::StringLiteral { Expression::StringLiteral {
string_literal: StringLiteral { cooked, .. }, string_literal: StringLiteral { cooked, .. },

View File

@ -379,8 +379,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
if self.next_is(ParenL) { if self.next_is(ParenL) {
let arguments = self.parse_sequence()?; let arguments = self.parse_sequence()?;
Ok(Expression::Call { Ok(Expression::Call {
function: name, thunk: Thunk::resolve(name, arguments)?,
arguments,
}) })
} else { } else {
Ok(Expression::Variable { name }) Ok(Expression::Variable { name })
@ -837,20 +836,20 @@ mod tests {
test! { test! {
name: call_one_arg, name: call_one_arg,
text: "x := foo(y)", text: "x := env_var(y)",
tree: (justfile (assignment x (call foo y))), tree: (justfile (assignment x (call env_var y))),
} }
test! { test! {
name: call_multiple_args, name: call_multiple_args,
text: "x := foo(y, z)", text: "x := env_var_or_default(y, z)",
tree: (justfile (assignment x (call foo y z))), tree: (justfile (assignment x (call env_var_or_default y z))),
} }
test! { test! {
name: call_trailing_comma, name: call_trailing_comma,
text: "x := foo(y,)", text: "x := env_var(y,)",
tree: (justfile (assignment x (call foo y))), tree: (justfile (assignment x (call env_var y))),
} }
test! { test! {
@ -1718,4 +1717,76 @@ mod tests {
setting: "shall", 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,
},
}
} }

View File

@ -26,9 +26,6 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
for recipe in resolver.resolved_recipes.values() { for recipe in resolver.resolved_recipes.values() {
for parameter in &recipe.parameters { for parameter in &recipe.parameters {
if let Some(expression) = &parameter.default { if let Some(expression) = &parameter.default {
for (function, argc) in expression.functions() {
Function::resolve(&function, argc)?;
}
for variable in expression.variables() { for variable in expression.variables() {
resolver.resolve_variable(&variable, &[])?; resolver.resolve_variable(&variable, &[])?;
} }
@ -38,9 +35,6 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
for line in &recipe.body { for line in &recipe.body {
for fragment in &line.fragments { for fragment in &line.fragments {
if let Fragment::Interpolation { expression, .. } = fragment { if let Fragment::Interpolation { expression, .. } = fragment {
for (function, argc) in expression.functions() {
Function::resolve(&function, argc)?;
}
for variable in expression.variables() { for variable in expression.variables() {
resolver.resolve_variable(&variable, &recipe.parameters)?; resolver.resolve_variable(&variable, &recipe.parameters)?;
} }
@ -186,26 +180,6 @@ mod tests {
kind: UndefinedVariable{variable: "lol"}, 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! { analysis_error! {
name: unknown_variable_in_default, name: unknown_variable_in_default,
input: "a f=foo:", input: "a f=foo:",

77
src/thunk.rs Normal file
View File

@ -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<String, String>,
},
Unary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(&FunctionContext, &str) -> Result<String, String>,
arg: Box<Expression<'src>>,
},
Binary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(&FunctionContext, &str, &str) -> Result<String, String>,
args: [Box<Expression<'src>>; 2],
},
}
impl<'src> Thunk<'src> {
pub(crate) fn resolve(
name: Name<'src>,
mut arguments: Vec<Expression<'src>>,
) -> 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),
}
}
}