From daff23dd98b03c3b58320ae4b094a63b1a201f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 1 Nov 2020 14:41:50 +0100 Subject: [PATCH] Add loop syntactic sugar; errors now use print instead of debug; add check for duplicate labels. --- README.md | 30 ++++++++++++++++++ crsn/src/asm/error.rs | 12 +++++--- crsn/src/asm/instr/flatten.rs | 4 +++ crsn/src/asm/parse/arg_parser.rs | 2 +- crsn/src/builtin/mod.rs | 53 ++++++++++++++++++++++++++++---- crsn_stdio/src/lib.rs | 3 +- examples/loop.csn | 43 ++++++++++++++++++++++++++ 7 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 examples/loop.csn diff --git a/README.md b/README.md index 100ebdb..be62722 100644 --- a/README.md +++ b/README.md @@ -408,6 +408,36 @@ to a varying number of skips and conditional instructions by the assembler. Only Jumping to a label is always safer than a manual skip. +### Loop syntactic sugar + +Infinite loops are a very common construct, so there is special syntax added for them. + +These are converted by the assembler to an anonymous label and a jump to it. + +``` +(loop + ...ops +) +``` + +If you want to have a convenient jump target, give the loop a name. This lets you easily "break" and "continue" +by jumping to the labels. + +``` +(loop :label + ...ops +) +``` + +becomes + +``` +(:label) +...ops +(j :label) +(:label-end) +``` + ## Built-in Instructions ...and pseudo-instructions diff --git a/crsn/src/asm/error.rs b/crsn/src/asm/error.rs index 80d2364..bdfdbab 100644 --- a/crsn/src/asm/error.rs +++ b/crsn/src/asm/error.rs @@ -12,13 +12,13 @@ use crate::asm::instr::Cond; /// csn_asm unified error type #[derive(Error, Debug)] pub enum CrsnError { - #[error("S-expression parsing error: {0:?}")] + #[error("S-expression parsing error: {0}")] Sexp(#[from] Box), - #[error("Parse error: {0:?} at {1:?}")] + #[error("Parse error: {0} at {1}")] Parse(Cow<'static, str>, SourcePosition), - #[error("Parse error: {0:?} at {1:?}")] + #[error("Parse error: {0} at {1}")] ParseOther(Box, SourcePosition), - #[error("Assembler error: {0:?} at {1:?}")] + #[error("Assembler error: {0} at {1}")] Asm(AsmError, SourcePosition), } @@ -33,8 +33,10 @@ pub enum AsmError { DiscardAsValue, #[error("Conditional branch already defined for \"{0}\"")] ConditionalAlreadyUsed(Cond), - #[error("Label \"{0:?}\" not defined")] + #[error("Label \"{0}\" not defined")] LabelNotDefined(Label), + #[error("Label \"{0}\" already defined in this scope")] + LabelDuplicate(Label), #[error("Bad register type: {0}")] BadRegisterType(Register), } diff --git a/crsn/src/asm/instr/flatten.rs b/crsn/src/asm/instr/flatten.rs index a180476..8e2b02b 100644 --- a/crsn/src/asm/instr/flatten.rs +++ b/crsn/src/asm/instr/flatten.rs @@ -174,6 +174,10 @@ pub fn jumps_to_skips(ops: Vec) -> Result, CrsnError> { let mut label_positions = HashMap::::new(); for (n, op) in ops.iter().enumerate() { if let OpKind::BuiltIn(BuiltinOp::Label(name)) = &op.kind { + if label_positions.contains_key(name) { + return Err(CrsnError::Asm(AsmError::LabelDuplicate(name.clone()), op.pos.clone())); + } + label_positions.insert(name.clone(), n - label_positions.len()); } } diff --git a/crsn/src/asm/parse/arg_parser.rs b/crsn/src/asm/parse/arg_parser.rs index 2b0a6e0..c5b4796 100644 --- a/crsn/src/asm/parse/arg_parser.rs +++ b/crsn/src/asm/parse/arg_parser.rs @@ -109,7 +109,7 @@ impl<'a> TokenParser<'a> { } /// Look at the next entry - pub fn peek(&mut self) -> Option<&Sexp> { + pub fn peek(&self) -> Option<&Sexp> { self.args.last() } diff --git a/crsn/src/builtin/mod.rs b/crsn/src/builtin/mod.rs index ba3ceba..e1b0987 100644 --- a/crsn/src/builtin/mod.rs +++ b/crsn/src/builtin/mod.rs @@ -1,15 +1,16 @@ -use sexp::SourcePosition; +use sexp::{SourcePosition, Sexp, Atom}; use crate::asm::error::CrsnError; use crate::asm::instr::op::OpKind; use crate::asm::parse::arg_parser::TokenParser; use crate::module::{CrsnExtension, ParseRes}; -use crate::asm::data::literal::Value; +use crate::asm::data::literal::{Value, Label}; use crate::asm::instr::{Flatten, Op}; use crate::asm::parse::parse_instructions; use crate::builtin::defs::BuiltinOp; use crate::asm::instr::flatten::jumps_to_skips; use crate::asm::data::{Rd, RdData}; +use crate::asm::parse::parse_data::parse_label; pub mod defs; pub mod exec; @@ -47,7 +48,7 @@ impl CrsnExtension for BuiltinOps { /// Parse a generic S-expression (non-op) that started with the given keyword /// /// pcx is available on the arg_tokens parser - fn parse_syntax<'a>(&self, pos: &SourcePosition, keyword: &str, tokens: TokenParser<'a>) + fn parse_syntax<'a>(&self, pos: &SourcePosition, keyword: &str, mut tokens: TokenParser<'a>) -> Result>, CrsnError> { if keyword == "crit" || keyword == "critical" { @@ -91,19 +92,59 @@ impl CrsnExtension for BuiltinOps { Box::new(Op { kind: OpKind::BuiltIn(BuiltinOp::CriticalBegin), cond: None, - pos: pos.clone() + pos: pos.clone(), }), Box::new(flattened), Box::new(Op { kind: OpKind::BuiltIn(BuiltinOp::CriticalEnd), cond: None, - pos: pos.clone() - }) + pos: pos.clone(), + }), ]; return Ok(ParseRes::Parsed(Box::new(vec))); } + if keyword == "loop" { + let mut end_label = None; + let label = if let Some(Sexp::Atom(Atom::S(_), _)) = tokens.peek() { + let label = parse_label(tokens.next().unwrap())?; + if let Label::Named(n) = &label { + end_label = Some(Label::Named(format!("{}-end", n))); + } + label + } else { + Label::unique(&tokens.pcx.state.borrow().label_num) + }; + + let pcx = tokens.pcx; + let inner = parse_instructions(tokens.into_iter(), pos, pcx)?; + + let mut vec : Vec> = vec![ + Box::new(Op { + kind: OpKind::BuiltIn(BuiltinOp::Label(label.clone())), + cond: None, + pos: pos.clone(), + }), + inner, + Box::new(Op { + kind: OpKind::BuiltIn(BuiltinOp::Jump(label)), + cond: None, + pos: pos.clone(), + }), + ]; + + if let Some(el) = end_label { + vec.push(Box::new(Op { + kind: OpKind::BuiltIn(BuiltinOp::Label(el)), + cond: None, + pos: pos.clone(), + })); + } + + return Ok(ParseRes::Parsed(Box::new(vec))) + } + Ok(ParseRes::Unknown(tokens)) } } diff --git a/crsn_stdio/src/lib.rs b/crsn_stdio/src/lib.rs index c9af344..141ecc4 100644 --- a/crsn_stdio/src/lib.rs +++ b/crsn_stdio/src/lib.rs @@ -14,7 +14,6 @@ use std::io; use crsn::asm::instr::cond::Flag; use std::fmt; use crsn::asm::data::Wr; -use std::time::Instant; mod console { use std::{io}; @@ -23,7 +22,7 @@ mod console { use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use crsn::runtime::fault::Fault; - use std::time::{Duration, Instant}; + use std::time::{Instant}; struct ReadCharState { bytes: [u8; 4], diff --git a/examples/loop.csn b/examples/loop.csn new file mode 100644 index 0000000..c2e52d3 --- /dev/null +++ b/examples/loop.csn @@ -0,0 +1,43 @@ +( + ; Unlabeled loop. Can be exited by jumping out + + (loop + (nop) + (nop) + (nop) + ) + + (barrier) + + ; The above is equivalent to: + + (:label) + (nop) + (nop) + (nop) + (j :label) + + (barrier) + + ; The label name can be specified. + ; This adds a start label and ":label-end" at the end of the loop: + + (loop :repeat + (nop) + (nop) + (j :repeat-end) + (nop) + ) + + (barrier) + + ; The above is equivalent to: (labels changed to avoid a compile error) + + (:repeat2) + (nop) + (nop) + (j :repeat2-end) + (nop) + (j :repeat2) + (:repeat2-end) +)