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)",
]
[[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"

View File

@ -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"

View File

@ -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 } => {

View File

@ -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"},
}
}

View File

@ -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,
};

View File

@ -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(())
}
}

View File

@ -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,

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 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;

View File

@ -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, .. },

View File

@ -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,
},
}
}

View File

@ -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) = &parameter.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
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),
}
}
}