Merge branch 'spanned-sexp' into master

pull/21/head
Ondřej Hruška 4 years ago
commit 017ec53b14
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 5
      Cargo.lock
  2. 2
      crsn/Cargo.toml
  3. 2
      crsn/src/asm/data/rd.rs
  4. 19
      crsn/src/asm/data/reg.rs
  5. 2
      crsn/src/asm/data/wr.rs
  6. 41
      crsn/src/asm/error.rs
  7. 6
      crsn/src/asm/instr/cond.rs
  8. 59
      crsn/src/asm/instr/flatten.rs
  9. 3
      crsn/src/asm/instr/mod.rs
  10. 8
      crsn/src/asm/instr/op.rs
  11. 6
      crsn/src/asm/mod.rs
  12. 41
      crsn/src/asm/parse/arg_parser.rs
  13. 7
      crsn/src/asm/parse/mod.rs
  14. 8
      crsn/src/asm/parse/parse_cond.rs
  15. 114
      crsn/src/asm/parse/parse_data.rs
  16. 21
      crsn/src/asm/parse/parse_instr.rs
  17. 15
      crsn/src/asm/parse/parse_op.rs
  18. 25
      crsn/src/asm/parse/parse_routine.rs
  19. 56
      crsn/src/asm/parse/sexp_expect.rs
  20. 61
      crsn/src/asm/patches/mod.rs
  21. 23
      crsn/src/asm/patches/sexp_is_a.rs
  22. 17
      crsn/src/asm/patches/try_remove.rs
  23. 9
      crsn/src/builtin/defs.rs
  24. 7
      crsn/src/builtin/exec.rs
  25. 12
      crsn/src/builtin/mod.rs
  26. 97
      crsn/src/builtin/parse.rs
  27. 9
      crsn/src/module/mod.rs
  28. 23
      crsn/src/runtime/program.rs
  29. 11
      crsn/src/utils/mod.rs
  30. 10
      crsn_arith/src/exec.rs
  31. 5
      crsn_arith/src/lib.rs
  32. 33
      crsn_arith/src/parse.rs
  33. 16
      crsn_screen/src/exec.rs
  34. 5
      crsn_screen/src/lib.rs
  35. 9
      crsn_screen/src/parse.rs
  36. 12
      crsn_stacks/src/exec.rs
  37. 5
      crsn_stacks/src/lib.rs
  38. 3
      crsn_stacks/src/parse.rs
  39. 25
      examples/screen_bounce.csn
  40. 2
      launcher/src/main.rs
  41. 2
      lib/spanned_sexp/.gitignore
  42. 14
      lib/spanned_sexp/Cargo.toml
  43. 19
      lib/spanned_sexp/LICENSE
  44. 9
      lib/spanned_sexp/README.md
  45. 85
      lib/spanned_sexp/src/error.rs
  46. 324
      lib/spanned_sexp/src/lib.rs
  47. 70
      lib/spanned_sexp/src/test.rs

5
Cargo.lock generated

