Preprocessor refactoring
This commit is contained in:
@@ -7,3 +7,4 @@ pub mod parse_error;
|
|||||||
pub mod vasm_assembler;
|
pub mod vasm_assembler;
|
||||||
pub mod vasm_evaluator;
|
pub mod vasm_evaluator;
|
||||||
pub mod vasm_parser;
|
pub mod vasm_parser;
|
||||||
|
pub mod vasm_preprocessor;
|
||||||
|
|||||||
@@ -23,3 +23,69 @@ impl Display for ParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
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 Display for AssembleError {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
AssembleError::ArgError(line, err) => {
|
||||||
|
write!(f, "Cannot calculate argument on line {}: {}", line, err)
|
||||||
|
}
|
||||||
|
AssembleError::NoCode => {
|
||||||
|
write!(f, "No output would be generated by this code")
|
||||||
|
}
|
||||||
|
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)]
|
||||||
|
pub enum EvalError {
|
||||||
|
MissingLabel(String),
|
||||||
|
UnknownAddress(usize),
|
||||||
|
OffsetError(usize, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EvalError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
EvalError::MissingLabel(label) => write!(f, "Unable to resolve label {}", label),
|
||||||
|
EvalError::UnknownAddress(line_num) => write!(
|
||||||
|
f,
|
||||||
|
"Unable to calculate starting address of line {}",
|
||||||
|
line_num
|
||||||
|
),
|
||||||
|
EvalError::OffsetError(line_num, offset) => {
|
||||||
|
write!(f, "Invalid line offset {} on line {}", offset, line_num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+7
-369
@@ -1,208 +1,8 @@
|
|||||||
use crate::ast::{Label, Macro, Scope, VASMLine};
|
use crate::ast::{Label, Scope, VASMLine};
|
||||||
use crate::parse_error::ParseError;
|
use crate::parse_error::AssembleError;
|
||||||
use crate::vasm_evaluator::{eval, EvalError};
|
use crate::vasm_evaluator::eval;
|
||||||
use crate::vasm_parser::parse_vasm_line;
|
use crate::vasm_parser::parse_vasm_line;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
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 Display for AssembleError {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
AssembleError::ArgError(line, err) => {
|
|
||||||
write!(f, "Cannot calculate argument on line {}: {}", line, err)
|
|
||||||
}
|
|
||||||
AssembleError::NoCode => {
|
|
||||||
write!(f, "No output would be generated by this code")
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Macro> for LoopType {
|
|
||||||
fn from(mac: Macro) -> Self {
|
|
||||||
match mac {
|
|
||||||
Macro::While => LoopType::While,
|
|
||||||
Macro::Until => LoopType::Until,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
enum ControlStructure {
|
|
||||||
Target(String),
|
|
||||||
Loop(String, LoopType),
|
|
||||||
LoopDo(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
let label = gensym();
|
|
||||||
control_stack.push(ControlStructure::Loop(label.clone(), mac.into()));
|
|
||||||
all_lines.push(Line {
|
|
||||||
line: VASMLine::LabelDef(Label(label)),
|
|
||||||
line_num: line_idx + 1,
|
|
||||||
file: filename_stack.last().unwrap().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Macro::Do => {
|
|
||||||
if let Some(ControlStructure::Loop(label, loop_type)) = control_stack.pop()
|
|
||||||
{
|
|
||||||
let after = gensym();
|
|
||||||
control_stack.push(ControlStructure::LoopDo(label, after.clone()));
|
|
||||||
let instr = match loop_type {
|
|
||||||
LoopType::While => "brz",
|
|
||||||
LoopType::Until => "brnz",
|
|
||||||
};
|
|
||||||
all_lines.push(Line {
|
|
||||||
line: parse_vasm_line(format!("{} @{}", instr, after).as_str())
|
|
||||||
.unwrap(),
|
|
||||||
line_num: line_idx + 1,
|
|
||||||
file: filename_stack.last().unwrap().clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Err(AssembleError::MacroError(line_idx + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Macro::End => match control_stack.pop() {
|
|
||||||
Some(ControlStructure::Target(label)) => all_lines.push(Line {
|
|
||||||
line: VASMLine::LabelDef(Label::from(label.as_str())),
|
|
||||||
line_num: line_idx + 1,
|
|
||||||
file: filename_stack.last().unwrap().clone(),
|
|
||||||
}),
|
|
||||||
Some(ControlStructure::LoopDo(start, after)) => {
|
|
||||||
all_lines.push(Line {
|
|
||||||
line: parse_vasm_line(format!("jmpr @{}", start).as_str()).unwrap(),
|
|
||||||
line_num: line_idx + 1,
|
|
||||||
file: filename_stack.last().unwrap().clone(),
|
|
||||||
});
|
|
||||||
all_lines.push(Line {
|
|
||||||
line: VASMLine::LabelDef(Label::from(after.as_str())),
|
|
||||||
line_num: line_idx + 1,
|
|
||||||
file: filename_stack.last().unwrap().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => return Err(AssembleError::MacroError(line_idx + 1)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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.
|
/// 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
|
/// .equ directives must be able to be solved in order, that is, in terms of
|
||||||
@@ -440,12 +240,11 @@ fn generate_code(lines: Vec<VASMLine>) -> Result<Vec<u8>, AssembleError> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::AssembleError::*;
|
use super::AssembleError::*;
|
||||||
use super::EvalError::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::{Node, VASMLine};
|
use crate::ast::VASMLine;
|
||||||
use crate::parse_error;
|
use crate::parse_error;
|
||||||
|
use crate::parse_error::EvalError::*;
|
||||||
use crate::vasm_parser::parse_vasm_line;
|
use crate::vasm_parser::parse_vasm_line;
|
||||||
use vcore::opcodes::Opcode;
|
|
||||||
|
|
||||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
|
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
|
||||||
lines
|
lines
|
||||||
@@ -576,11 +375,11 @@ mod test {
|
|||||||
fn test_unresolvable_orgs() {
|
fn test_unresolvable_orgs() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
place_labels_pass([".org 0xffffff - blah"]),
|
place_labels_pass([".org 0xffffff - blah"]),
|
||||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
|
Err(OrgResolveError(1, MissingLabel("blah".into())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
place_labels_pass(["blah: .org blah"]),
|
place_labels_pass(["blah: .org blah"]),
|
||||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
|
Err(OrgResolveError(1, MissingLabel("blah".into())))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,165 +431,4 @@ mod test {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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_end() {
|
|
||||||
let include = |_name: String| Ok(vec![]);
|
|
||||||
let lines = vec!["#if", "#end"];
|
|
||||||
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::LabelDef(Label::from("__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()))
|
|
||||||
}
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preprocess_while() {
|
|
||||||
let include = |_name: String| Ok(vec![]);
|
|
||||||
let lines = vec!["#while"];
|
|
||||||
assert_eq!(
|
|
||||||
preprocess(lines, "blah".to_string(), &include),
|
|
||||||
Ok(vec![Line {
|
|
||||||
line_num: 1,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::LabelDef(Label("__gensym_1".to_string()))
|
|
||||||
}])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preprocess_until() {
|
|
||||||
let include = |_name: String| Ok(vec![]);
|
|
||||||
let lines = vec!["#until"];
|
|
||||||
assert_eq!(
|
|
||||||
preprocess(lines, "blah".to_string(), &include),
|
|
||||||
Ok(vec![Line {
|
|
||||||
line_num: 1,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::LabelDef(Label("__gensym_1".to_string()))
|
|
||||||
}])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preprocess_do_end() {
|
|
||||||
let include = |_name: String| Ok(vec![]);
|
|
||||||
let lines = vec!["#while", "#do", "#end"];
|
|
||||||
assert_eq!(
|
|
||||||
preprocess(lines, "blah".to_string(), &include),
|
|
||||||
Ok(vec![
|
|
||||||
Line {
|
|
||||||
line_num: 1,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::LabelDef(Label("__gensym_1".to_string()))
|
|
||||||
},
|
|
||||||
Line {
|
|
||||||
line_num: 2,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::Instruction(
|
|
||||||
None,
|
|
||||||
Opcode::Brz,
|
|
||||||
Some(Node::RelativeLabel("__gensym_2".to_string()))
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Line {
|
|
||||||
line_num: 3,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::Instruction(
|
|
||||||
None,
|
|
||||||
Opcode::Jmpr,
|
|
||||||
Some(Node::RelativeLabel("__gensym_1".to_string()))
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Line {
|
|
||||||
line_num: 3,
|
|
||||||
file: "blah".to_string(),
|
|
||||||
line: VASMLine::LabelDef(Label::from("__gensym_2"))
|
|
||||||
}
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,6 @@
|
|||||||
use crate::ast::{Node, Operator, Scope};
|
use crate::ast::{Node, Operator, Scope};
|
||||||
|
use crate::parse_error::EvalError;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum EvalError {
|
|
||||||
MissingLabel(String),
|
|
||||||
UnknownAddress(usize),
|
|
||||||
OffsetError(usize, i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for EvalError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
EvalError::MissingLabel(label) => write!(f, "Unable to resolve label {}", label),
|
|
||||||
EvalError::UnknownAddress(line_num) => write!(
|
|
||||||
f,
|
|
||||||
"Unable to calculate starting address of line {}",
|
|
||||||
line_num
|
|
||||||
),
|
|
||||||
EvalError::OffsetError(line_num, offset) => {
|
|
||||||
write!(f, "Invalid line offset {} on line {}", offset, line_num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn offset_line(line_num: usize, offset: i32) -> Result<usize, EvalError> {
|
fn offset_line(line_num: usize, offset: i32) -> Result<usize, EvalError> {
|
||||||
if (line_num as i32) + offset < 0 {
|
if (line_num as i32) + offset < 0 {
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
use crate::ast::{Macro, VASMLine};
|
||||||
|
use crate::parse_error::AssembleError;
|
||||||
|
use crate::vasm_parser::parse_vasm_line;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::iter::Enumerate;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Line {
|
||||||
|
line: VASMLine,
|
||||||
|
line_num: usize,
|
||||||
|
file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum LoopType {
|
||||||
|
While,
|
||||||
|
Until,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Macro> for LoopType {
|
||||||
|
fn from(mac: Macro) -> Self {
|
||||||
|
match mac {
|
||||||
|
Macro::While => LoopType::While,
|
||||||
|
Macro::Until => LoopType::Until,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum ControlStructure {
|
||||||
|
Target(String),
|
||||||
|
Loop(String, LoopType),
|
||||||
|
LoopDo(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LineSource<
|
||||||
|
'a,
|
||||||
|
T: IntoIterator<Item = &'a str>,
|
||||||
|
F: Fn(String) -> Result<T, AssembleError>,
|
||||||
|
> {
|
||||||
|
generated_lines: VecDeque<Line>,
|
||||||
|
current_line: usize,
|
||||||
|
filename_stack: Vec<String>,
|
||||||
|
current_sym: usize,
|
||||||
|
control_stack: Vec<ControlStructure>,
|
||||||
|
iter_stack: Vec<Enumerate<<T as IntoIterator>::IntoIter>>,
|
||||||
|
include: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: IntoIterator<Item = &'a str>, F: Fn(String) -> Result<T, AssembleError>> Iterator
|
||||||
|
for LineSource<'a, T, F>
|
||||||
|
{
|
||||||
|
type Item = Result<Line, AssembleError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// Have we any macro-generated lines?
|
||||||
|
if let Some(line) = self.generated_lines.pop_front() {
|
||||||
|
return Some(Ok(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we out of lines in total?
|
||||||
|
if self.iter_stack.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch a line from the top iterator
|
||||||
|
if let Some((line_idx, line)) = self.iter_stack.last_mut().unwrap().next() {
|
||||||
|
self.current_line = line_idx + 1;
|
||||||
|
// Try and parse it
|
||||||
|
return match parse_vasm_line(line) {
|
||||||
|
// We failed to parse it
|
||||||
|
Err(err) => Some(Err(AssembleError::ParseError(self.current_line, err))),
|
||||||
|
|
||||||
|
// It's a macro, so do it and then try again
|
||||||
|
Ok(VASMLine::Macro(mac)) => {
|
||||||
|
self.handle_macro(mac);
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(normal_line) => Some(Ok(Line {
|
||||||
|
line: normal_line,
|
||||||
|
line_num: self.current_line,
|
||||||
|
file: self.filename_stack.last().unwrap().clone(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the iterator stack and try again
|
||||||
|
self.iter_stack.pop();
|
||||||
|
self.filename_stack.pop();
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: IntoIterator<Item = &'a str>, F: Fn(String) -> Result<T, AssembleError>>
|
||||||
|
LineSource<'a, T, F>
|
||||||
|
{
|
||||||
|
pub fn new(file: &str, lines: T, include: F) -> Self {
|
||||||
|
LineSource {
|
||||||
|
generated_lines: VecDeque::new(),
|
||||||
|
current_line: 0,
|
||||||
|
filename_stack: vec![file.to_string()],
|
||||||
|
current_sym: 0,
|
||||||
|
control_stack: vec![],
|
||||||
|
iter_stack: vec![lines.into_iter().enumerate()],
|
||||||
|
include,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(&mut self, line: String) {
|
||||||
|
self.generated_lines.push_back(Line {
|
||||||
|
line: parse_vasm_line(line.as_str()).unwrap(),
|
||||||
|
line_num: self.current_line,
|
||||||
|
file: self.filename_stack.last().unwrap().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gensym(&mut self) -> String {
|
||||||
|
self.current_sym += 1;
|
||||||
|
format!("__gensym_{}", self.current_sym)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_macro(&mut self, mac: Macro) -> Option<AssembleError> {
|
||||||
|
match mac {
|
||||||
|
Macro::Include(file) => {
|
||||||
|
let inc_result = (self.include)(file.clone());
|
||||||
|
if let Ok(it) = inc_result {
|
||||||
|
self.filename_stack.push(file);
|
||||||
|
self.iter_stack.push(it.into_iter().enumerate());
|
||||||
|
} else {
|
||||||
|
return inc_result.err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::If => {
|
||||||
|
let label = self.gensym();
|
||||||
|
self.control_stack
|
||||||
|
.push(ControlStructure::Target(label.clone()));
|
||||||
|
self.emit(format!("brz @{}", label));
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::Unless => {
|
||||||
|
let label = self.gensym();
|
||||||
|
self.control_stack
|
||||||
|
.push(ControlStructure::Target(label.clone()));
|
||||||
|
self.emit(format!("brnz @{}", label));
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::Else => {
|
||||||
|
if let Some(ControlStructure::Target(old_end)) = self.control_stack.pop() {
|
||||||
|
let new_end = self.gensym();
|
||||||
|
self.control_stack
|
||||||
|
.push(ControlStructure::Target(new_end.clone()));
|
||||||
|
self.emit(format!("jmpr @{}", new_end));
|
||||||
|
self.emit(format!("{}:", old_end));
|
||||||
|
} else {
|
||||||
|
return Some(AssembleError::MacroError(self.current_line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::While | Macro::Until => {
|
||||||
|
let label = self.gensym();
|
||||||
|
self.control_stack
|
||||||
|
.push(ControlStructure::Loop(label.clone(), mac.into()));
|
||||||
|
self.emit(format!("{}:", label));
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::Do => {
|
||||||
|
if let Some(ControlStructure::Loop(label, loop_type)) = self.control_stack.pop() {
|
||||||
|
let after = self.gensym();
|
||||||
|
self.control_stack
|
||||||
|
.push(ControlStructure::LoopDo(label, after.clone()));
|
||||||
|
let instr = match loop_type {
|
||||||
|
LoopType::While => "brz",
|
||||||
|
LoopType::Until => "brnz",
|
||||||
|
};
|
||||||
|
self.emit(format!("{} @{}", instr, after));
|
||||||
|
} else {
|
||||||
|
return Some(AssembleError::MacroError(self.current_line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro::End => match self.control_stack.pop() {
|
||||||
|
Some(ControlStructure::Target(label)) => self.emit(format!("{}:", label)),
|
||||||
|
Some(ControlStructure::LoopDo(start, after)) => {
|
||||||
|
self.emit(format!("jmpr @{}", start));
|
||||||
|
self.emit(format!("{}:", after));
|
||||||
|
}
|
||||||
|
_ => return Some(AssembleError::MacroError(self.current_line)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ast::{Label, Node};
|
||||||
|
use vcore::opcodes::Opcode::*;
|
||||||
|
|
||||||
|
fn lines_for(source: Vec<&str>) -> Vec<VASMLine> {
|
||||||
|
let include = |_name: String| panic!();
|
||||||
|
let src = LineSource::new("blah", source, include);
|
||||||
|
src.map(|line| line.unwrap().line).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preprocess() {
|
||||||
|
let include = |_name: String| panic!();
|
||||||
|
let lines = vec!["add"];
|
||||||
|
let mut src = LineSource::new("blah", lines, include);
|
||||||
|
assert_eq!(
|
||||||
|
src.next(),
|
||||||
|
Some(Ok(Line {
|
||||||
|
line_num: 1,
|
||||||
|
file: "blah".to_string(),
|
||||||
|
line: VASMLine::Instruction(None, Add, None)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert_eq!(src.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preprocess_include() {
|
||||||
|
let include = |_name: String| Ok(vec!["sub"]);
|
||||||
|
let lines = vec!["#include \"foo\"", "add"];
|
||||||
|
let src = LineSource::new("blah", lines, include);
|
||||||
|
assert_eq!(
|
||||||
|
src.collect::<Vec<Result<Line, AssembleError>>>(),
|
||||||
|
vec![
|
||||||
|
Ok(Line {
|
||||||
|
line_num: 1,
|
||||||
|
file: "foo".to_string(),
|
||||||
|
line: VASMLine::Instruction(None, Sub, None)
|
||||||
|
}),
|
||||||
|
Ok(Line {
|
||||||
|
line_num: 2,
|
||||||
|
file: "blah".to_string(),
|
||||||
|
line: VASMLine::Instruction(None, Add, None)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preprocess_if_end() {
|
||||||
|
assert_eq!(
|
||||||
|
lines_for(vec!["#if", "#end"]),
|
||||||
|
vec![
|
||||||
|
VASMLine::Instruction(None, Brz, Some(Node::relative_label("__gensym_1"))),
|
||||||
|
VASMLine::LabelDef(Label::from("__gensym_1"))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preprocess_else() {
|
||||||
|
assert_eq!(
|
||||||
|
lines_for(vec!["#if", "add", "#else", "sub", "#end"]),
|
||||||
|
vec![
|
||||||
|
VASMLine::Instruction(None, Brz, Some(Node::relative_label("__gensym_1"))),
|
||||||
|
VASMLine::Instruction(None, Add, None),
|
||||||
|
VASMLine::Instruction(None, Jmpr, Some(Node::relative_label("__gensym_2"))),
|
||||||
|
VASMLine::LabelDef(Label("__gensym_1".to_string())),
|
||||||
|
VASMLine::Instruction(None, Sub, None),
|
||||||
|
VASMLine::LabelDef(Label("__gensym_2".to_string())),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preprocess_do_end() {
|
||||||
|
assert_eq!(
|
||||||
|
lines_for(vec!["#while", "#do", "#end"]),
|
||||||
|
vec![
|
||||||
|
VASMLine::LabelDef(Label("__gensym_1".to_string())),
|
||||||
|
VASMLine::Instruction(
|
||||||
|
None,
|
||||||
|
Brz,
|
||||||
|
Some(Node::RelativeLabel("__gensym_2".to_string()))
|
||||||
|
),
|
||||||
|
VASMLine::Instruction(
|
||||||
|
None,
|
||||||
|
Jmpr,
|
||||||
|
Some(Node::RelativeLabel("__gensym_1".to_string()))
|
||||||
|
),
|
||||||
|
VASMLine::LabelDef(Label::from("__gensym_2"))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user