forked from MightyPork/crsn
cleaning
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32};
|
||||||
|
|
||||||
use crate::asm::data::{Rd, RdData};
|
use crate::asm::data::{Rd, RdData};
|
||||||
use crate::asm::data::literal::{Label, Value};
|
use crate::asm::data::literal::{Label, Value};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use crate::asm::error::CrsnError;
|
|||||||
use crate::asm::instr::cond::parse_cond;
|
use crate::asm::instr::cond::parse_cond;
|
||||||
use crate::asm::instr::Op;
|
use crate::asm::instr::Op;
|
||||||
use crate::asm::parse::arg_parser::TokenParser;
|
use crate::asm::parse::arg_parser::TokenParser;
|
||||||
use crate::builtin::parse::BuiltinOps;
|
|
||||||
use crate::module::ParseRes;
|
use crate::module::ParseRes;
|
||||||
|
use crate::builtin::BuiltinOps;
|
||||||
|
|
||||||
pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>) -> Result<Option<Op>, CrsnError> {
|
pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>) -> Result<Option<Op>, CrsnError> {
|
||||||
// Include built-in instructions
|
// Include built-in instructions
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use crate::runtime::fault::Fault;
|
|||||||
use crate::runtime::frame::StackFrame;
|
use crate::runtime::frame::StackFrame;
|
||||||
use crate::runtime::run_thread::{state::RunState, ThreadInfo};
|
use crate::runtime::run_thread::{state::RunState, ThreadInfo};
|
||||||
use sexp::Sexp;
|
use sexp::Sexp;
|
||||||
use sexp::Atom;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use crate::utils::A;
|
|
||||||
|
|
||||||
impl OpTrait for BuiltinOp {
|
impl OpTrait for BuiltinOp {
|
||||||
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
|
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
|
||||||
|
|||||||
@@ -1,3 +1,27 @@
|
|||||||
|
use crate::module::{CrsnExtension, ParseRes};
|
||||||
|
use crate::asm::parse::arg_parser::TokenParser;
|
||||||
|
use crate::asm::instr::op::OpKind;
|
||||||
|
use crate::asm::error::CrsnError;
|
||||||
|
|
||||||
pub mod defs;
|
pub mod defs;
|
||||||
pub mod exec;
|
pub mod exec;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BuiltinOps;
|
||||||
|
|
||||||
|
impl BuiltinOps {
|
||||||
|
pub fn new() -> Box<dyn CrsnExtension> {
|
||||||
|
Box::new(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrsnExtension for BuiltinOps {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"builtin"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_op<'a>(&self, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
|
||||||
|
parse::parse_op(keyword, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+153
-172
@@ -5,224 +5,205 @@ use crate::asm::data::reg::parse_reg;
|
|||||||
use crate::asm::error::CrsnError;
|
use crate::asm::error::CrsnError;
|
||||||
use crate::asm::instr::op::OpKind;
|
use crate::asm::instr::op::OpKind;
|
||||||
use crate::asm::parse::arg_parser::TokenParser;
|
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, parse_u64, parse_label_str};
|
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_rd, parse_reg_alias, parse_value, parse_label_str};
|
||||||
use crate::asm::parse::sexp_expect::expect_string_atom;
|
use crate::asm::parse::sexp_expect::expect_string_atom;
|
||||||
use crate::builtin::defs::{Barrier, BuiltinOp};
|
use crate::builtin::defs::{Barrier, BuiltinOp};
|
||||||
use crate::module::{CrsnExtension, ParseRes};
|
use crate::module::{CrsnExtension, ParseRes};
|
||||||
use crate::utils::A;
|
use crate::utils::A;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BuiltinOps {
|
|
||||||
_internal: ()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuiltinOps {
|
pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
|
||||||
pub fn new() -> Box<dyn CrsnExtension> {
|
let pcx = args.pcx;
|
||||||
Box::new(Self {
|
|
||||||
_internal: ()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CrsnExtension for BuiltinOps {
|
Ok(ParseRes::Parsed(OpKind::BuiltIn(match keyword {
|
||||||
fn name(&self) -> &'static str {
|
"nop" => {
|
||||||
"builtin"
|
BuiltinOp::Nop
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_op<'a>(&self, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
|
"halt" => {
|
||||||
let pcx = args.pcx;
|
BuiltinOp::Halt
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ParseRes::Parsed(OpKind::BuiltIn(match keyword {
|
"sleep" => {
|
||||||
"nop" => {
|
BuiltinOp::Sleep {
|
||||||
BuiltinOp::Nop
|
micros: args.next_rd()?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"sym" => {
|
||||||
|
let alias = parse_reg_alias(args.next())?;
|
||||||
|
trace!("alias={:?}", alias);
|
||||||
|
let register = parse_reg(&args.next_string()?)?;
|
||||||
|
trace!("register={:?}", alias);
|
||||||
|
|
||||||
|
let mut pstate = pcx.state.borrow_mut();
|
||||||
|
if pstate.reg_aliases.contains_key(&alias) {
|
||||||
|
return Err(CrsnError::Parse(format!("Register alias \"{}\" already defined!", alias).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
"halt" => {
|
if pstate.constants.contains_key(&alias) {
|
||||||
BuiltinOp::Halt
|
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a constant!", alias).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
"sleep" => {
|
if pstate.reg_aliases.iter().find(|x| x.1 == ®ister).is_some() {
|
||||||
BuiltinOp::Sleep {
|
return Err(CrsnError::Parse(format!("Register \"{}\" already aliased!", register).into()));
|
||||||
micros: args.next_rd()?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"sym" => {
|
pstate.reg_aliases.insert(alias, register);
|
||||||
let alias = parse_reg_alias(args.next())?;
|
|
||||||
trace!("alias={:?}", alias);
|
|
||||||
let register = parse_reg(&args.next_string()?)?;
|
|
||||||
trace!("register={:?}", alias);
|
|
||||||
|
|
||||||
let mut pstate = pcx.state.borrow_mut();
|
return Ok(ParseRes::ParsedNone);
|
||||||
if pstate.reg_aliases.contains_key(&alias) {
|
}
|
||||||
return Err(CrsnError::Parse(format!("Register alias \"{}\" already defined!", alias).into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if pstate.constants.contains_key(&alias) {
|
"unsym" => {
|
||||||
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a constant!", alias).into()));
|
let alias = parse_reg_alias(args.next())?;
|
||||||
}
|
|
||||||
|
|
||||||
if pstate.reg_aliases.iter().find(|x| x.1 == ®ister).is_some() {
|
let mut pstate = pcx.state.borrow_mut();
|
||||||
return Err(CrsnError::Parse(format!("Register \"{}\" already aliased!", register).into()));
|
if pstate.reg_aliases.remove(&alias).is_none() {
|
||||||
}
|
return Err(CrsnError::Parse(format!("Register alias \"{}\" not defined!", alias).into()));
|
||||||
|
}
|
||||||
|
return Ok(ParseRes::ParsedNone);
|
||||||
|
}
|
||||||
|
|
||||||
pstate.reg_aliases.insert(alias, register);
|
"def" => {
|
||||||
|
let name = parse_constant_name(args.next())?;
|
||||||
|
let value = parse_value(args.next(), pcx)?;
|
||||||
|
|
||||||
return Ok(ParseRes::ParsedNone);
|
let mut pstate = pcx.state.borrow_mut();
|
||||||
|
if pstate.constants.contains_key(&name) {
|
||||||
|
return Err(CrsnError::Parse(format!("Constant \"{}\" already defined!", name).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
"unsym" => {
|
if pstate.reg_aliases.contains_key(&name) {
|
||||||
let alias = parse_reg_alias(args.next())?;
|
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a register alias!", name).into()));
|
||||||
|
|
||||||
let mut pstate = pcx.state.borrow_mut();
|
|
||||||
if pstate.reg_aliases.remove(&alias).is_none() {
|
|
||||||
return Err(CrsnError::Parse(format!("Register alias \"{}\" not defined!", alias).into()));
|
|
||||||
}
|
|
||||||
return Ok(ParseRes::ParsedNone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"def" => {
|
pstate.constants.insert(name, value);
|
||||||
let name = parse_constant_name(args.next())?;
|
|
||||||
let value = parse_value(args.next(), pcx)?;
|
|
||||||
|
|
||||||
let mut pstate = pcx.state.borrow_mut();
|
return Ok(ParseRes::ParsedNone);
|
||||||
if pstate.constants.contains_key(&name) {
|
}
|
||||||
return Err(CrsnError::Parse(format!("Constant \"{}\" already defined!", name).into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if pstate.reg_aliases.contains_key(&name) {
|
"undef" => {
|
||||||
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a register alias!", name).into()));
|
let name = parse_constant_name(args.next())?;
|
||||||
}
|
|
||||||
|
|
||||||
pstate.constants.insert(name, value);
|
let mut pstate = pcx.state.borrow_mut();
|
||||||
|
if pstate.constants.remove(&name).is_none() {
|
||||||
return Ok(ParseRes::ParsedNone);
|
return Err(CrsnError::Parse(format!("Constant \"{}\" not defined!", name).into()));
|
||||||
}
|
}
|
||||||
|
return Ok(ParseRes::ParsedNone);
|
||||||
|
}
|
||||||
|
|
||||||
"undef" => {
|
"j" => {
|
||||||
let name = parse_constant_name(args.next())?;
|
let dest = parse_label(args.next())?;
|
||||||
|
BuiltinOp::Jump(dest)
|
||||||
|
}
|
||||||
|
|
||||||
let mut pstate = pcx.state.borrow_mut();
|
"fj" => {
|
||||||
if pstate.constants.remove(&name).is_none() {
|
let dest = parse_label(args.next())?;
|
||||||
return Err(CrsnError::Parse(format!("Constant \"{}\" not defined!", name).into()));
|
BuiltinOp::FarJump(dest)
|
||||||
}
|
}
|
||||||
return Ok(ParseRes::ParsedNone);
|
|
||||||
|
"call" => {
|
||||||
|
let dest = RoutineName { name: args.next_string()?, arity: args.len() as u8 };
|
||||||
|
|
||||||
|
let mut call_args = vec![];
|
||||||
|
for t in args {
|
||||||
|
call_args.push(parse_rd(Some(t), pcx)?);
|
||||||
}
|
}
|
||||||
|
BuiltinOp::Call(dest, call_args)
|
||||||
|
}
|
||||||
|
|
||||||
"j" => {
|
"ret" => {
|
||||||
let dest = parse_label(args.next())?;
|
let mut ret_vals = vec![];
|
||||||
BuiltinOp::Jump(dest)
|
for t in args {
|
||||||
|
ret_vals.push(parse_rd(Some(t), pcx)?);
|
||||||
}
|
}
|
||||||
|
BuiltinOp::Ret(ret_vals)
|
||||||
|
}
|
||||||
|
|
||||||
"fj" => {
|
"routine" => {
|
||||||
let dest = parse_label(args.next())?;
|
let name = args.next_string()?;
|
||||||
BuiltinOp::FarJump(dest)
|
BuiltinOp::Routine(parse_routine_name(name)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
"call" => {
|
"skip" => {
|
||||||
let dest = RoutineName { name: args.next_string()?, arity: args.len() as u8 };
|
BuiltinOp::Skip(args.next_rd()?)
|
||||||
|
}
|
||||||
|
|
||||||
let mut call_args = vec![];
|
"barrier" => {
|
||||||
for t in args {
|
BuiltinOp::Barrier {
|
||||||
call_args.push(parse_rd(Some(t), pcx)?);
|
kind: Barrier::Standalone,
|
||||||
}
|
msg: match args.next() {
|
||||||
BuiltinOp::Call(dest, call_args)
|
|
||||||
}
|
|
||||||
|
|
||||||
"ret" => {
|
|
||||||
let mut ret_vals = vec![];
|
|
||||||
for t in args {
|
|
||||||
ret_vals.push(parse_rd(Some(t), pcx)?);
|
|
||||||
}
|
|
||||||
BuiltinOp::Ret(ret_vals)
|
|
||||||
}
|
|
||||||
|
|
||||||
"routine" => {
|
|
||||||
let name = args.next_string()?;
|
|
||||||
BuiltinOp::Routine(parse_routine_name(name)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
"skip" => {
|
|
||||||
BuiltinOp::Skip(args.next_rd()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
"barrier" => {
|
|
||||||
BuiltinOp::Barrier {
|
|
||||||
kind: Barrier::Standalone,
|
|
||||||
msg: match args.next() {
|
|
||||||
None => None,
|
|
||||||
Some(s) => Some(expect_string_atom(Some(s))?.into()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"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,
|
None => None,
|
||||||
Some(s) => Some(expect_string_atom(Some(s))?.into()),
|
Some(s) => Some(expect_string_atom(Some(s))?.into()),
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"ld" => {
|
"barrier-open" => {
|
||||||
BuiltinOp::Move {
|
BuiltinOp::Barrier {
|
||||||
dst: args.next_wr()?,
|
kind: Barrier::Open(parse_label(args.next())?),
|
||||||
src: args.next_rd()?,
|
msg: None,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"sst" => {
|
"barrier-close" => {
|
||||||
BuiltinOp::StoreStatus {
|
BuiltinOp::Barrier {
|
||||||
dst: args.next_wr()?,
|
kind: Barrier::Close(parse_label(args.next())?),
|
||||||
}
|
msg: None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"sld" => {
|
"fault" => {
|
||||||
BuiltinOp::LoadStatus {
|
BuiltinOp::Fault(match args.next() {
|
||||||
src: args.next_rd()?,
|
None => None,
|
||||||
}
|
Some(s) => Some(expect_string_atom(Some(s))?.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
"ld" => {
|
||||||
|
BuiltinOp::Move {
|
||||||
|
dst: args.next_wr()?,
|
||||||
|
src: args.next_rd()?,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"drop" => {
|
"sst" => {
|
||||||
BuiltinOp::Drop(args.next_rdobj()?)
|
BuiltinOp::StoreStatus {
|
||||||
|
dst: args.next_wr()?,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"far" => {
|
"sld" => {
|
||||||
if let Some(Sexp::Atom(Atom::S(ref label))) = args.peek() {
|
BuiltinOp::LoadStatus {
|
||||||
if let Some(label) = label.strip_prefix(':') {
|
src: args.next_rd()?,
|
||||||
let label = Label::Named(label.to_string());
|
}
|
||||||
BuiltinOp::FarLabel(label)
|
}
|
||||||
} else {
|
|
||||||
return Ok(ParseRes::Unknown(args));
|
"drop" => {
|
||||||
}
|
BuiltinOp::Drop(args.next_rdobj()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
"far" => {
|
||||||
|
if let Some(Sexp::Atom(Atom::S(ref label))) = args.peek() {
|
||||||
|
if let Some(label) = label.strip_prefix(':') {
|
||||||
|
let label = Label::Named(label.to_string());
|
||||||
|
BuiltinOp::FarLabel(label)
|
||||||
} else {
|
} else {
|
||||||
return Ok(ParseRes::Unknown(args));
|
return Ok(ParseRes::Unknown(args));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(ParseRes::Unknown(args));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
other => {
|
other => {
|
||||||
if let Some(label) = other.strip_prefix(':') {
|
if let Some(label) = other.strip_prefix(':') {
|
||||||
BuiltinOp::Label(parse_label_str(label)?)
|
BuiltinOp::Label(parse_label_str(label)?)
|
||||||
} else {
|
} else {
|
||||||
return Ok(ParseRes::Unknown(args));
|
return Ok(ParseRes::Unknown(args));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})))
|
}
|
||||||
}
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_routine_name(name: String) -> Result<RoutineName, CrsnError> {
|
pub(crate) fn parse_routine_name(name: String) -> Result<RoutineName, CrsnError> {
|
||||||
@@ -304,16 +285,16 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::any::Any;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
|
|
||||||
use crate::asm::instr::{Flatten, InstrWithBranches};
|
use crate::asm::instr::{Flatten};
|
||||||
use crate::asm::parse::{parse_instructions, ParserContext};
|
use crate::asm::parse::{parse_instructions, ParserContext};
|
||||||
use crate::asm::parse::sexp_expect::expect_list;
|
use crate::asm::parse::sexp_expect::expect_list;
|
||||||
use crate::builtin::defs::BuiltinOp;
|
|
||||||
use crate::builtin::parse::BuiltinOps;
|
|
||||||
use crate::module::OpTrait;
|
use crate::module::OpTrait;
|
||||||
|
use crate::builtin::BuiltinOps;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip() {
|
fn roundtrip() {
|
||||||
@@ -374,7 +355,7 @@ mod test {
|
|||||||
let parsers = &[parser];
|
let parsers = &[parser];
|
||||||
|
|
||||||
for (sample, expected) in samples {
|
for (sample, expected) in samples {
|
||||||
let mut pcx = ParserContext {
|
let pcx = ParserContext {
|
||||||
parsers,
|
parsers,
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
};
|
};
|
||||||
@@ -386,7 +367,7 @@ mod test {
|
|||||||
.expect("parse sexp");
|
.expect("parse sexp");
|
||||||
let list = expect_list(Some(s), false).unwrap();
|
let list = expect_list(Some(s), false).unwrap();
|
||||||
let num = AtomicU32::new(0);
|
let num = AtomicU32::new(0);
|
||||||
let mut parsed = parse_instructions(list.into_iter(), &pcx)
|
let parsed = parse_instructions(list.into_iter(), &pcx)
|
||||||
.expect("parse instr").flatten(&num)
|
.expect("parse instr").flatten(&num)
|
||||||
.expect("flatten").remove(0);
|
.expect("flatten").remove(0);
|
||||||
|
|
||||||
@@ -402,7 +383,7 @@ mod test {
|
|||||||
.expect("parse sexp (2c)");
|
.expect("parse sexp (2c)");
|
||||||
let list = expect_list(Some(s), false).unwrap();
|
let list = expect_list(Some(s), false).unwrap();
|
||||||
let num = AtomicU32::new(0);
|
let num = AtomicU32::new(0);
|
||||||
let mut parsed = parse_instructions(list.into_iter(), &pcx)
|
let parsed = parse_instructions(list.into_iter(), &pcx)
|
||||||
.expect("parse instr (2c)").flatten(&num)
|
.expect("parse instr (2c)").flatten(&num)
|
||||||
.expect("flatten (2c)").remove(0);
|
.expect("flatten (2c)").remove(0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
|
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug};
|
||||||
|
|
||||||
pub use eval_res::EvalRes;
|
pub use eval_res::EvalRes;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ use crate::runtime::fault::Fault;
|
|||||||
use crate::runtime::run_thread::state::RunState;
|
use crate::runtime::run_thread::state::RunState;
|
||||||
use crate::runtime::run_thread::ThreadInfo;
|
use crate::runtime::run_thread::ThreadInfo;
|
||||||
use sexp::Sexp;
|
use sexp::Sexp;
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
mod eval_res;
|
mod eval_res;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user