Couple bugfixes, integration tests ported
This commit is contained in:
@@ -73,6 +73,7 @@ pub enum VASMLine {
|
||||
Equ(Label, Node),
|
||||
LabelDef(Label),
|
||||
Macro(Macro),
|
||||
Blank,
|
||||
}
|
||||
|
||||
impl VASMLine {
|
||||
|
||||
+7
-5
@@ -2,9 +2,11 @@ extern crate pest;
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
pub mod ast;
|
||||
mod ast;
|
||||
pub mod parse_error;
|
||||
pub mod vasm_assembler;
|
||||
pub mod vasm_evaluator;
|
||||
pub mod vasm_parser;
|
||||
pub mod vasm_preprocessor;
|
||||
mod vasm_assembler;
|
||||
mod vasm_evaluator;
|
||||
mod vasm_parser;
|
||||
mod vasm_preprocessor;
|
||||
|
||||
pub use vasm_assembler::assemble_snippet;
|
||||
|
||||
+3
-1
@@ -72,5 +72,7 @@ include = { "include" ~ string }
|
||||
control = { "if" | "unless" | "while" | "until" | "do" | "else" | "end" }
|
||||
preprocessor = {"#" ~ (control | include) }
|
||||
|
||||
blank = { WHITESPACE? ~ COMMENT? }
|
||||
|
||||
// Finally the entire pattern for an assembly line:
|
||||
line = { SOI ~ (preprocessor | db_word | db_string | org_directive | equ_directive | instruction | label_def) ~ EOI }
|
||||
line = { SOI ~ (preprocessor | db_word | db_string | org_directive | equ_directive | instruction | label_def | blank) ~ EOI }
|
||||
+87
-16
@@ -1,7 +1,7 @@
|
||||
use crate::ast::{Label, Scope, VASMLine};
|
||||
use crate::parse_error::AssembleError;
|
||||
use crate::vasm_evaluator::eval;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
use crate::vasm_preprocessor::{Line, LineSource};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// This will solve all the .equ directives and return a symbol table of them.
|
||||
@@ -72,7 +72,7 @@ fn measure_instructions(lines: &[VASMLine], scope: &Scope) -> LineLengths {
|
||||
VASMLine::StringDb(_, value) => {
|
||||
lengths.insert(line_num, value.len());
|
||||
}
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Blank => {
|
||||
lengths.insert(line_num, 0);
|
||||
}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
@@ -156,14 +156,37 @@ fn code_bounds(
|
||||
Ok((start, end + end_length - 1))
|
||||
}
|
||||
|
||||
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;
|
||||
parsed.push(parse_vasm_line(line).map_err(|err| AssembleError::ParseError(line_num, err))?)
|
||||
}
|
||||
/// Turn an iterable of strs into an assembled binary. This supports macros, but not
|
||||
/// the `#include` macro. The resulting Vec is only as large as it needs to be; if your
|
||||
/// code starts with `.org 0x400` and is five bytes long then the Vec will be five
|
||||
/// bytes long and index 0 will represent 0x400.
|
||||
/// ```
|
||||
/// assert_eq!(
|
||||
/// vasm::assemble_snippet(vec![".org 0x400", "push 5", "add 7"]),
|
||||
/// Ok(vec![0x01, 0x05, 0x05, 0x07])
|
||||
/// )
|
||||
/// ```
|
||||
pub fn assemble_snippet<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<Vec<u8>, AssembleError> {
|
||||
let mut line_results: Vec<Result<Line, AssembleError>> =
|
||||
LineSource::new("_snippet", lines, |_file| {
|
||||
Err(AssembleError::IncludeError(
|
||||
0,
|
||||
"Including is not supported in assembling snippets".to_string(),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
generate_code(parsed)
|
||||
if let Some(Err(error)) = line_results.iter().find(|line| line.is_err()) {
|
||||
Err(error.clone())
|
||||
} else {
|
||||
generate_code(
|
||||
line_results
|
||||
.iter_mut()
|
||||
.map(|line| line.clone().unwrap().line),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// At this point all lines have addresses and lengths, and all arguments are reduced to
|
||||
@@ -181,7 +204,7 @@ pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Result<Vec<u8>
|
||||
///
|
||||
/// 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(lines: Vec<VASMLine>) -> Result<Vec<u8>, AssembleError> {
|
||||
fn generate_code<T: IntoIterator<Item = VASMLine>>(lines: T) -> Result<Vec<u8>, AssembleError> {
|
||||
let lines: Vec<VASMLine> = lines.into_iter().collect();
|
||||
let scope = solve_equs(&lines)?;
|
||||
let line_lengths = measure_instructions(&lines, &scope);
|
||||
@@ -195,13 +218,13 @@ fn generate_code(lines: Vec<VASMLine>) -> Result<Vec<u8>, AssembleError> {
|
||||
let line_num = line_idx + 1;
|
||||
match line {
|
||||
VASMLine::Instruction(_, opcode, None) => {
|
||||
code[current_addr] = u8::from(*opcode) << 2;
|
||||
code[current_addr - start] = u8::from(*opcode) << 2;
|
||||
current_addr += 1;
|
||||
}
|
||||
VASMLine::Instruction(_, opcode, Some(arg)) => {
|
||||
let arg = eval(arg, line_num, &line_addresses, &scope)
|
||||
.map_err(|err| AssembleError::ArgError(line_num, err))?;
|
||||
let len = arg_length(arg);
|
||||
let len = line_lengths[&line_num] - 1;
|
||||
let instr = (u8::from(*opcode) << 2) + len as u8;
|
||||
code[current_addr - start] = instr;
|
||||
let [low, mid, high, _] = arg.to_le_bytes();
|
||||
@@ -229,7 +252,7 @@ fn generate_code(lines: Vec<VASMLine>) -> Result<Vec<u8>, AssembleError> {
|
||||
VASMLine::Org(_, _) => {
|
||||
current_addr = line_addresses[&(line_num + 1)] as usize;
|
||||
}
|
||||
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {}
|
||||
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Blank => {}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -333,6 +356,13 @@ mod test {
|
||||
measure_instructions(&parse([".org 256", "blah:", "foo: .equ 7"]), &[].into()),
|
||||
[(1, 0), (2, 0), (3, 0)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse([".org 0x400", "push 3", "call blah", "hlt", "blah: mul 2"]),
|
||||
&[].into()
|
||||
),
|
||||
[(1, 0), (2, 2), (3, 4), (4, 1), (5, 2)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse(["add 2 + foo", "add 3 + blah", "jmpr @foo"]),
|
||||
@@ -369,6 +399,28 @@ mod test {
|
||||
[("blah".to_string(), 266), ("start".to_string(), 256)].into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass([
|
||||
".org 1024",
|
||||
"nop 3",
|
||||
"call blah",
|
||||
"hlt",
|
||||
"blah: mul 2",
|
||||
"ret"
|
||||
]),
|
||||
Ok((
|
||||
[
|
||||
(1, 1024),
|
||||
(2, 1024),
|
||||
(3, 1026),
|
||||
(4, 1030),
|
||||
(5, 1031),
|
||||
(6, 1033)
|
||||
]
|
||||
.into(),
|
||||
[("blah".to_string(), 1031)].into()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -421,14 +473,33 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assemble() {
|
||||
assert_eq!(assemble(["add"]), Ok(vec![4]));
|
||||
fn test_assemble_snippet() {
|
||||
assert_eq!(assemble_snippet(["add"]), Ok(vec![4]));
|
||||
assert_eq!(
|
||||
assemble(["apple"]),
|
||||
assemble_snippet(["apple"]),
|
||||
Err(ParseError(
|
||||
1,
|
||||
parse_error::ParseError::InvalidInstruction("apple".into())
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
assemble_snippet(
|
||||
".org 0x400
|
||||
nop 3
|
||||
call blah
|
||||
hlt
|
||||
blah: mul 2
|
||||
ret"
|
||||
.lines()
|
||||
),
|
||||
Ok(vec![
|
||||
0x01, 0x03, // nop 3
|
||||
0x67, 0x07, 0x04, 0x00, // call blah (arg defaults to 3 bytes long)
|
||||
0x74, // hlt
|
||||
0x0d, 0x02, // mul 2
|
||||
0x68
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ pub fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError> {
|
||||
}
|
||||
Rule::label_def => Ok(VASMLine::LabelDef(Label::from(line.only().as_str()))),
|
||||
Rule::preprocessor => Ok(VASMLine::Macro(parse_macro(line))),
|
||||
Rule::blank => Ok(VASMLine::Blank),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -201,6 +202,7 @@ fn parse_macro(line: Pair) -> Macro {
|
||||
match pre.as_rule() {
|
||||
Rule::control => match pre.as_str() {
|
||||
"if" => Macro::If,
|
||||
"unless" => Macro::Unless,
|
||||
"else" => Macro::Else,
|
||||
"while" => Macro::While,
|
||||
"until" => Macro::Until,
|
||||
@@ -420,9 +422,21 @@ mod test {
|
||||
#[test]
|
||||
fn test_parse_macros() {
|
||||
assert_eq!(parse_vasm_line("#if"), Ok(VASMLine::Macro(Macro::If)));
|
||||
assert_eq!(
|
||||
parse_vasm_line("#unless"),
|
||||
Ok(VASMLine::Macro(Macro::Unless))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("#include \"blah\""),
|
||||
Ok(VASMLine::Macro(Macro::Include("blah".to_string())))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_blank() {
|
||||
assert_eq!(parse_vasm_line(""), Ok(VASMLine::Blank));
|
||||
assert_eq!(parse_vasm_line(" "), Ok(VASMLine::Blank));
|
||||
assert_eq!(parse_vasm_line("; foo"), Ok(VASMLine::Blank));
|
||||
assert_eq!(parse_vasm_line(" ;foo"), Ok(VASMLine::Blank));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::vasm_parser::parse_vasm_line;
|
||||
use std::collections::VecDeque;
|
||||
use std::iter::Enumerate;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Line {
|
||||
line: VASMLine,
|
||||
pub line: VASMLine,
|
||||
line_num: usize,
|
||||
file: String,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user