diff --git a/compile_examples.sh b/compile_examples.sh index cdc0960..5fec96e 100755 --- a/compile_examples.sh +++ b/compile_examples.sh @@ -4,8 +4,4 @@ set -e cargo build -for file in examples/*.csn -do - echo "--- $file ---" - target/debug/launcher -P "$file" -done +find examples -name '*.csn' -type f -exec target/debug/launcher -P {} \; diff --git a/crsn/crsn-sexp/src/error.rs b/crsn/crsn-sexp/src/error.rs index cb5dea9..8a3ada2 100644 --- a/crsn/crsn-sexp/src/error.rs +++ b/crsn/crsn-sexp/src/error.rs @@ -1,4 +1,5 @@ use std::{cmp, fmt}; +use std::fmt::{Formatter, Debug}; /// The representation of an s-expression parse error. pub struct Error { @@ -9,7 +10,7 @@ pub struct Error { } /// Position in the input string -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(PartialEq, Clone, Default)] pub struct SourcePosition { /// The line number on which the error occurred. pub line: u32, @@ -25,6 +26,21 @@ impl fmt::Display for SourcePosition { } } +impl Debug for SourcePosition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.debug_struct("SourcePosition") + .field("line", &self.line) + .field("column", &self.column) + .field("index", &self.index) + .finish() + } else { + // shorter version + write!(f, "Pos({}:{})", self.line, self.column) + } + } +} + /// Since errors are the uncommon case, they're boxed. This keeps the size of /// structs down, which helps performance in the common case. /// diff --git a/crsn/src/asm/mod.rs b/crsn/src/asm/mod.rs index 6798920..1974265 100644 --- a/crsn/src/asm/mod.rs +++ b/crsn/src/asm/mod.rs @@ -8,6 +8,8 @@ use crate::asm::parse::{ParserContext, ParserState}; use crate::module::{CrsnExtension, CrsnUniq}; use crate::runtime::program::Program; use crate::builtin::BuiltinOps; +use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken}; +use crate::runtime::frame::REG_COUNT; pub mod data; pub mod error; @@ -18,11 +20,12 @@ pub mod patches; /// Parse a program from string and assemble a low level instruction sequence from it. pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec>) -> Result, error::CrsnError> { parsers.insert(0, BuiltinOps::new()); - for p in &mut parsers { p.init(uniq); } + let parsers_arc = Arc::new(parsers); + // remove first line if it looks like a shebang let source = if source.starts_with("#!") { if let Some(nl) = source.find('\n') { @@ -34,18 +37,38 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec { orig_len: usize, args: Vec, @@ -18,6 +19,16 @@ pub struct TokenParser<'a> { pub pcx: &'a ParserContext<'a>, } +impl<'a> Debug for TokenParser<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("TokenParser") + .field("orig_len", &self.orig_len) + .field("args", &self.args) + .field("start_pos", &self.start_pos) + .finish() + } +} + impl<'a> IntoIterator for TokenParser<'a> { type Item = Sexp; type IntoIter = std::vec::IntoIter; @@ -42,6 +53,13 @@ impl<'a> TokenParser<'a> { } } + /// Prepend a token + pub fn prepend(&mut self, what: Sexp) { + // the list is reversed - actually, append it + self.args.push(what); + self.orig_len += 1; + } + /// Get error if not empty. /// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string" pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> { diff --git a/crsn/src/asm/parse/mod.rs b/crsn/src/asm/parse/mod.rs index 0107c02..a52125c 100644 --- a/crsn/src/asm/parse/mod.rs +++ b/crsn/src/asm/parse/mod.rs @@ -11,6 +11,8 @@ use crate::asm::error::CrsnError; use crate::asm::instr::Op; use crate::asm::parse::sexp_expect::expect_list; use crate::module::CrsnExtension; +use crate::runtime::run_thread::{ThreadInfo, RunState}; +use std::sync::Arc; pub mod parse_cond; pub mod parse_instr; @@ -28,7 +30,7 @@ pub struct ParserContext<'a> { pub state: RefCell, } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct ParserState { /// Register aliases valid globally pub global_reg_aliases: HashMap, @@ -41,6 +43,14 @@ pub struct ParserState { /// Global constants pub constants: HashMap, + + /// Dummy run state used for const eval + pub const_eval: RunState, + // ThreadInfo is needed separately from RunState. TODO check if this can be refactored + pub const_eval_ti: Arc, + + /// True if we are in an expression parser context + pub parsing_expr : bool, } pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result, CrsnError> { diff --git a/crsn/src/asm/parse/parse_data.rs b/crsn/src/asm/parse/parse_data.rs index 27e5152..c8251f6 100644 --- a/crsn/src/asm/parse/parse_data.rs +++ b/crsn/src/asm/parse/parse_data.rs @@ -6,9 +6,12 @@ use sexp::{Atom, Sexp, SourcePosition}; use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr}; use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value}; use crate::asm::error::CrsnError; -use crate::asm::parse::ParserContext; +use crate::asm::parse::{ParserContext, parse_instructions}; use crate::asm::parse::sexp_expect::expect_string_atom; use crate::asm::patches::ErrWithPos; +use std::sync::atomic::AtomicU32; +use crate::module::OpTrait; +use crate::asm::instr::Cond; fn is_valid_identifier(name: &str) -> bool { // ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~" @@ -87,7 +90,51 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result { Err(CrsnError::Parse("Quoted string not expected here".into(), pos)) } - Sexp::List(_list, pos) => { + Sexp::List(list, pos) => { + if let Some(Sexp::Atom(Atom::S(s), s_pos)) = list.first() { + let parsing_expr = pcx.state.borrow().parsing_expr; + + if !s.starts_with('=') && !parsing_expr { + return Err(CrsnError::Parse(format!("Invalid syntax, did you mean \"={}\"?", s).into(), s_pos.clone())); + } + + // start expr parsing mode + let orig_parsing_expr = pcx.state.borrow().parsing_expr; + pcx.state.borrow_mut().parsing_expr = true; + + let lmut = AtomicU32::new(0); + + let mut expr_ops = parse_instructions( + // TODO avoid this madness + vec![Sexp::List(list, pos.clone())].into_iter(), + &pos, pcx)? + .flatten(&lmut)?; + + if expr_ops.len() != 1 { + return Err(CrsnError::Parse(format!("Expected one top level operation in an expression, got: {:?}", expr_ops).into(), pos.clone())); + } + + let op = expr_ops.remove(0); + + let mut state_mut = pcx.state.borrow_mut(); + state_mut.const_eval.clear_all(); + + let ticl = state_mut.const_eval.thread_info.clone(); + op.execute(&ticl, &mut state_mut.const_eval) + .map_err(|f| CrsnError::ParseOther(Box::new(f), pos.clone()))?; + + if state_mut.const_eval.test_cond(Cond::Invalid) { + return Err(CrsnError::Parse("Expression evaluation failed - invalid flag set!".into(), pos.clone())); + } + + if !orig_parsing_expr { + // exit expr parsing mode + state_mut.parsing_expr = false; + } + + return Ok(DataDisp::Immediate(state_mut.const_eval.frame.gen[0])); + } + Err(CrsnError::Parse("List not expected here".into(), pos)) } Sexp::Atom(Atom::S(s), pos) => { diff --git a/crsn/src/asm/parse/parse_instr.rs b/crsn/src/asm/parse/parse_instr.rs index ac276a8..6fccd7e 100644 --- a/crsn/src/asm/parse/parse_instr.rs +++ b/crsn/src/asm/parse/parse_instr.rs @@ -1,4 +1,4 @@ -use sexp::{Sexp, SourcePosition, Atom}; +use sexp::{Sexp, SourcePosition, Atom, atom_s}; use crate::asm::error::CrsnError; use crate::asm::instr::{Flatten, InstrWithBranches}; @@ -18,9 +18,20 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, let (tokens, listpos) = expect_list(expr, false)?; let mut toki = tokens.into_iter(); - let (name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?; + let (mut name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?; + + let parsing_expr = pcx.state.borrow().parsing_expr; + + if parsing_expr { + if let Some(n) = name.strip_prefix('=') { + name = n.to_string(); + } + } if name == "proc" { + if parsing_expr { + return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone())); + } parsed.push(parse_routine(toki, pos, pcx)?); continue; } @@ -62,8 +73,13 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, }) .collect::>(); - let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx); - // debug!("branch_tokens: {:#?}", branch_tokens); + if parsing_expr && !branch_tokens.is_empty() { + return Err(CrsnError::Parse(format!("Conditional branches are not allowed in const expression: {:?}", branch_tokens).into(), pos.clone())); + } + + let mut arg_parser = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx); + + trace!("Parse op: {}\nargs {:?}\nbranches {:?}", name, arg_parser, branch_tokens); let branches = { let mut branches = vec![]; @@ -77,7 +93,12 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, } }; - if let Some(op) = parse_op(name.as_str(), arg_tokens, &namepos)? { + if parsing_expr { + // hackity hack for const expr eval + arg_parser.prepend(atom_s("r0")); + } + + if let Some(op) = parse_op(name.as_str(), arg_parser, &namepos)? { parsed.push(Box::new(InstrWithBranches { op, pos: namepos, diff --git a/crsn/src/asm/parse/parse_op.rs b/crsn/src/asm/parse/parse_op.rs index 9fb058d..ca057fd 100644 --- a/crsn/src/asm/parse/parse_op.rs +++ b/crsn/src/asm/parse/parse_op.rs @@ -16,6 +16,10 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S keyword = &keyword[..pos]; } + if cond.is_some() && arg_tokens.pcx.state.borrow().parsing_expr { + return Err(CrsnError::Parse("Conditional suffixes are not allowed in const expression".into(), spos.clone())); + } + for p in arg_tokens.pcx.parsers { arg_tokens = match p.parse_op(spos, keyword, arg_tokens) { Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op { diff --git a/crsn/src/module/mod.rs b/crsn/src/module/mod.rs index 1b160de..07442f9 100644 --- a/crsn/src/module/mod.rs +++ b/crsn/src/module/mod.rs @@ -52,7 +52,7 @@ pub trait OpTrait: Debug + Send + Sync + 'static { /// CRSN initializer object. /// Only one should be created for the lifespan of the parser and runtime. -#[derive(Default)] +#[derive(Default,Debug)] pub struct CrsnUniq { object_handle_counter : AtomicU64 } diff --git a/crsn/src/runtime/run_thread/info.rs b/crsn/src/runtime/run_thread/info.rs index 7559559..7e2ed08 100644 --- a/crsn/src/runtime/run_thread/info.rs +++ b/crsn/src/runtime/run_thread/info.rs @@ -7,6 +7,7 @@ use crate::runtime::program::Program; use crate::runtime::run_thread::ThreadToken; use crate::module::{CrsnExtension, CrsnUniq}; +#[derive(Debug)] pub struct ThreadInfo { /// Thread ID pub id: ThreadToken, diff --git a/crsn/src/runtime/run_thread/state.rs b/crsn/src/runtime/run_thread/state.rs index 1315ea5..48133e5 100644 --- a/crsn/src/runtime/run_thread/state.rs +++ b/crsn/src/runtime/run_thread/state.rs @@ -25,6 +25,16 @@ pub struct RunState { pub ext_data: ExtensionDataStore, } +impl RunState { + /// Clear everything. Caution: when done at runtime, this effectively reboots the thread + pub fn clear_all(&mut self) { + self.frame = Default::default(); + self.call_stack = Default::default(); + self.global_regs = Default::default(); + self.ext_data = Default::default(); + } +} + impl Debug for RunState { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RunState") diff --git a/examples/expr.csn b/examples/expr.csn new file mode 100644 index 0000000..5368c93 --- /dev/null +++ b/examples/expr.csn @@ -0,0 +1,12 @@ +( + (def FOO 14) + + (ld r0 (=add 14 1)) + (cmp r0 15 (ne? (fault "1"))) + + (ld r0 (=add FOO 1)) + (cmp r0 15 (ne? (fault "2"))) + + (ld r0 (=add (sub FOO 2) 3)) + (cmp r0 15 (ne? (fault "3"))) +) diff --git a/launcher/src/main.rs b/launcher/src/main.rs index c8e8fa0..13f441a 100644 --- a/launcher/src/main.rs +++ b/launcher/src/main.rs @@ -151,6 +151,7 @@ fn main() -> anyhow::Result<()> { ])?; if config.assemble_only { + println!("--- {} ---", config.program_file); for (n, op) in parsed.ops.iter().enumerate() { if config.assemble_debug { println!("{:04} : {:?}", n, op);