Skip to content

Commit

Permalink
Resolve functions (#550)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
casey authored Nov 21, 2019
1 parent ba93c5e commit d2decbf
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 215 deletions.
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
45 changes: 36 additions & 9 deletions src/assignment_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } => {
Expand Down
61 changes: 42 additions & 19 deletions src/assignment_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}

Expand Down Expand Up @@ -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"},
}
}
15 changes: 8 additions & 7 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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;
Expand All @@ -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,
};
Expand Down
35 changes: 7 additions & 28 deletions src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand All @@ -35,35 +32,17 @@ impl<'src> Expression<'src> {
pub(crate) fn variables<'expression>(&'expression self) -> Variables<'expression, 'src> {
Variables::new(self)
}

pub(crate) fn functions<'expression>(&'expression self) -> Functions<'expression, 'src> {
Functions::new(self)
}
}

impl<'src> Display for Expression<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
match self {
Expression::Backtick { contents, .. } => write!(f, "`{}`", contents)?,
Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs)?,
Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal)?,
Expression::Variable { name } => write!(f, "{}", name.lexeme())?,
Expression::Call {
function,
arguments,
} => {
write!(f, "{}(", function.lexeme())?;
for (i, argument) in arguments.iter().enumerate() {
if i > 0 {
write!(f, ", {}", argument)?;
} else {
write!(f, "{}", argument)?;
}
}
write!(f, ")")?;
}
Expression::Group { contents } => write!(f, "({})", contents)?,
Expression::Backtick { contents, .. } => write!(f, "`{}`", contents),
Expression::Concatination { lhs, rhs } => write!(f, "{} + {}", lhs, rhs),
Expression::StringLiteral { string_literal } => write!(f, "{}", string_literal),
Expression::Variable { name } => write!(f, "{}", name.lexeme()),
Expression::Call { thunk } => write!(f, "{}", thunk),
Expression::Group { contents } => write!(f, "({})", contents),
}
Ok(())
}
}
Loading

0 comments on commit d2decbf

Please sign in to comment.