Files
vulcan/vasm/src/vasm_assembler.rs
T

463 lines
16 KiB
Rust
Raw Normal View History

2022-03-27 23:10:08 -05:00
use crate::ast::{Label, VASMLine};
2022-04-02 00:15:33 -05:00
use crate::parse_error::ParseError;
2022-03-27 23:10:08 -05:00
use crate::vasm_evaluator::{eval, EvalError, Scope};
2022-04-02 00:15:33 -05:00
use crate::vasm_parser::parse_vasm_line;
2022-03-27 23:10:08 -05:00
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, PartialEq)]
pub enum AssembleError<'a> {
2022-04-02 00:15:33 -05:00
ParseError(i32, ParseError<'a>),
2022-03-27 23:10:08 -05:00
EquResolveError(i32, &'a str, EvalError<'a>),
EquDuplicateError(i32, &'a str),
OrgResolveError(i32, EvalError<'a>),
2022-04-01 18:38:46 -05:00
ArgError(i32, EvalError<'a>),
2022-04-01 22:00:18 -05:00
NoCode,
2022-03-27 23:10:08 -05:00
}
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)
}
2022-04-01 18:38:46 -05:00
AssembleError::ArgError(line, err) => {
write!(f, "Cannot calculate argument on line {}: {}", line, err)
}
2022-04-01 22:00:18 -05:00
AssembleError::NoCode => {
write!(f, "No output would be generated by this code")
}
2022-04-02 00:15:33 -05:00
AssembleError::ParseError(line, err) => {
write!(f, "Parse error on line {}: {}", line, err)
}
2022-03-27 23:10:08 -05:00
}
}
}
/// 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.
2022-04-02 00:15:33 -05:00
fn solve_equs<'a>(lines: &Vec<VASMLine<'a>>) -> Result<Scope<'a>, AssembleError<'a>> {
2022-03-27 23:10:08 -05:00
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 {
2022-04-02 00:15:33 -05:00
let value = eval(&expr, line_num as i32, &line_nums, &scope)
2022-03-27 23:10:08 -05:00
.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).
2022-04-02 00:15:33 -05:00
fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLengths {
2022-03-27 23:10:08 -05:00
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)) => {
2022-04-02 00:15:33 -05:00
let len = eval(&node, line_num as i32, &line_nums, scope).map_or(3, arg_length);
2022-03-27 23:10:08 -05:00
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>(
2022-04-02 00:15:33 -05:00
lines: &Vec<VASMLine<'a>>,
2022-03-27 23:10:08 -05:00
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 {
2022-04-02 00:15:33 -05:00
address = eval(&expr, line_num, &addresses, &scope)
2022-03-27 23:10:08 -05:00
.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))
}
2022-04-01 22:00:18 -05:00
fn poke_word(code: &mut Vec<u8>, at: usize, word: i32) {
let [low, mid, high, _] = word.to_le_bytes();
code[at] = low;
code[at + 1] = mid;
code[at + 2] = high;
}
/// Find the lower and upper bounds where this program will place memory
fn code_bounds<'a>(
2022-04-02 00:15:33 -05:00
lines: &Vec<VASMLine<'a>>,
2022-04-01 22:00:18 -05:00
line_addresses: &LineAddresses,
line_lengths: &LineLengths,
) -> Result<(usize, usize), AssembleError<'a>> {
let mut actual_lines = lines
.iter()
.enumerate()
.filter(|(_, line)| !line.zero_length());
let (first_idx, _) = actual_lines.next().ok_or(AssembleError::NoCode)?;
let start = line_addresses[&(first_idx as i32 + 1)] as usize;
let actual_lines = lines
.iter()
.enumerate()
.filter(|(_, line)| !line.zero_length());
let (last_idx, _) = actual_lines.last().unwrap();
let end = line_addresses[&(last_idx as i32 + 1)] as usize;
let end_length = line_lengths[&(last_idx as i32 + 1)];
Ok((start, end + end_length - 1))
}
2022-04-02 00:15:33 -05:00
pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
) -> Result<Vec<u8>, AssembleError<'a>> {
let mut parsed = Vec::new();
for (line_idx, line) in lines.into_iter().enumerate() {
let line_num = (line_idx + 1) as i32;
parsed.push(parse_vasm_line(line).map_err(|err| AssembleError::ParseError(line_num, err))?)
}
generate_code(parsed)
}
2022-04-01 22:00:18 -05:00
/// At this point all lines have addresses and lengths, and all arguments are reduced to
/// numeric constants. It's time to generate code.
///
/// - Make an array of zeroes, length (end - start)
/// - Go through the list of instructions, generating code for them:
/// - .db instructions turn into byte values starting at `address - start`
/// - Opcodes turn into instruction bytes at `address - start` followed (maybe) by
/// arguments.
2022-04-02 00:15:33 -05:00
/// - .orgs cause us to skip ahead some in the output
2022-04-01 22:00:18 -05:00
///
/// The instruction bytes are formed of six bits defining the instruction followed by two
/// bits denoting how many bytes of argument follow it.
///
/// Vulcan is a little-endian architecture: multi-byte arguments / .dbs will store the
/// least-significant byte at the lowest address, then the more significant bytes following.
2022-04-02 00:15:33 -05:00
fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<'a>> {
let lines: Vec<VASMLine<'a>> = lines.into_iter().collect();
let scope = solve_equs(&lines)?;
let line_lengths = measure_instructions(&lines, &scope);
let (line_addresses, scope) = place_labels(&lines, scope, &line_lengths)?;
let (start, end) = code_bounds(&lines, &line_addresses, &line_lengths)?;
2022-04-01 22:00:18 -05:00
let mut code = vec![0u8; end - start + 1];
let mut current_addr = start;
for (line_idx, line) in lines.iter().enumerate() {
let line_num = (line_idx + 1) as i32;
match line {
VASMLine::Instruction(_, opcode, None) => {
code[current_addr] = u8::from(*opcode) << 2;
current_addr += 1;
}
2022-04-02 00:15:33 -05:00
VASMLine::Instruction(_, opcode, Some(arg)) => {
let arg = eval(arg, line_num, &line_addresses, &scope)
.map_err(|err| AssembleError::ArgError(line_num, err))?;
2022-04-01 22:00:18 -05:00
let len = arg_length(arg);
let instr = (u8::from(*opcode) << 2) + len as u8;
code[current_addr - start] = instr;
let [low, mid, high, _] = arg.to_le_bytes();
code[current_addr - start + 1] = low;
if len > 1 {
code[current_addr - start + 2] = mid
}
if len > 2 {
code[current_addr - start + 3] = high
}
current_addr += len + 1;
}
2022-04-02 00:15:33 -05:00
VASMLine::Db(_, arg) => {
let arg = eval(arg, line_num, &line_addresses, &scope)
.map_err(|err| AssembleError::ArgError(line_num, err))?;
2022-04-01 22:00:18 -05:00
poke_word(&mut code, current_addr - start, arg);
current_addr += 3;
}
VASMLine::StringDb(_, string) => {
for ch in string.as_bytes() {
code[current_addr - start] = *ch;
current_addr += 1;
}
}
VASMLine::Org(_, _) => {
current_addr = line_addresses[&(line_num + 1)] as usize;
}
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {}
}
}
Ok(code)
}
2022-03-27 23:10:08 -05:00
#[cfg(test)]
mod test {
use super::AssembleError::*;
use super::EvalError::*;
use super::*;
use crate::ast::VASMLine;
2022-04-02 00:15:33 -05:00
use crate::parse_error;
2022-03-27 23:10:08 -05:00
use crate::vasm_parser::parse_vasm_line;
2022-04-02 00:15:33 -05:00
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine<'a>> {
2022-03-27 23:10:08 -05:00
lines
2022-04-02 00:15:33 -05:00
.into_iter()
.map(|line| parse_vasm_line(line).unwrap())
2022-03-27 23:10:08 -05:00
.collect()
}
2022-04-02 00:15:33 -05:00
fn place_labels_pass<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
2022-03-27 23:10:08 -05:00
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
2022-04-02 00:15:33 -05:00
let parsed_lines = parse(lines);
let scope = solve_equs(&parsed_lines).unwrap();
let lengths = measure_instructions(&parsed_lines, &scope);
place_labels(&parsed_lines, scope, &lengths)
2022-04-01 22:00:18 -05:00
}
2022-04-02 00:15:33 -05:00
fn bounds<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
) -> Result<(usize, usize), AssembleError<'a>> {
let parsed_lines = parse(lines);
let scope = solve_equs(&parsed_lines).unwrap();
let lengths = measure_instructions(&parsed_lines, &scope);
let (line_addresses, _scope) = place_labels(&parsed_lines, scope, &lengths)?;
code_bounds(&parsed_lines, &line_addresses, &lengths)
2022-04-01 18:38:46 -05:00
}
2022-03-27 23:10:08 -05:00
#[test]
fn test_equs() {
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ 5+3"])),
2022-03-27 23:10:08 -05:00
Ok([("blah", 8)].into())
);
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ 5", "foo: .equ 3"])),
2022-03-27 23:10:08 -05:00
Ok([("blah", 5), ("foo", 3)].into())
);
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ 5", "foo: .equ blah + 7"])),
2022-03-27 23:10:08 -05:00
Ok([("blah", 5), ("foo", 12)].into())
);
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["add", "blah: .equ 5"])),
2022-03-27 23:10:08 -05:00
Ok([("blah", 5)].into())
);
}
#[test]
fn test_unsolvable_equs() {
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])),
2022-03-27 23:10:08 -05:00
Err(EquResolveError(2, "foo", MissingLabel("banana")))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ foo+3", "foo: .equ 7"])),
2022-03-27 23:10:08 -05:00
Err(EquResolveError(1, "blah", MissingLabel("foo")))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
2022-03-27 23:10:08 -05:00
Err(EquDuplicateError(2, "blah"))
);
}
#[test]
fn test_lengths() {
assert_eq!(
measure_instructions(
2022-04-02 00:15:33 -05:00
&parse(["add", "add 1", "add 500", "add 70000", "add -7"]),
2022-03-27 23:10:08 -05:00
&[].into()
),
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 4)].into()
);
assert_eq!(
2022-04-02 00:15:33 -05:00
measure_instructions(&parse([".db 7", ".db \"hello\\0\""]), &[].into()),
2022-03-27 23:10:08 -05:00
[(1, 3), (2, 6)].into()
);
assert_eq!(
2022-04-02 00:15:33 -05:00
measure_instructions(&parse([".org 256", "blah:", "foo: .equ 7"]), &[].into()),
2022-03-27 23:10:08 -05:00
[(1, 0), (2, 0), (3, 0)].into()
);
assert_eq!(
measure_instructions(
2022-04-02 00:15:33 -05:00
&parse(["add 2 + foo", "add 3 + blah", "jmpr @foo"]),
2022-03-27 23:10:08 -05:00
&[("blah", 300)].into()
),
[(1, 4), (2, 3), (3, 4)].into()
);
}
#[test]
fn test_place_labels() {
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass(["start: .org 256", "add", "dup"]),
2022-03-27 23:10:08 -05:00
Ok((
[(1, 256), (2, 256), (3, 257)].into(),
[("start", 256)].into()
))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass(["push 70000", "dup"]),
2022-03-27 23:10:08 -05:00
Ok(([(1, 0), (2, 4)].into(), [].into()))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass(["start: .equ 256", "blah: .org start + 4", "add"]),
2022-03-27 23:10:08 -05:00
Ok((
[(1, 0), (2, 260), (3, 260)].into(),
[("blah", 260), ("start", 256)].into()
))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass(["start: .org 256", "blah: .org start + 10", "add"]),
2022-03-27 23:10:08 -05:00
Ok((
[(1, 256), (2, 266), (3, 266)].into(),
[("blah", 266), ("start", 256)].into()
))
);
}
#[test]
fn test_unresolvable_orgs() {
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass([".org 0xffffff - blah"]),
2022-03-27 23:10:08 -05:00
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
place_labels_pass(["blah: .org blah"]),
2022-03-27 23:10:08 -05:00
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
);
}
2022-04-01 18:38:46 -05:00
2022-04-01 22:00:18 -05:00
#[test]
fn test_bounds() {
2022-04-02 00:15:33 -05:00
assert_eq!(bounds(["add"]), Ok((0, 0)));
assert_eq!(bounds(["add -4"]), Ok((0, 3)));
assert_eq!(bounds(["add 7"]), Ok((0, 1)));
assert_eq!(bounds([".org 0x400", "add"]), Ok((1024, 1024)));
2022-04-01 22:00:18 -05:00
assert_eq!(
2022-04-02 00:15:33 -05:00
bounds(["start: .equ 1024", ".org start", "add"]),
2022-04-01 22:00:18 -05:00
Ok((1024, 1024))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
bounds([".org 0x400", "add", ".org 0x800"]),
2022-04-01 22:00:18 -05:00
Ok((1024, 1024))
);
assert_eq!(
2022-04-02 00:15:33 -05:00
bounds([".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]),
2022-04-01 22:00:18 -05:00
Ok((1024, 2050))
);
2022-04-02 00:15:33 -05:00
assert_eq!(bounds([]), Err(AssembleError::NoCode));
assert_eq!(bounds([".org 0x400"]), Err(AssembleError::NoCode));
2022-04-01 22:00:18 -05:00
assert_eq!(
2022-04-02 00:15:33 -05:00
bounds(["foo: .equ 3", ".org 0x400"]),
2022-04-01 22:00:18 -05:00
Err(AssembleError::NoCode)
);
}
#[test]
fn test_generate_code() {
2022-04-02 00:15:33 -05:00
assert_eq!(generate_code(parse(["add"])), Ok(vec![4]));
assert_eq!(
generate_code(parse([".org 0x400", "add 7"])),
Ok(vec![5, 7])
);
assert_eq!(generate_code(parse([".db 57"])), Ok(vec![57, 0, 0]));
assert_eq!(generate_code(parse([".db \"AZ\0\""])), Ok(vec![65, 90, 0]));
}
#[test]
fn test_assemble() {
assert_eq!(assemble(["add"]), Ok(vec![4]));
assert_eq!(
assemble(["apple"]),
Err(ParseError(
1,
parse_error::ParseError::InvalidInstruction("apple")
))
);
2022-04-01 22:00:18 -05:00
}
2022-03-27 23:10:08 -05:00
}