forked from MightyPork/crsn
add round-trip tests for built-in instructions
This commit is contained in:
@@ -8,9 +8,10 @@ use crate::asm::instr::{Cond, InstrWithBranches, Op, Routine};
|
||||
use crate::asm::instr::op::OpKind;
|
||||
use crate::builtin::defs::Barrier;
|
||||
use crate::builtin::defs::BuiltinOp;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A trait for something that can turn into multiple instructions
|
||||
pub trait Flatten {
|
||||
pub trait Flatten : Debug {
|
||||
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError>;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ pub mod cond;
|
||||
mod flatten;
|
||||
|
||||
/// A higher-level instruction
|
||||
#[derive(Debug)]
|
||||
pub struct InstrWithBranches {
|
||||
pub op: Op,
|
||||
pub branches: Option<Vec<(Cond, Box<dyn Flatten>)>>,
|
||||
}
|
||||
|
||||
/// A routine
|
||||
#[derive(Debug)]
|
||||
pub struct Routine {
|
||||
pub name: RoutineName,
|
||||
pub body: Box<dyn Flatten>,
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct ParserContext<'a> {
|
||||
pub state: RefCell<ParserState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ParserState {
|
||||
/// Register aliases within the routine
|
||||
pub reg_aliases: HashMap<RegisterAlias, Register>,
|
||||
@@ -40,7 +41,9 @@ pub struct ParserState {
|
||||
pub fn parse(source: &str, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
|
||||
let items = expect_list(Some(sexp::parse(source)?), true)?;
|
||||
|
||||
let label_num = AtomicU32::new(0);
|
||||
/* numbered labels start with a weird high number
|
||||
to avoid conflicts with user-defined numbered labels */
|
||||
let label_num = AtomicU32::new(0x7890_0000);
|
||||
parse_instructions(items.into_iter(), parsers)?
|
||||
.flatten(&label_num)
|
||||
}
|
||||
|
||||
@@ -43,9 +43,18 @@ pub fn parse_constant_name(name: Option<Sexp>) -> Result<ConstantName, CrsnError
|
||||
/// Parse a label
|
||||
pub fn parse_label(name: Option<Sexp>) -> Result<Label, CrsnError> {
|
||||
// trace!("parse label: {:?}", name);
|
||||
|
||||
let name = expect_string_atom(name)?;
|
||||
Ok(Label::Named(name.trim_start_matches(':').into()))
|
||||
Ok(parse_label_str(&name)?)
|
||||
}
|
||||
|
||||
pub fn parse_label_str(name: &str) -> Result<Label, CrsnError> {
|
||||
let label = name.trim_start_matches(':');
|
||||
|
||||
Ok(if label.starts_with('#') {
|
||||
Label::Numbered(u32::try_from(parse_u64(&label[1..])?).expect("numbered label fit in u32"))
|
||||
} else {
|
||||
Label::Named(label.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse data disposition (address/value, without the read/write restriction)
|
||||
@@ -82,7 +91,12 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
|
||||
}
|
||||
|
||||
if let Some(reference) = s.strip_prefix('@') {
|
||||
Ok(DataDisp::ObjectPtr(reg::parse_reg(reference)?))
|
||||
let pstate = pcx.state.borrow();
|
||||
if let Some(val) = pstate.reg_aliases.get(reference) {
|
||||
Ok(DataDisp::ObjectPtr(*val))
|
||||
} else {
|
||||
Ok(DataDisp::ObjectPtr(reg::parse_reg(reference)?))
|
||||
}
|
||||
} else if s.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(s)?) }))
|
||||
} else {
|
||||
@@ -96,7 +110,7 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
|
||||
}
|
||||
|
||||
/// Parse immediate value
|
||||
pub fn parse_value(tok: Option<Sexp>) -> Result<Value, CrsnError> {
|
||||
pub fn parse_value(tok: Option<Sexp>, pcx: &ParserContext) -> Result<Value, CrsnError> {
|
||||
let tok = if let Some(tok) = tok {
|
||||
tok
|
||||
} else {
|
||||
@@ -110,6 +124,11 @@ pub fn parse_value(tok: Option<Sexp>) -> Result<Value, CrsnError> {
|
||||
Ok(unsafe { std::mem::transmute(*val) })
|
||||
}
|
||||
Sexp::Atom(Atom::S(s)) => {
|
||||
let pstate = pcx.state.borrow();
|
||||
if let Some(val) = pstate.constants.get(s) {
|
||||
return Ok(*val);
|
||||
}
|
||||
|
||||
Ok(unsafe { std::mem::transmute(parse_i64(s)?) })
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -144,66 +144,6 @@ impl OpTrait for BuiltinOp {
|
||||
}
|
||||
|
||||
fn to_sexp(&self) -> Sexp {
|
||||
match self {
|
||||
BuiltinOp::Nop => sexp::list(&[A("nop")]),
|
||||
BuiltinOp::Halt => sexp::list(&[A("halt")]),
|
||||
BuiltinOp::Sleep { micros } => sexp::list(&[A("sleep"), A(micros)]),
|
||||
BuiltinOp::Label(label) => sexp::list(&[A(label)]),
|
||||
BuiltinOp::Jump(label) => sexp::list(&[A("j"), A(label)]),
|
||||
BuiltinOp::FarLabel(label) => sexp::list(&[A("far"), A(label)]),
|
||||
BuiltinOp::FarJump(label) => sexp::list(&[A("fj"), A(label)]),
|
||||
BuiltinOp::Call(name, args) => {
|
||||
if args.is_empty() {
|
||||
sexp::list(&[A("call"), A(name)])
|
||||
} else {
|
||||
let mut inner = vec![A("call"), A(&name.name)];
|
||||
inner.extend(args.iter().map(A));
|
||||
sexp::list(&inner)
|
||||
}
|
||||
},
|
||||
BuiltinOp::Ret(values) => {
|
||||
if values.is_empty() {
|
||||
sexp::list(&[A("ret")])
|
||||
} else {
|
||||
let mut inner = vec![A("ret")];
|
||||
inner.extend(values.iter().map(A));
|
||||
sexp::list(&inner)
|
||||
}
|
||||
}
|
||||
BuiltinOp::Routine(name) => sexp::list(&[A("routine"), A(name)]),
|
||||
BuiltinOp::Skip(n) => sexp::list(&[A("skip"), A(n)]),
|
||||
BuiltinOp::Barrier { kind, msg } => {
|
||||
let mut inner = vec![];
|
||||
match kind {
|
||||
Barrier::Open(label) => {
|
||||
inner.push(A("barrier-open"));
|
||||
inner.push(A(label));
|
||||
}
|
||||
Barrier::Close(label) => {
|
||||
inner.push(A("barrier-close"));
|
||||
inner.push(A(label));
|
||||
}
|
||||
Barrier::Standalone => {
|
||||
inner.push(A("barrier"));
|
||||
|
||||
if let Some(msg) = msg {
|
||||
inner.push(A(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
sexp::list(&inner)
|
||||
}
|
||||
BuiltinOp::Fault(msg) => {
|
||||
if let Some(msg) = msg {
|
||||
sexp::list(&[A("fault"), A(msg)])
|
||||
} else {
|
||||
sexp::list(&[A("fault")])
|
||||
}
|
||||
}
|
||||
BuiltinOp::Drop(obj) => sexp::list(&[A("drop"), A(obj)]),
|
||||
BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]),
|
||||
BuiltinOp::StoreStatus { dst } => sexp::list(&[A("sst"), A(dst)]),
|
||||
BuiltinOp::LoadStatus { src } => sexp::list(&[A("lst"), A(src)])
|
||||
}
|
||||
super::parse::to_sexp(self)
|
||||
}
|
||||
}
|
||||
|
||||
+193
-4
@@ -5,10 +5,12 @@ use crate::asm::data::reg::parse_reg;
|
||||
use crate::asm::error::CrsnError;
|
||||
use crate::asm::instr::op::OpKind;
|
||||
use crate::asm::parse::arg_parser::TokenParser;
|
||||
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_rd, parse_reg_alias, parse_value};
|
||||
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_rd, parse_reg_alias, parse_value, parse_u64, parse_label_str};
|
||||
use crate::asm::parse::sexp_expect::expect_string_atom;
|
||||
use crate::builtin::defs::{Barrier, BuiltinOp};
|
||||
use crate::module::{CrsnExtension, ParseRes};
|
||||
use crate::utils::A;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BuiltinOps {
|
||||
@@ -82,7 +84,7 @@ impl CrsnExtension for BuiltinOps {
|
||||
|
||||
"def" => {
|
||||
let name = parse_constant_name(args.next())?;
|
||||
let value = parse_value(args.next())?;
|
||||
let value = parse_value(args.next(), pcx)?;
|
||||
|
||||
let mut pstate = pcx.state.borrow_mut();
|
||||
if pstate.constants.contains_key(&name) {
|
||||
@@ -155,6 +157,20 @@ impl CrsnExtension for BuiltinOps {
|
||||
}
|
||||
}
|
||||
|
||||
"barrier-open" => {
|
||||
BuiltinOp::Barrier {
|
||||
kind: Barrier::Open(parse_label(args.next())?),
|
||||
msg: None,
|
||||
}
|
||||
}
|
||||
|
||||
"barrier-close" => {
|
||||
BuiltinOp::Barrier {
|
||||
kind: Barrier::Close(parse_label(args.next())?),
|
||||
msg: None,
|
||||
}
|
||||
}
|
||||
|
||||
"fault" => {
|
||||
BuiltinOp::Fault(match args.next() {
|
||||
None => None,
|
||||
@@ -200,8 +216,7 @@ impl CrsnExtension for BuiltinOps {
|
||||
|
||||
other => {
|
||||
if let Some(label) = other.strip_prefix(':') {
|
||||
let label = Label::Named(label.to_string());
|
||||
BuiltinOp::Label(label)
|
||||
BuiltinOp::Label(parse_label_str(label)?)
|
||||
} else {
|
||||
return Ok(ParseRes::Unknown(args));
|
||||
}
|
||||
@@ -222,3 +237,177 @@ pub(crate) fn parse_routine_name(name: String) -> Result<RoutineName, CrsnError>
|
||||
|
||||
Ok(RoutineName { name, arity })
|
||||
}
|
||||
|
||||
pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
|
||||
match op {
|
||||
BuiltinOp::Nop => sexp::list(&[A("nop")]),
|
||||
BuiltinOp::Halt => sexp::list(&[A("halt")]),
|
||||
BuiltinOp::Sleep { micros } => sexp::list(&[A("sleep"), A(micros)]),
|
||||
BuiltinOp::Label(label) => sexp::list(&[A(label)]),
|
||||
BuiltinOp::Jump(label) => sexp::list(&[A("j"), A(label)]),
|
||||
BuiltinOp::FarLabel(label) => sexp::list(&[A("far"), A(label)]),
|
||||
BuiltinOp::FarJump(label) => sexp::list(&[A("fj"), A(label)]),
|
||||
BuiltinOp::Call(name, args) => {
|
||||
if args.is_empty() {
|
||||
sexp::list(&[A("call"), A(&name.name)])
|
||||
} else {
|
||||
let mut inner = vec![A("call"), A(&name.name)];
|
||||
inner.extend(args.iter().map(A));
|
||||
sexp::list(&inner)
|
||||
}
|
||||
},
|
||||
BuiltinOp::Ret(values) => {
|
||||
if values.is_empty() {
|
||||
sexp::list(&[A("ret")])
|
||||
} else {
|
||||
let mut inner = vec![A("ret")];
|
||||
inner.extend(values.iter().map(A));
|
||||
sexp::list(&inner)
|
||||
}
|
||||
}
|
||||
BuiltinOp::Routine(name) => sexp::list(&[A("routine"), A(name)]),
|
||||
BuiltinOp::Skip(n) => sexp::list(&[A("skip"), A(n)]),
|
||||
BuiltinOp::Barrier { kind, msg } => {
|
||||
let mut inner = vec![];
|
||||
match kind {
|
||||
Barrier::Open(label) => {
|
||||
inner.push(A("barrier-open"));
|
||||
inner.push(A(label));
|
||||
}
|
||||
Barrier::Close(label) => {
|
||||
inner.push(A("barrier-close"));
|
||||
inner.push(A(label));
|
||||
}
|
||||
Barrier::Standalone => {
|
||||
inner.push(A("barrier"));
|
||||
|
||||
if let Some(msg) = msg {
|
||||
inner.push(A(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
sexp::list(&inner)
|
||||
}
|
||||
BuiltinOp::Fault(msg) => {
|
||||
if let Some(msg) = msg {
|
||||
sexp::list(&[A("fault"), A(msg)])
|
||||
} else {
|
||||
sexp::list(&[A("fault")])
|
||||
}
|
||||
}
|
||||
BuiltinOp::Drop(obj) => sexp::list(&[A("drop"), A(obj)]),
|
||||
BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]),
|
||||
BuiltinOp::StoreStatus { dst } => sexp::list(&[A("sst"), A(dst)]),
|
||||
BuiltinOp::LoadStatus { src } => sexp::list(&[A("sld"), A(src)])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
use crate::asm::instr::{Flatten, InstrWithBranches};
|
||||
use crate::asm::parse::{parse_instructions, ParserContext};
|
||||
use crate::asm::parse::sexp_expect::expect_list;
|
||||
use crate::builtin::defs::BuiltinOp;
|
||||
use crate::builtin::parse::BuiltinOps;
|
||||
use crate::module::OpTrait;
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let samples = vec![
|
||||
// sym, unsym, def, undef - pseudo-instructions
|
||||
// jump is translated to a skip
|
||||
("(nop)", "(nop)"),
|
||||
("(halt)", "(halt)"),
|
||||
("(sleep 1000)", "(sleep 1000)"),
|
||||
("(:x)\
|
||||
(j :x)", "(skip 0)"),
|
||||
("(:#7)\
|
||||
(j :#7)", "(skip 0)"),
|
||||
("(fj :x)", "(fj :x)"),
|
||||
("(skip 0)", "(skip 0)"),
|
||||
("(skip r0)", "(skip r0)"),
|
||||
("(sym banana r0)(unsym banana)(sym banana r1)(skip banana)", "(skip r1)"),
|
||||
("(def foo 123)(skip foo)", "(skip 123)"),
|
||||
("(def foo 123)(undef foo)(def foo 444)(skip foo)", "(skip 444)"),
|
||||
("(def foo -777)(def bar foo)(skip bar)", "(skip -777)"),
|
||||
("(skip -10)", "(skip -10)"),
|
||||
("(skip -10000)", "(skip -10000)"),
|
||||
("(call funcname)", "(call funcname)"),
|
||||
("(call funcname 13)", "(call funcname 13)"),
|
||||
("(sym haf r0)\
|
||||
(call štěkej haf haf)", "(call štěkej r0 r0)"),
|
||||
("(sym foo r0)\
|
||||
(sym bar r1)\
|
||||
(call funcname foo bar)", "(call funcname r0 r1)"),
|
||||
("(ret)", "(ret)"),
|
||||
("(ret r0)", "(ret r0)"),
|
||||
("(ret r0 r1 r2 5)", "(ret r0 r1 r2 5)"),
|
||||
("(routine ěščžřčřýřážíýáéáýúů)", "(routine ěščžřčřýřážíýáéáýúů/0)"),
|
||||
("(routine ahoj)", "(routine ahoj/0)"),
|
||||
("(routine ahoj/2)", "(routine ahoj/2)"),
|
||||
("(barrier)", "(barrier)"),
|
||||
("(barrier blablabla)", "(barrier blablabla)"),
|
||||
("(barrier \"s mezerou\")", "(barrier \"s mezerou\")"),
|
||||
("(barrier-open :xoxo)", "(barrier-open :xoxo)"),
|
||||
("(barrier-open :#10)", "(barrier-open :#10)"),
|
||||
("(barrier-close :xoxo)", "(barrier-close :xoxo)"),
|
||||
("(barrier-close :#10)", "(barrier-close :#10)"),
|
||||
("(fault)", "(fault)"),
|
||||
("(fault kur*a)", "(fault kur*a)"),
|
||||
("(fault \"do pr*ele\")", "(fault \"do pr*ele\")"),
|
||||
("(ld r0 r0)", "(ld r0 r0)"),
|
||||
("(ld r0 156)", "(ld r0 156)"),
|
||||
("(ld _ -32767)", "(ld _ -32767)"),
|
||||
("(sst r0)", "(sst r0)"),
|
||||
("(sld r0)", "(sld r0)"),
|
||||
("(far :label)", "(far :label)"),
|
||||
("(drop @r5)", "(drop @r5)"),
|
||||
("(sym cat r0)(drop @cat)", "(drop @r0)"),
|
||||
];
|
||||
|
||||
let parser = BuiltinOps::new();
|
||||
|
||||
let parsers = &[parser];
|
||||
|
||||
for (sample, expected) in samples {
|
||||
let mut pcx = ParserContext {
|
||||
parsers,
|
||||
state: Default::default(),
|
||||
};
|
||||
|
||||
println!("Parse: {}", sample);
|
||||
|
||||
/* first cycle */
|
||||
let s = sexp::parse(&format!("({})", sample))
|
||||
.expect("parse sexp");
|
||||
let list = expect_list(Some(s), false).unwrap();
|
||||
let num = AtomicU32::new(0);
|
||||
let mut parsed = parse_instructions(list.into_iter(), &pcx)
|
||||
.expect("parse instr").flatten(&num)
|
||||
.expect("flatten").remove(0);
|
||||
|
||||
let exported = parsed.to_sexp().to_string();
|
||||
println!("Parsed: {}", exported);
|
||||
|
||||
assert_eq!(expected, exported);
|
||||
|
||||
println!(" - 2nd cycle");
|
||||
|
||||
/* second cycle, nothing should change */
|
||||
let s = sexp::parse(&format!("({})", exported))
|
||||
.expect("parse sexp (2c)");
|
||||
let list = expect_list(Some(s), false).unwrap();
|
||||
let num = AtomicU32::new(0);
|
||||
let mut parsed = parse_instructions(list.into_iter(), &pcx)
|
||||
.expect("parse instr (2c)").flatten(&num)
|
||||
.expect("flatten (2c)").remove(0);
|
||||
|
||||
let exported2 = parsed.to_sexp().to_string();
|
||||
assert_eq!(expected, exported2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user