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:
parent
ba93c5e6af
commit
d2decbfdb8
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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::<Result<Vec<String>, 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 } => {
|
||||
|
@ -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"},
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -14,10 +14,7 @@ pub(crate) enum Expression<'src> {
|
||||
token: Token<'src>,
|
||||
},
|
||||
/// `name(arguments)`
|
||||
Call {
|
||||
function: Name<'src>,
|
||||
arguments: Vec<Expression<'src>>,
|
||||
},
|
||||
Call { thunk: Thunk<'src> },
|
||||
/// `lhs + rhs`
|
||||
Concatination {
|
||||
lhs: Box<Expression<'src>>,
|
||||
@ -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)?;
|
||||
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),
|
||||
}
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
Expression::Group { contents } => write!(f, "({})", contents)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,14 @@ use crate::common::*;
|
||||
|
||||
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! {
|
||||
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<String, String>),
|
||||
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
|
||||
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
|
||||
}
|
||||
|
||||
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<String, String> {
|
||||
fn arch(_context: &FunctionContext) -> Result<String, 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())
|
||||
}
|
||||
|
||||
pub(crate) fn os_family(_context: &FunctionContext) -> Result<String, String> {
|
||||
fn os_family(_context: &FunctionContext) -> Result<String, 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)
|
||||
.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::*;
|
||||
|
||||
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,
|
||||
key: &str,
|
||||
default: &str,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
27
src/node.rs
27
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, .. },
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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:",
|
||||
|
77
src/thunk.rs
Normal file
77
src/thunk.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user