Assembler passes up to label placement
This commit is contained in:
+15
-1
@@ -25,7 +25,7 @@ pub enum Node<'a> {
|
||||
Expr(Box<Node<'a>>, Vec<(Operator, Node<'a>)>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub struct Label<'a>(pub &'a str);
|
||||
|
||||
impl<'a> Display for Label<'a> {
|
||||
@@ -43,3 +43,17 @@ pub enum VASMLine<'a> {
|
||||
Equ(Label<'a>, Node<'a>),
|
||||
LabelDef(Label<'a>),
|
||||
}
|
||||
|
||||
impl<'a> VASMLine<'a> {
|
||||
pub fn label(&self) -> Option<Label<'a>> {
|
||||
match self {
|
||||
VASMLine::Instruction(Some(lbl), _, _)
|
||||
| VASMLine::Db(Some(lbl), _)
|
||||
| VASMLine::StringDb(Some(lbl), _)
|
||||
| VASMLine::Org(Some(lbl), _)
|
||||
| VASMLine::Equ(lbl, _)
|
||||
| VASMLine::LabelDef(lbl) => Some(*lbl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ extern crate pest_derive;
|
||||
|
||||
pub mod ast;
|
||||
pub mod parse_error;
|
||||
pub mod vasm_assembler;
|
||||
pub mod vasm_evaluator;
|
||||
pub mod vasm_parser;
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
use crate::ast::{Label, VASMLine};
|
||||
use crate::vasm_evaluator::{eval, EvalError, Scope};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AssembleError<'a> {
|
||||
EquResolveError(i32, &'a str, EvalError<'a>),
|
||||
EquDuplicateError(i32, &'a str),
|
||||
OrgResolveError(i32, EvalError<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Display for AssembleError<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AssembleError::EquResolveError(line, name, err) => {
|
||||
write!(f, "Cannot resolve .equ {} on line {}: {}", name, line, err)
|
||||
}
|
||||
AssembleError::EquDuplicateError(line, name) => {
|
||||
write!(f, "Duplicate .equ {} on line {}", name, line)
|
||||
}
|
||||
AssembleError::OrgResolveError(line, err) => {
|
||||
write!(f, "Cannot resolve .org on line {}: {}", line, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This will solve all the .equ directives and return a symbol table of them.
|
||||
/// .equ directives must be able to be solved in order, that is, in terms of
|
||||
/// only preceding .equ directives. Anything else is an error.
|
||||
pub fn solve_equs<'a>(lines: &[VASMLine<'a>]) -> Result<Scope<'a>, AssembleError<'a>> {
|
||||
let mut scope: Scope = Scope::new();
|
||||
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
if let VASMLine::Equ(Label(name), expr) = line {
|
||||
let value = eval(expr, line_num as i32, &line_nums, &scope)
|
||||
.map_err(|e| AssembleError::EquResolveError(line_num as i32, name, e))?;
|
||||
|
||||
if let Some(_old_value) = scope.insert(name, value) {
|
||||
return Err(AssembleError::EquDuplicateError(line_num as i32, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(scope)
|
||||
}
|
||||
|
||||
type LineLengths = BTreeMap<i32, usize>;
|
||||
type LineAddresses = BTreeMap<i32, i32>;
|
||||
|
||||
fn arg_length(val: i32) -> usize {
|
||||
if val < 0 {
|
||||
3
|
||||
} else if val < 256 {
|
||||
1
|
||||
} else if val < 65536 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
/// This figures out the instruction lengths. We'll do this naively; if we can't
|
||||
/// immediately tell that an instruction needs only a 0/1/2 byte argument (because it's
|
||||
/// a constant, or a .equ that we've solved, or something) then we'll assume it's a
|
||||
/// full 24-bit argument.
|
||||
///
|
||||
/// - Lines that don't represent output (.equ, .org, etc) have length 0
|
||||
/// - .db directives are either strings (set aside the length of the string), or
|
||||
/// numbers (set aside three bytes. If it's shorter than that it still may be a variable,
|
||||
/// which might grow to be larger).
|
||||
/// - Opcodes with no argument are 1 byte long.
|
||||
/// - Opcodes with an argument, if that argument is a constant or decidable solely with
|
||||
/// what we know right now (.equs), are however long that argument is. If we don't
|
||||
/// know right now (based on a label, say) then we'll set aside the full 3 bytes (so it's
|
||||
/// 4 bytes long, with the instruction byte).
|
||||
pub fn measure_instructions<'a>(lines: &[VASMLine<'a>], scope: &Scope) -> LineLengths {
|
||||
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
|
||||
let mut lengths = LineLengths::new();
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
match line {
|
||||
VASMLine::Instruction(_, _, None) => {
|
||||
lengths.insert(line_num, 1);
|
||||
}
|
||||
VASMLine::Instruction(_, _, Some(node)) => {
|
||||
let len = eval(node, line_num as i32, &line_nums, scope).map_or(3, arg_length);
|
||||
lengths.insert(line_num, len + 1);
|
||||
}
|
||||
VASMLine::Db(_, _) => {
|
||||
lengths.insert(line_num, 3);
|
||||
}
|
||||
VASMLine::StringDb(_, value) => {
|
||||
lengths.insert(line_num, value.len());
|
||||
}
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {
|
||||
lengths.insert(line_num, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
lengths
|
||||
}
|
||||
|
||||
/// Time to start placing labels. The tricky part here is the .org directives, which can have
|
||||
/// expressions as their arguments. We'll compromise a little bit and say that a .org directive
|
||||
/// can only refer to labels that precede it, so, you can use .orgs to generate (say) a jump table
|
||||
/// but still make it easy for me to figure out what refers to what.
|
||||
///
|
||||
/// We'll go through the lines, adding each one's length (calculated in measure_instructions) to it.
|
||||
/// If it has a label, we'll store that label's new value to the scope.
|
||||
///
|
||||
/// But, we'll skip labels that come before .equs: that would make every .equ set to its address,
|
||||
/// rather than the argument.
|
||||
fn place_labels<'a>(
|
||||
lines: &[VASMLine<'a>],
|
||||
scope: Scope<'a>,
|
||||
lengths: &LineLengths,
|
||||
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
|
||||
let mut scope = scope;
|
||||
let mut address = 0;
|
||||
let mut addresses = LineAddresses::new();
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
|
||||
if let VASMLine::Org(_, expr) = line {
|
||||
address = eval(expr, line_num, &addresses, &scope)
|
||||
.map_err(|err| AssembleError::OrgResolveError(line_num, err))?;
|
||||
addresses.insert(line_num, address);
|
||||
}
|
||||
|
||||
if let Some(Label(label)) = line.label() {
|
||||
if !scope.contains_key(label) {
|
||||
scope.insert(label, address as i32);
|
||||
}
|
||||
}
|
||||
|
||||
match line {
|
||||
VASMLine::Org(_, _) => {}
|
||||
_ => {
|
||||
addresses.insert(line_num, address);
|
||||
address += *lengths.get(&line_num).unwrap_or(&0) as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((addresses, scope))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::AssembleError::*;
|
||||
use super::EvalError::*;
|
||||
use super::*;
|
||||
use crate::ast::VASMLine;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
|
||||
fn parse<'a>(lines: &'a [&str]) -> Vec<VASMLine<'a>> {
|
||||
lines
|
||||
.iter()
|
||||
.map(|line| parse_vasm_line(*line).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn place_labels_pass<'a>(
|
||||
lines: &'a [&str],
|
||||
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
|
||||
let lines = parse(lines);
|
||||
let scope = solve_equs(&lines).unwrap();
|
||||
let lengths = measure_instructions(&lines, &scope);
|
||||
place_labels(&lines, scope, &lengths)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ 5+3"])),
|
||||
Ok([("blah", 8)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ 5", "foo: .equ 3"])),
|
||||
Ok([("blah", 5), ("foo", 3)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ 5", "foo: .equ blah + 7"])),
|
||||
Ok([("blah", 5), ("foo", 12)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["add", "blah: .equ 5"])),
|
||||
Ok([("blah", 5)].into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsolvable_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ 5", "foo: .equ banana"])),
|
||||
Err(EquResolveError(2, "foo", MissingLabel("banana")))
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ foo+3", "foo: .equ 7"])),
|
||||
Err(EquResolveError(1, "blah", MissingLabel("foo")))
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(&["blah: .equ 3", "blah: .equ 7"])),
|
||||
Err(EquDuplicateError(2, "blah"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lengths() {
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse(&["add", "add 1", "add 500", "add 70000", "add -7"]),
|
||||
&[].into()
|
||||
),
|
||||
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 4)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(&parse(&[".db 7", ".db \"hello\\0\""]), &[].into()),
|
||||
[(1, 3), (2, 6)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(&parse(&[".org 256", "blah:", "foo: .equ 7"]), &[].into()),
|
||||
[(1, 0), (2, 0), (3, 0)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse(&["add 2 + foo", "add 3 + blah", "jmpr @foo"]),
|
||||
&[("blah", 300)].into()
|
||||
),
|
||||
[(1, 4), (2, 3), (3, 4)].into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_place_labels() {
|
||||
assert_eq!(
|
||||
place_labels_pass(&["start: .org 256", "add", "dup"]),
|
||||
Ok((
|
||||
[(1, 256), (2, 256), (3, 257)].into(),
|
||||
[("start", 256)].into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(&["push 70000", "dup"]),
|
||||
Ok(([(1, 0), (2, 4)].into(), [].into()))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(&["start: .equ 256", "blah: .org start + 4", "add"]),
|
||||
Ok((
|
||||
[(1, 0), (2, 260), (3, 260)].into(),
|
||||
[("blah", 260), ("start", 256)].into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(&["start: .org 256", "blah: .org start + 10", "add"]),
|
||||
Ok((
|
||||
[(1, 256), (2, 266), (3, 266)].into(),
|
||||
[("blah", 266), ("start", 256)].into()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unresolvable_orgs() {
|
||||
assert_eq!(
|
||||
place_labels_pass(&[".org 0xffffff - blah"]),
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(&["blah: .org blah"]),
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
|
||||
);
|
||||
}
|
||||
}
|
||||
+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]
|
||||
|
||||
@@ -307,6 +307,32 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_label_exprs() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 2 + foo"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Number(2)),
|
||||
vec![(Operator::Add, Node::Label("foo"))]
|
||||
))
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add foo + 2"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Label("foo")),
|
||||
vec![(Operator::Add, Node::Number(2))]
|
||||
))
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_labels() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user