Preprocessor refactoring
This commit is contained in:
+7
-369
@@ -1,208 +1,8 @@
|
||||
use crate::ast::{Label, Macro, Scope, VASMLine};
|
||||
use crate::parse_error::ParseError;
|
||||
use crate::vasm_evaluator::{eval, EvalError};
|
||||
use crate::ast::{Label, Scope, VASMLine};
|
||||
use crate::parse_error::AssembleError;
|
||||
use crate::vasm_evaluator::eval;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
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.
|
||||
/// .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)]
|
||||
mod test {
|
||||
use super::AssembleError::*;
|
||||
use super::EvalError::*;
|
||||
use super::*;
|
||||
use crate::ast::{Node, VASMLine};
|
||||
use crate::ast::VASMLine;
|
||||
use crate::parse_error;
|
||||
use crate::parse_error::EvalError::*;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
|
||||
lines
|
||||
@@ -576,11 +375,11 @@ mod test {
|
||||
fn test_unresolvable_orgs() {
|
||||
assert_eq!(
|
||||
place_labels_pass([".org 0xffffff - blah"]),
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
|
||||
Err(OrgResolveError(1, MissingLabel("blah".into())))
|
||||
);
|
||||
assert_eq!(
|
||||
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"))
|
||||
}
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user