diff --git a/vasm/src/lib.rs b/vasm/src/lib.rs index 13b92db..fb2b86c 100644 --- a/vasm/src/lib.rs +++ b/vasm/src/lib.rs @@ -7,3 +7,4 @@ pub mod parse_error; pub mod vasm_assembler; pub mod vasm_evaluator; pub mod vasm_parser; +pub mod vasm_preprocessor; diff --git a/vasm/src/parse_error.rs b/vasm/src/parse_error.rs index 99b951b..b0106ba 100644 --- a/vasm/src/parse_error.rs +++ b/vasm/src/parse_error.rs @@ -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) + } + } + } +} diff --git a/vasm/src/vasm_assembler.rs b/vasm/src/vasm_assembler.rs index bfb6e3d..c1c9266 100644 --- a/vasm/src/vasm_assembler.rs +++ b/vasm/src/vasm_assembler.rs @@ -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 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, AssembleError> -where - T: IntoIterator, - F: Fn(String) -> Result, -{ - 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) -> Result, 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>(lines: T) -> Vec { 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")) - } - ]) - ) - } } diff --git a/vasm/src/vasm_evaluator.rs b/vasm/src/vasm_evaluator.rs index 40e43ee..b6e4422 100644 --- a/vasm/src/vasm_evaluator.rs +++ b/vasm/src/vasm_evaluator.rs @@ -1,29 +1,6 @@ use crate::ast::{Node, Operator, Scope}; +use crate::parse_error::EvalError; 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 { if (line_num as i32) + offset < 0 { diff --git a/vasm/src/vasm_preprocessor.rs b/vasm/src/vasm_preprocessor.rs new file mode 100644 index 0000000..7e66ddd --- /dev/null +++ b/vasm/src/vasm_preprocessor.rs @@ -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 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, + F: Fn(String) -> Result, +> { + generated_lines: VecDeque, + current_line: usize, + filename_stack: Vec, + current_sym: usize, + control_stack: Vec, + iter_stack: Vec::IntoIter>>, + include: F, +} + +impl<'a, T: IntoIterator, F: Fn(String) -> Result> Iterator + for LineSource<'a, T, F> +{ + type Item = Result; + + fn next(&mut self) -> Option { + // 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, F: Fn(String) -> Result> + 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 { + 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 { + 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![ + 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")) + ] + ) + } +}