@ -879,8 +879,9 @@ dependencies = [
[[package]]
name = "sexp"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8fa7ac9df84000b0238cf497cb2d3056bac2ff2a7d8cf179d2803b4b58571f"
dependencies = [
"log",
]
[[package]]
name = "sha-1"

@ -6,7 +6,7 @@ edition = "2018"
publish = false
[dependencies]
sexp = "1.1.4"
sexp = { path = "../lib/spanned_sexp" }
thiserror = "1.0.20"
anyhow = "1.0.32"
dyn-clonable = "0.9.0"

@ -1,4 +1,4 @@
use std::fmt::{Debug, Formatter, Display};
use std::fmt::{Debug, Display, Formatter};
use std::fmt;
use crate::asm::data::{DataDisp, Mask, RdData, Register};

@ -1,6 +1,9 @@
use std::fmt::{self, Display, Formatter};
use sexp::SourcePosition;
use crate::asm::error::CrsnError;
use crate::asm::patches::ErrWithPos;
/// Register name
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@ -23,27 +26,27 @@ impl Display for Register {
}
}
pub fn parse_reg(name: &str) -> anyhow::Result<Register> {
pub fn parse_reg(name: &str, at: &SourcePosition) -> Result<Register, CrsnError> {
// TODO deduplicate code
if let Some(rn) = name.strip_prefix("arg") {
if rn.chars().find(|c: &char| !c.is_ascii_digit()).is_some() {
Err(CrsnError::Parse(format!("Bad register: {}", name).into()))?;
return Err(CrsnError::Parse(format!("Bad register: {}", name).into(), at.clone()))?;
}
let val: u8 = rn.parse()?;
let val: u8 = rn.parse().err_pos(at)?;
Ok(Register::Arg(val))
} else if let Some(rn) = name.strip_prefix("res") {
if rn.chars().find(|c: &char| !c.is_ascii_digit()).is_some() {
Err(CrsnError::Parse(format!("Bad register: {}", name).into()))?;
return Err(CrsnError::Parse(format!("Bad register: {}", name).into(), at.clone()))?;
}
let val: u8 = rn.parse()?;
let val: u8 = rn.parse().err_pos(at)?;
Ok(Register::Res(val))
} else if let Some(rn) = name.strip_prefix("r") {
if rn.chars().find(|c: &char| !c.is_ascii_digit()).is_some() {
Err(CrsnError::Parse(format!("Bad register: {}", name).into()))?;
return Err(CrsnError::Parse(format!("Bad register: {}", name).into(), at.clone()))?;
}
let val: u8 = rn.parse()?;
let val: u8 = rn.parse().err_pos(at)?;
Ok(Register::Gen(val))
} else {
Err(CrsnError::Parse(format!("Bad reg name: {}", name).into()))?
Err(CrsnError::Parse(format!("Bad reg name: {}", name).into(), at.clone()))?
}
}

@ -1,4 +1,4 @@
use std::fmt::{Debug, Formatter, Display};
use std::fmt::{Debug, Display, Formatter};
use std::fmt;
use crate::asm::data::{DataDisp, Mask, Rd, WrData};

@ -1,8 +1,10 @@
use std::borrow::Cow;
use std::num::ParseIntError;
use std::error::Error;
use thiserror::Error;
use sexp::SourcePosition;
use crate::asm::data::{Mask, Register};
use crate::asm::data::literal::Label;
use crate::asm::instr::Cond;
@ -10,24 +12,14 @@ use crate::asm::instr::Cond;
/// csn_asm unified error type
#[derive(Error, Debug)]
pub enum CrsnError {
#[error("S-expression syntax error: {0:?}")]
PreParse(#[from] Box<sexp::Error>),
#[error("Parse error: {0:?}")]
Parse(Cow<'static, str>),
#[error("Parse error in {1:?}: {0:?}")]
ParseIn(Cow<'static, str>, sexp::Sexp),
#[error("Assembler error: {0:?}")]
Asm(AsmError),
#[error("Architecture error: {0:?}")]
Arch(ArchError),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
impl From<std::num::ParseIntError> for CrsnError {
fn from(e: ParseIntError) -> Self {
CrsnError::Other(anyhow::anyhow!(e))
}
#[error("S-expression parsing error: {0:?}")]
Sexp(#[from] Box<sexp::Error>),
#[error("Parse error: {0:?} at {1:?}")]
Parse(Cow<'static, str>, SourcePosition),
#[error("Parse error: {0:?} at {1:?}")]
ParseOther(Box<dyn Error + Send + Sync>, SourcePosition),
#[error("Assembler error: {0:?} at {1:?}")]
Asm(AsmError, SourcePosition),
}
/// Error from the assembler stage (after parsing S-expressions and basic validation)
@ -50,14 +42,3 @@ pub enum AsmError {
#[error("Bad register type: {0}")]
BadRegisterType(Register),
}
/// Architectural error - the code is syntactically OK, but cannot run
#[derive(Error, Debug)]
pub enum ArchError {
#[error("Register {0} does not exist")]
RegisterNotExist(Register),
#[error("Register {0} is not writable")]
RegisterNotWritable(Register),
#[error("Register {0} is not readable")]
RegisterNotReadable(Register),
}

@ -1,6 +1,8 @@
use std::fmt::{self, Display, Formatter};
use std::ops::Not;
use sexp::SourcePosition;
use crate::asm::error::CrsnError;
/// Condition flag
@ -45,7 +47,7 @@ pub enum Cond {
NotCarry,
}
pub fn parse_cond(text: &str) -> Result<Cond, CrsnError> {
pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
Ok(match text.trim_end_matches('?') {
"eq" | "=" | "==" => Cond::Equal,
"ne" | "<>" | "!=" | "≠" => Cond::NotEqual,
@ -66,7 +68,7 @@ pub fn parse_cond(text: &str) -> Result<Cond, CrsnError> {
"ov" | "^" => Cond::Overflow,
"nov" | "!ov" | "!^" => Cond::NotOverflow,
_ => {
return Err(CrsnError::Parse(format!("Unknown cond: {}", text).into()));
return Err(CrsnError::Parse(format!("Unknown cond: {}", text).into(), pos.clone()));
}
})
}

@ -1,5 +1,8 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32};
use std::fmt::Debug;
use std::sync::atomic::AtomicU32;
use sexp::SourcePosition;
use crate::asm::data::{Rd, RdData};
use crate::asm::data::literal::{Label, Value};
@ -8,22 +11,32 @@ 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 : Debug {
pub trait Flatten: Debug {
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError>;
fn pos(&self) -> SourcePosition;
}
impl Flatten for () {
fn flatten(self: Box<Self>, _label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
Ok(vec![])
}
fn pos(&self) -> SourcePosition {
SourcePosition::default()
}
}
impl Flatten for InstrWithBranches {
fn pos(&self) -> SourcePosition {
self.pos.clone()
}
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
let mut ops = vec![self.op];
let parent_pos = self.pos;
if let Some(branches) = self.branches {
let labels = HashMap::<Cond, u32>::new();
@ -31,7 +44,7 @@ impl Flatten for InstrWithBranches {
let end_lbl = Label::unique(label_num);
for (cnt, (cond, branch)) in branches.into_iter().enumerate() {
if labels.contains_key(&cond) {
return Err(CrsnError::Asm(AsmError::ConditionalAlreadyUsed(cond)));
return Err(CrsnError::Asm(AsmError::ConditionalAlreadyUsed(cond), branch.pos()));
}
let next_lbl = if cnt == branch_count - 1 {
@ -40,27 +53,29 @@ impl Flatten for InstrWithBranches {
Label::unique(label_num)
};
let pos = branch.pos().clone();
let mut flattened = branch.flatten(label_num)?;
if flattened.len() == 0 {
ops.push(Op { cond: Some(cond), kind: BuiltinOp::Jump(end_lbl.clone()).into() });
ops.push(Op { cond: Some(cond), pos: pos.clone(), kind: BuiltinOp::Jump(end_lbl.clone()).into() });
} else if flattened.len() == 1 && flattened[0].cond.is_none() && branch_count == 1 {
// optimization for single-branch conditionals with a single instruction
ops.push(Op { cond: Some(cond), kind: flattened.remove(0).kind });
ops.push(Op { cond: Some(cond), pos: pos.clone(), kind: flattened.remove(0).kind });
} else {
ops.push(Op {
kind: OpKind::BuiltIn(BuiltinOp::Jump(next_lbl.clone())),
pos: pos.clone(),
cond: Some(!cond),
});
ops.extend(flattened);
}
if cnt != branch_count - 1 {
ops.push(BuiltinOp::Jump(end_lbl.clone()).into());
ops.push(BuiltinOp::Label(next_lbl).into());
ops.push(BuiltinOp::Jump(end_lbl.clone()).into_op(pos.clone()));
ops.push(BuiltinOp::Label(next_lbl).into_op(pos.clone()));
}
}
ops.push(BuiltinOp::Label(end_lbl).into());
ops.push(BuiltinOp::Label(end_lbl).into_op(parent_pos));
}
Ok(ops)
@ -68,6 +83,17 @@ impl Flatten for InstrWithBranches {
}
impl Flatten for Vec<Box<dyn Flatten>> {
fn pos(&self) -> SourcePosition {
match self.first() {
None => {
Default::default()
}
Some(f) => {
f.pos()
}
}
}
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
let mut ops = vec![];
for item in self.into_iter() {
@ -78,15 +104,21 @@ impl Flatten for Vec<Box<dyn Flatten>> {
}
impl Flatten for Routine {
fn pos(&self) -> SourcePosition {
self.pos.clone()
}
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
let skip_label = Label::unique(label_num);
let self_pos = self.pos();
let mut ops: Vec<Op> = vec![
BuiltinOp::Barrier {
kind: Barrier::Open(skip_label.clone()),
msg: Some(format!("proc {} start", self.name).into()),
}.into(),
BuiltinOp::Routine(self.name.clone()).into(),
}.into_op(self.pos()),
BuiltinOp::Routine(self.name.clone()).into_op(self.pos()),
];
ops.extend(self.body.flatten(label_num)?);
@ -95,7 +127,7 @@ impl Flatten for Routine {
BuiltinOp::Barrier {
kind: Barrier::Close(skip_label.clone()),
msg: Some(format!("proc {} end", self.name).into()),
}.into()
}.into_op(self_pos)
);
labels_to_skips(ops)
@ -123,10 +155,11 @@ pub fn labels_to_skips(ops: Vec<Op>) -> Result<Vec<Op>, CrsnError> {
let skip = *dest as isize - n as isize + skipped;
cleaned.push(Op {
cond: op.cond,
pos: op.pos.clone(),
kind: OpKind::BuiltIn(BuiltinOp::Skip(Rd::new(RdData::Immediate(skip as Value)))),
});
} else {
return Err(CrsnError::Asm(AsmError::LabelNotDefined(target)));
return Err(CrsnError::Asm(AsmError::LabelNotDefined(target), op.pos));
}
}
_ => {

@ -1,6 +1,7 @@
pub use cond::Cond;
pub use flatten::Flatten;
pub use op::Op;
use sexp::SourcePosition;
use crate::asm::data::literal::RoutineName;
@ -12,6 +13,7 @@ pub mod flatten;
#[derive(Debug)]
pub struct InstrWithBranches {
pub op: Op,
pub pos: SourcePosition,
pub branches: Option<Vec<(Cond, Box<dyn Flatten>)>>,
}
@ -19,5 +21,6 @@ pub struct InstrWithBranches {
#[derive(Debug)]
pub struct Routine {
pub name: RoutineName,
pub pos: SourcePosition,
pub body: Box<dyn Flatten>,
}

@ -1,11 +1,12 @@
use std::fmt::Debug;
use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::instr::Cond;
use crate::builtin::defs::BuiltinOp;
use crate::module::{EvalRes, OpTrait};
use crate::runtime::fault::Fault;
use crate::runtime::run_thread::{info::ThreadInfo, state::RunState};
use sexp::{Sexp, Atom};
/// A higher level simple opration
#[derive(Debug)]
@ -18,6 +19,7 @@ pub enum OpKind {
#[derive(Debug)]
pub struct Op {
pub cond: Option<Cond>,
pub pos: SourcePosition,
pub kind: OpKind,
}
@ -49,8 +51,8 @@ impl OpTrait for Op {
};
if let Some(cond) = self.cond {
if let Sexp::List(items) = &mut se {
if let Some(Sexp::Atom(Atom::S(s))) = &mut items.get_mut(0) {
if let Sexp::List(items, _) = &mut se {
if let Some(Sexp::Atom(Atom::S(s), _)) = &mut items.get_mut(0) {
s.push('.');
s.push_str(&cond.to_string());
}

@ -1,10 +1,12 @@
use std::cell::RefCell;
use std::sync::Arc;
use sexp::SourcePosition;
use crate::asm::instr::flatten::labels_to_skips;
use crate::asm::parse::{ParserContext, ParserState};
use crate::module::CrsnExtension;
use crate::runtime::program::Program;
use crate::asm::instr::flatten::labels_to_skips;
pub mod data;
pub mod error;
@ -23,7 +25,7 @@ pub fn assemble(source: &str, parsers: Arc<Vec<Box<dyn CrsnExtension>>>) -> Resu
}),
};
let ops = parse::parse(source, &pcx)?;
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
let ops = labels_to_skips(ops)?;
Ok(Program::new(ops, parsers)?)

@ -1,6 +1,7 @@
use sexp::Sexp;
use sexp::{Sexp, SourcePosition};
use crate::asm::data::{Mask, Rd, RdData, RdObj, Wr};
use crate::asm::error::CrsnError;
use crate::asm::parse::parse_data::{parse_rd, parse_wr};
use crate::asm::parse::ParserContext;
use crate::asm::parse::sexp_expect::expect_string_atom;
@ -9,6 +10,7 @@ use crate::asm::parse::sexp_expect::expect_string_atom;
pub struct TokenParser<'a> {
orig_len: usize,
args: Vec<Sexp>,
start_pos: &'a SourcePosition,
pub pcx: &'a ParserContext<'a>,
}
@ -26,10 +28,11 @@ impl<'a> IntoIterator for TokenParser<'a> {
impl<'a> TokenParser<'a> {
/// Create a new argument parser
pub fn new(mut args: Vec<Sexp>, pcx: &'a ParserContext) -> Self {
pub fn new(mut args: Vec<Sexp>, start_pos: &'a SourcePosition, pcx: &'a ParserContext) -> Self {
args.reverse();
Self {
orig_len: args.len(),
start_pos,
args,
pcx,
}
@ -59,37 +62,51 @@ impl<'a> TokenParser<'a> {
self.args.pop()
}
/// Get the next entry, or raise an error
pub fn next_or_err(&mut self) -> Result<Sexp, CrsnError> {
match self.next() {
None => {
Err(CrsnError::Parse("Unexpected end of token list".into(), self.start_pos.clone()))
}
Some(removed) => Ok(removed)
}
}
/// Look at the next entry
pub fn peek(&mut self) -> Option<&Sexp> {
self.args.last()
}
/// Get the next string entry
pub fn next_string(&mut self) -> anyhow::Result<String> {
let next = self.next();
pub fn next_string(&mut self) -> Result<(String, SourcePosition), CrsnError> {
let next = self.next_or_err()?;
let esa = expect_string_atom(next)?;
Ok(esa)
}
/// Get the next entry as read location
pub fn next_rd(&mut self) -> anyhow::Result<Rd> {
parse_rd(self.next(), self.pcx)
pub fn next_rd(&mut self) -> Result<Rd, CrsnError> {
let next = self.next_or_err()?;
parse_rd(next, self.pcx)
}
/// Get the next entry as read location
pub fn next_rdobj(&mut self) -> anyhow::Result<RdObj> {
match parse_rd(self.next(), self.pcx)? {
pub fn next_rdobj(&mut self) -> Result<RdObj, CrsnError> {
match parse_rd(self.next_or_err()?, self.pcx)? {
Rd(RdData::ObjectPtr(reg), Mask::FULL) => {
return Ok(RdObj::new(reg));
Ok(RdObj::new(reg))
}
other => {
anyhow::bail!("Not a valid object handle syntax: {:?}", other);
Err(CrsnError::Parse(
format!("Not a valid object handle syntax: {:?}", other).into(),
self.start_pos.clone(),
))
}
}
}
/// Get the next entry as write location
pub fn next_wr(&mut self) -> anyhow::Result<Wr> {
parse_wr(self.next(), self.pcx)
pub fn next_wr(&mut self) -> Result<Wr, CrsnError> {
parse_wr(self.next_or_err()?, self.pcx)
}
}

@ -3,6 +3,7 @@ use std::collections::HashMap;
use std::sync::atomic::AtomicU32;
pub use parse_instr::parse_instructions;
use sexp::SourcePosition;
use crate::asm::data::literal::{ConstantName, RegisterAlias, Value};
use crate::asm::data::Register;
@ -38,12 +39,12 @@ pub struct ParserState {
pub constants: HashMap<ConstantName, Value>,
}
pub fn parse(source: &str, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
let items = expect_list(Some(sexp::parse(source)?), true)?;
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
let (items, _pos) = expect_list(sexp::parse(source)?, true)?;
/* 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)?
parse_instructions(items.into_iter(), pos, parsers)?
.flatten(&label_num)
}

@ -8,13 +8,13 @@ use crate::asm::parse::sexp_expect::{expect_list, expect_string_atom};
use crate::asm::patches::TryRemove;
pub fn parse_cond_branch(tok: Sexp, parsers: &ParserContext) -> Result<(Cond, Box<dyn Flatten>), CrsnError> {
let mut list = expect_list(Some(tok), false)?;
let kw = expect_string_atom(list.try_remove(0))?;
let (mut list, pos) = expect_list(tok, false)?;
let (kw, kw_pos) = expect_string_atom(list.remove_or_err(0, &pos, "Missing \"cond?\" keyword in conditional branch")?)?;
if !kw.ends_with('?') {
return Err(CrsnError::Parse(format!("Condition must end with '?': {}", kw).into()));
return Err(CrsnError::Parse(format!("Condition must end with '?': {}", kw).into(), kw_pos));
}
Ok((cond::parse_cond(&kw)?, parse_instructions(list.into_iter(), parsers)?))
Ok((cond::parse_cond(&kw, &kw_pos)?, parse_instructions(list.into_iter(), &pos, parsers)?))
}

@ -1,13 +1,14 @@
use std::borrow::Cow;
use std::convert::TryFrom;
use sexp::{Atom, Sexp};
use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData};
use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value};
use crate::asm::error::CrsnError;
use crate::asm::parse::ParserContext;
use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::patches::ErrWithPos;
fn is_valid_identifier(name: &str) -> bool {
name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
@ -15,65 +16,60 @@ fn is_valid_identifier(name: &str) -> bool {
}
/// Parse register alias
pub fn parse_reg_alias(name: Option<Sexp>) -> Result<RegisterAlias, CrsnError> {
pub fn parse_reg_alias(name: Sexp) -> Result<(RegisterAlias, SourcePosition), CrsnError> {
// trace!("parse reg alias: {:?}", name);
let name = expect_string_atom(name)?;
let (name, namepos) = expect_string_atom(name)?;
if !is_valid_identifier(&name) {
return Err(CrsnError::Parse(format!("\"{}\" is not an allowed register alias.", name).into()));
return Err(CrsnError::Parse(format!("\"{}\" is not an allowed register alias.", name).into(), namepos));
}
Ok(name)
Ok((name, namepos))
}
/// Parse constant name
pub fn parse_constant_name(name: Option<Sexp>) -> Result<ConstantName, CrsnError> {
pub fn parse_constant_name(name: Sexp) -> Result<(ConstantName, SourcePosition), CrsnError> {
// trace!("parse const name: {:?}", name);
let name = expect_string_atom(name)?;
let (name, namepos) = expect_string_atom(name)?;
if !is_valid_identifier(&name) {
return Err(CrsnError::Parse(format!("\"{}\" is not an allowed constant name.", name).into()));
return Err(CrsnError::Parse(format!("\"{}\" is not an allowed constant name.", name).into(), namepos));
}
Ok(name)
Ok((name, namepos))
}
/// Parse a label
pub fn parse_label(name: Option<Sexp>) -> Result<Label, CrsnError> {
pub fn parse_label(name: Sexp) -> Result<Label, CrsnError> {
// trace!("parse label: {:?}", name);
let name = expect_string_atom(name)?;
Ok(parse_label_str(&name)?)
let (name, namepos) = expect_string_atom(name)?;
Ok(parse_label_str(&name, &namepos)?)
}
pub fn parse_label_str(name: &str) -> Result<Label, CrsnError> {
pub fn parse_label_str(name: &str, pos: &SourcePosition) -> 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"))
let val = parse_u64(&label[1..], pos)?;
Label::Numbered(u32::try_from(val).err_pos(pos)?)
} else {
Label::Named(label.to_string())
})
}
/// Parse data disposition (address/value, without the read/write restriction)
pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
// trace!("parse data: {:?}", tok);
let tok = if let Some(tok) = tok {
tok
} else {
return Err(CrsnError::Parse("Expected data disposition token".into()));
};
// TODO implement masks
match &tok {
Sexp::Atom(Atom::I(val)) => {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(*val) }))
match tok {
Sexp::Atom(Atom::I(val), _pos) => {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
}
Sexp::Atom(Atom::S(s)) => {
Sexp::Atom(Atom::S(s), pos) => {
if s == "_" {
return Ok(DataDisp::Discard);
}
@ -81,11 +77,11 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
// check if we have an alias defined
{
let pstate = pcx.state.borrow();
if let Some(val) = pstate.constants.get(s) {
if let Some(val) = pstate.constants.get(&s) {
return Ok(DataDisp::Immediate(*val));
}
if let Some(val) = pstate.reg_aliases.get(s) {
if let Some(val) = pstate.reg_aliases.get(&s) {
return Ok(DataDisp::Register(*val));
}
}
@ -95,63 +91,55 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
if let Some(val) = pstate.reg_aliases.get(reference) {
Ok(DataDisp::ObjectPtr(*val))
} else {
let reg = reg::parse_reg(reference)?;
let reg = reg::parse_reg(reference, &pos)?;
if pstate.reg_aliases.values().find(|v| **v == reg).is_some() {
Err(CrsnError::Parse(format!("Sym exists for register {}, direct access denied. Unsym it if needed.", reg).into()))
Err(CrsnError::Parse(format!("Sym exists for register {}, direct access denied. Unsym it if needed.", reg).into(), pos))
} else {
Ok(DataDisp::ObjectPtr(reg))
}
}
} else if s.starts_with(|c: char| c.is_ascii_digit()) {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(s)?) }))
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(&s, &pos)?) }))
} else {
let reg = reg::parse_reg(s)?;
let reg = reg::parse_reg(&s, &pos)?;
let pstate = pcx.state.borrow();
if pstate.reg_aliases.values().find(|v| **v == reg).is_some() {
Err(CrsnError::Parse(format!("Sym exists for register {}, direct access denied. Unsym it if needed.", reg).into()))
Err(CrsnError::Parse(format!("Sym exists for register {}, direct access denied. Unsym it if needed.", reg).into(), pos))
} else {
Ok(DataDisp::Register(reg))
}
}
}
_ => {
Err(CrsnError::Parse(format!("bad data disp: {:?}", tok).into()))
other => {
Err(CrsnError::Parse(format!("bad data disp: {:?}", other).into(), other.pos().clone()))
}
}
}
/// Parse immediate value
pub fn parse_value(tok: Option<Sexp>, pcx: &ParserContext) -> Result<Value, CrsnError> {
let tok = if let Some(tok) = tok {
tok
} else {
return Err(CrsnError::Parse("Expected value token".into()));
};
// trace!("parse value: {:?}", tok);
match &tok {
Sexp::Atom(Atom::I(val)) => {
Ok(unsafe { std::mem::transmute(*val) })
pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<Value, CrsnError> {
match tok {
Sexp::Atom(Atom::I(val), _pos) => {
Ok(unsafe { std::mem::transmute(val) })
}
Sexp::Atom(Atom::S(s)) => {
Sexp::Atom(Atom::S(s), pos) => {
let pstate = pcx.state.borrow();
if let Some(val) = pstate.constants.get(s) {
if let Some(val) = pstate.constants.get(&s) {
return Ok(*val);
}
Ok(unsafe { std::mem::transmute(parse_i64(s)?) })
Ok(unsafe { std::mem::transmute(parse_i64(&s, &pos)?) })
}
_ => {
Err(CrsnError::Parse(format!("bad value format: {:?}", tok).into()))
other => {
Err(CrsnError::Parse(format!("bad value format: {:?}", other).into(), other.pos().clone()))
}
}
}
pub fn parse_u64(literal: &str) -> anyhow::Result<u64> {
pub fn parse_u64(literal: &str, pos: &SourcePosition) -> Result<u64, CrsnError> {
// trace!("parse u64 from {}", literal);
let mut without_underscores = Cow::Borrowed(literal);
if without_underscores.contains('_') {
@ -159,27 +147,29 @@ pub fn parse_u64(literal: &str) -> anyhow::Result<u64> {
}
if let Some(hex) = without_underscores.strip_prefix("0x") {
Ok(u64::from_str_radix(hex, 16)?)
Ok(u64::from_str_radix(hex, 16).err_pos(pos)?)
} else if let Some(hex) = without_underscores.strip_prefix("0b") {
Ok(u64::from_str_radix(hex, 2)?)
Ok(u64::from_str_radix(hex, 2).err_pos(pos)?)
} else {
Ok(u64::from_str_radix(&without_underscores, 10)?)
Ok(u64::from_str_radix(&without_underscores, 10).err_pos(pos)?)
}
}
pub fn parse_i64(literal: &str) -> anyhow::Result<i64> {
pub fn parse_i64(literal: &str, pos: &SourcePosition) -> Result<i64, CrsnError> {
// trace!("parse i64 from {}", literal);
if let Some(_value) = literal.strip_prefix("-") {
Ok(-1 * i64::try_from(parse_u64(literal)?)?)
Ok(-1 * i64::try_from(parse_u64(literal, pos)?).err_pos(pos)?)
} else {
Ok(i64::try_from(parse_u64(literal)?)?)
Ok(i64::try_from(parse_u64(literal, pos)?).err_pos(pos)?)
}
}
pub fn parse_rd(tok: Option<Sexp>, pcx: &ParserContext) -> anyhow::Result<Rd> {
Ok(Rd::new(RdData::try_from(parse_data_disp(tok, pcx)?)?))
pub fn parse_rd(tok: Sexp, pcx: &ParserContext) -> Result<Rd, CrsnError> {
let pos = tok.pos().clone();
Ok(Rd::new(RdData::try_from(parse_data_disp(tok, pcx)?).err_pos(&pos)?))
}
pub fn parse_wr(tok: Option<Sexp>, pcx: &ParserContext) -> anyhow::Result<Wr> {
Ok(Wr::new(WrData::try_from(parse_data_disp(tok, pcx)?)?))
pub fn parse_wr(tok: Sexp, pcx: &ParserContext) -> Result<Wr, CrsnError> {
let pos = tok.pos().clone();
Ok(Wr::new(WrData::try_from(parse_data_disp(tok, pcx)?).err_pos(&pos)?))
}

@ -1,4 +1,4 @@
use sexp::Sexp;
use sexp::{Sexp, SourcePosition};
use crate::asm::error::CrsnError;
use crate::asm::instr::{Flatten, InstrWithBranches};
@ -7,27 +7,27 @@ use crate::asm::parse::parse_cond::parse_cond_branch;
use crate::asm::parse::parse_routine::parse_routine;
use crate::asm::parse::ParserContext;
use crate::asm::parse::sexp_expect::{expect_list, expect_string_atom};
use crate::asm::patches::SexpIsA;
use crate::asm::patches::NextOrErr;
use crate::module::ParseRes;
use super::parse_op::parse_op;
pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
let mut parsed = vec![];
for expr in items {
let tokens = expect_list(Some(expr), false)?;
let (tokens, listpos) = expect_list(expr, false)?;
let mut toki = tokens.into_iter();
let name = expect_string_atom(toki.next())?;
let (name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?;
if name == "proc" {
parsed.push(parse_routine(toki, pcx)?);
parsed.push(parse_routine(toki, pos, pcx)?);
continue;
}
let mut token_parser = TokenParser::new(toki.collect(), pcx);
let mut token_parser = TokenParser::new(toki.collect(), &listpos, pcx);
for p in pcx.parsers {
token_parser = match p.parse_syntax(&name, token_parser) {
token_parser = match p.parse_syntax(pos, &name, token_parser) {
Ok(ParseRes::Parsed(op)) => return Ok(op),
Ok(ParseRes::ParsedNone) => return Ok(Box::new(())),
Ok(ParseRes::Unknown(to_reuse)) => {
@ -45,7 +45,7 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pcx: &ParserContext)
// Get back the original iterator
let toki = token_parser.into_iter();
let arg_tokens = TokenParser::new(toki.clone().take_while(|e| e.is_atom()).collect(), pcx);
let arg_tokens = TokenParser::new(toki.clone().take_while(|e| e.is_atom()).collect(), &listpos, pcx);
let branch_tokens = toki
.skip_while(|e| e.is_atom())
.take_while(|e| e.is_list());
@ -62,9 +62,10 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pcx: &ParserContext)
}
};
if let Some(op) = parse_op(name.as_str(), arg_tokens)? {
if let Some(op) = parse_op(name.as_str(), arg_tokens, &namepos)? {
parsed.push(Box::new(InstrWithBranches {
op,
pos: namepos,
branches,
}));
}

@ -1,30 +1,33 @@
use sexp::SourcePosition;
use crate::asm::error::CrsnError;
use crate::asm::instr::cond::parse_cond;
use crate::asm::instr::Op;
use crate::asm::parse::arg_parser::TokenParser;
use crate::module::ParseRes;
use crate::builtin::BuiltinOps;
use crate::module::ParseRes;
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>, spos: &SourcePosition) -> Result<Option<Op>, CrsnError> {
// Include built-in instructions
let builtins = [BuiltinOps::new()];
let mut cond = None;
if let Some(pos) = keyword.find('.') {
cond = Some(parse_cond(&keyword[(pos + 1)..])?);
cond = Some(parse_cond(&keyword[(pos + 1)..], spos)?);
keyword = &keyword[..pos];
}
for p in builtins.iter().chain(arg_tokens.pcx.parsers) {
arg_tokens = match p.parse_op(keyword, arg_tokens) {
arg_tokens = match p.parse_op(spos, keyword, arg_tokens) {
Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op {
cond,
pos: spos.clone(),
kind,
})),
Ok(ParseRes::ParsedNone) => return Ok(None),
Ok(ParseRes::Unknown(to_reuse)) => {
if to_reuse.parsing_started() {
panic!("Module \"{}\" started parsing {}, but returned Unknown!", p.name(), keyword);
return Err(CrsnError::Parse(format!("Module \"{}\" started parsing {}, but returned Unknown!", p.name(), keyword).into(), spos.clone()));
}
to_reuse
}
@ -34,5 +37,5 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>) -> Resul
}
}
return Err(CrsnError::Parse(format!("Unknown instruction: {}", keyword).into()));
return Err(CrsnError::Parse(format!("Unknown instruction: {}", keyword).into(), spos.clone()));
}

@ -1,4 +1,4 @@
use sexp::Sexp;
use sexp::{Sexp, SourcePosition};
use crate::asm::data::Register;
use crate::asm::error::CrsnError;
@ -7,14 +7,14 @@ use crate::asm::parse::{parse_instructions, ParserContext};
use crate::asm::parse::arg_parser::TokenParser;
use crate::asm::parse::parse_data::parse_reg_alias;
use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::patches::SexpIsA;
use crate::asm::patches::NextOrErr;
use crate::builtin::parse::parse_routine_name;
pub fn parse_routine(mut toki: impl Iterator<Item=Sexp> + Clone, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
let name = expect_string_atom(toki.next())?;
let mut name = parse_routine_name(name)?;
pub fn parse_routine(mut toki: impl Iterator<Item=Sexp> + Clone, rt_pos: &SourcePosition, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
let (name, namepos) = expect_string_atom(toki.next_or_err(rt_pos.clone(), "Expected routine name")?)?;
let mut name = parse_routine_name(name, &namepos)?;
let arg_tokens = TokenParser::new(toki.clone().take_while(|e| e.is_atom()).collect(), pcx);
let arg_tokens = TokenParser::new(toki.clone().take_while(|e| e.is_atom()).collect(), rt_pos, pcx);
// If arity is explicitly given, then either no named argument must be provided,
// or their count must match the arity. If no arity is given, then arity is determined
@ -22,7 +22,7 @@ pub fn parse_routine(mut toki: impl Iterator<Item=Sexp> + Clone, pcx: &ParserCon
if name.arity == 0 && arg_tokens.len() != 0 {
name.arity = arg_tokens.len() as u8;
} else if arg_tokens.len() != 0 && name.arity as usize != arg_tokens.len() {
return Err(CrsnError::Parse(format!("arity mismatch in routine {}", name.name).into()));
return Err(CrsnError::Parse(format!("arity mismatch in routine {}", name.name).into(), rt_pos.clone()));
}
let toki = toki.skip_while(|e| e.is_atom());
@ -34,17 +34,17 @@ pub fn parse_routine(mut toki: impl Iterator<Item=Sexp> + Clone, pcx: &ParserCon
pstate.reg_alias_stack.push(old);
for (n, tok) in arg_tokens.into_iter().enumerate() {
let alias = parse_reg_alias(Some(tok))?;
let alias = parse_reg_alias(tok)?;
if pstate.constants.contains_key(&alias) {
return Err(CrsnError::Parse(format!("Symbol \"{}\" already used for a constant.", alias).into()));
if pstate.constants.contains_key(&alias.0) {
return Err(CrsnError::Parse(format!("Symbol \"{}\" already used for a constant.", alias.0).into(), alias.1));
}
pstate.reg_aliases.insert(alias, Register::Arg(n as u8));
pstate.reg_aliases.insert(alias.0, Register::Arg(n as u8));
}
}
let body = parse_instructions(toki, pcx)?;
let body = parse_instructions(toki, rt_pos, pcx)?;
{
let mut pstate = pcx.state.borrow_mut();
@ -54,6 +54,7 @@ pub fn parse_routine(mut toki: impl Iterator<Item=Sexp> + Clone, pcx: &ParserCon
return Ok(Box::new(Routine {
name,
pos: rt_pos.clone(),
body,
}));
}

@ -1,53 +1,37 @@
use sexp::{Atom, Sexp};
use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::error::CrsnError;
pub fn expect_list(expr: Option<Sexp>, allow_empty: bool) -> Result<Vec<Sexp>, CrsnError> {
if let Some(expr) = expr {
match &expr {
Sexp::Atom(_) => {
return Err(CrsnError::ParseIn("Expected a list".into(), expr));
pub fn expect_list(expr: Sexp, allow_empty: bool) -> Result<(Vec<Sexp>, SourcePosition), CrsnError> {
match expr {
Sexp::Atom(_, pos) => {
return Err(CrsnError::Parse("Expected a list".into(), pos));
}
Sexp::List(list, pos) => {
if !allow_empty && list.is_empty() {
return Err(CrsnError::Parse("Routine: Empty list".into(), pos));
}
Sexp::List(list) => {
if !allow_empty && list.is_empty() {
return Err(CrsnError::ParseIn("Routine: Empty list".into(), expr));
}
if let Sexp::List(list) = expr {
return Ok(list);
} else {
unreachable!();
}
}
Ok((list, pos))
}
}
Err(CrsnError::Parse("Expected a list, got nothing".into()))
}
pub fn expect_atom(expr: Option<Sexp>) -> Result<Atom, CrsnError> {
if let Some(expr) = expr {
match &expr {
Sexp::Atom(_atom) => {
if let Sexp::Atom(a) = expr {
return Ok(a);
} else {
unreachable!();
}
}
Sexp::List(_) => {
return Err(CrsnError::ParseIn("Expected atom got list".into(), expr));
}
pub fn expect_atom(expr: Sexp) -> Result<(Atom, SourcePosition), CrsnError> {
match expr {
Sexp::Atom(a, pos) => {
Ok((a, pos))
}
Sexp::List(_, pos) => {
return Err(CrsnError::Parse("Expected atom got list".into(), pos));
}
}
Err(CrsnError::Parse("Expected atom, got nothing".into()))
}
pub fn expect_string_atom(expr: Option<Sexp>) -> Result<String, CrsnError> {
pub fn expect_string_atom(expr: Sexp) -> Result<(String, SourcePosition), CrsnError> {
match expect_atom(expr) {
Ok(Atom::S(s)) => Ok(s),
Ok(atom) => Err(CrsnError::ParseIn("Expected string atom".into(), Sexp::Atom(atom))),
Ok((Atom::S(s), pos)) => Ok((s, pos)),
Ok((_, pos)) => Err(CrsnError::Parse("Expected string atom".into(), pos)),
Err(e) => Err(e),
}
}

@ -1,6 +1,59 @@
pub use sexp_is_a::SexpIsA;
pub use try_remove::TryRemove;
use sexp::SourcePosition;
mod try_remove;
mod sexp_is_a;
use crate::asm::error::CrsnError;
pub trait TryRemove {
type Item;
fn try_remove(&mut self, index: usize) -> Option<Self::Item>;
fn remove_or_err(&mut self, index: usize, pos: &SourcePosition, err: &'static str) -> Result<Self::Item, CrsnError>;
}
impl<T> TryRemove for Vec<T> {
type Item = T;
fn try_remove(&mut self, index: usize) -> Option<T> {
if self.is_empty() {
None
} else {
Some(self.remove(index))
}
}
fn remove_or_err(&mut self, index: usize, pos: &SourcePosition, err: &'static str) -> Result<Self::Item, CrsnError> {
match self.try_remove(index) {
None => {
Err(CrsnError::Parse(err.into(), pos.clone()))
}
Some(removed) => Ok(removed)
}
}
}
pub trait NextOrErr<T> {
fn next_or_err(&mut self, pos: SourcePosition, err: &'static str) -> Result<T, CrsnError>;
}
impl<T, K: Iterator<Item=T>> NextOrErr<T> for K {
fn next_or_err(&mut self, pos: SourcePosition, err: &'static str) -> Result<T, CrsnError> {
match self.next() {
None => {
Err(CrsnError::Parse(err.into(), pos))
}
Some(removed) => Ok(removed)
}
}
}
pub trait ErrWithPos<T> {
fn err_pos(self, pos: &SourcePosition) -> Result<T, CrsnError>;
}
impl<T, E: std::error::Error + Send + Sync + 'static> ErrWithPos<T> for Result<T, E> {
fn err_pos(self, pos: &SourcePosition) -> Result<T, CrsnError> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(CrsnError::ParseOther(Box::new(e), pos.clone()))
}
}
}

@ -1,23 +0,0 @@
use sexp::Sexp;
pub trait SexpIsA {
fn is_atom(&self) -> bool;
fn is_list(&self) -> bool;
}
impl SexpIsA for Sexp {
fn is_atom(&self) -> bool {
match self {
Sexp::Atom(_) => true,
_ => false,
}
}
fn is_list(&self) -> bool {
match self {
Sexp::List(_) => true,
_ => false,
}
}
}

@ -1,17 +0,0 @@
pub trait TryRemove {
type Item;
fn try_remove(&mut self, index: usize) -> Option<Self::Item>;
}
impl<T> TryRemove for Vec<T> {
type Item = T;
fn try_remove(&mut self, index: usize) -> Option<T> {
if self.is_empty() {
None
} else {
Some(self.remove(index))
}
}
}

@ -1,3 +1,5 @@
use sexp::SourcePosition;
use crate::asm::data::{Rd, RdObj, Wr};
use crate::asm::data::literal::{DebugMsg, Label, RoutineName};
use crate::asm::instr::Op;
@ -60,10 +62,11 @@ pub enum BuiltinOp {
LoadStatus { src: Rd },
}
impl From<BuiltinOp> for Op {
fn from(bo: BuiltinOp) -> Self {
impl BuiltinOp {
pub fn into_op(self: BuiltinOp, pos: SourcePosition) -> Op {
Op {
kind: bo.into(),
kind: self.into(),
pos,
cond: None,
}
}

@ -1,5 +1,7 @@
use std::time::Duration;
use sexp::Sexp;
use crate::asm::data::{Rd, RdData};
use crate::asm::data::literal::Addr;
use crate::asm::instr::Cond;
@ -8,11 +10,6 @@ use crate::module::{EvalRes, OpTrait};
use crate::runtime::fault::Fault;
use crate::runtime::frame::StackFrame;
use crate::runtime::run_thread::{state::RunState, ThreadInfo};
use sexp::Sexp;
impl OpTrait for BuiltinOp {
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {

@ -1,7 +1,9 @@
use crate::module::{CrsnExtension, ParseRes};
use crate::asm::parse::arg_parser::TokenParser;
use crate::asm::instr::op::OpKind;
use sexp::SourcePosition;
use crate::asm::error::CrsnError;
use crate::asm::instr::op::OpKind;
use crate::asm::parse::arg_parser::TokenParser;
use crate::module::{CrsnExtension, ParseRes};
pub mod defs;
pub mod exec;
@ -21,7 +23,7 @@ impl CrsnExtension for BuiltinOps {
"builtin"
}
fn parse_op<'a>(&self, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse_op(keyword, args)
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse_op(pos, keyword, args)
}
}

@ -1,18 +1,18 @@
use sexp::{Atom, Sexp};
use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::data::literal::{Label, RoutineName};
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, parse_label_str};
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_label_str, parse_rd, parse_reg_alias, parse_value};
use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::patches::ErrWithPos;
use crate::builtin::defs::{Barrier, BuiltinOp};
use crate::module::{ParseRes};
use crate::module::ParseRes;
use crate::utils::A;
pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
let pcx = args.pcx;
Ok(ParseRes::Parsed(OpKind::BuiltIn(match keyword {
@ -31,22 +31,23 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
}
"sym" => {
let alias = parse_reg_alias(args.next())?;
let (alias, aliaspos) = parse_reg_alias(args.next_or_err()?)?;
trace!("alias={:?}", alias);
let register = parse_reg(&args.next_string()?)?;
let (rn, rpos) = args.next_string()?;
let register = parse_reg(&rn, &rpos)?;
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()));
return Err(CrsnError::Parse(format!("Register alias \"{}\" already defined!", alias).into(), rpos));
}
if pstate.constants.contains_key(&alias) {
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a constant!", alias).into()));
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a constant!", alias).into(), aliaspos));
}
if pstate.reg_aliases.iter().find(|x| x.1 == &register).is_some() {
return Err(CrsnError::Parse(format!("Register \"{}\" already aliased!", register).into()));
return Err(CrsnError::Parse(format!("Register \"{}\" already aliased!", register).into(), rpos));
}
pstate.reg_aliases.insert(alias, register);
@ -55,26 +56,26 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
}
"unsym" => {
let alias = parse_reg_alias(args.next())?;
let alias = parse_reg_alias(args.next_or_err()?)?;
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()));
if pstate.reg_aliases.remove(&alias.0).is_none() {
return Err(CrsnError::Parse(format!("Register alias \"{}\" not defined!", alias.0).into(), alias.1));
}
return Ok(ParseRes::ParsedNone);
}
"def" => {
let name = parse_constant_name(args.next())?;
let value = parse_value(args.next(), pcx)?;
let (name, namepos) = parse_constant_name(args.next_or_err()?)?;
let value = parse_value(args.next_or_err()?, pcx)?;
let mut pstate = pcx.state.borrow_mut();
if pstate.constants.contains_key(&name) {
return Err(CrsnError::Parse(format!("Constant \"{}\" already defined!", name).into()));
return Err(CrsnError::Parse(format!("Constant \"{}\" already defined!", name).into(), namepos));
}
if pstate.reg_aliases.contains_key(&name) {
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a register alias!", name).into()));
return Err(CrsnError::Parse(format!("Name \"{}\" already used for a register alias!", name).into(), namepos));
}
pstate.constants.insert(name, value);
@ -83,31 +84,31 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
}
"undef" => {
let name = parse_constant_name(args.next())?;
let (name, namepos) = parse_constant_name(args.next_or_err()?)?;
let mut pstate = pcx.state.borrow_mut();
if pstate.constants.remove(&name).is_none() {
return Err(CrsnError::Parse(format!("Constant \"{}\" not defined!", name).into()));
return Err(CrsnError::Parse(format!("Constant \"{}\" not defined!", name).into(), namepos));
}
return Ok(ParseRes::ParsedNone);
}
"j" => {
let dest = parse_label(args.next())?;
let dest = parse_label(args.next_or_err()?)?;
BuiltinOp::Jump(dest)
}
"fj" => {
let dest = parse_label(args.next())?;
let dest = parse_label(args.next_or_err()?)?;
BuiltinOp::FarJump(dest)
}
"call" => {
let dest = RoutineName { name: args.next_string()?, arity: args.len() as u8 };
let dest = RoutineName { name: args.next_string()?.0, arity: args.len() as u8 };
let mut call_args = vec![];
for t in args {
call_args.push(parse_rd(Some(t), pcx)?);
call_args.push(parse_rd(t, pcx)?);
}
BuiltinOp::Call(dest, call_args)
}
@ -115,14 +116,14 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
"ret" => {
let mut ret_vals = vec![];
for t in args {
ret_vals.push(parse_rd(Some(t), pcx)?);
ret_vals.push(parse_rd(t, pcx)?);
}
BuiltinOp::Ret(ret_vals)
}
"routine" => {
let name = args.next_string()?;
BuiltinOp::Routine(parse_routine_name(name)?)
BuiltinOp::Routine(parse_routine_name(name.0, &name.1)?)
}
"skip" => {
@ -134,21 +135,21 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
kind: Barrier::Standalone,
msg: match args.next() {
None => None,
Some(s) => Some(expect_string_atom(Some(s))?.into()),
Some(s) => Some(expect_string_atom(s)?.0.into()),
},
}
}
"barrier-open" => {
BuiltinOp::Barrier {
kind: Barrier::Open(parse_label(args.next())?),
kind: Barrier::Open(parse_label(args.next_or_err()?)?),
msg: None,
}
}
"barrier-close" => {
BuiltinOp::Barrier {
kind: Barrier::Close(parse_label(args.next())?),
kind: Barrier::Close(parse_label(args.next_or_err()?)?),
msg: None,
}
}
@ -156,7 +157,7 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
"fault" => {
BuiltinOp::Fault(match args.next() {
None => None,
Some(s) => Some(expect_string_atom(Some(s))?.into()),
Some(s) => Some(expect_string_atom(s)?.0.into()),
})
}
@ -184,7 +185,7 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
}
"far" => {
if let Some(Sexp::Atom(Atom::S(ref label))) = args.peek() {
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)
@ -198,7 +199,7 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
other => {
if let Some(label) = other.strip_prefix(':') {
BuiltinOp::Label(parse_label_str(label)?)
BuiltinOp::Label(parse_label_str(label, &op_pos)?)
} else {
return Ok(ParseRes::Unknown(args));
}
@ -206,11 +207,11 @@ pub(crate) fn parse_op<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<P
})))
}
pub(crate) fn parse_routine_name(name: String) -> Result<RoutineName, CrsnError> {
pub(crate) fn parse_routine_name(name: String, pos: &SourcePosition) -> Result<RoutineName, CrsnError> {
let (name, arity) = if let Some(n) = name.find('/') {
(
(&name[0..n]).to_string(),
(&name[(n + 1)..]).parse::<u8>()?
(&name[(n + 1)..]).parse::<u8>().err_pos(pos)?
)
} else {
(name, 0)
@ -236,7 +237,7 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
inner.extend(args.iter().map(A));
sexp::list(&inner)
}
},
}
BuiltinOp::Ret(values) => {
if values.is_empty() {
sexp::list(&[A("ret")])
@ -285,16 +286,14 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
#[cfg(test)]
mod test {
use std::sync::atomic::AtomicU32;
use crate::asm::instr::{Flatten};
use sexp::SourcePosition;
use crate::asm::parse::{parse_instructions, ParserContext};
use crate::asm::parse::sexp_expect::expect_list;
use crate::module::OpTrait;
use crate::builtin::BuiltinOps;
use crate::module::OpTrait;
#[test]
fn roundtrip() {
@ -304,10 +303,10 @@ mod test {
("(nop)", "(nop)"),
("(halt)", "(halt)"),
("(sleep 1000)", "(sleep 1000)"),
("(:x)\
(j :x)", "(skip 0)"),
("(:#7)\
(j :#7)", "(skip 0)"),
("(:x)", "(:x)"),
("(j :x)", "(j :x)"),
("(:#7)", "(:#7)"),
("(j :#7)", "(j :#7)"),
("(fj :x)", "(fj :x)"),
("(skip 0)", "(skip 0)"),
("(skip r0)", "(skip r0)"),
@ -365,9 +364,9 @@ mod test {
/* first cycle */
let s = sexp::parse(&format!("({})", sample))
.expect("parse sexp");
let list = expect_list(Some(s), false).unwrap();
let list = expect_list(s, false).unwrap();
let num = AtomicU32::new(0);
let parsed = parse_instructions(list.into_iter(), &pcx)
let parsed = parse_instructions(list.0.into_iter(), &SourcePosition::default(), &pcx)
.expect("parse instr").flatten(&num)
.expect("flatten").remove(0);
@ -377,13 +376,17 @@ mod test {
assert_eq!(expected, exported);
println!(" - 2nd cycle");
let pcx = ParserContext {
parsers,
state: Default::default(),
};
/* second cycle, nothing should change */
let s = sexp::parse(&format!("({})", exported))
.expect("parse sexp (2c)");
let list = expect_list(Some(s), false).unwrap();
let list = expect_list(s, false).unwrap();
let num = AtomicU32::new(0);
let parsed = parse_instructions(list.into_iter(), &pcx)
let parsed = parse_instructions(list.0.into_iter(), &SourcePosition::default(), &pcx)
.expect("parse instr (2c)").flatten(&num)
.expect("flatten (2c)").remove(0);

@ -1,8 +1,9 @@
#![allow(unused_variables)]
use std::fmt::{Debug};
use std::fmt::Debug;
pub use eval_res::EvalRes;
use sexp::{Sexp, SourcePosition};
use crate::asm::data::literal::Value;
use crate::asm::data::Mask;
@ -13,8 +14,6 @@ use crate::asm::parse::arg_parser::TokenParser;
use crate::runtime::fault::Fault;
use crate::runtime::run_thread::state::RunState;
use crate::runtime::run_thread::ThreadInfo;
use sexp::Sexp;
mod eval_res;
@ -53,12 +52,12 @@ pub trait CrsnExtension: Debug + Send + Sync + 'static {
/// If the instruction keyword is not recognized, return Unknown with the unchanged argument list.
///
/// pcx is available on the arg_tokens parser
fn parse_op<'a>(&self, keyword: &str, arg_tokens: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError>;
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, arg_tokens: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError>;
/// Parse a generic S-expression (non-op)
///
/// pcx is available on the arg_tokens parser
fn parse_syntax<'a>(&self, keyword: &str, tokens: TokenParser<'a>)
fn parse_syntax<'a>(&self, pos: &SourcePosition, keyword: &str, tokens: TokenParser<'a>)
-> Result<ParseRes<'a, Box<dyn Flatten>>, CrsnError>
{
Ok(ParseRes::Unknown(tokens))

@ -1,7 +1,10 @@
use std::collections::HashMap;
use std::sync::Arc;
use sexp::SourcePosition;
use crate::asm::data::literal::{Addr, Label, RoutineName};
use crate::asm::error::CrsnError;
use crate::asm::instr::Op;
use crate::asm::instr::op::OpKind;
use crate::builtin::defs::{Barrier, BuiltinOp};
@ -20,7 +23,7 @@ pub struct Program {
}
impl Program {
pub fn new(ops: Vec<Op>, extensions: Arc<Vec<Box<dyn CrsnExtension>>>) -> anyhow::Result<Arc<Self>> {
pub fn new(ops: Vec<Op>, extensions: Arc<Vec<Box<dyn CrsnExtension>>>) -> Result<Arc<Self>, CrsnError> {
let mut p = Self {
ops,
extensions,
@ -33,7 +36,7 @@ impl Program {
}
/// Find all the named things
fn scan(&mut self) -> anyhow::Result<()> {
fn scan(&mut self) -> Result<(), CrsnError> {
let mut barrier_starts: HashMap<&Label, Addr> = HashMap::new();
for (pos, op) in self.ops.iter().enumerate() {
match &op.kind {
@ -60,7 +63,7 @@ impl Program {
self.barriers.push((start_pos, pos.into()));
self.far_labels.insert(lbl.clone(), pos.into());
} else {
anyhow::bail!("Block barrier \"{:?}\" closed without being open!", msg);
return Err(CrsnError::Parse(format!("Block barrier \"{:?}\" closed without being open!", msg).into(), op.pos.clone()));
}
}
OpKind::BuiltIn(
@ -76,7 +79,9 @@ impl Program {
}
if !barrier_starts.is_empty() {
anyhow::bail!("Some block barriers open without being closed!");
return Err(CrsnError::Parse(format!("Block barrier open without being closed: {}",
barrier_starts.iter().next().unwrap().0).into(),
Default::default()));
}
trace!("Program scanned: {:?}", self);
@ -87,7 +92,15 @@ impl Program {
/// Read a program instruction at address
pub fn read(&self, addr: Addr) -> &Op {
if addr.0 >= self.ops.len() as u64 {
&Op { kind: OpKind::BuiltIn(BuiltinOp::Halt), cond: None }
&Op {
kind: OpKind::BuiltIn(BuiltinOp::Halt),
pos: SourcePosition {
line: 0,
column: 0,
index: 0,
},
cond: None,
}
} else {
&self.ops[addr.0 as usize]
}

@ -1,8 +1,9 @@
pub use option_ext::UncheckedOptionExt;
use std::fmt::Display;
use sexp::{Sexp, Atom};
use std::str::FromStr;
pub use option_ext::UncheckedOptionExt;
use sexp::{Atom, Sexp};
mod option_ext;
/// Convert a string token to Sexp
@ -12,13 +13,13 @@ pub fn A(s: impl Display) -> Sexp {
let x: Result<i64, _> = FromStr::from_str(&s);
if let Ok(x) = x {
return Sexp::Atom(Atom::I(x));
return Sexp::Atom(Atom::I(x), Default::default());
}
let y: Result<f64, _> = FromStr::from_str(&s);
if let Ok(y) = y {
return Sexp::Atom(Atom::F(y));
return Sexp::Atom(Atom::F(y), Default::default());
}
Sexp::Atom(Atom::S(s))
Sexp::Atom(Atom::S(s), Default::default())
}

@ -2,16 +2,16 @@ use std::ops::Rem;
use num_traits::PrimInt;
use crsn::asm::data::{Rd, Wr};
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::defs::ArithOp;
use crsn::sexp::Sexp;
use crsn::sexp;
use crsn::sexp::Sexp;
use crsn::utils::A;
use crsn::asm::data::{Rd, Wr};
use crate::defs::ArithOp;
impl OpTrait for ArithOp {
fn execute(&self, _ti: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
@ -238,7 +238,7 @@ impl OpTrait for ArithOp {
}
}
fn to_sexp_2_or_3(name: &str, dst : &Wr, a: &Rd, b: &Rd) -> Sexp {
fn to_sexp_2_or_3(name: &str, dst: &Wr, a: &Rd, b: &Rd) -> Sexp {
if &dst.as_rd() == a {
sexp::list(&[A(name), A(dst), A(b)])
} else {

@ -2,6 +2,7 @@ use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes};
use crsn::sexp::SourcePosition;
mod defs;
mod parse;
@ -21,7 +22,7 @@ impl CrsnExtension for ArithOps {
"arith"
}
fn parse_op<'a>(&self, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(keyword, args)
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(pos, keyword, args)
}
}

@ -3,10 +3,11 @@ use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::ParseRes;
use crsn::sexp::SourcePosition;
use crate::defs::ArithOp;
pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
pub(crate) fn parse<'a>(pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
Ok(ParseRes::ext(match keyword {
"cmp" => {
ArithOp::Compare {
@ -56,7 +57,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Add requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Add requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -79,7 +80,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Sub requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Sub requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -102,7 +103,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Mul requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Mul requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -129,7 +130,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("DivR requires 3 or 4 arguments".into()));
return Err(CrsnError::Parse("DivR requires 3 or 4 arguments".into(), pos.clone()));
}
}
}
@ -155,7 +156,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Div requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Div requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -179,7 +180,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Mod requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Mod requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -202,7 +203,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("And requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("And requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -225,7 +226,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Or requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Or requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -248,7 +249,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Xor requires 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Xor requires 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -269,7 +270,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Cpl requires 1 or 2 arguments".into()));
return Err(CrsnError::Parse("Cpl requires 1 or 2 arguments".into(), pos.clone()));
}
}
}
@ -300,7 +301,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Rol requires 1, 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Rol requires 1, 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -331,7 +332,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Ror requires 1, 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Ror requires 1, 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -362,7 +363,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Lsl requires 1, 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Lsl requires 1, 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -393,7 +394,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Lsr requires 1, 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Lsr requires 1, 2 or 3 arguments".into(), pos.clone()));
}
}
}
@ -424,7 +425,7 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
}
}
_ => {
return Err(CrsnError::Parse("Asr requires 1, 2 or 3 arguments".into()));
return Err(CrsnError::Parse("Asr requires 1, 2 or 3 arguments".into(), pos.clone()));
}
}
}

@ -1,19 +1,19 @@
use std::ops::Sub;
use std::time::{Duration, Instant};
use minifb::{ScaleMode, Window, WindowOptions, MouseMode, Key, MouseButton};
use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions};
use crsn::asm::data::literal::Value;
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::defs::ScreenOp;
use crsn::sexp::Sexp;
use crsn::sexp;
use crsn::sexp::Sexp;
use crsn::utils::A;
use crate::defs::ScreenOp;
#[derive(Debug)]
struct Opts {
auto_blit: bool,
@ -299,7 +299,7 @@ fn blit(backend: &mut Backend) {
backend.last_render = Instant::now();
}
fn num2key(num : Value) -> Option<Key> {
fn num2key(num: Value) -> Option<Key> {
let remap = [
Key::Key0,
Key::Key1,
@ -311,7 +311,6 @@ fn num2key(num : Value) -> Option<Key> {
Key::Key7,
Key::Key8,
Key::Key9,
Key::A, // 10
Key::B,
Key::C,
@ -361,7 +360,6 @@ fn num2key(num : Value) -> Option<Key> {
Key::Up,
Key::Apostrophe,
Key::Backquote,
Key::Backslash, // 57
Key::Comma,
Key::Equal,
@ -370,20 +368,17 @@ fn num2key(num : Value) -> Option<Key> {
Key::Period,
Key::RightBracket,
Key::Semicolon,
Key::Slash, // 65
Key::Backspace,
Key::Delete,
Key::End,
Key::Enter,
Key::Escape, // 70
Key::Home,
Key::Insert,
Key::Menu,
Key::PageDown,
Key::PageUp,
Key::Pause, // 76
Key::Space,
Key::Tab,
@ -394,7 +389,6 @@ fn num2key(num : Value) -> Option<Key> {
Key::RightShift,
Key::LeftCtrl,
Key::RightCtrl,
Key::NumPad0, // 86
Key::NumPad1,
Key::NumPad2,

@ -5,6 +5,7 @@ use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes};
use crsn::sexp::SourcePosition;
mod defs;
mod parse;
@ -24,7 +25,7 @@ impl CrsnExtension for ScreenOps {
"screen"
}
fn parse_op<'a>(&self, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(keyword, args)
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(pos, keyword, args)
}
}

@ -1,12 +1,13 @@
use crsn::asm::data::{Rd};
use crsn::asm::data::Rd;
use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::ParseRes;
use crsn::sexp::SourcePosition;
use crate::defs::ScreenOp;
pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
Ok(ParseRes::ext(match keyword {
"sc-init" => {
ScreenOp::ScreenInit {
@ -64,14 +65,14 @@ pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<Pars
"sc-key" => {
ScreenOp::TestKey {
pressed: args.next_wr()?,
code: args.next_rd()?
code: args.next_rd()?,
}
}
"sc-mbtn" => {
ScreenOp::TestMouse {
pressed: args.next_wr()?,
button: args.next_rd()?
button: args.next_rd()?,
}
}

@ -1,16 +1,16 @@
use std::collections::{HashMap, VecDeque};
use crsn::asm::data::{Rd, RdObj, Wr};
use crsn::asm::data::literal::Value;
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::defs::StackOp;
use crsn::sexp::Sexp;
use crsn::sexp;
use crsn::sexp::Sexp;
use crsn::utils::A;
use crsn::asm::data::{RdObj, Rd, Wr};
use crate::defs::StackOp;
#[derive(Debug, Default)]
struct Stacks {
@ -66,7 +66,7 @@ pub(crate) fn drop_obj(state: &mut RunState, handle: Value) -> Result<Option<()>
Ok(stacks.store.remove(&handle).map(|_| ()))
}
fn prepare_push(state: &mut RunState, obj: &RdObj, src: &Rd, pushfn : impl FnOnce(&mut VecDeque<Value>, Value) -> ()) -> Result<(), Fault> {
fn prepare_push(state: &mut RunState, obj: &RdObj, src: &Rd, pushfn: impl FnOnce(&mut VecDeque<Value>, Value) -> ()) -> Result<(), Fault> {
state.clear_status();
let handle = state.read_obj(*obj)?;
let val = state.read(*src)?;
@ -79,7 +79,7 @@ fn prepare_push(state: &mut RunState, obj: &RdObj, src: &Rd, pushfn : impl FnOnc
Ok(())
}
fn prepare_pop(state: &mut RunState, dst: &Wr, obj: &RdObj, popfn : impl FnOnce(&mut VecDeque<Value>) -> Option<Value>) -> Result<(), Fault> {
fn prepare_pop(state: &mut RunState, dst: &Wr, obj: &RdObj, popfn: impl FnOnce(&mut VecDeque<Value>) -> Option<Value>) -> Result<(), Fault> {
state.clear_status();
let handle = state.read_obj(*obj)?;

@ -5,6 +5,7 @@ use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{RunState, ThreadInfo};
use crsn::sexp::SourcePosition;
mod defs;
mod parse;
@ -24,8 +25,8 @@ impl CrsnExtension for StackOps {
"stacks"
}
fn parse_op<'a>(&self, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(keyword, args)
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse(pos, keyword, args)
}
fn drop_obj(&self, _ti: &ThreadInfo, state: &mut RunState, handle: Value) -> Result<Option<()>, Fault> {

@ -2,10 +2,11 @@ use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::ParseRes;
use crsn::sexp::SourcePosition;
use crate::defs::StackOp;
pub(crate) fn parse<'a>(keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
Ok(ParseRes::ext(match keyword {
"stack" => {
StackOp::NewStack {

@ -4,21 +4,20 @@
(sc-opt 1 1) ; auto blit
(sc-opt 2 25) ; frame rate
(ld r0 5) ; x
(ld r1 0) ; y
(sym x r0) (sym y r1) (sym dx r2) (sym dy r3) (sym color r5)
(ld r2 1) ; dx
(ld r3 1) ; dy
(ld r5 0x3300ff)
(ld x 5) (ld y 0)
(ld dx 1) (ld dy 1)
(ld color 0x3300ff)
(:loop)
(add r5 0x000001)
(sc-px r0 r1 r5)
(add r0 r2)
(add r1 r3)
(cmp r0 799 (eq? (ld r2 -1)) (ne? (cmp r0 0 (eq? (ld r2 1)))))
(cmp r1 599 (eq? (ld r3 -1)) (ne? (cmp r1 0 (eq? (ld r3 1)))))
(add color 0x000001)
(sc-px x y color)
(add x dx (z? (ld dx 1)))
(cmp x 799 (eq? (ld dx -1)))
(add y dy (z? (ld dy 1)))
(cmp y 599 (eq? (ld dy -1)))
(j :loop)
(fault "unreachable")
)

@ -10,11 +10,11 @@ use clappconfig::clap::ArgMatches;
use serde::{Deserialize, Serialize};
use crsn::asm::data::literal::Addr;
use crsn::module::OpTrait;
use crsn::runtime::run_thread::{RunThread, ThreadToken};
use crsn_arith::ArithOps;
use crsn_screen::ScreenOps;
use crsn_stacks::StackOps;
use crsn::module::OpTrait;
mod read_file;
mod serde_duration_millis;

@ -0,0 +1,2 @@
target
Cargo.lock

@ -0,0 +1,14 @@
[package]
name = "sexp"
version = "1.1.4"
authors = ["Clark Gaebel <cg.wowus.cg@gmail.com>"]
documentation = "https://cgaebel.github.io/sexp"
homepage = "https://github.com/cgaebel/sexp"
repository = "https://github.com/cgaebel/sexp"
readme = "README.md"
keywords = ["sexp", "parsing", "s-expression", "file-format"]
description = "A small, simple, self-contained, s-expression parser and pretty-printer."
license = "MIT"
[dependencies]
log = "0.4.11"

@ -0,0 +1,19 @@
Copyright (c) 2015 Clark Gaebel <cg.wowus.cg@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,9 @@
Source-location tracking Sexp
=====
**This is a fork of "sexp", updated to the 2018 edition, where each parsed node tracks its
source file position. This enables better error reporting in subsequent parsing and processing.**
---
Original version by Clark Gaebel: [https://github.com/cgaebel/sexp](https://github.com/cgaebel/sexp).

@ -0,0 +1,85 @@
use std::{cmp, fmt};
/// The representation of an s-expression parse error.
pub struct Error {
/// The error message.
pub message: &'static str,
/// Position in the source string where the error was detected
pub pos: SourcePosition,
}
/// Position in the input string
#[derive(Debug, PartialEq, Clone, Default)]
pub struct SourcePosition {
/// The line number on which the error occurred.
pub line: u32,
/// The column number on which the error occurred.
pub column: u32,
/// The index in the given string which caused the error.
pub index: u32,
}
/// Since errors are the uncommon case, they're boxed. This keeps the size of
/// structs down, which helps performance in the common case.
///
/// For example, an `ERes<()>` becomes 8 bytes, instead of the 24 bytes it would
/// be if `Err` were unboxed.
type Err = Box<Error>;
/// Helps clean up type signatures, but shouldn't be exposed to the outside
/// world.
pub(crate) type ERes<T> = Result<T, Err>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}: {}", self.pos.line, self.pos.column, self.message)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}
impl std::error::Error for Error {}
pub(crate) fn get_line_and_column(s: &str, pos: usize) -> SourcePosition {
let mut line: usize = 1;
let mut col: isize = -1;
for c in s.chars().take(pos + 1) {
if c == '\n' {
line += 1;
col = -1;
} else {
col += 1;
}
}
SourcePosition {
line: line as u32,
column: cmp::max(col, 0) as u32,
index: pos as u32,
}
}
#[cold]
fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err {
Box::new(Error {
message,
pos: get_line_and_column(s, *pos),
})
}
/// Build an error with span information
pub(crate) fn err<T>(message: &'static str, s: &str, pos: &usize) -> ERes<T> {
Err(err_impl(message, s, pos))
}
/// Build a span
pub(crate) fn spos(s: &str, pos: &usize) -> SourcePosition {
if *pos >= s.len() {
Default::default()
} else {
get_line_and_column(s, *pos)
}
}

@ -0,0 +1,324 @@
//! A lightweight, self-contained s-expression parser and data format.
//! Use `parse` to get an s-expression from its string representation, and the
//! `Display` trait to serialize it, potentially by doing `sexp.to_string()`.
#![deny(unsafe_code)]
#[macro_use]
extern crate log;
use std::borrow::Cow;
use std::fmt;
use std::str::{self, FromStr};
use error::{ERes, err, spos};
pub use error::Error;
pub use error::SourcePosition;
#[cfg(test)]
mod test;
mod error;
/// A single data element in an s-expression. Floats are excluded to ensure
/// atoms may be used as keys in ordered and hashed data structures.
///
/// All strings must be valid utf-8.
#[derive(PartialEq, Clone, PartialOrd)]
#[allow(missing_docs)]
pub enum Atom {
S(String),
I(i64),
F(f64),
}
/// An s-expression is either an atom or a list of s-expressions. This is
/// similar to the data format used by lisp.
#[derive(Clone)]
pub enum Sexp {
/// Atom
Atom(Atom, SourcePosition),
/// List of expressions
List(Vec<Sexp>, SourcePosition),
}
impl Sexp {
pub fn pos(&self) -> &SourcePosition {
match self {
Sexp::List(_, pos) | Sexp::Atom(_, pos) => pos
}
}
/// Check fi thsi Sexp is an atom
pub fn is_atom(&self) -> bool {
match self {
Sexp::Atom(_, _) => true,
_ => false,
}
}
/// Check fi thsi Sexp is a list
pub fn is_list(&self) -> bool {
match self {
Sexp::List(_, _) => true,
_ => false,
}
}
}
impl PartialEq for Sexp {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Sexp::Atom(a, _), Sexp::Atom(b, _)) => {
a == b
}
(Sexp::List(a, _), Sexp::List(b, _)) => {
a == b
}
_ => false
}
}
}
fn atom_of_string(s: String) -> Atom {
match FromStr::from_str(&s) {
Ok(i) => return Atom::I(i),
Err(_) => {}
};
match FromStr::from_str(&s) {
Ok(f) => return Atom::F(f),
Err(_) => {}
};
Atom::S(s)
}
// returns the char it found, and the new size if you wish to consume that char
fn peek(s: &str, pos: &usize) -> ERes<(char, usize)> {
trace!("peek {}", pos);
if *pos == s.len() { return err("unexpected eof", s, pos); }
if s.is_char_boundary(*pos) {
let ch = s[*pos..].chars().next().unwrap();
let next = *pos + ch.len_utf8();
Ok((ch, next))
} else {
// strings must be composed of valid utf-8 chars.
unreachable!()
}
}
fn expect(s: &str, pos: &mut usize, c: char) -> ERes<()> {
trace!("expect {}", pos);
let (ch, next) = peek(s, pos)?;
*pos = next;
if ch == c { Ok(()) } else { err("unexpected character", s, pos) }
}
fn consume_until_newline(s: &str, pos: &mut usize) -> ERes<()> {
loop {
if *pos == s.len() { return Ok(()); }
let (ch, next) = peek(s, pos)?;
*pos = next;
if ch == '\n' { return Ok(()); }
}
}
// zero or more spaces
fn zspace(s: &str, pos: &mut usize) -> ERes<()> {
trace!("zspace {}", pos);
loop {
if *pos == s.len() { return Ok(()); }
let (ch, next) = peek(s, pos)?;
if ch == ';' { consume_until_newline(s, pos)? } else if ch.is_whitespace() { *pos = next; } else { return Ok(()); }
}
}
fn parse_quoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_quoted_atom {}", pos);
let mut cs: String = String::new();
expect(s, pos, '"')?;
loop {
let (ch, next) = peek(s, pos)?;
if ch == '"' {
*pos = next;
break;
} else if ch == '\\' {
let (postslash, nextnext) = peek(s, &next)?;
if postslash == '"' || postslash == '\\' {
cs.push(postslash);
} else {
cs.push(ch);
cs.push(postslash);
}
*pos = nextnext;
} else {
cs.push(ch);
*pos = next;
}
}
// Do not try i64 conversion, since this atom was explicitly quoted.
Ok(Atom::S(cs))
}
fn parse_unquoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_unquoted_atom {}", pos);
let mut cs: String = String::new();
loop {
if *pos == s.len() { break; }
let (c, next) = peek(s, pos)?;
if c == ';' {
consume_until_newline(s, pos)?;
break;
}
if c.is_whitespace() || c == '(' || c == ')' { break; }
cs.push(c);
*pos = next;
}
Ok(atom_of_string(cs))
}
fn parse_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_atom {}", pos);
let (ch, _) = peek(s, pos)?;
if ch == '"' { parse_quoted_atom(s, pos) } else { parse_unquoted_atom(s, pos) }
}
fn parse_list(s: &str, pos: &mut usize) -> ERes<Vec<Sexp>> {
trace!("parse_list {}", pos);
zspace(s, pos)?;
expect(s, pos, '(')?;
let mut sexps: Vec<Sexp> = Vec::new();
loop {
zspace(s, pos)?;
let (c, next) = peek(s, pos)?;
if c == ')' {
*pos = next;
break;
}
sexps.push(parse_sexp(s, pos)?);
}
zspace(s, pos)?;
Ok(sexps)
}
fn parse_sexp(s: &str, pos: &mut usize) -> ERes<Sexp> {
trace!("parse_sexp {}", pos);
zspace(s, pos)?;
let (c, _) = peek(s, pos)?;
let r = if c == '(' {
Ok(Sexp::List(parse_list(s, pos)?, spos(s, pos)))
} else {
Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, pos)))
};
zspace(s, pos)?;
r
}
/// Constructs an atomic s-expression from a string.
pub fn atom_s(s: &str) -> Sexp {
Sexp::Atom(Atom::S(s.to_owned()), Default::default())
}
/// Constructs an atomic s-expression from an int.
pub fn atom_i(i: i64) -> Sexp {
Sexp::Atom(Atom::I(i), Default::default())
}
/// Constructs an atomic s-expression from a float.
pub fn atom_f(f: f64) -> Sexp {
Sexp::Atom(Atom::F(f), Default::default())
}
/// Constructs a list s-expression given a slice of s-expressions.
pub fn list(xs: &[Sexp]) -> Sexp {
Sexp::List(xs.to_owned(), Default::default())
}
/// Reads an s-expression out of a `&str`.
#[inline(never)]
pub fn parse(s: &str) -> Result<Sexp, Box<Error>> {
let mut pos = 0;
let ret = parse_sexp(s, &mut pos)?;
if pos == s.len() { Ok(ret) } else { err("unrecognized post-s-expression data", s, &pos) }
}
// TODO: Pretty print in lisp convention, instead of all on the same line,
// packed as tightly as possible. It's kinda ugly.
fn is_num_string(s: &str) -> bool {
let x: Result<i64, _> = FromStr::from_str(&s);
let y: Result<f64, _> = FromStr::from_str(&s);
x.is_ok() || y.is_ok()
}
fn string_contains_whitespace(s: &str) -> bool {
for c in s.chars() {
if c.is_whitespace() { return true; }
}
false
}
fn quote(s: &str) -> Cow<str> {
if !s.contains("\"")
&& !string_contains_whitespace(s)
&& !is_num_string(s) {
Cow::Borrowed(s)
} else {
let mut r: String = "\"".to_string();
r.push_str(&s.replace("\\", "\\\\").replace("\"", "\\\""));
r.push_str("\"");
Cow::Owned(r)
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Atom::S(ref s) => write!(f, "{}", quote(s)),
Atom::I(i) => write!(f, "{}", i),
Atom::F(d) => write!(f, "{}", d),
}
}
}
impl fmt::Display for Sexp {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Sexp::Atom(ref a, _) => write!(f, "{}", a),
Sexp::List(ref xs, _) => {
write!(f, "(")?;
for (i, x) in xs.iter().enumerate() {
let s = if i == 0 { "" } else { " " };
write!(f, "{}{}", s, x)?;
}
write!(f, ")")
}
}
}
}
impl fmt::Debug for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}
impl fmt::Debug for Sexp {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}

@ -0,0 +1,70 @@
use super::*;
use super::error::get_line_and_column;
#[test]
fn test_hello_world() {
assert_eq!(
parse("(hello -42\n\t -4.0 \"world\") ; comment").unwrap(),
list(&[atom_s("hello"), atom_i(-42), atom_f(-4.0), atom_s("world")]));
}
#[test]
fn test_escaping() {
assert_eq!(
parse("(\"\\\"\\q\" \"1234\" 1234)").unwrap(),
list(&[atom_s("\"\\q"), atom_s("1234"), atom_i(1234)]));
}
#[test]
fn test_pp() {
let s = "(hello world (what is (up) (4 6.4 you \"123\\\\ \\\"\")))";
let sexp = parse(s).unwrap();
assert_eq!(s, sexp.to_string());
assert_eq!(s, format!("{:?}", sexp));
}
#[test]
fn test_tight_parens() {
let s = "(hello(world))";
let sexp = parse(s).unwrap();
assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into()), Default::default()),
Sexp::List(vec![Sexp::Atom(Atom::S("world".into()), Default::default())], Default::default())], Default::default()));
let s = "(this (has)tight(parens))";
let s2 = "( this ( has ) tight ( parens ) )";
assert_eq!(parse(s).unwrap(), parse(s2).unwrap());
}
#[test]
fn test_space_in_atom() {
let sexp = list(&[atom_s("hello world")]);
let sexp_as_string = sexp.to_string();
assert_eq!("(\"hello world\")", sexp_as_string);
assert_eq!(sexp, parse(&sexp_as_string).unwrap());
}
#[test]
fn show_an_error() {
assert_eq!(format!("{:?}", parse("(aaaa").unwrap_err()), "1:4: unexpected eof");
}
#[test]
fn line_and_col_test() {
let s = "0123456789\n0123456789\n\n6";
assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 4 });
assert_eq!(get_line_and_column(s, 10), SourcePosition { line: 2, column: 0, index: 10 });
assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11 });
assert_eq!(get_line_and_column(s, 15), SourcePosition { line: 2, column: 4, index: 15 });
assert_eq!(get_line_and_column(s, 21), SourcePosition { line: 3, column: 0, index: 21 });
assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22 });
assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23 });
assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500 });
}
#[test]
fn sexp_size() {
// I just want to see when this changes, in the diff.
use std::mem;
assert_eq!(mem::size_of::<Sexp>(), mem::size_of::<isize>() * 6);
}
Loading…
Cancel
Save