Assembler passes up to label placement
This commit is contained in:
+48
-13
@@ -21,14 +21,38 @@ impl<'a> Display for EvalError<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Scope<'a> = BTreeMap<&'a str, i32>;
|
||||
|
||||
/// ## Evaluating expressions
|
||||
/// Now that we have a parsed file, that file has a bunch of numeric symbols in it: labels,
|
||||
/// .equ directives, that sort of thing. We need to resolve all of those to constant values
|
||||
/// before we can generate code. So, first part of that is being able to evaluate expressions.
|
||||
///
|
||||
/// This evaluates an expression in the context of a symbol table, and returns either what the
|
||||
/// expression evaluates to (a number) or an error (if it references a symbol not in
|
||||
/// the given symbol table, or needs to know an address that's not calculated yet).
|
||||
///
|
||||
/// It's a depth-first recursive traversal of the expression AST:
|
||||
///
|
||||
/// - If the node is a number, then it returns that number.
|
||||
/// - If the node is a string, it tries to look it up in the symbol table or explodes.
|
||||
/// - If the node is a relative label, it tries to look it up in the symbol table, and then
|
||||
/// subtracts a given start_address. If start_address is nil (as when we're solving .equs)
|
||||
/// then it errors.
|
||||
/// - If the node is an expr or term, then it evaluates the children: the children are a
|
||||
/// sequence of evaluate-able nodes separated by operators. So first evaluate the left-most
|
||||
/// child, then use the operator to combine it with the following one, and so on.
|
||||
/// - If the node is an absolute or relative line offset, it attempts to look up the start of
|
||||
/// the given line in the table of line start addresses and gives that address relatively or
|
||||
/// absolutely.
|
||||
pub fn eval<'a>(
|
||||
node: Node<'a>,
|
||||
node: &Node<'a>,
|
||||
line_num: i32,
|
||||
line_addresses: &BTreeMap<i32, i32>,
|
||||
scope: &BTreeMap<&'a str, i32>,
|
||||
scope: &Scope,
|
||||
) -> Result<i32, EvalError<'a>> {
|
||||
match node {
|
||||
Node::Number(n) => Ok(n),
|
||||
Node::Number(n) => Ok(*n),
|
||||
Node::Label(label) => scope
|
||||
.get(label)
|
||||
.map_or_else(|| Err(EvalError::MissingLabel(label)), |val| Ok(*val)),
|
||||
@@ -60,17 +84,16 @@ pub fn eval<'a>(
|
||||
}
|
||||
}
|
||||
Node::Expr(car, cdr) => {
|
||||
let car = eval(*car, line_num, line_addresses, scope);
|
||||
let car = eval(car, line_num, line_addresses, scope);
|
||||
if let Ok(mut acc) = car {
|
||||
for (op, node) in cdr {
|
||||
if let Ok(rhs) = eval(node, line_num, line_addresses, scope) {
|
||||
match op {
|
||||
Operator::Add => acc += rhs,
|
||||
Operator::Sub => acc -= rhs,
|
||||
Operator::Mul => acc *= rhs,
|
||||
Operator::Div => acc /= rhs,
|
||||
Operator::Mod => acc %= rhs,
|
||||
}
|
||||
let rhs = eval(node, line_num, line_addresses, scope)?;
|
||||
match op {
|
||||
Operator::Add => acc += rhs,
|
||||
Operator::Sub => acc -= rhs,
|
||||
Operator::Mul => acc *= rhs,
|
||||
Operator::Div => acc /= rhs,
|
||||
Operator::Mod => acc %= rhs,
|
||||
}
|
||||
}
|
||||
Ok(acc)
|
||||
@@ -111,7 +134,7 @@ mod test {
|
||||
line: &'a str,
|
||||
) -> Result<i32, EvalError<'a>> {
|
||||
if let Ok(VASMLine::Instruction(_, _, Some(arg))) = parse_vasm_line(line) {
|
||||
eval(arg, 1, &line_addresses, &scope)
|
||||
eval(&arg, 1, &line_addresses, &scope)
|
||||
} else {
|
||||
panic!("Failed to parse an instruction line with an argument")
|
||||
}
|
||||
@@ -134,6 +157,10 @@ mod test {
|
||||
test_scope_eval([("apple", 5)].into(), "add apple + 7"),
|
||||
Ok(12)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 5)].into(), "add 5 + apple"),
|
||||
Ok(10)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 5), ("banana", 3)].into(), "add apple * banana"),
|
||||
Ok(15)
|
||||
@@ -142,6 +169,14 @@ mod test {
|
||||
test_scope_eval([("apple", 5)].into(), "add banana"),
|
||||
Err(EvalError::MissingLabel("banana"))
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([].into(), "add apple + 2"),
|
||||
Err(EvalError::MissingLabel("apple"))
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([].into(), "add 2 + apple"),
|
||||
Err(EvalError::MissingLabel("apple"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user