forked from MightyPork/crsn
Merge branch 'consteval'
This commit is contained in:
@@ -196,6 +196,35 @@ Some instructions use bit offsets and widths. Width is directly attached to the
|
|||||||
offsets are attached at the respective arguments after a colon: `(ld16 r0:8 r1:32)` -
|
offsets are attached at the respective arguments after a colon: `(ld16 r0:8 r1:32)` -
|
||||||
load 16 bits, starting at offset 32 of `r1`, into `r0`, starting at offset 8. The rest if the register is not affected.
|
load 16 bits, starting at offset 32 of `r1`, into `r0`, starting at offset 8. The rest if the register is not affected.
|
||||||
|
|
||||||
|
### Compile-time arithmetics
|
||||||
|
|
||||||
|
Quite often you will want to do some calculations at compile time, for example to create a constant whose value depends on another,
|
||||||
|
or to compose a binary value using bit shifts and masking. This can be done in any place that expects an input operand (`Rd`).
|
||||||
|
|
||||||
|
The syntax for this is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
(ld r0 (=add 123 456))
|
||||||
|
```
|
||||||
|
|
||||||
|
The expressions can be nested. The equals sign is not required in the inner levels, and the output operand must be omitted
|
||||||
|
(the compiler inserts a placeholder register there during evaluation):
|
||||||
|
|
||||||
|
```
|
||||||
|
(def WIDTH 1024)
|
||||||
|
(def HALFW_MAX (=sub (div WIDTH 2) 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
Almost any instruction can be evaluated this way, excluding instructions that perform IO, work with the screen, objects etc.
|
||||||
|
Instructions that are not compile-time evaluation safe will produce a compile error.
|
||||||
|
|
||||||
|
There are several limitations to consider:
|
||||||
|
|
||||||
|
- Only immediate values (literals) and constant names may be used.
|
||||||
|
- Registers, if used, will always read as zero and writes have no effect. (This should also produce an error)
|
||||||
|
- Only instructions that take the form `(op Wr ...)` can be used. The `Wr` operand is inserted automatically and must NOT be specified!
|
||||||
|
- Conditionals are not allowed inside expressions: branches, conditional suffixes
|
||||||
|
|
||||||
### Conditional branches
|
### Conditional branches
|
||||||
|
|
||||||
Conditonal branches are written like this:
|
Conditonal branches are written like this:
|
||||||
@@ -296,7 +325,9 @@ Jumping to a label is always safer than a manual skip.
|
|||||||
; However, if the register is a global register, then the alias is valid everywhere.
|
; However, if the register is a global register, then the alias is valid everywhere.
|
||||||
(sym SYM REG)
|
(sym SYM REG)
|
||||||
|
|
||||||
; Define a constant. These are valid in the whole program.
|
; Define a constant. These are valid in the entire program following the definition point, unless
|
||||||
|
; un-defined. Constants are evaluated and assigned at compile time, the program control flow has no
|
||||||
|
; effect.
|
||||||
; Value must be known at compile time.
|
; Value must be known at compile time.
|
||||||
(def CONST VALUE)
|
(def CONST VALUE)
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -4,8 +4,4 @@ set -e
|
|||||||
|
|
||||||
cargo build
|
cargo build
|
||||||
|
|
||||||
for file in examples/*.csn
|
find examples -name '*.csn' -type f -exec target/debug/launcher -P {} \;
|
||||||
do
|
|
||||||
echo "--- $file ---"
|
|
||||||
target/debug/launcher -P "$file"
|
|
||||||
done
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt};
|
||||||
|
use std::fmt::{Formatter, Debug};
|
||||||
|
|
||||||
/// The representation of an s-expression parse error.
|
/// The representation of an s-expression parse error.
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
@@ -9,7 +10,7 @@ pub struct Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Position in the input string
|
/// Position in the input string
|
||||||
#[derive(Debug, PartialEq, Clone, Default)]
|
#[derive(PartialEq, Clone, Default)]
|
||||||
pub struct SourcePosition {
|
pub struct SourcePosition {
|
||||||
/// The line number on which the error occurred.
|
/// The line number on which the error occurred.
|
||||||
pub line: u32,
|
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
|
/// Since errors are the uncommon case, they're boxed. This keeps the size of
|
||||||
/// structs down, which helps performance in the common case.
|
/// structs down, which helps performance in the common case.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -65,4 +65,11 @@ impl OpTrait for Op {
|
|||||||
|
|
||||||
se
|
se
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_compile_time_evaluable(&self) -> bool {
|
||||||
|
match &self.kind {
|
||||||
|
OpKind::BuiltIn(op) => op.is_compile_time_evaluable(),
|
||||||
|
OpKind::Ext(op) => op.is_compile_time_evaluable()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-3
@@ -8,6 +8,8 @@ use crate::asm::parse::{ParserContext, ParserState};
|
|||||||
use crate::module::{CrsnExtension, CrsnUniq};
|
use crate::module::{CrsnExtension, CrsnUniq};
|
||||||
use crate::runtime::program::Program;
|
use crate::runtime::program::Program;
|
||||||
use crate::builtin::BuiltinOps;
|
use crate::builtin::BuiltinOps;
|
||||||
|
use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken};
|
||||||
|
use crate::runtime::frame::REG_COUNT;
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@@ -18,11 +20,12 @@ pub mod patches;
|
|||||||
/// Parse a program from string and assemble a low level instruction sequence from it.
|
/// Parse a program from string and assemble a low level instruction sequence from it.
|
||||||
pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> {
|
pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> {
|
||||||
parsers.insert(0, BuiltinOps::new());
|
parsers.insert(0, BuiltinOps::new());
|
||||||
|
|
||||||
for p in &mut parsers {
|
for p in &mut parsers {
|
||||||
p.init(uniq);
|
p.init(uniq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parsers_arc = Arc::new(parsers);
|
||||||
|
|
||||||
// remove first line if it looks like a shebang
|
// remove first line if it looks like a shebang
|
||||||
let source = if source.starts_with("#!") {
|
let source = if source.starts_with("#!") {
|
||||||
if let Some(nl) = source.find('\n') {
|
if let Some(nl) = source.find('\n') {
|
||||||
@@ -34,18 +37,38 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
|
|||||||
source
|
source
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ti = Arc::new(ThreadInfo {
|
||||||
|
id: ThreadToken(0),
|
||||||
|
uniq: Default::default(),
|
||||||
|
program: Program::new(vec![], parsers_arc.clone()).unwrap(),
|
||||||
|
cycle_time: Default::default(),
|
||||||
|
extensions: parsers_arc.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
let pcx = ParserContext {
|
let pcx = ParserContext {
|
||||||
parsers: &parsers,
|
parsers: &parsers_arc,
|
||||||
state: RefCell::new(ParserState {
|
state: RefCell::new(ParserState {
|
||||||
reg_aliases: Default::default(),
|
reg_aliases: Default::default(),
|
||||||
reg_alias_stack: vec![],
|
reg_alias_stack: vec![],
|
||||||
global_reg_aliases: Default::default(),
|
global_reg_aliases: Default::default(),
|
||||||
constants: Default::default(),
|
constants: Default::default(),
|
||||||
|
|
||||||
|
// This is a fake thread to pass to constant expressions when evaluating them.
|
||||||
|
// This allows to evaluate nearly all instructions at compile time.
|
||||||
|
const_eval: RunState {
|
||||||
|
thread_info: ti.clone(),
|
||||||
|
frame: Default::default(),
|
||||||
|
call_stack: vec![],
|
||||||
|
global_regs: [0; REG_COUNT],
|
||||||
|
ext_data: Default::default()
|
||||||
|
},
|
||||||
|
const_eval_ti: ti,
|
||||||
|
parsing_expr: false
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
|
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
|
||||||
let ops = labels_to_skips(ops)?;
|
let ops = labels_to_skips(ops)?;
|
||||||
|
|
||||||
Ok(Program::new(ops, Arc::new(parsers))?)
|
Ok(Program::new(ops, parsers_arc)?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ use crate::asm::parse::sexp_expect::expect_string_atom;
|
|||||||
use crate::asm::data::literal::Value;
|
use crate::asm::data::literal::Value;
|
||||||
use crate::builtin::defs::BitMask;
|
use crate::builtin::defs::BitMask;
|
||||||
use crate::asm::patches::ErrWithPos;
|
use crate::asm::patches::ErrWithPos;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// Utility for argument parsing
|
/// Utility for argument parsing
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TokenParser<'a> {
|
pub struct TokenParser<'a> {
|
||||||
orig_len: usize,
|
orig_len: usize,
|
||||||
args: Vec<Sexp>,
|
args: Vec<Sexp>,
|
||||||
@@ -18,6 +19,16 @@ pub struct TokenParser<'a> {
|
|||||||
pub pcx: &'a ParserContext<'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> {
|
impl<'a> IntoIterator for TokenParser<'a> {
|
||||||
type Item = Sexp;
|
type Item = Sexp;
|
||||||
type IntoIter = std::vec::IntoIter<Sexp>;
|
type IntoIter = std::vec::IntoIter<Sexp>;
|
||||||
@@ -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.
|
/// Get error if not empty.
|
||||||
/// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string"
|
/// 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> {
|
pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use crate::asm::error::CrsnError;
|
|||||||
use crate::asm::instr::Op;
|
use crate::asm::instr::Op;
|
||||||
use crate::asm::parse::sexp_expect::expect_list;
|
use crate::asm::parse::sexp_expect::expect_list;
|
||||||
use crate::module::CrsnExtension;
|
use crate::module::CrsnExtension;
|
||||||
|
use crate::runtime::run_thread::{ThreadInfo, RunState};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod parse_cond;
|
pub mod parse_cond;
|
||||||
pub mod parse_instr;
|
pub mod parse_instr;
|
||||||
@@ -28,7 +30,7 @@ pub struct ParserContext<'a> {
|
|||||||
pub state: RefCell<ParserState>,
|
pub state: RefCell<ParserState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ParserState {
|
pub struct ParserState {
|
||||||
/// Register aliases valid globally
|
/// Register aliases valid globally
|
||||||
pub global_reg_aliases: HashMap<RegisterAlias, Register>,
|
pub global_reg_aliases: HashMap<RegisterAlias, Register>,
|
||||||
@@ -41,6 +43,14 @@ pub struct ParserState {
|
|||||||
|
|
||||||
/// Global constants
|
/// Global constants
|
||||||
pub constants: HashMap<ConstantName, Value>,
|
pub constants: HashMap<ConstantName, Value>,
|
||||||
|
|
||||||
|
/// 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<ThreadInfo>,
|
||||||
|
|
||||||
|
/// True if we are in an expression parser context
|
||||||
|
pub parsing_expr : bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
|
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ use std::convert::TryFrom;
|
|||||||
|
|
||||||
use sexp::{Atom, Sexp, SourcePosition};
|
use sexp::{Atom, Sexp, SourcePosition};
|
||||||
|
|
||||||
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr};
|
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr, Register};
|
||||||
use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value};
|
use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value};
|
||||||
use crate::asm::error::CrsnError;
|
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::parse::sexp_expect::expect_string_atom;
|
||||||
use crate::asm::patches::ErrWithPos;
|
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 {
|
fn is_valid_identifier(name: &str) -> bool {
|
||||||
// ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
// ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
@@ -72,6 +75,7 @@ pub fn parse_label_str(name: &str, pos: &SourcePosition) -> Result<Label, CrsnEr
|
|||||||
pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
||||||
// trace!("parse data: {:?}", tok);
|
// trace!("parse data: {:?}", tok);
|
||||||
match tok {
|
match tok {
|
||||||
|
// immediates are okay in const eval - no tests needed
|
||||||
Sexp::Atom(Atom::I(val), _pos) => {
|
Sexp::Atom(Atom::I(val), _pos) => {
|
||||||
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
|
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
|
||||||
}
|
}
|
||||||
@@ -87,7 +91,57 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
|
|||||||
Sexp::Atom(Atom::QS(_s), pos) => {
|
Sexp::Atom(Atom::QS(_s), pos) => {
|
||||||
Err(CrsnError::Parse("Quoted string not expected here".into(), pos))
|
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() {
|
||||||
|
// Const eval
|
||||||
|
|
||||||
|
let parsing_expr = pcx.state.borrow().parsing_expr;
|
||||||
|
|
||||||
|
if !s.starts_with('=') && !parsing_expr {
|
||||||
|
return Err(CrsnError::Parse(format!("Invalid syntax, did you mean \"={}\" (a compile-time expression)?", 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);
|
||||||
|
|
||||||
|
if !op.is_compile_time_evaluable() {
|
||||||
|
return Err(CrsnError::Parse(format!("Instruction {} cannot be used in an expression!", op.to_sexp()).into(), pos.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
Err(CrsnError::Parse("List not expected here".into(), pos))
|
||||||
}
|
}
|
||||||
Sexp::Atom(Atom::S(s), pos) => {
|
Sexp::Atom(Atom::S(s), pos) => {
|
||||||
@@ -97,12 +151,16 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse data disp from a string token (a &str is used so the string can be preprocessed)
|
/// Parse data disp from a string token (a &str is used so the string can be preprocessed)
|
||||||
pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
||||||
if s == "_" {
|
if s == "_" {
|
||||||
return Ok(DataDisp::Discard);
|
return Ok(DataDisp::Discard);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(reference) = s.strip_prefix('@') {
|
if let Some(reference) = s.strip_prefix('@') {
|
||||||
|
if pcx.state.borrow().parsing_expr {
|
||||||
|
return Err(CrsnError::Parse("Object handles cannot be used in expressions!".into(), pos.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
/* extension constants (pre-defined handles) */
|
/* extension constants (pre-defined handles) */
|
||||||
for p in pcx.parsers {
|
for p in pcx.parsers {
|
||||||
if let Some(val) = p.get_constant_value(reference) {
|
if let Some(val) = p.get_constant_value(reference) {
|
||||||
@@ -141,7 +199,7 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
|
|||||||
return Ok(DataDisp::Immediate(*val));
|
return Ok(DataDisp::Immediate(*val));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* register aliases */
|
/* register aliases - no need to check for const_eval, the aliases could never be defined */
|
||||||
if let Some(val) = pstate.reg_aliases.get(s) {
|
if let Some(val) = pstate.reg_aliases.get(s) {
|
||||||
return Ok(DataDisp::Register(*val));
|
return Ok(DataDisp::Register(*val));
|
||||||
} else if let Some(val) = pstate.global_reg_aliases.get(s) {
|
} else if let Some(val) = pstate.global_reg_aliases.get(s) {
|
||||||
@@ -168,6 +226,14 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pcx.state.borrow().parsing_expr {
|
||||||
|
if s == "=result" {
|
||||||
|
return Ok(DataDisp::Register(Register::Gen(0)));
|
||||||
|
} else {
|
||||||
|
return Err(CrsnError::Parse("Registers cannot be used in expressions!".into(), pos.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* register */
|
/* register */
|
||||||
let reg = reg::parse_reg(&s, &pos)?;
|
let reg = reg::parse_reg(&s, &pos)?;
|
||||||
|
|
||||||
@@ -184,39 +250,13 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
|
|||||||
|
|
||||||
/// Parse immediate value
|
/// Parse immediate value
|
||||||
pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<(Value, SourcePosition), CrsnError> {
|
pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<(Value, SourcePosition), CrsnError> {
|
||||||
match tok {
|
let pos = tok.pos().clone();
|
||||||
Sexp::Atom(Atom::I(val), pos) => {
|
let parsed = parse_rd(tok, pcx)?;
|
||||||
Ok((unsafe { std::mem::transmute(val) }, pos))
|
|
||||||
}
|
|
||||||
Sexp::Atom(Atom::U(val), pos) => {
|
|
||||||
Ok((val, pos))
|
|
||||||
}
|
|
||||||
Sexp::Atom(Atom::C(val), pos) => {
|
|
||||||
Ok((val as u64, pos))
|
|
||||||
}
|
|
||||||
Sexp::Atom(Atom::QS(_), pos) => {
|
|
||||||
Err(CrsnError::Parse("quoted string not expected here".into(), pos))
|
|
||||||
}
|
|
||||||
Sexp::Atom(Atom::F(val), pos) => {
|
|
||||||
Ok((unsafe { std::mem::transmute(val) }, pos))
|
|
||||||
}
|
|
||||||
Sexp::Atom(Atom::S(s), pos) => {
|
|
||||||
let pstate = pcx.state.borrow();
|
|
||||||
if let Some(val) = pstate.constants.get(&s) {
|
|
||||||
return Ok((*val, pos));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* extension constants */
|
match parsed {
|
||||||
for p in pcx.parsers {
|
Rd(RdData::Immediate(value)) => Ok((value, pos)),
|
||||||
if let Some(val) = p.get_constant_value(&s) {
|
_ => {
|
||||||
return Ok((val, pos));
|
Err(CrsnError::Parse("expected an immediate value".into(), pos))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(CrsnError::Parse(format!("unknown constant: {}", s).into(), pos))
|
|
||||||
}
|
|
||||||
Sexp::List(_, pos) => {
|
|
||||||
Err(CrsnError::Parse("expected a value".into(), pos))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use sexp::{Sexp, SourcePosition, Atom};
|
use sexp::{Sexp, SourcePosition, Atom, atom_s};
|
||||||
|
|
||||||
use crate::asm::error::CrsnError;
|
use crate::asm::error::CrsnError;
|
||||||
use crate::asm::instr::{Flatten, InstrWithBranches};
|
use crate::asm::instr::{Flatten, InstrWithBranches};
|
||||||
@@ -18,9 +18,20 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
|
|||||||
let (tokens, listpos) = expect_list(expr, false)?;
|
let (tokens, listpos) = expect_list(expr, false)?;
|
||||||
|
|
||||||
let mut toki = tokens.into_iter();
|
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 name == "proc" {
|
||||||
|
if parsing_expr {
|
||||||
|
return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone()));
|
||||||
|
}
|
||||||
parsed.push(parse_routine(toki, pos, pcx)?);
|
parsed.push(parse_routine(toki, pos, pcx)?);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -62,8 +73,13 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx);
|
if parsing_expr && !branch_tokens.is_empty() {
|
||||||
// debug!("branch_tokens: {:#?}", branch_tokens);
|
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 branches = {
|
||||||
let mut branches = vec![];
|
let mut branches = vec![];
|
||||||
@@ -77,7 +93,12 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, 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("=result"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(op) = parse_op(name.as_str(), arg_parser, &namepos)? {
|
||||||
parsed.push(Box::new(InstrWithBranches {
|
parsed.push(Box::new(InstrWithBranches {
|
||||||
op,
|
op,
|
||||||
pos: namepos,
|
pos: namepos,
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S
|
|||||||
keyword = &keyword[..pos];
|
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 {
|
for p in arg_tokens.pcx.parsers {
|
||||||
arg_tokens = match p.parse_op(spos, keyword, arg_tokens) {
|
arg_tokens = match p.parse_op(spos, keyword, arg_tokens) {
|
||||||
Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op {
|
Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op {
|
||||||
|
|||||||
@@ -204,6 +204,13 @@ impl OpTrait for BuiltinOp {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_compile_time_evaluable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
BuiltinOp::Load { .. } | BuiltinOp::LoadBits { .. } => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_sexp(&self) -> Sexp {
|
fn to_sexp(&self) -> Sexp {
|
||||||
super::parse::to_sexp(self)
|
super::parse::to_sexp(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,18 @@ pub trait OpTrait: Debug + Send + Sync + 'static {
|
|||||||
|
|
||||||
/// Turn into an S-expression that produces this instruction when parsed
|
/// Turn into an S-expression that produces this instruction when parsed
|
||||||
fn to_sexp(&self) -> Sexp;
|
fn to_sexp(&self) -> Sexp;
|
||||||
|
|
||||||
|
/// Check if an operation is safe to use in compile-time arithmetics - has no side effects
|
||||||
|
/// outside the input and output registers (and flags), has the form (op Wr ...), and does not
|
||||||
|
/// use object handles or extension data.
|
||||||
|
fn is_compile_time_evaluable(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CRSN initializer object.
|
/// CRSN initializer object.
|
||||||
/// Only one should be created for the lifespan of the parser and runtime.
|
/// Only one should be created for the lifespan of the parser and runtime.
|
||||||
#[derive(Default)]
|
#[derive(Default,Debug)]
|
||||||
pub struct CrsnUniq {
|
pub struct CrsnUniq {
|
||||||
object_handle_counter : AtomicU64
|
object_handle_counter : AtomicU64
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::runtime::program::Program;
|
|||||||
use crate::runtime::run_thread::ThreadToken;
|
use crate::runtime::run_thread::ThreadToken;
|
||||||
use crate::module::{CrsnExtension, CrsnUniq};
|
use crate::module::{CrsnExtension, CrsnUniq};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ThreadInfo {
|
pub struct ThreadInfo {
|
||||||
/// Thread ID
|
/// Thread ID
|
||||||
pub id: ThreadToken,
|
pub id: ThreadToken,
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ pub struct RunState {
|
|||||||
pub ext_data: ExtensionDataStore,
|
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 {
|
impl Debug for RunState {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("RunState")
|
f.debug_struct("RunState")
|
||||||
|
|||||||
@@ -757,6 +757,15 @@ impl OpTrait for ArithOp {
|
|||||||
ArithOp::FloatHypAcot { dst, a } => to_sexp_1_or_2("facoth", dst, a),
|
ArithOp::FloatHypAcot { dst, a } => to_sexp_1_or_2("facoth", dst, a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_compile_time_evaluable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ArithOp::Test { .. } | ArithOp::Compare { .. } | ArithOp::RangeTest { .. }
|
||||||
|
| ArithOp::FloatTest { .. } | ArithOp::FloatCompare { .. } | ArithOp::FloatRangeTest { .. } => false,
|
||||||
|
// rng is allowed... makes little sense, but it also has no side effects
|
||||||
|
_ => true, // a bold claim...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
(
|
||||||
|
(def A 10)
|
||||||
|
(def FOO (=add A (sub 10 5)))
|
||||||
|
|
||||||
|
; FOO is 15
|
||||||
|
|
||||||
|
(ld r0 (=add 14 1))
|
||||||
|
(cmp r0 15 (ne? (fault "1")))
|
||||||
|
|
||||||
|
(ld r0 (=add FOO 1))
|
||||||
|
(cmp r0 16 (ne? (fault "2")))
|
||||||
|
|
||||||
|
(ld r0 (=add (sub FOO 2) 3))
|
||||||
|
(cmp r0 16 (ne? (fault "3")))
|
||||||
|
)
|
||||||
@@ -151,6 +151,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
])?;
|
])?;
|
||||||
|
|
||||||
if config.assemble_only {
|
if config.assemble_only {
|
||||||
|
println!("--- {} ---", config.program_file);
|
||||||
for (n, op) in parsed.ops.iter().enumerate() {
|
for (n, op) in parsed.ops.iter().enumerate() {
|
||||||
if config.assemble_debug {
|
if config.assemble_debug {
|
||||||
println!("{:04} : {:?}", n, op);
|
println!("{:04} : {:?}", n, op);
|
||||||
|
|||||||
Reference in New Issue
Block a user