just/src/assignment_resolver.rs
Casey Rodarmor 1b0fafea75
Add loader and refactor errors (#917)
This commit adds a `Loader` type, which can be used to load multiple
source strings. This was done to support the work on modules, but
coincidentally enabled consolidating errors, since now `Config::run`
can take a `&Loader`, and in the event of an error, return and `Error`
that borrows from loaded strings. Multiple error types have been
consolidated, and a bunch of ad-hoc error printing was removed.
2021-07-26 01:26:06 -07:00

179 lines
4.5 KiB
Rust

use crate::common::*;
use CompileErrorKind::*;
pub(crate) struct AssignmentResolver<'src: 'run, 'run> {
assignments: &'run Table<'src, Assignment<'src>>,
stack: Vec<&'src str>,
evaluated: BTreeSet<&'src str>,
}
impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
pub(crate) fn resolve_assignments(
assignments: &Table<'src, Assignment<'src>>,
) -> CompileResult<'src, ()> {
let mut resolver = AssignmentResolver {
stack: Vec::new(),
evaluated: BTreeSet::new(),
assignments,
};
for name in assignments.keys() {
resolver.resolve_assignment(name)?;
}
Ok(())
}
fn resolve_assignment(&mut self, name: &'src str) -> CompileResult<'src, ()> {
if self.evaluated.contains(name) {
return Ok(());
}
self.stack.push(name);
if let Some(assignment) = self.assignments.get(name) {
self.resolve_expression(&assignment.value)?;
self.evaluated.insert(name);
} else {
let message = format!("attempted to resolve unknown assignment `{}`", name);
let token = Token {
src: "",
offset: 0,
line: 0,
column: 0,
length: 0,
kind: TokenKind::Unspecified,
};
return Err(CompileError {
kind: Internal { message },
token,
});
}
self.stack.pop();
Ok(())
}
fn resolve_expression(&mut self, expression: &Expression<'src>) -> CompileResult<'src, ()> {
match expression {
Expression::Variable { name } => {
let variable = name.lexeme();
if self.evaluated.contains(variable) {
Ok(())
} else if self.stack.contains(&variable) {
let token = self.assignments[variable].name.token();
self.stack.push(variable);
Err(token.error(CircularVariableDependency {
variable,
circle: self.stack.clone(),
}))
} else if self.assignments.contains_key(variable) {
self.resolve_assignment(variable)
} else {
Err(name.token().error(UndefinedVariable { variable }))
}
},
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)
},
Thunk::Ternary {
args: [a, b, c], ..
} => {
self.resolve_expression(a)?;
self.resolve_expression(b)?;
self.resolve_expression(c)
},
},
Expression::Concatination { lhs, rhs } => {
self.resolve_expression(lhs)?;
self.resolve_expression(rhs)
},
Expression::Conditional {
lhs,
rhs,
then,
otherwise,
..
} => {
self.resolve_expression(lhs)?;
self.resolve_expression(rhs)?;
self.resolve_expression(then)?;
self.resolve_expression(otherwise)
},
Expression::StringLiteral { .. } | Expression::Backtick { .. } => Ok(()),
Expression::Group { contents } => self.resolve_expression(contents),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
analysis_error! {
name: circular_variable_dependency,
input: "a := b\nb := a",
offset: 0,
line: 0,
column: 0,
width: 1,
kind: CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]},
}
analysis_error! {
name: self_variable_dependency,
input: "a := a",
offset: 0,
line: 0,
column: 0,
width: 1,
kind: CircularVariableDependency{variable: "a", circle: vec!["a", "a"]},
}
analysis_error! {
name: unknown_expression_variable,
input: "x := yy",
offset: 5,
line: 0,
column: 5,
width: 2,
kind: UndefinedVariable{variable: "yy"},
}
analysis_error! {
name: unknown_function_parameter,
input: "x := env_var(yy)",
offset: 13,
line: 0,
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"},
}
}