WIP, implementing macros
This commit is contained in:
+50
-19
@@ -1,5 +1,6 @@
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// One of the five arithmetical operators
|
||||
@@ -16,50 +17,80 @@ pub enum Operator {
|
||||
/// line offset, or an expr containing a sequence of other Nodes joined by same-precedence
|
||||
/// `Operator`s.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Node<'a> {
|
||||
pub enum Node {
|
||||
Number(i32),
|
||||
Label(&'a str),
|
||||
RelativeLabel(&'a str),
|
||||
Label(String),
|
||||
RelativeLabel(String),
|
||||
AbsoluteOffset(i32),
|
||||
RelativeOffset(i32),
|
||||
Expr(Box<Node<'a>>, Vec<(Operator, Node<'a>)>),
|
||||
Expr(Box<Node>, Vec<(Operator, Node)>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub struct Label<'a>(pub &'a str);
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Label<'a> {
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum VASMLine<'a> {
|
||||
Instruction(Option<Label<'a>>, Opcode, Option<Node<'a>>),
|
||||
Db(Option<Label<'a>>, Node<'a>),
|
||||
StringDb(Option<Label<'a>>, String),
|
||||
Org(Option<Label<'a>>, Node<'a>),
|
||||
Equ(Label<'a>, Node<'a>),
|
||||
LabelDef(Label<'a>),
|
||||
impl From<&str> for Label {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VASMLine<'a> {
|
||||
pub fn label(&self) -> Option<Label<'a>> {
|
||||
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),
|
||||
}
|
||||
|
||||
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),
|
||||
| VASMLine::LabelDef(lbl) => Some(lbl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn zero_length(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_)
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) | VASMLine::Macro(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ use std::fmt::{Display, Formatter};
|
||||
use vcore::opcodes::InvalidMnemonic;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ParseError<'a> {
|
||||
pub enum ParseError {
|
||||
LineParseFailure,
|
||||
InvalidInstruction(&'a str),
|
||||
InvalidInstruction(String),
|
||||
}
|
||||
|
||||
impl<'a> From<InvalidMnemonic<'a>> for ParseError<'a> {
|
||||
impl<'a> From<InvalidMnemonic<'a>> for ParseError {
|
||||
fn from(err: InvalidMnemonic<'a>) -> Self {
|
||||
Self::InvalidInstruction(err.0)
|
||||
Self::InvalidInstruction(err.0.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for ParseError<'a> {
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
use ParseError::*;
|
||||
match self {
|
||||
|
||||
+5
-1
@@ -68,5 +68,9 @@ 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) }
|
||||
|
||||
// Finally the entire pattern for an assembly line:
|
||||
line = { SOI ~ (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) ~ EOI }
|
||||
+266
-60
@@ -1,21 +1,23 @@
|
||||
use crate::ast::{Label, VASMLine};
|
||||
use crate::ast::{Label, Macro, Scope, VASMLine};
|
||||
use crate::parse_error::ParseError;
|
||||
use crate::vasm_evaluator::{eval, EvalError, Scope};
|
||||
use crate::vasm_evaluator::{eval, EvalError};
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AssembleError<'a> {
|
||||
ParseError(i32, ParseError<'a>),
|
||||
EquResolveError(i32, &'a str, EvalError<'a>),
|
||||
EquDuplicateError(i32, &'a str),
|
||||
OrgResolveError(i32, EvalError<'a>),
|
||||
ArgError(i32, EvalError<'a>),
|
||||
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<'a> Display for AssembleError<'a> {
|
||||
impl Display for AssembleError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AssembleError::EquResolveError(line, name, err) => {
|
||||
@@ -36,32 +38,140 @@ impl<'a> Display for AssembleError<'a> {
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum ControlStructure {
|
||||
Target(String),
|
||||
Loop(String, LoopType),
|
||||
}
|
||||
|
||||
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 => {}
|
||||
Macro::Do => {}
|
||||
Macro::End => {}
|
||||
},
|
||||
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
|
||||
/// only preceding .equ directives. Anything else is an error.
|
||||
fn solve_equs<'a>(lines: &Vec<VASMLine<'a>>) -> Result<Scope<'a>, AssembleError<'a>> {
|
||||
fn solve_equs(lines: &[VASMLine]) -> Result<Scope, AssembleError> {
|
||||
let mut scope: Scope = Scope::new();
|
||||
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
|
||||
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
let line_num = line_idx + 1;
|
||||
if let VASMLine::Equ(Label(name), expr) = line {
|
||||
let value = eval(&expr, line_num as i32, &line_nums, &scope)
|
||||
.map_err(|e| AssembleError::EquResolveError(line_num as i32, name, e))?;
|
||||
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, value) {
|
||||
return Err(AssembleError::EquDuplicateError(line_num as i32, name));
|
||||
if let Some(_old_value) = scope.insert(name.clone(), value) {
|
||||
return Err(AssembleError::EquDuplicateError(line_num, name.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(scope)
|
||||
}
|
||||
|
||||
type LineLengths = BTreeMap<i32, usize>;
|
||||
type LineAddresses = BTreeMap<i32, i32>;
|
||||
type LineLengths = BTreeMap<usize, usize>;
|
||||
type LineAddresses = BTreeMap<usize, i32>;
|
||||
|
||||
fn arg_length(val: i32) -> usize {
|
||||
if val < 0 {
|
||||
@@ -89,17 +199,17 @@ fn arg_length(val: i32) -> usize {
|
||||
/// 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<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLengths {
|
||||
let line_nums: BTreeMap<i32, i32> = BTreeMap::new();
|
||||
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) as i32;
|
||||
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 as i32, &line_nums, scope).map_or(3, arg_length);
|
||||
let len = eval(node, line_num, &line_nums, scope).map_or(3, arg_length);
|
||||
lengths.insert(line_num, len + 1);
|
||||
}
|
||||
VASMLine::Db(_, _) => {
|
||||
@@ -111,6 +221,7 @@ fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLen
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {
|
||||
lengths.insert(line_num, 0);
|
||||
}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
lengths
|
||||
@@ -126,26 +237,26 @@ fn measure_instructions<'a>(lines: &Vec<VASMLine<'a>>, scope: &Scope) -> LineLen
|
||||
///
|
||||
/// 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<'a>(
|
||||
lines: &Vec<VASMLine<'a>>,
|
||||
scope: Scope<'a>,
|
||||
fn place_labels(
|
||||
lines: &[VASMLine],
|
||||
scope: Scope,
|
||||
lengths: &LineLengths,
|
||||
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
|
||||
) -> 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) as i32;
|
||||
let line_num = line_idx + 1;
|
||||
|
||||
if let VASMLine::Org(_, expr) = line {
|
||||
address = eval(&expr, line_num, &addresses, &scope)
|
||||
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, address as i32);
|
||||
scope.insert(label.clone(), address as i32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,35 +279,33 @@ fn poke_word(code: &mut Vec<u8>, at: usize, word: i32) {
|
||||
}
|
||||
|
||||
/// Find the lower and upper bounds where this program will place memory
|
||||
fn code_bounds<'a>(
|
||||
lines: &Vec<VASMLine<'a>>,
|
||||
fn code_bounds(
|
||||
lines: &[VASMLine],
|
||||
line_addresses: &LineAddresses,
|
||||
line_lengths: &LineLengths,
|
||||
) -> Result<(usize, usize), AssembleError<'a>> {
|
||||
) -> 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 as i32 + 1)] as usize;
|
||||
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 as i32 + 1)] as usize;
|
||||
let end_length = line_lengths[&(last_idx as i32 + 1)];
|
||||
let end = line_addresses[&(last_idx + 1)] as usize;
|
||||
let end_length = line_lengths[&(last_idx + 1)];
|
||||
|
||||
Ok((start, end + end_length - 1))
|
||||
}
|
||||
|
||||
pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<Vec<u8>, AssembleError<'a>> {
|
||||
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) as i32;
|
||||
let line_num = line_idx + 1;
|
||||
parsed.push(parse_vasm_line(line).map_err(|err| AssembleError::ParseError(line_num, err))?)
|
||||
}
|
||||
|
||||
@@ -218,8 +327,8 @@ pub fn assemble<'a, T: IntoIterator<Item = &'a str>>(
|
||||
///
|
||||
/// 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<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<'a>> {
|
||||
let lines: Vec<VASMLine<'a>> = lines.into_iter().collect();
|
||||
fn generate_code(lines: Vec<VASMLine>) -> 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)?;
|
||||
@@ -229,7 +338,7 @@ fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<
|
||||
let mut current_addr = start;
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
let line_num = line_idx + 1;
|
||||
match line {
|
||||
VASMLine::Instruction(_, opcode, None) => {
|
||||
code[current_addr] = u8::from(*opcode) << 2;
|
||||
@@ -267,6 +376,7 @@ fn generate_code<'a>(lines: Vec<VASMLine<'a>>) -> Result<Vec<u8>, AssembleError<
|
||||
current_addr = line_addresses[&(line_num + 1)] as usize;
|
||||
}
|
||||
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {}
|
||||
VASMLine::Macro(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,11 +388,12 @@ mod test {
|
||||
use super::AssembleError::*;
|
||||
use super::EvalError::*;
|
||||
use super::*;
|
||||
use crate::ast::VASMLine;
|
||||
use crate::ast::{Node, VASMLine};
|
||||
use crate::parse_error;
|
||||
use crate::vasm_parser::parse_vasm_line;
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine<'a>> {
|
||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<VASMLine> {
|
||||
lines
|
||||
.into_iter()
|
||||
.map(|line| parse_vasm_line(line).unwrap())
|
||||
@@ -291,7 +402,7 @@ mod test {
|
||||
|
||||
fn place_labels_pass<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> {
|
||||
) -> Result<(LineAddresses, Scope), AssembleError> {
|
||||
let parsed_lines = parse(lines);
|
||||
let scope = solve_equs(&parsed_lines).unwrap();
|
||||
let lengths = measure_instructions(&parsed_lines, &scope);
|
||||
@@ -300,7 +411,7 @@ mod test {
|
||||
|
||||
fn bounds<'a, T: IntoIterator<Item = &'a str>>(
|
||||
lines: T,
|
||||
) -> Result<(usize, usize), AssembleError<'a>> {
|
||||
) -> Result<(usize, usize), AssembleError> {
|
||||
let parsed_lines = parse(lines);
|
||||
let scope = solve_equs(&parsed_lines).unwrap();
|
||||
let lengths = measure_instructions(&parsed_lines, &scope);
|
||||
@@ -312,19 +423,19 @@ mod test {
|
||||
fn test_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5+3"])),
|
||||
Ok([("blah", 8)].into())
|
||||
Ok([("blah".to_string(), 8)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ 3"])),
|
||||
Ok([("blah", 5), ("foo", 3)].into())
|
||||
Ok([("blah".to_string(), 5), ("foo".to_string(), 3)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ blah + 7"])),
|
||||
Ok([("blah", 5), ("foo", 12)].into())
|
||||
Ok([("blah".to_string(), 5), ("foo".to_string(), 12)].into())
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["add", "blah: .equ 5"])),
|
||||
Ok([("blah", 5)].into())
|
||||
Ok([("blah".to_string(), 5)].into())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -332,15 +443,23 @@ mod test {
|
||||
fn test_unsolvable_equs() {
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])),
|
||||
Err(EquResolveError(2, "foo", MissingLabel("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", MissingLabel("foo")))
|
||||
Err(EquResolveError(
|
||||
1,
|
||||
"blah".into(),
|
||||
MissingLabel("foo".into())
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
|
||||
Err(EquDuplicateError(2, "blah"))
|
||||
Err(EquDuplicateError(2, "blah".into()))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -364,7 +483,7 @@ mod test {
|
||||
assert_eq!(
|
||||
measure_instructions(
|
||||
&parse(["add 2 + foo", "add 3 + blah", "jmpr @foo"]),
|
||||
&[("blah", 300)].into()
|
||||
&[("blah".to_string(), 300)].into()
|
||||
),
|
||||
[(1, 4), (2, 3), (3, 4)].into()
|
||||
);
|
||||
@@ -376,7 +495,7 @@ mod test {
|
||||
place_labels_pass(["start: .org 256", "add", "dup"]),
|
||||
Ok((
|
||||
[(1, 256), (2, 256), (3, 257)].into(),
|
||||
[("start", 256)].into()
|
||||
[("start".to_string(), 256)].into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -387,14 +506,14 @@ mod test {
|
||||
place_labels_pass(["start: .equ 256", "blah: .org start + 4", "add"]),
|
||||
Ok((
|
||||
[(1, 0), (2, 260), (3, 260)].into(),
|
||||
[("blah", 260), ("start", 256)].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", 266), ("start", 256)].into()
|
||||
[("blah".to_string(), 266), ("start".to_string(), 256)].into()
|
||||
))
|
||||
);
|
||||
}
|
||||
@@ -403,11 +522,11 @@ mod test {
|
||||
fn test_unresolvable_orgs() {
|
||||
assert_eq!(
|
||||
place_labels_pass([".org 0xffffff - blah"]),
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
|
||||
);
|
||||
assert_eq!(
|
||||
place_labels_pass(["blah: .org blah"]),
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah")))
|
||||
Err(OrgResolveError(1, EvalError::MissingLabel("blah".into())))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -455,8 +574,95 @@ mod test {
|
||||
assemble(["apple"]),
|
||||
Err(ParseError(
|
||||
1,
|
||||
parse_error::ParseError::InvalidInstruction("apple")
|
||||
parse_error::ParseError::InvalidInstruction("apple".into())
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[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() {
|
||||
let include = |_name: String| Ok(vec![]);
|
||||
let lines = vec!["#if"];
|
||||
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"))
|
||||
)
|
||||
}])
|
||||
)
|
||||
}
|
||||
|
||||
#[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()))
|
||||
}
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+59
-44
@@ -1,14 +1,15 @@
|
||||
use crate::ast::{Node, Operator};
|
||||
use crate::ast::{Node, Operator, Scope};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum EvalError<'a> {
|
||||
MissingLabel(&'a str),
|
||||
UnknownAddress(i32),
|
||||
pub enum EvalError {
|
||||
MissingLabel(String),
|
||||
UnknownAddress(usize),
|
||||
OffsetError(usize, i32),
|
||||
}
|
||||
|
||||
impl<'a> Display for EvalError<'a> {
|
||||
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),
|
||||
@@ -17,11 +18,20 @@ impl<'a> Display for EvalError<'a> {
|
||||
"Unable to calculate starting address of line {}",
|
||||
line_num
|
||||
),
|
||||
EvalError::OffsetError(line_num, offset) => {
|
||||
write!(f, "Invalid line offset {} on line {}", offset, line_num)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Scope<'a> = BTreeMap<&'a str, i32>;
|
||||
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,
|
||||
@@ -45,21 +55,22 @@ pub type Scope<'a> = BTreeMap<&'a str, i32>;
|
||||
/// - 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<'a>(
|
||||
node: &Node<'a>,
|
||||
line_num: i32,
|
||||
line_addresses: &BTreeMap<i32, i32>,
|
||||
pub fn eval(
|
||||
node: &Node,
|
||||
line_num: usize,
|
||||
line_addresses: &BTreeMap<usize, i32>,
|
||||
scope: &Scope,
|
||||
) -> Result<i32, EvalError<'a>> {
|
||||
) -> Result<i32, EvalError> {
|
||||
match node {
|
||||
Node::Number(n) => Ok(*n),
|
||||
Node::Label(label) => scope
|
||||
.get(label)
|
||||
.map_or_else(|| Err(EvalError::MissingLabel(label)), |val| Ok(*val)),
|
||||
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).map_or_else(
|
||||
|| Err(EvalError::MissingLabel(label)),
|
||||
scope.get(label.into()).map_or_else(
|
||||
|| Err(EvalError::MissingLabel(label.to_string())),
|
||||
|val| Ok(*val - address),
|
||||
)
|
||||
} else {
|
||||
@@ -67,20 +78,21 @@ pub fn eval<'a>(
|
||||
}
|
||||
}
|
||||
Node::AbsoluteOffset(offset) => {
|
||||
if let Some(dest_address) = line_addresses.get(&(line_num + offset)) {
|
||||
let addr = offset_line(line_num, *offset)?;
|
||||
if let Some(dest_address) = line_addresses.get(&addr) {
|
||||
Ok(*dest_address)
|
||||
} else {
|
||||
Err(EvalError::UnknownAddress(line_num + offset))
|
||||
Err(EvalError::UnknownAddress(addr))
|
||||
}
|
||||
}
|
||||
Node::RelativeOffset(offset) => {
|
||||
if let (Some(line_address), Some(dest_address)) = (
|
||||
line_addresses.get(&line_num),
|
||||
line_addresses.get(&(line_num + 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(line_num + offset))
|
||||
Err(EvalError::UnknownAddress(addr))
|
||||
}
|
||||
}
|
||||
Node::Expr(car, cdr) => {
|
||||
@@ -114,25 +126,22 @@ mod test {
|
||||
test_scope_addresses_eval(BTreeMap::new(), [(1, 0x400)].into(), line)
|
||||
}
|
||||
|
||||
fn test_scope_eval<'a>(
|
||||
scope: BTreeMap<&'a str, i32>,
|
||||
line: &'a str,
|
||||
) -> Result<i32, EvalError<'a>> {
|
||||
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<i32, i32>,
|
||||
line_addresses: BTreeMap<usize, i32>,
|
||||
line: &str,
|
||||
) -> Result<i32, EvalError> {
|
||||
test_scope_addresses_eval([].into(), line_addresses, line)
|
||||
}
|
||||
|
||||
fn test_scope_addresses_eval<'a>(
|
||||
scope: BTreeMap<&'a str, i32>,
|
||||
line_addresses: BTreeMap<i32, i32>,
|
||||
line: &'a str,
|
||||
) -> Result<i32, EvalError<'a>> {
|
||||
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 {
|
||||
@@ -152,45 +161,51 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_labels() {
|
||||
assert_eq!(test_scope_eval([("apple", 5)].into(), "add apple"), Ok(5));
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 5)].into(), "add apple + 7"),
|
||||
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", 5)].into(), "add 5 + apple"),
|
||||
test_scope_eval([("apple".into(), 5)].into(), "add 5 + apple"),
|
||||
Ok(10)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 5), ("banana", 3)].into(), "add apple * banana"),
|
||||
test_scope_eval(
|
||||
[("apple".into(), 5), ("banana".into(), 3)].into(),
|
||||
"add apple * banana"
|
||||
),
|
||||
Ok(15)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 5)].into(), "add banana"),
|
||||
Err(EvalError::MissingLabel("banana"))
|
||||
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"))
|
||||
Err(EvalError::MissingLabel("apple".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([].into(), "add 2 + apple"),
|
||||
Err(EvalError::MissingLabel("apple"))
|
||||
Err(EvalError::MissingLabel("apple".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_labels() {
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 0x500)].into(), "jmpr @apple"),
|
||||
test_scope_eval([("apple".into(), 0x500)].into(), "jmpr @apple"),
|
||||
Ok(0x100)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_eval([("apple", 0x300)].into(), "jmpr @apple"),
|
||||
test_scope_eval([("apple".into(), 0x300)].into(), "jmpr @apple"),
|
||||
Ok(-256)
|
||||
);
|
||||
assert_eq!(
|
||||
test_scope_addresses_eval([("apple", 0x300)].into(), [].into(), "jmpr @apple"),
|
||||
test_scope_addresses_eval([("apple".into(), 0x300)].into(), [].into(), "jmpr @apple"),
|
||||
Err(EvalError::UnknownAddress(1))
|
||||
);
|
||||
}
|
||||
|
||||
+51
-26
@@ -4,7 +4,7 @@ use pest::Parser;
|
||||
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
use crate::ast::{Label, Node, Operator, VASMLine};
|
||||
use crate::ast::{Label, Macro, Node, Operator, VASMLine};
|
||||
use crate::parse_error::ParseError;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -134,18 +134,18 @@ fn parse(pair: Pair) -> Node {
|
||||
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::RelativeLabel(pair.as_str().get(1..).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(label.only().as_str()))
|
||||
pair.map(|label| Label::from(label.only().as_str()))
|
||||
}
|
||||
|
||||
pub fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError<'_>> {
|
||||
pub fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError> {
|
||||
let line = VASMParser::parse(Rule::line, line)
|
||||
.map_err(|_| ParseError::LineParseFailure)?
|
||||
.next()
|
||||
@@ -164,35 +164,51 @@ pub fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError<'_>> {
|
||||
let argument = iter
|
||||
.next_if(|pair| pair.as_rule() == Rule::expr)
|
||||
.map(|expr| shake(parse(expr)));
|
||||
return Ok(VASMLine::Instruction(label, opcode, argument));
|
||||
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()));
|
||||
return Ok(VASMLine::Db(label, argument));
|
||||
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());
|
||||
return Ok(VASMLine::StringDb(label, argument));
|
||||
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()));
|
||||
return Ok(VASMLine::Org(label, argument));
|
||||
Ok(VASMLine::Org(label, argument))
|
||||
}
|
||||
Rule::equ_directive => {
|
||||
let mut iter = line.into_inner();
|
||||
let label = Label(iter.next().unwrap().only().as_str());
|
||||
let label = Label::from(iter.next().unwrap().only().as_str());
|
||||
let argument = shake(parse(iter.next().unwrap()));
|
||||
return Ok(VASMLine::Equ(label, argument));
|
||||
}
|
||||
Rule::label_def => {
|
||||
return Ok(VASMLine::LabelDef(Label(line.only().as_str())));
|
||||
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))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_macro(line: Pair) -> Macro {
|
||||
let pre = line.only();
|
||||
match pre.as_rule() {
|
||||
Rule::control => match pre.as_str() {
|
||||
"if" => Macro::If,
|
||||
"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!(),
|
||||
}
|
||||
}
|
||||
@@ -204,7 +220,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::ast::Operator;
|
||||
|
||||
fn number(number: i32) -> Node<'static> {
|
||||
fn number(number: i32) -> Node {
|
||||
Node::Number(number)
|
||||
}
|
||||
|
||||
@@ -220,7 +236,7 @@ mod test {
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("blah"),
|
||||
Err(ParseError::InvalidInstruction("blah"))
|
||||
Err(ParseError::InvalidInstruction("blah".into()))
|
||||
);
|
||||
assert_eq!(parse_vasm_line("47"), Err(ParseError::LineParseFailure));
|
||||
}
|
||||
@@ -234,7 +250,7 @@ mod test {
|
||||
assert_eq!(
|
||||
parse_vasm_line("blah: add 45"),
|
||||
Ok(VASMLine::Instruction(
|
||||
Some(Label("blah")),
|
||||
Some(Label::from("blah")),
|
||||
Add,
|
||||
Some(number(45))
|
||||
))
|
||||
@@ -273,7 +289,7 @@ mod test {
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: .db 47"),
|
||||
Ok(VASMLine::Db(Some(Label("foo")), number(47)))
|
||||
Ok(VASMLine::Db(Some(Label::from("foo")), number(47)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -316,7 +332,7 @@ mod test {
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Number(2)),
|
||||
vec![(Operator::Add, Node::Label("foo"))]
|
||||
vec![(Operator::Add, Node::label("foo"))]
|
||||
))
|
||||
))
|
||||
);
|
||||
@@ -326,7 +342,7 @@ mod test {
|
||||
None,
|
||||
Add,
|
||||
Some(Node::Expr(
|
||||
Box::from(Node::Label("foo")),
|
||||
Box::from(Node::label("foo")),
|
||||
vec![(Operator::Add, Node::Number(2))]
|
||||
))
|
||||
))
|
||||
@@ -337,14 +353,14 @@ mod test {
|
||||
fn test_expr_labels() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("loadw foo"),
|
||||
Ok(VASMLine::Instruction(None, Loadw, Some(Node::Label("foo"))))
|
||||
Ok(VASMLine::Instruction(None, Loadw, Some(Node::label("foo"))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("brz @blah"),
|
||||
Ok(VASMLine::Instruction(
|
||||
None,
|
||||
Brz,
|
||||
Some(Node::RelativeLabel("blah"))
|
||||
Some(Node::relative_label("blah"))
|
||||
))
|
||||
)
|
||||
}
|
||||
@@ -373,16 +389,16 @@ mod test {
|
||||
fn test_parse_labels() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: add"),
|
||||
Ok(VASMLine::Instruction(Some(Label("foo")), Add, None))
|
||||
Ok(VASMLine::Instruction(Some(Label::from("foo")), Add, None))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("bar:"),
|
||||
Ok(VASMLine::LabelDef(Label("bar")))
|
||||
Ok(VASMLine::LabelDef(Label::from("bar")))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: add 43"),
|
||||
Ok(VASMLine::Instruction(
|
||||
Some(Label("foo")),
|
||||
Some(Label::from("foo")),
|
||||
Add,
|
||||
Some(number(43))
|
||||
))
|
||||
@@ -393,11 +409,20 @@ mod test {
|
||||
fn test_parse_directives() {
|
||||
assert_eq!(
|
||||
parse_vasm_line("foo: .equ 47"),
|
||||
Ok(VASMLine::Equ(Label("foo"), number(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("#include \"blah\""),
|
||||
Ok(VASMLine::Macro(Macro::Include("blah".to_string())))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user