WIP, implementing macros

This commit is contained in:
2022-04-05 18:05:24 -05:00
parent 2a9a92a0c8
commit dec2d22b80
6 changed files with 436 additions and 155 deletions
+266 -60
View File
@@ -1,21 +1,23 @@
use crate::ast::{Label, VASMLine};
use crate::ast::{Label, Macro, Scope, VASMLine};
use crate::parse_error::ParseError;
use crate::vasm_evaluator::{eval, EvalError, Scope};
use crate::vasm_evaluator::{eval, EvalError};
use crate::vasm_parser::parse_vasm_line;
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, PartialEq)]
pub enum AssembleError<'a> {
ParseError(i32, ParseError<'a>),
EquResolveError(i32, &'a str, EvalError<'a>),
EquDuplicateError(i32, &'a str),
OrgResolveError(i32, EvalError<'a>),
ArgError(i32, EvalError<'a>),
pub enum AssembleError {
ParseError(usize, ParseError),
EquResolveError(usize, String, EvalError),
EquDuplicateError(usize, String),
OrgResolveError(usize, EvalError),
ArgError(usize, EvalError),
NoCode,
IncludeError(usize, String),
MacroError(usize),
}
impl<'a> Display for AssembleError<'a> {
impl Display for AssembleError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AssembleError::EquResolveError(line, name, err) => {
@@ -36,32 +38,140 @@ impl<'a> Display for AssembleError<'a> {
AssembleError::ParseError(line, err) => {
write!(f, "Parse error on line {}: {}", line, err)
}
AssembleError::IncludeError(line, file) => {
write!(f, "Cannot read \"{}\" on line {}", file, line)
}
AssembleError::MacroError(line) => {
write!(f, "Malformed macro control structure on line {}", line)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct Line {
line: VASMLine,
line_num: usize,
file: String,
}
#[derive(Debug, Clone, PartialEq)]
enum LoopType {
While,
Until,
}
#[derive(Debug, Clone, PartialEq)]
enum ControlStructure {
Target(String),
Loop(String, LoopType),
}
fn preprocess<'a, T, F>(iter: T, filename: String, include: &F) -> Result<Vec<Line>, AssembleError>
where
T: IntoIterator<Item = &'a str>,
F: Fn(String) -> Result<T, AssembleError>,
{
let mut current_sym = 0;
let mut gensym = || {
current_sym += 1;
format!("__gensym_{}", current_sym)
};
let mut all_lines = Vec::new();
let mut iter_stack = vec![iter.into_iter().enumerate()];
let mut filename_stack = vec![filename];
let mut control_stack = Vec::new();
while !iter_stack.is_empty() {
if let Some((line_idx, line)) = iter_stack.last_mut().unwrap().next() {
match parse_vasm_line(line)
.map_err(|err| AssembleError::ParseError(line_idx + 1, err))?
{
VASMLine::Macro(mac) => match mac {
Macro::Include(file) => {
filename_stack.push(file.clone());
iter_stack.push(include(file)?.into_iter().enumerate());
}
Macro::If => {
let label = gensym();
control_stack.push(ControlStructure::Target(label.clone()));
all_lines.push(Line {
line: parse_vasm_line(format!("brz @{}", label).as_str()).unwrap(),
line_num: line_idx + 1,
file: filename_stack.last().unwrap().clone(),
})
}
Macro::Unless => {
let label = gensym();
control_stack.push(ControlStructure::Target(label.clone()));
all_lines.push(Line {
line: parse_vasm_line(format!("brnz @{}", label).as_str()).unwrap(),
line_num: line_idx + 1,
file: filename_stack.last().unwrap().clone(),
})
}
Macro::Else => {
if let Some(ControlStructure::Target(old_end)) = control_stack.pop() {
let new_end = gensym();
control_stack.push(ControlStructure::Target(new_end.clone()));
all_lines.push(Line {
line: parse_vasm_line(format!("jmpr @{}", new_end).as_str())
.unwrap(),
line_num: line_idx + 1,
file: filename_stack.last().unwrap().clone(),
});
all_lines.push(Line {
line: VASMLine::LabelDef(Label(old_end)),
line_num: line_idx + 1,
file: filename_stack.last().unwrap().clone(),
})
} else {
return Err(AssembleError::MacroError(line_idx + 1));
}
}
Macro::While => {}
Macro::Until => {}
Macro::Do => {}
Macro::End => {}
},
normal_line => all_lines.push(Line {
line: normal_line,
line_num: line_idx + 1,
file: filename_stack.last().unwrap().clone(),
}),
}
} else {
iter_stack.pop();
filename_stack.pop();
}
}
Ok(all_lines)
}
/// 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.
fn solve_equs<'a>(lines: &Vec<VASMLine<'a>>) -> Result<Scope<'a>, AssembleError<'a>> {
fn solve_equs(lines: &[VASMLine]) -> Result<Scope, AssembleError> {
let mut scope: Scope = Scope::new();
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
for (line_idx, line) in lines.iter().enumerate() {
let line_num = (line_idx + 1) as i32;
let line_num = line_idx + 1;
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))?;
let value = eval(expr, line_num, &line_nums, &scope)
.map_err(|e| AssembleError::EquResolveError(line_num, name.to_string(), e))?;
if let Some(_old_value) = scope.insert(name, value) {
return Err(AssembleError::EquDuplicateError(line_num as i32, name));
if let Some(_old_value) = scope.insert(name.clone(), value) {
return Err(AssembleError::EquDuplicateError(line_num, name.to_string()));
}
}
}
Ok(scope)
}
type LineLengths = BTreeMap<i32, usize>;
type LineAddresses = BTreeMap<i32, i32>;
type LineLengths = BTreeMap<usize, usize>;
type LineAddresses = BTreeMap<usize, i32>;
fn arg_length(val: i32) -> usize {
if val < 0 {
@@ -89,17 +199,17 @@ fn arg_length(val: i32) -> usize {
/// 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).
fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLengths {
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
fn measure_instructions(lines: &[VASMLine], scope: &Scope) -> LineLengths {
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
let mut lengths = LineLengths::new();
for (line_idx, line) in lines.iter().enumerate() {
let line_num = (line_idx + 1) as i32;
let line_num = line_idx + 1;
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);
let len = eval(node, line_num, &line_nums, scope).map_or(3, arg_length);
lengths.insert(line_num, len + 1);
}
VASMLine::Db(_, _) => {
@@ -111,6 +221,7 @@ fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLen
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {
lengths.insert(line_num, 0);
}
VASMLine::Macro(_) => unreachable!(),
}
}
lengths
@@ -126,26 +237,26 @@ fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLen
///
/// 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: &Vec<VASMLine<'a>>,
scope: Scope<'a>,
fn place_labels(
lines: &[VASMLine],
scope: Scope,
lengths: &LineLengths,
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
) -> Result<(LineAddresses, Scope), AssembleError> {
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;
let line_num = line_idx + 1;
if let VASMLine::Org(_, expr) = line {
address = eval(&expr, line_num, &addresses, &scope)
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);
scope.insert(label.clone(), address as i32);
}
}
@@ -168,35 +279,33 @@ fn poke_word(code: &mut Vec<u8>, at: usize, word: i32) {
}
/// Find the lower and upper bounds where this program will place memory
fn code_bounds<'a>(
lines: &Vec<VASMLine<'a>>,
fn code_bounds(
lines: &[VASMLine],
line_addresses: &LineAddresses,
line_lengths: &LineLengths,
) -> Result<(usize, usize), AssembleError<'a>> {
) -> Result<(usize, usize), AssembleError> {
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 start = line_addresses[&(first_idx + 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)];
let end = line_addresses[&(last_idx + 1)] as usize;
let end_length = line_lengths[&(last_idx + 1)];
Ok((start, end + end_length - 1))
}
pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
) -> Result<Vec<u8>, AssembleError<'a>> {
pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Result<Vec<u8>, AssembleError> {
let mut parsed = Vec::new();
for (line_idx, line) in lines.into_iter().enumerate() {
let line_num = (line_idx + 1) as i32;
let line_num = line_idx + 1;
parsed.push(parse_vasm_line(line).map_err(|err| AssembleError::ParseError(line_num, err))?)
}
@@ -218,8 +327,8 @@ pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(
///
/// 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.
fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<'a>> {
let lines: Vec<VASMLine<'a>> = lines.into_iter().collect();
fn generate_code(lines: Vec<VASMLine>) -> Result<Vec<u8>, AssembleError> {
let lines: Vec<VASMLine> = 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)?;
@@ -229,7 +338,7 @@ fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<
let mut current_addr = start;
for (line_idx, line) in lines.iter().enumerate() {
let line_num = (line_idx + 1) as i32;
let line_num = line_idx + 1;
match line {
VASMLine::Instruction(_, opcode, None) => {
code[current_addr] = u8::from(*opcode) << 2;
@@ -267,6 +376,7 @@ fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<
current_addr = line_addresses[&(line_num + 1)] as usize;
}
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {}
VASMLine::Macro(_) => unreachable!(),
}
}
@@ -278,11 +388,12 @@ mod test {
use super::AssembleError::*;
use super::EvalError::*;
use super::*;
use crate::ast::VASMLine;
use crate::ast::{Node, VASMLine};
use crate::parse_error;
use crate::vasm_parser::parse_vasm_line;
use vcore::opcodes::Opcode;
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine<'a>> {
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
lines
.into_iter()
.map(|line| parse_vasm_line(line).unwrap())
@@ -291,7 +402,7 @@ mod test {
fn place_labels_pass<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
) -> Result<(LineAddresses, Scope), AssembleError> {
let parsed_lines = parse(lines);
let scope = solve_equs(&parsed_lines).unwrap();
let lengths = measure_instructions(&parsed_lines, &scope);
@@ -300,7 +411,7 @@ mod test {
fn bounds<'a, T: IntoIterator<Item = &'a str>>(
lines: T,
) -> Result<(usize, usize), AssembleError<'a>> {
) -> Result<(usize, usize), AssembleError> {
let parsed_lines = parse(lines);
let scope = solve_equs(&parsed_lines).unwrap();
let lengths = measure_instructions(&parsed_lines, &scope);
@@ -312,19 +423,19 @@ mod test {
fn test_equs() {
assert_eq!(
solve_equs(&parse(["blah: .equ 5+3"])),
Ok([("blah", 8)].into())
Ok([("blah".to_string(), 8)].into())
);
assert_eq!(
solve_equs(&parse(["blah: .equ 5", "foo: .equ 3"])),
Ok([("blah", 5), ("foo", 3)].into())
Ok([("blah".to_string(), 5), ("foo".to_string(), 3)].into())
);
assert_eq!(
solve_equs(&parse(["blah: .equ 5", "foo: .equ blah + 7"])),
Ok([("blah", 5), ("foo", 12)].into())
Ok([("blah".to_string(), 5), ("foo".to_string(), 12)].into())
);
assert_eq!(
solve_equs(&parse(["add", "blah: .equ 5"])),
Ok([("blah", 5)].into())
Ok([("blah".to_string(), 5)].into())
);
}
@@ -332,15 +443,23 @@ mod test {
fn test_unsolvable_equs() {
assert_eq!(
solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])),
Err(EquResolveError(2, "foo", MissingLabel("banana")))
Err(EquResolveError(
2,
"foo".into(),
MissingLabel("banana".into())
))
);
assert_eq!(
solve_equs(&parse(["blah: .equ foo+3", "foo: .equ 7"])),
Err(EquResolveError(1, "blah", MissingLabel("foo")))
Err(EquResolveError(
1,
"blah".into(),
MissingLabel("foo".into())
))
);
assert_eq!(
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
Err(EquDuplicateError(2, "blah"))
Err(EquDuplicateError(2, "blah".into()))
);
}
@@ -364,7 +483,7 @@ mod test {
assert_eq!(
measure_instructions(
&parse(["add 2 + foo", "add 3 + blah", "jmpr @foo"]),
&[("blah", 300)].into()
&[("blah".to_string(), 300)].into()
),
[(1, 4), (2, 3), (3, 4)].into()
);
@@ -376,7 +495,7 @@ mod test {
place_labels_pass(["start: .org 256", "add", "dup"]),
Ok((
[(1, 256), (2, 256), (3, 257)].into(),
[("start", 256)].into()
[("start".to_string(), 256)].into()
))
);
assert_eq!(
@@ -387,14 +506,14 @@ mod test {
place_labels_pass(["start: .equ 256", "blah: .org start + 4", "add"]),
Ok((
[(1, 0), (2, 260), (3, 260)].into(),
[("blah", 260), ("start", 256)].into()
[("blah".to_string(), 260), ("start".to_string(), 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()
[("blah".to_string(), 266), ("start".to_string(), 256)].into()
))
);
}
@@ -403,11 +522,11 @@ mod test {
fn test_unresolvable_orgs() {
assert_eq!(
place_labels_pass([".org 0xffffff - blah"]),
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
);
assert_eq!(
place_labels_pass(["blah: .org blah"]),
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
);
}
@@ -455,8 +574,95 @@ mod test {
assemble(["apple"]),
Err(ParseError(
1,
parse_error::ParseError::InvalidInstruction("apple")
parse_error::ParseError::InvalidInstruction("apple".into())
))
);
}
#[test]
fn test_preprocess() {
let include = |name: String| Err(AssembleError::IncludeError(1, name));
let lines = vec!["add"];
assert_eq!(
preprocess(lines, "blah".to_string(), &include),
Ok(vec![Line {
line_num: 1,
file: "blah".to_string(),
line: VASMLine::Instruction(None, Opcode::Add, None)
}])
);
}
#[test]
fn test_preprocess_include() {
let include = |_name: String| Ok(vec!["sub"]);
let lines = vec!["#include \"foo\"", "add"];
assert_eq!(
preprocess(lines, "blah".to_string(), &include),
Ok(vec![
Line {
line_num: 1,
file: "foo".to_string(),
line: VASMLine::Instruction(None, Opcode::Sub, None)
},
Line {
line_num: 2,
file: "blah".to_string(),
line: VASMLine::Instruction(None, Opcode::Add, None)
}
])
);
}
#[test]
fn test_preprocess_if() {
let include = |_name: String| Ok(vec![]);
let lines = vec!["#if"];
assert_eq!(
preprocess(lines, "blah".to_string(), &include),
Ok(vec![Line {
line_num: 1,
file: "blah".to_string(),
line: VASMLine::Instruction(
None,
Opcode::Brz,
Some(Node::relative_label("__gensym_1"))
)
}])
)
}
#[test]
fn test_preprocess_else() {
let include = |_name: String| Ok(vec![]);
let lines = vec!["#if", "#else"];
assert_eq!(
preprocess(lines, "blah".to_string(), &include),
Ok(vec![
Line {
line_num: 1,
file: "blah".to_string(),
line: VASMLine::Instruction(
None,
Opcode::Brz,
Some(Node::relative_label("__gensym_1"))
)
},
Line {
line_num: 2,
file: "blah".to_string(),
line: VASMLine::Instruction(
None,
Opcode::Jmpr,
Some(Node::relative_label("__gensym_2"))
)
},
Line {
line_num: 2,
file: "blah".to_string(),
line: VASMLine::LabelDef(Label("__gensym_1".to_string()))
}
])
)
}
}