Renamed vasm to vasm_core
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "vasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
vcore = { path = "../vcore" }
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
@@ -0,0 +1,97 @@
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// One of the five arithmetical operators
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Operator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
}
|
||||
|
||||
/// An AST node for an instruction argument. This can be a string, number, label reference,
|
||||
/// line offset, or an expr containing a sequence of other Nodes joined by same-precedence
|
||||
/// `Operator`s.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Node {
|
||||
Number(i32),
|
||||
Label(String),
|
||||
RelativeLabel(String),
|
||||
AbsoluteOffset(i32),
|
||||
RelativeOffset(i32),
|
||||
Expr(Box<Node>, Vec<(Operator, Node)>),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn label(lbl: &str) -> Self {
|
||||
Self::Label(lbl.to_string())
|
||||
}
|
||||
pub fn relative_label(lbl: &str) -> Self {
|
||||
Self::RelativeLabel(lbl.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Label(pub String);
|
||||
|
||||
impl Display for Label {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Label {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Scope = BTreeMap<String, i32>;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Macro {
|
||||
Include(String),
|
||||
If,
|
||||
Unless,
|
||||
Else,
|
||||
While,
|
||||
Until,
|
||||
Do,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum VASMLine {
|
||||
Instruction(Option<Label>, Opcode, Option<Node>),
|
||||
Db(Option<Label>, Node),
|
||||
StringDb(Option<Label>, String),
|
||||
Org(Option<Label>, Node),
|
||||
Equ(Label, Node),
|
||||
LabelDef(Label),
|
||||
Macro(Macro),
|
||||
Blank,
|
||||
}
|
||||
|
||||
impl VASMLine {
|
||||
pub fn label(&self) -> Option<&Label> {
|
||||
match self {
|
||||
VASMLine::Instruction(Some(lbl), _, _)
|
||||
| VASMLine::Db(Some(lbl), _)
|
||||
| VASMLine::StringDb(Some(lbl), _)
|
||||
| VASMLine::Org(Some(lbl), _)
|
||||
| VASMLine::Equ(lbl, _)
|
||||
| VASMLine::LabelDef(lbl) => Some(lbl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn zero_length(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Macro(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
extern crate pest;
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
mod ast;
|
||||
pub mod parse_error;
|
||||
mod vasm_assembler;
|
||||
mod vasm_evaluator;
|
||||
mod vasm_parser;
|
||||
mod vasm_preprocessor;
|
||||
|
||||
pub use vasm_assembler::assemble_snippet;
|
||||
pub use vasm_assembler::assemble_file;
|
||||
@@ -0,0 +1,95 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use vcore::opcodes::InvalidMnemonic;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ParseError {
|
||||
LineParseFailure,
|
||||
InvalidInstruction(String),
|
||||
}
|
||||
|
||||
impl<'a> From<InvalidMnemonic<'a>> for ParseError {
|
||||
fn from(err: InvalidMnemonic<'a>) -> Self {
|
||||
Self::InvalidInstruction(err.0.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
use ParseError::*;
|
||||
match self {
|
||||
LineParseFailure => write!(f, "Failed to parse line"),
|
||||
InvalidInstruction(p) => write!(f, "Cannot parse {} as instruction", p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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),
|
||||
FileError(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)
|
||||
}
|
||||
AssembleError::FileError(file) => {
|
||||
write!(f, "File read error in {}", file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
WHITESPACE = _{ " " | "\t" }
|
||||
|
||||
// A comment will start with a semicolon and go to the end of the line. Actually everything is parsed
|
||||
// line by line, so anything that starts with a semicolon is a comment:
|
||||
COMMENT = _{ ";" ~ ANY* ~ EOI }
|
||||
|
||||
// Numbers are more complicated. We'll support three formats:
|
||||
// - Decimal numbers like 42
|
||||
// - Hexadecimal like 0x2a
|
||||
// - Binary like 0b00101010
|
||||
// - Decimal zero needs its own pattern: it's not a decimal because
|
||||
// it starts with a 0, but it has to be matched after hex and bin
|
||||
// because otherwise any "0x" will parse as "decimal 0 followed
|
||||
// by unparseable x"
|
||||
dec_number = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
|
||||
neg_number = ${ "-" ~ dec_number }
|
||||
hex_number = ${ "0x" ~ ASCII_HEX_DIGIT+ }
|
||||
bin_number = ${ "0b" ~ ASCII_BIN_DIGIT+ }
|
||||
oct_number = ${ "0o" ~ ASCII_OCT_DIGIT+ }
|
||||
dec_zero = @{ "0" }
|
||||
number = { dec_number | hex_number | bin_number | oct_number | dec_zero | neg_number }
|
||||
|
||||
// A label can be any sequence of C-identifier-y characters, as long as it doesn't start with
|
||||
// a digit:
|
||||
label_char = { ASCII_ALPHA_LOWER | ASCII_ALPHA_UPPER | "_" | "$" }
|
||||
label = @{ label_char ~ (label_char | ASCII_DIGIT | "$")* }
|
||||
label_def = { label ~ ":" }
|
||||
|
||||
// To make relative jumps easier, we'll also allow an '@' at the start of a label, and interpret
|
||||
// that as meaning "relative to the first byte of this instruction:"
|
||||
relative_label = ${ "@" ~ label }
|
||||
|
||||
// We'll also have a special form, $+nnn (and $-nnn) which is the first byte of the an earlier or later line:
|
||||
absolute_line_offset = { "$" ~ sign ~ dec_number }
|
||||
|
||||
// And the relative form of that, @+nnn and @-nnn:
|
||||
relative_line_offset = { "@" ~ sign ~ dec_number }
|
||||
|
||||
opcode = @{ ASCII_ALPHA_LOWER+ }
|
||||
|
||||
// The .equ directive isn't much use without the ability to have expressions based on
|
||||
// symbols, so, a quick arithmetic expression parser:
|
||||
sign = { "+" | "-" }
|
||||
term_op = { "/" | "*" | "%" }
|
||||
expr = { term ~ (sign ~ term)* }
|
||||
term = { fact ~ (term_op ~ fact)* }
|
||||
fact = { ("(" ~ expr ~ ")") | number | relative_line_offset | relative_label | absolute_line_offset | label }
|
||||
|
||||
// Likewise, .db would get tedious quick without a string syntax, so, let's define one of those. An escape
|
||||
// sequence is a backslash followed by certain other characters:
|
||||
escape = @{ "\\" ~ ("t" | "r" | "n" | "0" | "\\" | "\"") }
|
||||
|
||||
// And a string is a quoted sequence of escapes or other characters:
|
||||
string_inner = ${ !("\"" | "\\") ~ ANY | escape }
|
||||
string = ${ "\"" ~ string_inner* ~ "\"" }
|
||||
|
||||
// Parsing a line
|
||||
// Normally an assembly line will be a sequence of "label, opcode, argument, comment."
|
||||
// However, only some combinations of these are valid. Comments are already handled by
|
||||
// the COMMENT pattern.
|
||||
// Also, a line might be an actual instruction, or the assembler will support some directives:
|
||||
// - .org to set the current address
|
||||
// - .db to embed some data
|
||||
// - .equ to define some constants
|
||||
instruction = { label_def? ~ opcode ~ expr? }
|
||||
db_word = { label_def? ~ ".db" ~ expr }
|
||||
db_string = { label_def? ~ ".db" ~ string }
|
||||
org_directive = { label_def? ~ ".org" ~ expr }
|
||||
equ_directive = { label_def ~ ".equ" ~ expr }
|
||||
|
||||
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 | blank) ~ EOI }
|
||||
@@ -0,0 +1,549 @@
|
||||
use crate::ast::{Label, Scope, VASMLine};
|
||||
use crate::parse_error::AssembleError;
|
||||
use crate::vasm_evaluator::eval;
|
||||
use crate::vasm_preprocessor::{Line, LineSource};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
|
||||
/// 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(lines: &[VASMLine]) -> Result<Scope, AssembleError> {
|
||||
let mut scope: Scope = Scope::new();
|
||||
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = line_idx + 1;
|
||||
if let VASMLine::Equ(Label(name), expr) = line {
|
||||
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.clone(), value) {
|
||||
return Err(AssembleError::EquDuplicateError(line_num, name.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(scope)
|
||||
}
|
||||
|
||||
type LineLengths = BTreeMap<usize, usize>;
|
||||
type LineAddresses = BTreeMap<usize, i32>;
|
||||
|
||||
fn arg_length(val: i32) -> usize {
|
||||
if val < 0 {
|
||||
3
|
||||
} else if val < 256 {
|
||||
1
|
||||
} else if val < 65536 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
/// This figures out the instruction lengths. We'll do this naively; if we can't
|
||||
/// immediately tell that an instruction needs only a 0/1/2 byte argument (because it's
|
||||
/// a constant, or a .equ that we've solved, or something) then we'll assume it's a
|
||||
/// full 24-bit argument.
|
||||
///
|
||||
/// - Lines that don't represent output (.equ, .org, etc) have length 0
|
||||
/// - .db directives are either strings (set aside the length of the string), or
|
||||
/// numbers (set aside three bytes. If it's shorter than that it still may be a variable,
|
||||
/// which might grow to be larger).
|
||||
/// - Opcodes with no argument are 1 byte long.
|
||||
/// - Opcodes with an argument, if that argument is a constant or decidable solely with
|
||||
/// 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(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;
|
||||
match line {
|
||||
VASMLine::Instruction(_, _, None) => {
|
||||
lengths.insert(line_num, 1);
|
||||
}
|
||||
VASMLine::Instruction(_, _, Some(node)) => {
|
||||
let len = eval(node, line_num, &line_nums, scope).map_or(3, arg_length);
|
||||
lengths.insert(line_num, len + 1);
|
||||
}
|
||||
VASMLine::Db(_, _) => {
|
||||
lengths.insert(line_num, 3);
|
||||
}
|
||||
VASMLine::StringDb(_, value) => {
|
||||
lengths.insert(line_num, value.len());
|
||||
}
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Blank => {
|
||||
lengths.insert(line_num, 0);
|
||||
}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
lengths
|
||||
}
|
||||
|
||||
/// Time to start placing labels. The tricky part here is the .org directives, which can have
|
||||
/// expressions as their arguments. We'll compromise a little bit and say that a .org directive
|
||||
/// can only refer to labels that precede it, so, you can use .orgs to generate (say) a jump table
|
||||
/// but still make it easy for me to figure out what refers to what.
|
||||
///
|
||||
/// We'll go through the lines, adding each one's length (calculated in measure_instructions) to it.
|
||||
/// If it has a label, we'll store that label's new value to the scope.
|
||||
///
|
||||
/// 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(
|
||||
lines: &[VASMLine],
|
||||
scope: Scope,
|
||||
lengths: &LineLengths,
|
||||
) -> 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;
|
||||
|
||||
if let VASMLine::Org(_, expr) = line {
|
||||
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.clone(), address as i32);
|
||||
}
|
||||
}
|
||||
|
||||
match line {
|
||||
VASMLine::Org(_, _) => {}
|
||||
_ => {
|
||||
addresses.insert(line_num, address);
|
||||
address += *lengths.get(&line_num).unwrap_or(&0) as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((addresses, scope))
|
||||
}
|
||||
|
||||
fn poke_word(code: &mut Vec<u8>, at: usize, word: i32) {
|
||||
let [low, mid, high, _] = word.to_le_bytes();
|
||||
code[at] = low;
|
||||
code[at + 1] = mid;
|
||||
code[at + 2] = high;
|
||||
}
|
||||
|
||||
/// Find the lower and upper bounds where this program will place memory
|
||||
fn code_bounds(
|
||||
lines: &[VASMLine],
|
||||
line_addresses: &LineAddresses,
|
||||
line_lengths: &LineLengths,
|
||||
) -> 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 + 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 + 1)] as usize;
|
||||
let end_length = line_lengths[&(last_idx + 1)];
|
||||
|
||||
Ok((start, end + end_length - 1))
|
||||
}
|
||||
|
||||
/// 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(".org 0x400 \n push 5 \n add 7".lines().map(String::from)),
|
||||
/// Ok(vec![0x01, 0x05, 0x05, 0x07])
|
||||
/// )
|
||||
/// ```
|
||||
pub fn assemble_snippet<T: IntoIterator<Item = String>>(
|
||||
lines: T,
|
||||
) -> Result<Vec<u8>, AssembleError> {
|
||||
let 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();
|
||||
|
||||
assemble_line_results(line_results)
|
||||
}
|
||||
|
||||
/// Assemble a file from the filesystem, opening other files as it includes them.
|
||||
pub fn assemble_file(filename: &str) -> Result<Vec<u8>, AssembleError> {
|
||||
let lines = lines_from_file(filename)?;
|
||||
let line_results: Vec<Result<Line, AssembleError>> = LineSource::new(filename, lines, |file| {
|
||||
println!("Including {}", file);
|
||||
lines_from_file(file.as_str())
|
||||
})
|
||||
.collect();
|
||||
|
||||
assemble_line_results(line_results)
|
||||
}
|
||||
|
||||
fn assemble_line_results(
|
||||
mut line_results: Vec<Result<Line, AssembleError>>,
|
||||
) -> Result<Vec<u8>, AssembleError> {
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn lines_from_file(filename: &str) -> Result<Vec<String>, AssembleError> {
|
||||
let file =
|
||||
fs::read_to_string(filename).map_err(|_e| AssembleError::FileError(filename.into()))?;
|
||||
Ok(file.lines().map(String::from).collect())
|
||||
}
|
||||
|
||||
/// At this point all lines have addresses and lengths, and all arguments are reduced to
|
||||
/// numeric constants. It's time to generate code.
|
||||
///
|
||||
/// - Make an array of zeroes, length (end - start)
|
||||
/// - Go through the list of instructions, generating code for them:
|
||||
/// - .db instructions turn into byte values starting at `address - start`
|
||||
/// - Opcodes turn into instruction bytes at `address - start` followed (maybe) by
|
||||
/// arguments.
|
||||
/// - .orgs cause us to skip ahead some in the output
|
||||
///
|
||||
/// The instruction bytes are formed of six bits defining the instruction followed by two
|
||||
/// bits denoting how many bytes of argument follow it.
|
||||
///
|
||||
/// 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<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);
|
||||
let (line_addresses, scope) = place_labels(&lines, scope, &line_lengths)?;
|
||||
let (start, end) = code_bounds(&lines, &line_addresses, &line_lengths)?;
|
||||
|
||||
let mut code = vec![0u8; end - start + 1];
|
||||
let mut current_addr = start;
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = line_idx + 1;
|
||||
match line {
|
||||
VASMLine::Instruction(_, opcode, None) => {
|
||||
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 = 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();
|
||||
code[current_addr - start + 1] = low;
|
||||
if len > 1 {
|
||||
code[current_addr - start + 2] = mid
|
||||
}
|
||||
if len > 2 {
|
||||
code[current_addr - start + 3] = high
|
||||
}
|
||||
current_addr += len + 1;
|
||||
}
|
||||
VASMLine::Db(_, arg) => {
|
||||
let arg = eval(arg, line_num, &line_addresses, &scope)
|
||||
.map_err(|err| AssembleError::ArgError(line_num, err))?;
|
||||
poke_word(&mut code, current_addr - start, arg);
|
||||
current_addr += 3;
|
||||
}
|
||||
VASMLine::StringDb(_, string) => {
|
||||
for ch in string.as_bytes() {
|
||||
code[current_addr - start] = *ch;
|
||||
current_addr += 1;
|
||||
}
|
||||
}
|
||||
VASMLine::Org(_, _) => {
|
||||
current_addr = line_addresses[&(line_num + 1)] as usize;
|
||||
}
|
||||
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Blank => {}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::AssembleError::*;
|
||||
use super::*;
|
||||
use crate::ast::VASMLine;
|
||||
use crate::parse_error;
|
||||
use crate::parse_error::EvalError::*;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
|
||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
|
||||
lines
|
||||
.into_iter()
|
||||
.map(|line| parse_vasm_line(line).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn place_labels_pass<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<(LineAddresses, Scope), AssembleError> {
|
||||
let parsed_lines = parse(lines);
|
||||
let scope = solve_equs(&parsed_lines).unwrap();
|
||||
let lengths = measure_instructions(&parsed_lines, &scope);
|
||||
place_labels(&parsed_lines, scope, &lengths)
|
||||
}
|
||||
|
||||
fn bounds<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<(usize, usize), AssembleError> {
|
||||
let parsed_lines = parse(lines);
|
||||
let scope = solve_equs(&parsed_lines).unwrap();
|
||||
let lengths = measure_instructions(&parsed_lines, &scope);
|
||||
let (line_addresses, _scope) = place_labels(&parsed_lines, scope, &lengths)?;
|
||||
code_bounds(&parsed_lines, &line_addresses, &lengths)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5+3"])),
|
||||
Ok([("blah".to_string(), 8)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ 3"])),
|
||||
Ok([("blah".to_string(), 5), ("foo".to_string(), 3)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ blah + 7"])),
|
||||
Ok([("blah".to_string(), 5), ("foo".to_string(), 12)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["add", "blah: .equ 5"])),
|
||||
Ok([("blah".to_string(), 5)].into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsolvable_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ 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".into(),
|
||||
MissingLabel("foo".into())
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
|
||||
Err(EquDuplicateError(2, "blah".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lengths() {
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse(["add", "add 1", "add 500", "add 70000", "add -7"]),
|
||||
&[].into()
|
||||
),
|
||||
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 4)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
measure_instructions(&parse([".db 7", ".db \"hello\\0\""]), &[].into()),
|
||||
[(1, 3), (2, 6)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
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"]),
|
||||
&[("blah".to_string(), 300)].into()
|
||||
),
|
||||
[(1, 4), (2, 3), (3, 4)].into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_place_labels() {
|
||||
assert_eq!(
|
||||
place_labels_pass(["start: .org 256", "add", "dup"]),
|
||||
Ok((
|
||||
[(1, 256), (2, 256), (3, 257)].into(),
|
||||
[("start".to_string(), 256)].into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(["push 70000", "dup"]),
|
||||
Ok(([(1, 0), (2, 4)].into(), [].into()))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(["start: .equ 256", "blah: .org start + 4", "add"]),
|
||||
Ok((
|
||||
[(1, 0), (2, 260), (3, 260)].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".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]
|
||||
fn test_unresolvable_orgs() {
|
||||
assert_eq!(
|
||||
place_labels_pass([".org 0xffffff - blah"]),
|
||||
Err(OrgResolveError(1, MissingLabel("blah".into())))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(["blah: .org blah"]),
|
||||
Err(OrgResolveError(1, MissingLabel("blah".into())))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounds() {
|
||||
assert_eq!(bounds(["add"]), Ok((0, 0)));
|
||||
assert_eq!(bounds(["add -4"]), Ok((0, 3)));
|
||||
assert_eq!(bounds(["add 7"]), Ok((0, 1)));
|
||||
assert_eq!(bounds([".org 0x400", "add"]), Ok((1024, 1024)));
|
||||
assert_eq!(
|
||||
bounds(["start: .equ 1024", ".org start", "add"]),
|
||||
Ok((1024, 1024))
|
||||
);
|
||||
assert_eq!(
|
||||
bounds([".org 0x400", "add", ".org 0x800"]),
|
||||
Ok((1024, 1024))
|
||||
);
|
||||
assert_eq!(
|
||||
bounds([".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]),
|
||||
Ok((1024, 2050))
|
||||
);
|
||||
assert_eq!(bounds([]), Err(AssembleError::NoCode));
|
||||
assert_eq!(bounds([".org 0x400"]), Err(AssembleError::NoCode));
|
||||
assert_eq!(
|
||||
bounds(["foo: .equ 3", ".org 0x400"]),
|
||||
Err(AssembleError::NoCode)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_code() {
|
||||
assert_eq!(generate_code(parse(["add"])), Ok(vec![4]));
|
||||
assert_eq!(
|
||||
generate_code(parse([".org 0x400", "add 7"])),
|
||||
Ok(vec![5, 7])
|
||||
);
|
||||
assert_eq!(generate_code(parse([".db 57"])), Ok(vec![57, 0, 0]));
|
||||
assert_eq!(generate_code(parse([".db \"AZ\0\""])), Ok(vec![65, 90, 0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assemble_snippet() {
|
||||
assert_eq!(assemble_snippet(["add"].map(String::from)), Ok(vec![4]));
|
||||
assert_eq!(
|
||||
assemble_snippet(["apple"].map(String::from)),
|
||||
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()
|
||||
.map(String::from)
|
||||
),
|
||||
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
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_blanks() {
|
||||
assert_eq!(
|
||||
assemble_snippet(
|
||||
".org 0x400
|
||||
nop $+2
|
||||
|
||||
nop 0x111111
|
||||
nop 0x222222"
|
||||
.lines()
|
||||
.map(String::from)
|
||||
),
|
||||
Ok(vec![
|
||||
0x03, 0x08, 0x04, 0x00, 0x03, 0x11, 0x11, 0x11, 0x03, 0x22, 0x22, 0x22
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
use crate::ast::{Node, Operator, Scope};
|
||||
use crate::parse_error::EvalError;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn offset_line(line_num: usize, offset: i32) -> Result<usize, EvalError> {
|
||||
if (line_num as i32) + offset < 0 {
|
||||
Err(EvalError::OffsetError(line_num, offset))
|
||||
} else {
|
||||
Ok(((line_num as i32) + offset) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Evaluating expressions
|
||||
/// Now that we have a parsed file, that file has a bunch of numeric symbols in it: labels,
|
||||
/// .equ directives, that sort of thing. We need to resolve all of those to constant values
|
||||
/// before we can generate code. So, first part of that is being able to evaluate expressions.
|
||||
///
|
||||
/// This evaluates an expression in the context of a symbol table, and returns either what the
|
||||
/// expression evaluates to (a number) or an error (if it references a symbol not in
|
||||
/// the given symbol table, or needs to know an address that's not calculated yet).
|
||||
///
|
||||
/// It's a depth-first recursive traversal of the expression AST:
|
||||
///
|
||||
/// - If the node is a number, then it returns that number.
|
||||
/// - If the node is a string, it tries to look it up in the symbol table or explodes.
|
||||
/// - If the node is a relative label, it tries to look it up in the symbol table, and then
|
||||
/// subtracts a given start_address. If start_address is nil (as when we're solving .equs)
|
||||
/// then it errors.
|
||||
/// - If the node is an expr or term, then it evaluates the children: the children are a
|
||||
/// sequence of evaluate-able nodes separated by operators. So first evaluate the left-most
|
||||
/// child, then use the operator to combine it with the following one, and so on.
|
||||
/// - If the node is an absolute or relative line offset, it attempts to look up the start of
|
||||
/// the given line in the table of line start addresses and gives that address relatively or
|
||||
/// absolutely.
|
||||
pub fn eval(
|
||||
node: &Node,
|
||||
line_num: usize,
|
||||
line_addresses: &BTreeMap<usize, i32>,
|
||||
scope: &Scope,
|
||||
) -> Result<i32, EvalError> {
|
||||
match node {
|
||||
Node::Number(n) => Ok(*n),
|
||||
Node::Label(label) => scope.get(label.into()).map_or_else(
|
||||
|| Err(EvalError::MissingLabel(label.to_string())),
|
||||
|val| Ok(*val),
|
||||
),
|
||||
Node::RelativeLabel(label) => {
|
||||
if let Some(address) = line_addresses.get(&line_num) {
|
||||
scope.get(label.into()).map_or_else(
|
||||
|| Err(EvalError::MissingLabel(label.to_string())),
|
||||
|val| Ok(*val - address),
|
||||
)
|
||||
} else {
|
||||
Err(EvalError::UnknownAddress(line_num))
|
||||
}
|
||||
}
|
||||
Node::AbsoluteOffset(offset) => {
|
||||
let addr = offset_line(line_num, *offset)?;
|
||||
if let Some(dest_address) = line_addresses.get(&addr) {
|
||||
Ok(*dest_address)
|
||||
} else {
|
||||
Err(EvalError::UnknownAddress(addr))
|
||||
}
|
||||
}
|
||||
Node::RelativeOffset(offset) => {
|
||||
let addr = offset_line(line_num, *offset)?;
|
||||
if let (Some(line_address), Some(dest_address)) =
|
||||
(line_addresses.get(&line_num), line_addresses.get(&addr))
|
||||
{
|
||||
Ok(*dest_address - *line_address)
|
||||
} else {
|
||||
Err(EvalError::UnknownAddress(addr))
|
||||
}
|
||||
}
|
||||
Node::Expr(car, cdr) => {
|
||||
let car = eval(car, line_num, line_addresses, scope);
|
||||
if let Ok(mut acc) = car {
|
||||
for (op, node) in cdr {
|
||||
let rhs = eval(node, line_num, line_addresses, scope)?;
|
||||
match op {
|
||||
Operator::Add => acc += rhs,
|
||||
Operator::Sub => acc -= rhs,
|
||||
Operator::Mul => acc *= rhs,
|
||||
Operator::Div => acc /= rhs,
|
||||
Operator::Mod => acc %= rhs,
|
||||
}
|
||||
}
|
||||
Ok(acc)
|
||||
} else {
|
||||
car
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::ast::VASMLine;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
|
||||
fn test_eval(line: &str) -> Result<i32, EvalError> {
|
||||
test_scope_addresses_eval(BTreeMap::new(), [(1, 0x400)].into(), line)
|
||||
}
|
||||
|
||||
fn test_scope_eval(scope: Scope, line: &str) -> Result<i32, EvalError> {
|
||||
test_scope_addresses_eval(scope, [(1, 0x400)].into(), line)
|
||||
}
|
||||
|
||||
fn test_addresses_eval(
|
||||
line_addresses: BTreeMap<usize, i32>,
|
||||
line: &str,
|
||||
) -> Result<i32, EvalError> {
|
||||
test_scope_addresses_eval([].into(), line_addresses, line)
|
||||
}
|
||||
|
||||
fn test_scope_addresses_eval(
|
||||
scope: Scope,
|
||||
line_addresses: BTreeMap<usize, i32>,
|
||||
line: &str,
|
||||
) -> Result<i32, EvalError> {
|
||||
if let Ok(VASMLine::Instruction(_, _, Some(arg))) = parse_vasm_line(line) {
|
||||
eval(&arg, 1, &line_addresses, &scope)
|
||||
} else {
|
||||
panic!("Failed to parse an instruction line with an argument")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic() {
|
||||
assert_eq!(test_eval("add 4"), Ok(4));
|
||||
assert_eq!(test_eval("add 2 + 3"), Ok(5));
|
||||
assert_eq!(test_eval("add 6 - 3 - 1"), Ok(2));
|
||||
assert_eq!(test_eval("add 6 - (3 - 1)"), Ok(4));
|
||||
assert_eq!(test_eval("add 6 / (3-1) * 7"), Ok(21));
|
||||
assert_eq!(test_eval("add (1+2+4) % 5"), Ok(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_labels() {
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 5)].into(), "add apple"),
|
||||
Ok(5)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 5)].into(), "add apple + 7"),
|
||||
Ok(12)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 5)].into(), "add 5 + apple"),
|
||||
Ok(10)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval(
|
||||
[("apple".into(), 5), ("banana".into(), 3)].into(),
|
||||
"add apple * banana"
|
||||
),
|
||||
Ok(15)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 5)].into(), "add banana"),
|
||||
Err(EvalError::MissingLabel("banana".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([].into(), "add apple + 2"),
|
||||
Err(EvalError::MissingLabel("apple".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([].into(), "add 2 + apple"),
|
||||
Err(EvalError::MissingLabel("apple".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_labels() {
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 0x500)].into(), "jmpr @apple"),
|
||||
Ok(0x100)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple".into(), 0x300)].into(), "jmpr @apple"),
|
||||
Ok(-256)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_addresses_eval([("apple".into(), 0x300)].into(), [].into(), "jmpr @apple"),
|
||||
Err(EvalError::UnknownAddress(1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absolute_offset() {
|
||||
assert_eq!(
|
||||
test_addresses_eval([(4, 0x410)].into(), "jmp $+3"),
|
||||
Ok(0x410)
|
||||
);
|
||||
assert_eq!(
|
||||
test_addresses_eval([(4, 0x410)].into(), "jmp $+1"),
|
||||
Err(EvalError::UnknownAddress(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_offset() {
|
||||
assert_eq!(
|
||||
test_addresses_eval([(1, 0x400), (4, 0x410)].into(), "brz @+3"),
|
||||
Ok(0x10)
|
||||
);
|
||||
assert_eq!(
|
||||
test_addresses_eval([(4, 0x410)].into(), "brz @+2"),
|
||||
Err(EvalError::UnknownAddress(3))
|
||||
);
|
||||
assert_eq!(
|
||||
test_addresses_eval([(1, 0x400)].into(), "brz @+7"),
|
||||
Err(EvalError::UnknownAddress(8))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use pest::Parser;
|
||||
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
use crate::ast::{Label, Macro, Node, Operator, VASMLine};
|
||||
use crate::parse_error::ParseError;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod inner {
|
||||
#[derive(Parser)]
|
||||
#[grammar = "vasm.pest"]
|
||||
pub struct VASMParser;
|
||||
}
|
||||
|
||||
use inner::*;
|
||||
|
||||
type Pair<'a> = pest::iterators::Pair<'a, Rule>;
|
||||
|
||||
trait Children {
|
||||
fn first(self) -> Self;
|
||||
fn only(self) -> Self;
|
||||
}
|
||||
|
||||
impl<'a> Children for Pair<'a> {
|
||||
fn first(self) -> Self {
|
||||
self.into_inner().next().unwrap()
|
||||
}
|
||||
|
||||
fn only(self) -> Pair<'a> {
|
||||
let mut iter = self.into_inner();
|
||||
let child = iter.next().unwrap();
|
||||
debug_assert_eq!(iter.next(), None);
|
||||
child
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform tree-shaking on a `Node` by recursively removing single-`Node` exprs.
|
||||
fn shake(node: Node) -> Node {
|
||||
match node {
|
||||
Node::Expr(first, rest) => {
|
||||
let shaken_car = shake(*first);
|
||||
if rest.is_empty() {
|
||||
shaken_car
|
||||
} else {
|
||||
let shaken_cdr = rest.into_iter().map(|(op, n)| (op, shake(n))).collect();
|
||||
Node::Expr(Box::from(shaken_car), shaken_cdr)
|
||||
}
|
||||
}
|
||||
_ => node,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pair<'_>> for Operator {
|
||||
fn from(s: Pair) -> Self {
|
||||
use Operator::*;
|
||||
match s.as_str() {
|
||||
"+" => Add,
|
||||
"-" => Sub,
|
||||
"*" => Mul,
|
||||
"/" => Div,
|
||||
"%" => Mod,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a String containing the string represented by a given pair. Since the pair will
|
||||
/// reference bytes containing escape sequences, this isn't the same as an &str to the
|
||||
/// original code; this is a new string translating those escape sequences to their actual
|
||||
/// bytes.
|
||||
fn create_string(pair: Pair) -> String {
|
||||
let mut string = String::with_capacity(pair.as_str().len());
|
||||
for inner in pair.into_inner() {
|
||||
let string_inner = inner.as_str();
|
||||
match string_inner {
|
||||
"\\t" => string.push('\t'),
|
||||
"\\r" => string.push('\r'),
|
||||
"\\n" => string.push('\n'),
|
||||
"\\0" => string.push('\0'),
|
||||
"\\\\" => string.push('\\'),
|
||||
"\\\"" => string.push('\"'),
|
||||
_ => string.push_str(string_inner),
|
||||
}
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
/// Create a `Node` containing the line offset represented by a pair.
|
||||
fn create_line_offset_node(pair: Pair) -> Node {
|
||||
let outer = pair.as_rule();
|
||||
let mut inner = pair.into_inner();
|
||||
let sign = inner.next().unwrap().as_str();
|
||||
let mut num = i32::from_str(inner.next().unwrap().as_str()).unwrap();
|
||||
if sign == "-" {
|
||||
num *= -1
|
||||
}
|
||||
match outer {
|
||||
Rule::relative_line_offset => Node::RelativeOffset(num),
|
||||
Rule::absolute_line_offset => Node::AbsoluteOffset(num),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a given pair into the `Node` it represents. The result of this is a
|
||||
/// `Node` that might contain a borrow of part of the original code, but which in no way
|
||||
/// depends on the original pair's object model.
|
||||
fn parse(pair: Pair) -> Node {
|
||||
match pair.as_rule() {
|
||||
Rule::expr | Rule::term => {
|
||||
let mut iter = pair.into_inner();
|
||||
let first = parse(iter.next().unwrap());
|
||||
let mut rest = Vec::<(Operator, Node)>::new();
|
||||
|
||||
while let Some(operator) = iter.next() {
|
||||
let rhs = iter.next().unwrap();
|
||||
let op = Operator::from(operator);
|
||||
let node = parse(rhs);
|
||||
rest.push((op, node));
|
||||
}
|
||||
Node::Expr(Box::from(first), rest)
|
||||
}
|
||||
Rule::fact | Rule::number => parse(pair.only()),
|
||||
Rule::dec_number | Rule::dec_zero | Rule::neg_number => {
|
||||
Node::Number(i32::from_str(pair.as_str()).unwrap())
|
||||
}
|
||||
Rule::hex_number => {
|
||||
Node::Number(i32::from_str_radix(pair.as_str().get(2..).unwrap(), 16).unwrap())
|
||||
}
|
||||
Rule::bin_number => {
|
||||
Node::Number(i32::from_str_radix(pair.as_str().get(2..).unwrap(), 2).unwrap())
|
||||
}
|
||||
Rule::oct_number => {
|
||||
Node::Number(i32::from_str_radix(pair.as_str().get(2..).unwrap(), 8).unwrap())
|
||||
}
|
||||
Rule::label => Node::label(pair.as_str()),
|
||||
Rule::relative_label => Node::relative_label(pair.as_str().get(1..).unwrap()),
|
||||
Rule::absolute_line_offset | Rule::relative_line_offset => create_line_offset_node(pair),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn optional_label(pair: Option<Pair>) -> Option<Label> {
|
||||
pair.map(|label| Label::from(label.only().as_str()))
|
||||
}
|
||||
|
||||
pub fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError> {
|
||||
let line = VASMParser::parse(Rule::line, line)
|
||||
.map_err(|_| ParseError::LineParseFailure)?
|
||||
.next()
|
||||
.unwrap()
|
||||
.first();
|
||||
|
||||
match line.as_rule() {
|
||||
Rule::instruction => {
|
||||
let mut iter = line.into_inner().peekable();
|
||||
let label = optional_label(iter.next_if(|pair| pair.as_rule() == Rule::label_def));
|
||||
let opcode = Opcode::try_from(
|
||||
iter.next_if(|pair| pair.as_rule() == Rule::opcode)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
)?;
|
||||
let argument = iter
|
||||
.next_if(|pair| pair.as_rule() == Rule::expr)
|
||||
.map(|expr| shake(parse(expr)));
|
||||
Ok(VASMLine::Instruction(label, opcode, argument))
|
||||
}
|
||||
Rule::db_word => {
|
||||
let mut iter = line.into_inner().peekable();
|
||||
let label = optional_label(iter.next_if(|pair| pair.as_rule() == Rule::label_def));
|
||||
let argument = shake(parse(iter.next().unwrap()));
|
||||
Ok(VASMLine::Db(label, argument))
|
||||
}
|
||||
Rule::db_string => {
|
||||
let mut iter = line.into_inner().peekable();
|
||||
let label = optional_label(iter.next_if(|pair| pair.as_rule() == Rule::label_def));
|
||||
let argument = create_string(iter.next().unwrap());
|
||||
Ok(VASMLine::StringDb(label, argument))
|
||||
}
|
||||
Rule::org_directive => {
|
||||
let mut iter = line.into_inner().peekable();
|
||||
let label = optional_label(iter.next_if(|pair| pair.as_rule() == Rule::label_def));
|
||||
let argument = shake(parse(iter.next().unwrap()));
|
||||
Ok(VASMLine::Org(label, argument))
|
||||
}
|
||||
Rule::equ_directive => {
|
||||
let mut iter = line.into_inner();
|
||||
let label = Label::from(iter.next().unwrap().only().as_str());
|
||||
let argument = shake(parse(iter.next().unwrap()));
|
||||
Ok(VASMLine::Equ(label, argument))
|
||||
}
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_macro(line: Pair) -> Macro {
|
||||
let pre = line.only();
|
||||
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,
|
||||
"do" => Macro::Do,
|
||||
"end" => Macro::End,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Rule::include => Macro::Include(create_string(pre.only())),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use vcore::opcodes::Opcode::*;
|
||||
|
||||
use super::*;
|
||||
use crate::ast::Operator;
|
||||
|
||||
fn number(number: i32) -> Node {
|
||||
Node::Number(number)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("add"),
|
||||
Ok(VASMLine::Instruction(None, Add, None))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("sub"),
|
||||
Ok(VASMLine::Instruction(None, Sub, None))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("blah"),
|
||||
Err(ParseError::InvalidInstruction("blah".into()))
|
||||
);
|
||||
assert_eq!(parse_vasm_line("47"), Err(ParseError::LineParseFailure));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_numbers() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 45"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(45))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("blah: add 45"),
|
||||
Ok(VASMLine::Instruction(
|
||||
Some(Label::from("blah")),
|
||||
Add,
|
||||
Some(number(45))
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 0"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(0))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 0x10"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(16))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 0b1111"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(15))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 0o377"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(255))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add -17"),
|
||||
Ok(VASMLine::Instruction(None, Add, Some(number(-17))))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dbs() {
|
||||
assert_eq!(
|
||||
parse_vasm_line(".db \"blah\""),
|
||||
Ok(VASMLine::StringDb(None, "blah".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line(".db \"blah\\twith escapes\\0\""),
|
||||
Ok(VASMLine::StringDb(None, "blah\twith escapes\0".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: .db 47"),
|
||||
Ok(VASMLine::Db(Some(Label::from("foo")), number(47)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exprs() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 2 + 3 * (4 - 5) + 6"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Number(2)),
|
||||
vec![
|
||||
(
|
||||
Operator::Add,
|
||||
Node::Expr(
|
||||
Box::from(Node::Number(3)),
|
||||
vec![(
|
||||
Operator::Mul,
|
||||
Node::Expr(
|
||||
Box::from(Node::Number(4)),
|
||||
vec![(Operator::Sub, Node::Number(5))],
|
||||
)
|
||||
)],
|
||||
)
|
||||
),
|
||||
(Operator::Add, Node::Number(6))
|
||||
],
|
||||
))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_label_exprs() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("add 2 + foo"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Number(2)),
|
||||
vec![(Operator::Add, Node::label("foo"))]
|
||||
))
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("add foo + 2"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::label("foo")),
|
||||
vec![(Operator::Add, Node::Number(2))]
|
||||
))
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_labels() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("loadw foo"),
|
||||
Ok(VASMLine::Instruction(None, Loadw, Some(Node::label("foo"))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("brz @blah"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Brz,
|
||||
Some(Node::relative_label("blah"))
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_offsets() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("jmp $-2"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Jmp,
|
||||
Some(Node::AbsoluteOffset(-2))
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("brz @+3"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Brz,
|
||||
Some(Node::RelativeOffset(3))
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_labels() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: add"),
|
||||
Ok(VASMLine::Instruction(Some(Label::from("foo")), Add, None))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("bar:"),
|
||||
Ok(VASMLine::LabelDef(Label::from("bar")))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: add 43"),
|
||||
Ok(VASMLine::Instruction(
|
||||
Some(Label::from("foo")),
|
||||
Add,
|
||||
Some(number(43))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_directives() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: .equ 47"),
|
||||
Ok(VASMLine::Equ(Label::from("foo"), number(47)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line(".org 0x400"),
|
||||
Ok(VASMLine::Org(None, number(1024)))
|
||||
);
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
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, Clone)]
|
||||
pub struct Line {
|
||||
pub 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<
|
||||
T: IntoIterator<Item = String>,
|
||||
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<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>> Iterator
|
||||
for LineSource<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.as_str()) {
|
||||
// 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()
|
||||
}
|
||||
|
||||
// TODO: This makes line numbers on error messages wrong, but it's hard to fix. We need
|
||||
// to rip out and redo the whole error system. Parse lines initially to tuples of their
|
||||
// line and location (file and line number) and then pass one of those tuples to create
|
||||
// an error.
|
||||
Ok(VASMLine::Blank) => { 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<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
||||
LineSource<T, F>
|
||||
{
|
||||
// TODO: linesource should take a deref instead so it accepts either str or string.
|
||||
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<String>) -> Vec<VASMLine> {
|
||||
let include = |_name: String| panic!();
|
||||
let src = LineSource::new("blah", source, include);
|
||||
src.map(|line| line.unwrap().line).collect()
|
||||
}
|
||||
|
||||
fn stringify(a: Vec<&str>) -> Vec<String> {
|
||||
a.into_iter().map(String::from).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocess() {
|
||||
let include = |_name: String| panic!();
|
||||
let lines = stringify(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(stringify(vec!["sub"]));
|
||||
let lines = stringify(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(stringify(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(stringify(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(stringify(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