Reform scope and binding (#556)
Clean up scope handling by introducing `Binding` and `Scope` objects.
This commit is contained in:
parent
d0e813cd8b
commit
2d3134a91c
@ -1,18 +1,4 @@
|
||||
use crate::common::*;
|
||||
|
||||
/// An assignment, e.g `foo := bar`
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Assignment<'src> {
|
||||
/// Assignment was prefixed by the `export` keyword
|
||||
pub(crate) export: bool,
|
||||
/// Left-hand side of the assignment
|
||||
pub(crate) name: Name<'src>,
|
||||
/// Right-hand side of the assignment
|
||||
pub(crate) expression: Expression<'src>,
|
||||
}
|
||||
|
||||
impl<'src> Keyed<'src> for Assignment<'src> {
|
||||
fn key(&self) -> &'src str {
|
||||
self.name.lexeme()
|
||||
}
|
||||
}
|
||||
pub(crate) type Assignment<'src> = Binding<'src, Expression<'src>>;
|
||||
|
@ -1,231 +0,0 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) struct AssignmentEvaluator<'a: 'b, 'b> {
|
||||
pub(crate) assignments: &'b Table<'a, Assignment<'a>>,
|
||||
pub(crate) config: &'a Config,
|
||||
pub(crate) dotenv: &'b BTreeMap<String, String>,
|
||||
pub(crate) evaluated: BTreeMap<&'a str, (bool, String)>,
|
||||
pub(crate) scope: &'b BTreeMap<&'a str, (bool, String)>,
|
||||
pub(crate) working_directory: &'b Path,
|
||||
pub(crate) overrides: &'b BTreeMap<String, String>,
|
||||
pub(crate) settings: &'b Settings<'b>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
|
||||
pub(crate) fn evaluate_assignments(
|
||||
config: &'a Config,
|
||||
working_directory: &'b Path,
|
||||
dotenv: &'b BTreeMap<String, String>,
|
||||
assignments: &Table<'a, Assignment<'a>>,
|
||||
overrides: &BTreeMap<String, String>,
|
||||
settings: &'b Settings<'b>,
|
||||
) -> RunResult<'a, BTreeMap<&'a str, (bool, String)>> {
|
||||
let mut evaluator = AssignmentEvaluator {
|
||||
evaluated: empty(),
|
||||
scope: &empty(),
|
||||
settings,
|
||||
overrides,
|
||||
config,
|
||||
assignments,
|
||||
working_directory,
|
||||
dotenv,
|
||||
};
|
||||
|
||||
for name in assignments.keys() {
|
||||
evaluator.evaluate_assignment(name)?;
|
||||
}
|
||||
|
||||
Ok(evaluator.evaluated)
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_line(
|
||||
&mut self,
|
||||
line: &[Fragment<'a>],
|
||||
arguments: &BTreeMap<&'a str, Cow<str>>,
|
||||
) -> RunResult<'a, String> {
|
||||
let mut evaluated = String::new();
|
||||
for fragment in line {
|
||||
match fragment {
|
||||
Fragment::Text { token } => evaluated += token.lexeme(),
|
||||
Fragment::Interpolation { expression } => {
|
||||
evaluated += &self.evaluate_expression(expression, arguments)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(evaluated)
|
||||
}
|
||||
|
||||
fn evaluate_assignment(&mut self, name: &'a str) -> RunResult<'a, ()> {
|
||||
if self.evaluated.contains_key(name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(assignment) = self.assignments.get(name) {
|
||||
if let Some(value) = self.overrides.get(name) {
|
||||
self
|
||||
.evaluated
|
||||
.insert(name, (assignment.export, value.to_string()));
|
||||
} else {
|
||||
let value = self.evaluate_expression(&assignment.expression, &empty())?;
|
||||
self.evaluated.insert(name, (assignment.export, value));
|
||||
}
|
||||
} else {
|
||||
return Err(RuntimeError::Internal {
|
||||
message: format!("attempted to evaluated unknown assignment {}", name),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_expression(
|
||||
&mut self,
|
||||
expression: &Expression<'a>,
|
||||
arguments: &BTreeMap<&'a str, Cow<str>>,
|
||||
) -> RunResult<'a, String> {
|
||||
match expression {
|
||||
Expression::Variable { name, .. } => {
|
||||
let variable = name.lexeme();
|
||||
if self.evaluated.contains_key(variable) {
|
||||
Ok(self.evaluated[variable].1.clone())
|
||||
} else if self.scope.contains_key(variable) {
|
||||
Ok(self.scope[variable].1.clone())
|
||||
} else if self.assignments.contains_key(variable) {
|
||||
self.evaluate_assignment(variable)?;
|
||||
Ok(self.evaluated[variable].1.clone())
|
||||
} else if arguments.contains_key(variable) {
|
||||
Ok(arguments[variable].to_string())
|
||||
} else {
|
||||
Err(RuntimeError::Internal {
|
||||
message: format!("attempted to evaluate undefined variable `{}`", variable),
|
||||
})
|
||||
}
|
||||
}
|
||||
Expression::Call { thunk } => {
|
||||
let context = FunctionContext {
|
||||
invocation_directory: &self.config.invocation_directory,
|
||||
working_directory: &self.working_directory,
|
||||
dotenv: self.dotenv,
|
||||
};
|
||||
|
||||
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 } => {
|
||||
if self.config.dry_run {
|
||||
Ok(format!("`{}`", contents))
|
||||
} else {
|
||||
Ok(self.run_backtick(self.dotenv, contents, token)?)
|
||||
}
|
||||
}
|
||||
Expression::Concatination { lhs, rhs } => {
|
||||
Ok(self.evaluate_expression(lhs, arguments)? + &self.evaluate_expression(rhs, arguments)?)
|
||||
}
|
||||
Expression::Group { contents } => self.evaluate_expression(contents, arguments),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_backtick(
|
||||
&self,
|
||||
dotenv: &BTreeMap<String, String>,
|
||||
raw: &str,
|
||||
token: &Token<'a>,
|
||||
) -> RunResult<'a, String> {
|
||||
let mut cmd = self.settings.shell_command(self.config);
|
||||
|
||||
cmd.arg(raw);
|
||||
|
||||
cmd.current_dir(self.working_directory);
|
||||
|
||||
cmd.export_environment_variables(self.scope, dotenv)?;
|
||||
|
||||
cmd.stdin(process::Stdio::inherit());
|
||||
|
||||
cmd.stderr(if self.config.quiet {
|
||||
process::Stdio::null()
|
||||
} else {
|
||||
process::Stdio::inherit()
|
||||
});
|
||||
|
||||
InterruptHandler::guard(|| {
|
||||
output(cmd).map_err(|output_error| RuntimeError::Backtick {
|
||||
token: *token,
|
||||
output_error,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
run_error! {
|
||||
name: backtick_code,
|
||||
src: "
|
||||
a:
|
||||
echo {{`f() { return 100; }; f`}}
|
||||
",
|
||||
args: ["a"],
|
||||
error: RuntimeError::Backtick {
|
||||
token,
|
||||
output_error: OutputError::Code(code),
|
||||
},
|
||||
check: {
|
||||
assert_eq!(code, 100);
|
||||
assert_eq!(token.lexeme(), "`f() { return 100; }; f`");
|
||||
}
|
||||
}
|
||||
|
||||
run_error! {
|
||||
name: export_assignment_backtick,
|
||||
src: r#"
|
||||
export exported_variable = "A"
|
||||
b = `echo $exported_variable`
|
||||
|
||||
recipe:
|
||||
echo {{b}}
|
||||
"#,
|
||||
args: ["--quiet", "recipe"],
|
||||
error: RuntimeError::Backtick {
|
||||
token,
|
||||
output_error: OutputError::Code(_),
|
||||
},
|
||||
check: {
|
||||
assert_eq!(token.lexeme(), "`echo $exported_variable`");
|
||||
}
|
||||
}
|
||||
}
|
@ -2,17 +2,17 @@ use crate::common::*;
|
||||
|
||||
use CompilationErrorKind::*;
|
||||
|
||||
pub(crate) struct AssignmentResolver<'a: 'b, 'b> {
|
||||
assignments: &'b Table<'a, Assignment<'a>>,
|
||||
stack: Vec<&'a str>,
|
||||
seen: BTreeSet<&'a str>,
|
||||
evaluated: BTreeSet<&'a str>,
|
||||
pub(crate) struct AssignmentResolver<'src: 'run, 'run> {
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
stack: Vec<&'src str>,
|
||||
seen: BTreeSet<&'src str>,
|
||||
evaluated: BTreeSet<&'src str>,
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
||||
pub(crate) fn resolve_assignments(
|
||||
assignments: &Table<'a, Assignment<'a>>,
|
||||
) -> CompilationResult<'a, ()> {
|
||||
assignments: &Table<'src, Assignment<'src>>,
|
||||
) -> CompilationResult<'src, ()> {
|
||||
let mut resolver = AssignmentResolver {
|
||||
stack: empty(),
|
||||
seen: empty(),
|
||||
@ -27,7 +27,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_assignment(&mut self, name: &'a str) -> CompilationResult<'a, ()> {
|
||||
fn resolve_assignment(&mut self, name: &'src str) -> CompilationResult<'src, ()> {
|
||||
if self.evaluated.contains(name) {
|
||||
return Ok(());
|
||||
}
|
||||
@ -36,7 +36,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
self.stack.push(name);
|
||||
|
||||
if let Some(assignment) = self.assignments.get(name) {
|
||||
self.resolve_expression(&assignment.expression)?;
|
||||
self.resolve_expression(&assignment.value)?;
|
||||
self.evaluated.insert(name);
|
||||
} else {
|
||||
let message = format!("attempted to resolve unknown assignment `{}`", name);
|
||||
@ -56,7 +56,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_expression(&mut self, expression: &Expression<'a>) -> CompilationResult<'a, ()> {
|
||||
fn resolve_expression(&mut self, expression: &Expression<'src>) -> CompilationResult<'src, ()> {
|
||||
match expression {
|
||||
Expression::Variable { name } => {
|
||||
let variable = name.lexeme();
|
||||
|
18
src/binding.rs
Normal file
18
src/binding.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use crate::common::*;
|
||||
|
||||
/// A binding of `name` to `value`
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Binding<'src, V = String> {
|
||||
/// Export binding as an environment variable to child processes
|
||||
pub(crate) export: bool,
|
||||
/// Binding name
|
||||
pub(crate) name: Name<'src>,
|
||||
/// Binding value
|
||||
pub(crate) value: V,
|
||||
}
|
||||
|
||||
impl<'src, V> Keyed<'src> for Binding<'src, V> {
|
||||
fn key(&self) -> &'src str {
|
||||
self.name.lexeme()
|
||||
}
|
||||
}
|
@ -1,29 +1,31 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) trait CommandExt {
|
||||
fn export_environment_variables<'a>(
|
||||
&mut self,
|
||||
scope: &BTreeMap<&'a str, (bool, String)>,
|
||||
dotenv: &BTreeMap<String, String>,
|
||||
) -> RunResult<'a, ()>;
|
||||
fn export(&mut self, dotenv: &BTreeMap<String, String>, scope: &Scope);
|
||||
|
||||
fn export_scope(&mut self, scope: &Scope);
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn export_environment_variables<'a>(
|
||||
&mut self,
|
||||
scope: &BTreeMap<&'a str, (bool, String)>,
|
||||
dotenv: &BTreeMap<String, String>,
|
||||
) -> RunResult<'a, ()> {
|
||||
fn export(&mut self, dotenv: &BTreeMap<String, String>, scope: &Scope) {
|
||||
for (name, value) in dotenv {
|
||||
self.env(name, value);
|
||||
}
|
||||
|
||||
for (name, (export, value)) in scope {
|
||||
if *export {
|
||||
self.env(name, value);
|
||||
if let Some(parent) = scope.parent() {
|
||||
self.export_scope(parent);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn export_scope(&mut self, scope: &Scope) {
|
||||
if let Some(parent) = scope.parent() {
|
||||
self.export_scope(parent);
|
||||
}
|
||||
|
||||
for binding in scope.bindings() {
|
||||
if binding.export {
|
||||
self.env(binding.name.lexeme(), &binding.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,16 +49,16 @@ pub(crate) use crate::{
|
||||
// structs and enums
|
||||
pub(crate) use crate::{
|
||||
alias::Alias, analyzer::Analyzer, assignment::Assignment,
|
||||
assignment_evaluator::AssignmentEvaluator, assignment_resolver::AssignmentResolver, color::Color,
|
||||
assignment_resolver::AssignmentResolver, binding::Binding, color::Color,
|
||||
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, 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,
|
||||
dependency::Dependency, enclosure::Enclosure, evaluator::Evaluator, expression::Expression,
|
||||
fragment::Fragment, 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, scope::Scope, 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, thunk::Thunk, token::Token,
|
||||
|
@ -1,55 +1,55 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum CompilationErrorKind<'a> {
|
||||
pub(crate) enum CompilationErrorKind<'src> {
|
||||
AliasShadowsRecipe {
|
||||
alias: &'a str,
|
||||
alias: &'src str,
|
||||
recipe_line: usize,
|
||||
},
|
||||
CircularRecipeDependency {
|
||||
recipe: &'a str,
|
||||
circle: Vec<&'a str>,
|
||||
recipe: &'src str,
|
||||
circle: Vec<&'src str>,
|
||||
},
|
||||
CircularVariableDependency {
|
||||
variable: &'a str,
|
||||
circle: Vec<&'a str>,
|
||||
variable: &'src str,
|
||||
circle: Vec<&'src str>,
|
||||
},
|
||||
DependencyHasParameters {
|
||||
recipe: &'a str,
|
||||
dependency: &'a str,
|
||||
recipe: &'src str,
|
||||
dependency: &'src str,
|
||||
},
|
||||
DuplicateAlias {
|
||||
alias: &'a str,
|
||||
alias: &'src str,
|
||||
first: usize,
|
||||
},
|
||||
DuplicateDependency {
|
||||
recipe: &'a str,
|
||||
dependency: &'a str,
|
||||
recipe: &'src str,
|
||||
dependency: &'src str,
|
||||
},
|
||||
DuplicateParameter {
|
||||
recipe: &'a str,
|
||||
parameter: &'a str,
|
||||
recipe: &'src str,
|
||||
parameter: &'src str,
|
||||
},
|
||||
DuplicateRecipe {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
first: usize,
|
||||
},
|
||||
DuplicateVariable {
|
||||
variable: &'a str,
|
||||
variable: &'src str,
|
||||
},
|
||||
DuplicateSet {
|
||||
setting: &'a str,
|
||||
setting: &'src str,
|
||||
first: usize,
|
||||
},
|
||||
ExtraLeadingWhitespace,
|
||||
FunctionArgumentCountMismatch {
|
||||
function: &'a str,
|
||||
function: &'src str,
|
||||
found: usize,
|
||||
expected: usize,
|
||||
},
|
||||
InconsistentLeadingWhitespace {
|
||||
expected: &'a str,
|
||||
found: &'a str,
|
||||
expected: &'src str,
|
||||
found: &'src str,
|
||||
},
|
||||
Internal {
|
||||
message: String,
|
||||
@ -58,38 +58,38 @@ pub(crate) enum CompilationErrorKind<'a> {
|
||||
character: char,
|
||||
},
|
||||
MixedLeadingWhitespace {
|
||||
whitespace: &'a str,
|
||||
whitespace: &'src str,
|
||||
},
|
||||
ParameterFollowsVariadicParameter {
|
||||
parameter: &'a str,
|
||||
parameter: &'src str,
|
||||
},
|
||||
ParameterShadowsVariable {
|
||||
parameter: &'a str,
|
||||
parameter: &'src str,
|
||||
},
|
||||
RequiredParameterFollowsDefaultParameter {
|
||||
parameter: &'a str,
|
||||
parameter: &'src str,
|
||||
},
|
||||
UndefinedVariable {
|
||||
variable: &'a str,
|
||||
variable: &'src str,
|
||||
},
|
||||
UnexpectedToken {
|
||||
expected: Vec<TokenKind>,
|
||||
found: TokenKind,
|
||||
},
|
||||
UnknownAliasTarget {
|
||||
alias: &'a str,
|
||||
target: &'a str,
|
||||
alias: &'src str,
|
||||
target: &'src str,
|
||||
},
|
||||
UnknownDependency {
|
||||
recipe: &'a str,
|
||||
unknown: &'a str,
|
||||
recipe: &'src str,
|
||||
unknown: &'src str,
|
||||
},
|
||||
UnknownFunction {
|
||||
function: &'a str,
|
||||
function: &'src str,
|
||||
},
|
||||
UnknownStartOfToken,
|
||||
UnknownSetting {
|
||||
setting: &'a str,
|
||||
setting: &'src str,
|
||||
},
|
||||
UnpairedCarriageReturn,
|
||||
UnterminatedInterpolation,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(crate) struct Dependency<'a>(pub(crate) Rc<Recipe<'a>>);
|
||||
pub(crate) struct Dependency<'src>(pub(crate) Rc<Recipe<'src>>);
|
||||
|
266
src/evaluator.rs
Normal file
266
src/evaluator.rs
Normal file
@ -0,0 +1,266 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) struct Evaluator<'src: 'run, 'run> {
|
||||
assignments: Option<&'run Table<'src, Assignment<'src>>>,
|
||||
config: &'run Config,
|
||||
dotenv: &'run BTreeMap<String, String>,
|
||||
scope: Scope<'src, 'run>,
|
||||
settings: &'run Settings<'run>,
|
||||
working_directory: &'run Path,
|
||||
}
|
||||
|
||||
impl<'src, 'run> Evaluator<'src, 'run> {
|
||||
pub(crate) fn evaluate_assignments(
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
config: &'run Config,
|
||||
dotenv: &'run BTreeMap<String, String>,
|
||||
overrides: Scope<'src, 'run>,
|
||||
settings: &'run Settings<'run>,
|
||||
working_directory: &'run Path,
|
||||
) -> RunResult<'src, Scope<'src, 'run>> {
|
||||
let mut evaluator = Evaluator {
|
||||
scope: overrides,
|
||||
assignments: Some(assignments),
|
||||
config,
|
||||
dotenv,
|
||||
settings,
|
||||
working_directory,
|
||||
};
|
||||
|
||||
for assignment in assignments.values() {
|
||||
evaluator.evaluate_assignment(assignment)?;
|
||||
}
|
||||
|
||||
Ok(evaluator.scope)
|
||||
}
|
||||
|
||||
fn evaluate_assignment(&mut self, assignment: &Assignment<'src>) -> RunResult<'src, &str> {
|
||||
let name = assignment.name.lexeme();
|
||||
|
||||
if !self.scope.bound(name) {
|
||||
let value = self.evaluate_expression(&assignment.value)?;
|
||||
self.scope.bind(assignment.export, assignment.name, value);
|
||||
}
|
||||
|
||||
Ok(self.scope.value(name).unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_expression(
|
||||
&mut self,
|
||||
expression: &Expression<'src>,
|
||||
) -> RunResult<'src, String> {
|
||||
match expression {
|
||||
Expression::Variable { name, .. } => {
|
||||
let variable = name.lexeme();
|
||||
if let Some(value) = self.scope.value(variable) {
|
||||
Ok(value.to_owned())
|
||||
} else if let Some(assignment) = self
|
||||
.assignments
|
||||
.and_then(|assignments| assignments.get(variable))
|
||||
{
|
||||
Ok(self.evaluate_assignment(assignment)?.to_owned())
|
||||
} else {
|
||||
Err(RuntimeError::Internal {
|
||||
message: format!("attempted to evaluate undefined variable `{}`", variable),
|
||||
})
|
||||
}
|
||||
}
|
||||
Expression::Call { thunk } => {
|
||||
let context = FunctionContext {
|
||||
invocation_directory: &self.config.invocation_directory,
|
||||
working_directory: &self.working_directory,
|
||||
dotenv: self.dotenv,
|
||||
};
|
||||
|
||||
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)?).map_err(|message| {
|
||||
RuntimeError::FunctionCall {
|
||||
function: *name,
|
||||
message,
|
||||
}
|
||||
}),
|
||||
Binary {
|
||||
name,
|
||||
function,
|
||||
args: [a, b],
|
||||
..
|
||||
} => function(
|
||||
&context,
|
||||
&self.evaluate_expression(a)?,
|
||||
&self.evaluate_expression(b)?,
|
||||
)
|
||||
.map_err(|message| RuntimeError::FunctionCall {
|
||||
function: *name,
|
||||
message,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.to_string()),
|
||||
Expression::Backtick { contents, token } => {
|
||||
if self.config.dry_run {
|
||||
Ok(format!("`{}`", contents))
|
||||
} else {
|
||||
Ok(self.run_backtick(contents, token)?)
|
||||
}
|
||||
}
|
||||
Expression::Concatination { lhs, rhs } => {
|
||||
Ok(self.evaluate_expression(lhs)? + &self.evaluate_expression(rhs)?)
|
||||
}
|
||||
Expression::Group { contents } => self.evaluate_expression(contents),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_backtick(&self, raw: &str, token: &Token<'src>) -> RunResult<'src, String> {
|
||||
let mut cmd = self.settings.shell_command(self.config);
|
||||
|
||||
cmd.arg(raw);
|
||||
|
||||
cmd.current_dir(self.working_directory);
|
||||
|
||||
cmd.export(self.dotenv, &self.scope);
|
||||
|
||||
cmd.stdin(process::Stdio::inherit());
|
||||
|
||||
cmd.stderr(if self.config.quiet {
|
||||
process::Stdio::null()
|
||||
} else {
|
||||
process::Stdio::inherit()
|
||||
});
|
||||
|
||||
InterruptHandler::guard(|| {
|
||||
output(cmd).map_err(|output_error| RuntimeError::Backtick {
|
||||
token: *token,
|
||||
output_error,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_line(&mut self, line: &Line<'src>) -> RunResult<'src, String> {
|
||||
let mut evaluated = String::new();
|
||||
for fragment in &line.fragments {
|
||||
match fragment {
|
||||
Fragment::Text { token } => evaluated += token.lexeme(),
|
||||
Fragment::Interpolation { expression } => {
|
||||
evaluated += &self.evaluate_expression(expression)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(evaluated)
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_parameters(
|
||||
config: &'run Config,
|
||||
dotenv: &'run BTreeMap<String, String>,
|
||||
parameters: &[Parameter<'src>],
|
||||
arguments: &[&str],
|
||||
scope: &'run Scope<'src, 'run>,
|
||||
settings: &'run Settings,
|
||||
working_directory: &'run Path,
|
||||
) -> RunResult<'src, Scope<'src, 'run>> {
|
||||
let mut evaluator = Evaluator {
|
||||
assignments: None,
|
||||
scope: Scope::child(scope),
|
||||
settings,
|
||||
dotenv,
|
||||
config,
|
||||
working_directory,
|
||||
};
|
||||
|
||||
let mut scope = Scope::child(scope);
|
||||
|
||||
let mut rest = arguments;
|
||||
for parameter in parameters {
|
||||
let value = if rest.is_empty() {
|
||||
match parameter.default {
|
||||
Some(ref default) => evaluator.evaluate_expression(default)?,
|
||||
None => {
|
||||
return Err(RuntimeError::Internal {
|
||||
message: "missing parameter without default".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if parameter.variadic {
|
||||
let value = rest.to_vec().join(" ");
|
||||
rest = &[];
|
||||
value
|
||||
} else {
|
||||
let value = rest[0].to_owned();
|
||||
rest = &rest[1..];
|
||||
value
|
||||
};
|
||||
scope.bind(false, parameter.name, value);
|
||||
}
|
||||
|
||||
Ok(scope)
|
||||
}
|
||||
|
||||
pub(crate) fn line_evaluator(
|
||||
config: &'run Config,
|
||||
dotenv: &'run BTreeMap<String, String>,
|
||||
scope: &'run Scope<'src, 'run>,
|
||||
settings: &'run Settings,
|
||||
working_directory: &'run Path,
|
||||
) -> Evaluator<'src, 'run> {
|
||||
Evaluator {
|
||||
assignments: None,
|
||||
scope: Scope::child(scope),
|
||||
settings,
|
||||
dotenv,
|
||||
config,
|
||||
working_directory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
run_error! {
|
||||
name: backtick_code,
|
||||
src: "
|
||||
a:
|
||||
echo {{`f() { return 100; }; f`}}
|
||||
",
|
||||
args: ["a"],
|
||||
error: RuntimeError::Backtick {
|
||||
token,
|
||||
output_error: OutputError::Code(code),
|
||||
},
|
||||
check: {
|
||||
assert_eq!(code, 100);
|
||||
assert_eq!(token.lexeme(), "`f() { return 100; }; f`");
|
||||
}
|
||||
}
|
||||
|
||||
run_error! {
|
||||
name: export_assignment_backtick,
|
||||
src: r#"
|
||||
export exported_variable = "A"
|
||||
b = `echo $exported_variable`
|
||||
|
||||
recipe:
|
||||
echo {{b}}
|
||||
"#,
|
||||
args: ["--quiet", "recipe"],
|
||||
error: RuntimeError::Backtick {
|
||||
token,
|
||||
output_error: OutputError::Code(_),
|
||||
},
|
||||
check: {
|
||||
assert_eq!(token.lexeme(), "`echo $exported_variable`");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) struct FunctionContext<'a> {
|
||||
pub(crate) invocation_directory: &'a Path,
|
||||
pub(crate) working_directory: &'a Path,
|
||||
pub(crate) dotenv: &'a BTreeMap<String, String>,
|
||||
pub(crate) struct FunctionContext<'run> {
|
||||
pub(crate) invocation_directory: &'run Path,
|
||||
pub(crate) working_directory: &'run Path,
|
||||
pub(crate) dotenv: &'run BTreeMap<String, String>,
|
||||
}
|
||||
|
@ -81,23 +81,48 @@ impl<'src> Justfile<'src> {
|
||||
|
||||
let dotenv = load_dotenv()?;
|
||||
|
||||
let scope = AssignmentEvaluator::evaluate_assignments(
|
||||
config,
|
||||
working_directory,
|
||||
&dotenv,
|
||||
let scope = {
|
||||
let mut scope = Scope::new();
|
||||
let mut unknown_overrides = Vec::new();
|
||||
|
||||
for (name, value) in overrides {
|
||||
if let Some(assignment) = self.assignments.get(name) {
|
||||
scope.bind(assignment.export, assignment.name, value.clone());
|
||||
} else {
|
||||
unknown_overrides.push(name.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
if !unknown_overrides.is_empty() {
|
||||
return Err(RuntimeError::UnknownOverrides {
|
||||
overrides: unknown_overrides,
|
||||
});
|
||||
}
|
||||
|
||||
Evaluator::evaluate_assignments(
|
||||
&self.assignments,
|
||||
overrides,
|
||||
config,
|
||||
&dotenv,
|
||||
scope,
|
||||
&self.settings,
|
||||
)?;
|
||||
working_directory,
|
||||
)?
|
||||
};
|
||||
|
||||
if let Subcommand::Evaluate { .. } = config.subcommand {
|
||||
let mut width = 0;
|
||||
for name in scope.keys() {
|
||||
|
||||
for name in scope.names() {
|
||||
width = cmp::max(name.len(), width);
|
||||
}
|
||||
|
||||
for (name, (_export, value)) in scope {
|
||||
println!("{0:1$} := \"{2}\"", name, width, value);
|
||||
for binding in scope.bindings() {
|
||||
println!(
|
||||
"{0:1$} := \"{2}\"",
|
||||
binding.name.lexeme(),
|
||||
width,
|
||||
binding.value
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -152,7 +177,7 @@ impl<'src> Justfile<'src> {
|
||||
|
||||
let mut ran = empty();
|
||||
for (recipe, arguments) in grouped {
|
||||
self.run_recipe(&context, recipe, arguments, &dotenv, &mut ran, overrides)?
|
||||
self.run_recipe(&context, recipe, arguments, &dotenv, &mut ran)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -172,21 +197,20 @@ impl<'src> Justfile<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_recipe<'b>(
|
||||
fn run_recipe<'run>(
|
||||
&self,
|
||||
context: &'b RecipeContext<'src>,
|
||||
context: &'run RecipeContext<'src, 'run>,
|
||||
recipe: &Recipe<'src>,
|
||||
arguments: &[&'src str],
|
||||
dotenv: &BTreeMap<String, String>,
|
||||
ran: &mut BTreeSet<&'src str>,
|
||||
overrides: &BTreeMap<String, String>,
|
||||
) -> RunResult<()> {
|
||||
) -> RunResult<'src, ()> {
|
||||
for Dependency(dependency) in &recipe.dependencies {
|
||||
if !ran.contains(dependency.name()) {
|
||||
self.run_recipe(context, dependency, &[], dotenv, ran, overrides)?;
|
||||
self.run_recipe(context, dependency, &[], dotenv, ran)?;
|
||||
}
|
||||
}
|
||||
recipe.run(context, arguments, dotenv, overrides)?;
|
||||
recipe.run(context, arguments, dotenv)?;
|
||||
ran.insert(recipe.name());
|
||||
Ok(())
|
||||
}
|
||||
@ -199,7 +223,7 @@ impl<'src> Display for Justfile<'src> {
|
||||
if assignment.export {
|
||||
write!(f, "export ")?;
|
||||
}
|
||||
write!(f, "{} := {}", name, assignment.expression)?;
|
||||
write!(f, "{} := {}", name, assignment.value)?;
|
||||
items -= 1;
|
||||
if items != 0 {
|
||||
write!(f, "\n\n")?;
|
||||
|
@ -18,8 +18,8 @@ pub(crate) mod fuzzing;
|
||||
mod alias;
|
||||
mod analyzer;
|
||||
mod assignment;
|
||||
mod assignment_evaluator;
|
||||
mod assignment_resolver;
|
||||
mod binding;
|
||||
mod color;
|
||||
mod command_ext;
|
||||
mod common;
|
||||
@ -36,6 +36,7 @@ mod empty;
|
||||
mod enclosure;
|
||||
mod error;
|
||||
mod error_result_ext;
|
||||
mod evaluator;
|
||||
mod expression;
|
||||
mod fragment;
|
||||
mod function;
|
||||
@ -68,6 +69,7 @@ mod recipe_context;
|
||||
mod recipe_resolver;
|
||||
mod run;
|
||||
mod runtime_error;
|
||||
mod scope;
|
||||
mod search;
|
||||
mod search_config;
|
||||
mod search_error;
|
||||
|
@ -42,7 +42,7 @@ impl<'src> Node<'src> for Assignment<'src> {
|
||||
Tree::atom("assignment")
|
||||
}
|
||||
.push(self.name.lexeme())
|
||||
.push(self.expression.tree())
|
||||
.push(self.value.tree())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,12 +338,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
fn parse_assignment(&mut self, export: bool) -> CompilationResult<'src, Assignment<'src>> {
|
||||
let name = self.parse_name()?;
|
||||
self.presume_any(&[Equals, ColonEquals])?;
|
||||
let expression = self.parse_expression()?;
|
||||
let value = self.parse_expression()?;
|
||||
self.expect_eol()?;
|
||||
Ok(Assignment {
|
||||
name,
|
||||
export,
|
||||
expression,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -24,18 +24,18 @@ fn error_from_signal(
|
||||
|
||||
/// A recipe, e.g. `foo: bar baz`
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(crate) struct Recipe<'a, D = Dependency<'a>> {
|
||||
pub(crate) struct Recipe<'src, D = Dependency<'src>> {
|
||||
pub(crate) dependencies: Vec<D>,
|
||||
pub(crate) doc: Option<&'a str>,
|
||||
pub(crate) body: Vec<Line<'a>>,
|
||||
pub(crate) name: Name<'a>,
|
||||
pub(crate) parameters: Vec<Parameter<'a>>,
|
||||
pub(crate) doc: Option<&'src str>,
|
||||
pub(crate) body: Vec<Line<'src>>,
|
||||
pub(crate) name: Name<'src>,
|
||||
pub(crate) parameters: Vec<Parameter<'src>>,
|
||||
pub(crate) private: bool,
|
||||
pub(crate) quiet: bool,
|
||||
pub(crate) shebang: bool,
|
||||
}
|
||||
|
||||
impl<'a, D> Recipe<'a, D> {
|
||||
impl<'src, D> Recipe<'src, D> {
|
||||
pub(crate) fn argument_range(&self) -> RangeInclusive<usize> {
|
||||
self.min_arguments()..=self.max_arguments()
|
||||
}
|
||||
@ -56,7 +56,7 @@ impl<'a, D> Recipe<'a, D> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &'a str {
|
||||
pub(crate) fn name(&self) -> &'src str {
|
||||
self.name.lexeme()
|
||||
}
|
||||
|
||||
@ -64,13 +64,12 @@ impl<'a, D> Recipe<'a, D> {
|
||||
self.name.line
|
||||
}
|
||||
|
||||
pub(crate) fn run(
|
||||
pub(crate) fn run<'run>(
|
||||
&self,
|
||||
context: &RecipeContext<'a>,
|
||||
arguments: &[&'a str],
|
||||
context: &RecipeContext<'src, 'run>,
|
||||
arguments: &[&'src str],
|
||||
dotenv: &BTreeMap<String, String>,
|
||||
overrides: &BTreeMap<String, String>,
|
||||
) -> RunResult<'a, ()> {
|
||||
) -> RunResult<'src, ()> {
|
||||
let config = &context.config;
|
||||
|
||||
if config.verbosity.loquacious() {
|
||||
@ -83,46 +82,28 @@ impl<'a, D> Recipe<'a, D> {
|
||||
);
|
||||
}
|
||||
|
||||
let mut argument_map = BTreeMap::new();
|
||||
|
||||
let mut evaluator = AssignmentEvaluator {
|
||||
assignments: &empty(),
|
||||
evaluated: empty(),
|
||||
working_directory: context.working_directory,
|
||||
scope: &context.scope,
|
||||
settings: &context.settings,
|
||||
overrides,
|
||||
config,
|
||||
let scope = Evaluator::evaluate_parameters(
|
||||
context.config,
|
||||
dotenv,
|
||||
};
|
||||
&self.parameters,
|
||||
arguments,
|
||||
&context.scope,
|
||||
context.settings,
|
||||
context.working_directory,
|
||||
)?;
|
||||
|
||||
let mut rest = arguments;
|
||||
for parameter in &self.parameters {
|
||||
let value = if rest.is_empty() {
|
||||
match parameter.default {
|
||||
Some(ref default) => Cow::Owned(evaluator.evaluate_expression(default, &empty())?),
|
||||
None => {
|
||||
return Err(RuntimeError::Internal {
|
||||
message: "missing parameter without default".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if parameter.variadic {
|
||||
let value = Cow::Owned(rest.to_vec().join(" "));
|
||||
rest = &[];
|
||||
value
|
||||
} else {
|
||||
let value = Cow::Borrowed(rest[0]);
|
||||
rest = &rest[1..];
|
||||
value
|
||||
};
|
||||
argument_map.insert(parameter.name.lexeme(), value);
|
||||
}
|
||||
let mut evaluator = Evaluator::line_evaluator(
|
||||
context.config,
|
||||
dotenv,
|
||||
&scope,
|
||||
context.settings,
|
||||
context.working_directory,
|
||||
);
|
||||
|
||||
if self.shebang {
|
||||
let mut evaluated_lines = vec![];
|
||||
for line in &self.body {
|
||||
evaluated_lines.push(evaluator.evaluate_line(&line.fragments, &argument_map)?);
|
||||
evaluated_lines.push(evaluator.evaluate_line(line)?);
|
||||
}
|
||||
|
||||
if config.dry_run || self.quiet {
|
||||
@ -202,7 +183,7 @@ impl<'a, D> Recipe<'a, D> {
|
||||
output_error,
|
||||
})?;
|
||||
|
||||
command.export_environment_variables(&context.scope, dotenv)?;
|
||||
command.export(dotenv, &scope);
|
||||
|
||||
// run it!
|
||||
match InterruptHandler::guard(|| command.status()) {
|
||||
@ -242,7 +223,7 @@ impl<'a, D> Recipe<'a, D> {
|
||||
}
|
||||
let line = lines.next().unwrap();
|
||||
line_number += 1;
|
||||
evaluated += &evaluator.evaluate_line(&line.fragments, &argument_map)?;
|
||||
evaluated += &evaluator.evaluate_line(line)?;
|
||||
if line.is_continuation() {
|
||||
evaluated.pop();
|
||||
} else {
|
||||
@ -286,7 +267,7 @@ impl<'a, D> Recipe<'a, D> {
|
||||
cmd.stdout(Stdio::null());
|
||||
}
|
||||
|
||||
cmd.export_environment_variables(&context.scope, dotenv)?;
|
||||
cmd.export(dotenv, &scope);
|
||||
|
||||
match InterruptHandler::guard(|| cmd.status()) {
|
||||
Ok(exit_status) => {
|
||||
@ -344,7 +325,7 @@ impl<'src, D> Keyed<'src> for Recipe<'src, D> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Recipe<'a> {
|
||||
impl<'src> Display for Recipe<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
if let Some(doc) = self.doc {
|
||||
writeln!(f, "# {}", doc)?;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) struct RecipeContext<'a> {
|
||||
pub(crate) config: &'a Config,
|
||||
pub(crate) scope: BTreeMap<&'a str, (bool, String)>,
|
||||
pub(crate) working_directory: &'a Path,
|
||||
pub(crate) settings: &'a Settings<'a>,
|
||||
pub(crate) struct RecipeContext<'src: 'run, 'run> {
|
||||
pub(crate) config: &'run Config,
|
||||
pub(crate) scope: Scope<'src, 'run>,
|
||||
pub(crate) working_directory: &'run Path,
|
||||
pub(crate) settings: &'run Settings<'src>,
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ use crate::common::*;
|
||||
|
||||
use CompilationErrorKind::*;
|
||||
|
||||
pub(crate) struct RecipeResolver<'a: 'b, 'b> {
|
||||
unresolved_recipes: Table<'a, Recipe<'a, Name<'a>>>,
|
||||
resolved_recipes: Table<'a, Rc<Recipe<'a>>>,
|
||||
assignments: &'b Table<'a, Assignment<'a>>,
|
||||
pub(crate) struct RecipeResolver<'src: 'run, 'run> {
|
||||
unresolved_recipes: Table<'src, Recipe<'src, Name<'src>>>,
|
||||
resolved_recipes: Table<'src, Rc<Recipe<'src>>>,
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> RecipeResolver<'a, 'b> {
|
||||
impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
||||
pub(crate) fn resolve_recipes(
|
||||
unresolved_recipes: Table<'a, Recipe<'a, Name<'a>>>,
|
||||
assignments: &Table<'a, Assignment<'a>>,
|
||||
) -> CompilationResult<'a, Table<'a, Rc<Recipe<'a>>>> {
|
||||
unresolved_recipes: Table<'src, Recipe<'src, Name<'src>>>,
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
) -> CompilationResult<'src, Table<'src, Rc<Recipe<'src>>>> {
|
||||
let mut resolver = RecipeResolver {
|
||||
resolved_recipes: empty(),
|
||||
unresolved_recipes,
|
||||
@ -48,9 +48,9 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
|
||||
|
||||
fn resolve_variable(
|
||||
&self,
|
||||
variable: &Token<'a>,
|
||||
variable: &Token<'src>,
|
||||
parameters: &[Parameter],
|
||||
) -> CompilationResult<'a, ()> {
|
||||
) -> CompilationResult<'src, ()> {
|
||||
let name = variable.lexeme();
|
||||
let undefined =
|
||||
!self.assignments.contains_key(name) && !parameters.iter().any(|p| p.name.lexeme() == name);
|
||||
@ -64,9 +64,9 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
|
||||
|
||||
fn resolve_recipe(
|
||||
&mut self,
|
||||
stack: &mut Vec<&'a str>,
|
||||
recipe: Recipe<'a, Name<'a>>,
|
||||
) -> CompilationResult<'a, Rc<Recipe<'a>>> {
|
||||
stack: &mut Vec<&'src str>,
|
||||
recipe: Recipe<'src, Name<'src>>,
|
||||
) -> CompilationResult<'src, Rc<Recipe<'src>>> {
|
||||
if let Some(resolved) = self.resolved_recipes.get(recipe.name()) {
|
||||
return Ok(resolved.clone());
|
||||
}
|
||||
|
@ -1,75 +1,75 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum RuntimeError<'a> {
|
||||
pub(crate) enum RuntimeError<'src> {
|
||||
ArgumentCountMismatch {
|
||||
recipe: &'a str,
|
||||
parameters: Vec<&'a Parameter<'a>>,
|
||||
recipe: &'src str,
|
||||
parameters: Vec<&'src Parameter<'src>>,
|
||||
found: usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
},
|
||||
Backtick {
|
||||
token: Token<'a>,
|
||||
token: Token<'src>,
|
||||
output_error: OutputError,
|
||||
},
|
||||
Code {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
line_number: Option<usize>,
|
||||
code: i32,
|
||||
},
|
||||
Cygpath {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
output_error: OutputError,
|
||||
},
|
||||
Dotenv {
|
||||
dotenv_error: dotenv::Error,
|
||||
},
|
||||
FunctionCall {
|
||||
function: Name<'a>,
|
||||
function: Name<'src>,
|
||||
message: String,
|
||||
},
|
||||
Internal {
|
||||
message: String,
|
||||
},
|
||||
IoError {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
io_error: io::Error,
|
||||
},
|
||||
Shebang {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
command: String,
|
||||
argument: Option<String>,
|
||||
io_error: io::Error,
|
||||
},
|
||||
Signal {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
line_number: Option<usize>,
|
||||
signal: i32,
|
||||
},
|
||||
TmpdirIoError {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
io_error: io::Error,
|
||||
},
|
||||
UnknownOverrides {
|
||||
overrides: Vec<&'a str>,
|
||||
overrides: Vec<&'src str>,
|
||||
},
|
||||
UnknownRecipes {
|
||||
recipes: Vec<&'a str>,
|
||||
suggestion: Option<&'a str>,
|
||||
recipes: Vec<&'src str>,
|
||||
suggestion: Option<&'src str>,
|
||||
},
|
||||
Unknown {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
line_number: Option<usize>,
|
||||
},
|
||||
NoRecipes,
|
||||
DefaultRecipeRequiresArguments {
|
||||
recipe: &'a str,
|
||||
recipe: &'src str,
|
||||
min_arguments: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error for RuntimeError<'_> {
|
||||
impl<'src> Error for RuntimeError<'src> {
|
||||
fn code(&self) -> i32 {
|
||||
match *self {
|
||||
Self::Code { code, .. } => code,
|
||||
@ -82,7 +82,7 @@ impl Error for RuntimeError<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RuntimeError<'a> {
|
||||
impl<'src> RuntimeError<'src> {
|
||||
fn context(&self) -> Option<Token> {
|
||||
use RuntimeError::*;
|
||||
match self {
|
||||
@ -93,7 +93,7 @@ impl<'a> RuntimeError<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for RuntimeError<'a> {
|
||||
impl<'src> Display for RuntimeError<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
use RuntimeError::*;
|
||||
|
||||
|
57
src/scope.rs
Normal file
57
src/scope.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Scope<'src: 'run, 'run> {
|
||||
parent: Option<&'run Scope<'src, 'run>>,
|
||||
bindings: Table<'src, Binding<'src, String>>,
|
||||
}
|
||||
|
||||
impl<'src, 'run> Scope<'src, 'run> {
|
||||
pub(crate) fn child(parent: &'run Scope<'src, 'run>) -> Scope<'src, 'run> {
|
||||
Scope {
|
||||
parent: Some(parent),
|
||||
bindings: Table::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Scope<'src, 'run> {
|
||||
Scope {
|
||||
parent: None,
|
||||
bindings: Table::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bind(&mut self, export: bool, name: Name<'src>, value: String) {
|
||||
self.bindings.insert(Binding {
|
||||
name,
|
||||
export,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn bound(&self, name: &str) -> bool {
|
||||
self.bindings.contains_key(name)
|
||||
}
|
||||
|
||||
pub(crate) fn value(&self, name: &str) -> Option<&str> {
|
||||
if let Some(binding) = self.bindings.get(name) {
|
||||
Some(binding.value.as_ref())
|
||||
} else if let Some(parent) = self.parent {
|
||||
parent.value(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bindings(&self) -> impl Iterator<Item = &Binding<String>> {
|
||||
self.bindings.values()
|
||||
}
|
||||
|
||||
pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
|
||||
self.bindings.keys().cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn parent(&self) -> Option<&'run Scope<'src, 'run>> {
|
||||
self.parent
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
pub(crate) struct Shebang<'a> {
|
||||
pub(crate) interpreter: &'a str,
|
||||
pub(crate) argument: Option<&'a str>,
|
||||
pub(crate) struct Shebang<'line> {
|
||||
pub(crate) interpreter: &'line str,
|
||||
pub(crate) argument: Option<&'line str>,
|
||||
}
|
||||
|
||||
impl<'a> Shebang<'a> {
|
||||
pub(crate) fn new(text: &'a str) -> Option<Shebang<'a>> {
|
||||
if !text.starts_with("#!") {
|
||||
impl<'line> Shebang<'line> {
|
||||
pub(crate) fn new(line: &'line str) -> Option<Shebang<'line>> {
|
||||
if !line.starts_with("#!") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pieces = text[2..]
|
||||
let mut pieces = line[2..]
|
||||
.lines()
|
||||
.nth(0)
|
||||
.unwrap_or("")
|
||||
|
10
src/token.rs
10
src/token.rs
@ -1,21 +1,21 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub(crate) struct Token<'a> {
|
||||
pub(crate) struct Token<'src> {
|
||||
pub(crate) offset: usize,
|
||||
pub(crate) length: usize,
|
||||
pub(crate) line: usize,
|
||||
pub(crate) column: usize,
|
||||
pub(crate) src: &'a str,
|
||||
pub(crate) src: &'src str,
|
||||
pub(crate) kind: TokenKind,
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
pub(crate) fn lexeme(&self) -> &'a str {
|
||||
impl<'src> Token<'src> {
|
||||
pub(crate) fn lexeme(&self) -> &'src str {
|
||||
&self.src[self.offset..self.offset + self.length]
|
||||
}
|
||||
|
||||
pub(crate) fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> {
|
||||
pub(crate) fn error(&self, kind: CompilationErrorKind<'src>) -> CompilationError<'src> {
|
||||
CompilationError { token: *self, kind }
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user