Compare commits

...

11 Commits

  1. 109
      README.md
  2. 6
      compile_examples.sh
  3. 18
      crsn/crsn-sexp/src/error.rs
  4. 7
      crsn/src/asm/instr/op.rs
  5. 29
      crsn/src/asm/mod.rs
  6. 20
      crsn/src/asm/parse/arg_parser.rs
  7. 12
      crsn/src/asm/parse/mod.rs
  8. 114
      crsn/src/asm/parse/parse_data.rs
  9. 31
      crsn/src/asm/parse/parse_instr.rs
  10. 4
      crsn/src/asm/parse/parse_op.rs
  11. 7
      crsn/src/builtin/exec.rs
  12. 9
      crsn/src/module/mod.rs
  13. 1
      crsn/src/runtime/run_thread/info.rs
  14. 10
      crsn/src/runtime/run_thread/state.rs
  15. 11
      crsn_arith/src/exec.rs
  16. 15
      examples/expr.csn
  17. 6
      examples/flipcoin.csn
  18. 23
      examples/life.csn
  19. 87
      examples/mandelbrot/mandelbrot-ascii.csn
  20. 95
      examples/mandelbrot/mandelbrot-full.csn
  21. 305
      examples/mandelbrot/mandelbrot-interactive.csn
  22. 97
      examples/mandelbrot/mandelbrot-lowres.csn
  23. 48
      examples/mandelbrot/output-ascii.txt
  24. BIN
      examples/mandelbrot/output-full.png
  25. 1
      launcher/src/main.rs

@ -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)` -
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
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.
(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.
(def CONST VALUE)
@ -557,92 +588,96 @@ Here's an abridged summary of the floating point submodule:
(most of these support the shorthand version too - `RW` in place of `Wr Rd`)
```
; IntToFloat
; Convert int to float
(itf Wr Rd)
; FloatToInt (round)
; Convert float to int (round)
(fti Wr Rd)
; FloatToInt (ceil)
; Convert float to int (ceil)
(ftic Wr Rd)
; FloatToInt (floor)
; Convert float to int (floor)
(ftif Wr Rd)
; FloatTest - nan->invalid, infinities->overflow, positive, negative, zero
; Test properties of a float
; Set flags:
; NaN -> invalid
; Infinities -> overflow
; Positive, negative, zero
(ftst Rd)
; FloatCompare
; Float compare
(fcmp Rd Rd)
; FloatRangeTest
; Float range test
(fcmpr Rd Rd'min Rd'max)
; FloatRng
; FloatRng. Unlike rng, frng is exclusive in the higher bound
(frng Wr Rd'min Rd'max)
; --- Basic float arith ---
; FloatAdd
; Float add
(fadd Wr Rd Rd)
; FloatSub
; Float subtract
(fsub Wr Rd Rd)
; FloatMul
; Float multiply
(fmul Wr Rd Rd)
; FloatPow
; Float power
(fpow Wr Rd Rd'pow)
; FloatRoot
; Float root
(froot Wr Rd Rd'root)
; FloatHyp
; Float hyp - sqrt(a*a + b*b)
(fhyp Wr Rd Rd)
; FloatDiv
; Float divide
(fdiv Wr Wr'rem Rd'a Rd'div)
; FloatMod
; Float modulo
(fmod Wr Rd'a Rd'div)
; FloatAbs
; Float abs value
(fabs Wr Rd)
; FloatSgn
; Float signum (returns -1 or 1)
(fsgn Wr Rd)
; --- Basic trig ---
; FloatSin
; Float sine
(fsin Wr Rd)
; FloatAsin
; Float arcsine
(fasin Wr Rd)
; FloatCos
; Float cosine
(fcos Wr Rd)
; FloatAcos
; Float arccosine
(facos Wr Rd)
; FloatTan
; Float tangent
(ftan Wr Rd)
; FloatAtan
; Float arctangent
(fatan Wr Rd)
; FloatAtan2
; Float 2-argumenmt arctangent
(fatan2 Wr Rd'y Rd'x)
; FloatCot
; Float cotangent
(fcot Wr Rd)
; FloatAcot
; Float arccotangent
(facot Wr Rd)
; --- Hyperbolic trig ---
; FloatHypSin
; Float hyperbolic sine
(fsinh Wr Rd)
; FloatHypAsin
; Float hyperbolic arcsine
(fasinh Wr Rd)
; FloatHypCos
; Float hyperbolic cosine
(fcosh Wr Rd)
; FloatHypAcos
; Float hyperbolic arccosine
(facosh Wr Rd)
; FloatHypTan
; Float hyperbolic tangent
(ftanh Wr Rd)
; FloatHypAtan
; Float hyperbolic arctangent
(fatanh Wr Rd)
; FloatHypCot
; Float hyperbolic cotangent
(fcoth Wr Rd)
; FloatHypAcot
; Float hyperbolic arccotangent
(facoth Wr Rd)
```
Wow, thats a lot. I didn't test many of these yet. There may be bugs.
There are also some pre-defined constants: `PI`, `PI_2`, `TAU`, `E`
There are also some pre-defined constants: `PI`, `PI_2` (½×`PI`), `TAU` (2×`PI`), `E`
## Buffers Module

@ -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 {} \;

@ -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.
///

@ -65,4 +65,11 @@ impl OpTrait for Op {
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()
}
}
}

@ -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<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, 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<Box<dyn CrsnExt
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 {
parsers: &parsers,
parsers: &parsers_arc,
state: RefCell::new(ParserState {
reg_aliases: Default::default(),
reg_alias_stack: vec![],
global_reg_aliases: 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 = 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::builtin::defs::BitMask;
use crate::asm::patches::ErrWithPos;
use std::fmt::{Debug, Formatter};
use std::fmt;
/// Utility for argument parsing
#[derive(Debug)]
pub struct TokenParser<'a> {
orig_len: usize,
args: Vec<Sexp>,
@ -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<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.
/// 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> {

@ -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<ParserState>,
}
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct ParserState {
/// Register aliases valid globally
pub global_reg_aliases: HashMap<RegisterAlias, Register>,
@ -41,6 +43,14 @@ pub struct ParserState {
/// Global constants
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> {

@ -3,12 +3,15 @@ use std::convert::TryFrom;
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::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 "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~"
@ -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> {
// trace!("parse data: {:?}", tok);
match tok {
// immediates are okay in const eval - no tests needed
Sexp::Atom(Atom::I(val), _pos) => {
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) => {
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))
}
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)
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 == "_" {
return Ok(DataDisp::Discard);
}
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) */
for p in pcx.parsers {
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));
}
/* 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) {
return Ok(DataDisp::Register(*val));
} 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 */
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
pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<(Value, SourcePosition), CrsnError> {
match tok {
Sexp::Atom(Atom::I(val), pos) => {
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 */
for p in pcx.parsers {
if let Some(val) = p.get_constant_value(&s) {
return Ok((val, pos));
}
}
let pos = tok.pos().clone();
let parsed = parse_rd(tok, pcx)?;
Err(CrsnError::Parse(format!("unknown constant: {}", s).into(), pos))
}
Sexp::List(_, pos) => {
Err(CrsnError::Parse("expected a value".into(), pos))
match parsed {
Rd(RdData::Immediate(value)) => Ok((value, pos)),
_ => {
Err(CrsnError::Parse("expected an immediate 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::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 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<Item=Sexp>, pos: &SourcePosition,
})
.collect::<Vec<_>>();
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<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 {
op,
pos: namepos,

@ -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 {

@ -204,6 +204,13 @@ impl OpTrait for BuiltinOp {
Ok(res)
}
fn is_compile_time_evaluable(&self) -> bool {
match self {
BuiltinOp::Load { .. } | BuiltinOp::LoadBits { .. } => true,
_ => false
}
}
fn to_sexp(&self) -> Sexp {
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
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.
/// 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
}

@ -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,

@ -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")

@ -84,7 +84,7 @@ impl OpTrait for ArithOp {
offset + if max == u64::MAX {
rand::thread_rng().gen()
} else {
rand::thread_rng().gen_range(0, max)
rand::thread_rng().gen_range(0, max + 1)
}
};
state.write(dst, val)?;
@ -757,6 +757,15 @@ impl OpTrait for ArithOp {
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 {

@ -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")))
)

@ -0,0 +1,6 @@
(
(ld r0 5)
(:next)
(rng r1 0 1 (z? (lds @cout "(1€)\n")) (else? (lds @cout "(::)\n")))
(dec r0 (nz? (j :next)))
)

@ -10,23 +10,22 @@
(def GENERATION_MS 200)
; Real pixel size
(sc-init 400 400)
; Upscaling factor (bug pixels)
(sc-opt SCREEN_UPSCALE 10)
; Number of big pixels
; Number of pixels
(def W 40)
(def H 40)
(def UPSCALE 10)
; --- end of config ---
; !!! If you change size, also update the following constants.
; Compile-time math is not implemented yet.
; Real pixel size
(sc-init (=mul UPSCALE W) (=mul UPSCALE H))
; Upscaling factor (bug pixels)
(sc-opt SCREEN_UPSCALE UPSCALE)
(def XMAX 39) ; W-1
(def YMAX 39) ; H-1
(def NCELLS 1600) ; W*H
(def XMAX (=sub W 1))
(def YMAX (=sub H 1))
(def NCELLS (=mul W H))
; --- end of config ---
(sc-opt SCREEN_AUTO_BLIT 0)
(sc-erase 0) ; all black

@ -0,0 +1,87 @@
(
(def W 128)
(def H 48)
(def MAXITER 50)
(sym asciigr r10)
(mkbf asciigr (
'.' ',' ':' '+' '=' '%' '@' '#'
'$' '&' '*' '|' '-' ':' '.' ' '
))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd @cout @asciigr r0)
)
(else?
(ld @cout ' ')
)
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(ld @cout '\n')
(cmp y H)
(j.ne :row)
(halt)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,95 @@
(
; High resolution mandelbrot
(def W 800)
(def H 600)
(def MAXITER 50) ; Increase for more detail but slower render
(sc-init W H)
(sc-opt SCREEN_AUTOBLIT 0)
(sym gradient r9)
(sym asciigr r10)
(mkbf gradient (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @gradient r0)
)
(else? (ld r0 0))
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(sc-blit) ; Render after every row
(cmp y H)
(j.ne :row)
;(sc-blit)
(:slp)
(sc-poll)
(mslp 10)
(j :slp)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,305 @@
(
(def W 1024)
(def H 768)
(def DEF_MAXITER 50)
; ---
(sc-init W H)
(sc-opt SCREEN_AUTOBLIT 0)
(sym maxiter g0)
(ld maxiter DEF_MAXITER)
(lds @cout "Interactive Mandelbrot\n")
(lds @cout "----------------------\n")
(lds @cout "Navigate using WASD, zoom: Q+/E-, detail: R+/F-; fast move/zoom: LShift, force redraw: G\n")
(lds @cout "To get a high-res image, stop interacting for while\n")
(sym mb_x r7)
(sym mb_y r8)
(sym mb_s r9)
(sym mb_row r10)
; index into the skip/size table
(sym mb_skip_index r11)
; Interactive movement speed
(def ZOOM_STEP 0.1)
(def PAN_STEP 0.3)
; size of big pixels
(def COL_SKIP 8)
(def ROW_SKIP 8)
; incrementally renders 1x1, 2x2, 4x4, ...
(def MB_SKIP_TABLE_SIZE 80)
(def MB_SKIP_REPEAT_INDEX 16)
(sym mb_skip_table_x r12)
(sym mb_skip_table_y r13)
(sym mb_size_table r14)
(mkbf mb_skip_table_x (
0 4 0 4 2 6 0 2 4 6 2 6 0 2 4 6
; 8x8 grid
1 3 5 7 1 3 5 7 1 3 5 7 1 3 5 7
0 2 4 6 0 2 4 6 0 2 4 6 0 2 4 6
1 3 5 7 1 3 5 7 1 3 5 7 1 3 5 7
; do top left pixels again
0 2 4 6 0 2 4 6 0 2 4 6 0 2 4 6
))
(mkbf mb_skip_table_y (
0 0 4 4 0 0 2 2 2 2 4 4 6 6 6 6
; 8x8 grid
0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6
1 1 1 1 3 3 3 3 5 5 5 5 7 7 7 7
1 1 1 1 3 3 3 3 5 5 5 5 7 7 7 7
; do top left pixels again
0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6
))
(mkbf mb_size_table (
8 4 4 4 2 2 2 2 2 2 2 2 2 2 2 2
; 8x8 grid
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
; do top left pixels again
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
))
(ld mb_x 0.0)
(ld mb_y 0.0)
(ld mb_s 1.0)
(sym did_change r15)
(sym is_first_render r6)
(ld is_first_render 1)
(sym GRADIENT g1)
(mkbf GRADIENT (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
; render row
; size - pixel size
; col - column offset
; px - offset x
; py - offset y
; scale - scaling
; row - row position
(proc render_row size col px py scale row
(sym x r7)
(sym y r8)
(add y row)
(ld x col)
(:col)
(call pixel x y px py scale)
(sub r0 maxiter 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @GRADIENT r0)
)
(else? (ld r0 0))
)
(sc-rect x y size size r0)
(add x COL_SKIP)
(cmp x W)
(j.lt :col)
(sc-blit)
(ret)
)
(:loop)
(sc-poll)
; did_change -- did the user interact during this frame?
(ld did_change 0)
(sym pstep r2)
(sym zstep r3)
(ld pstep PAN_STEP)
(ld zstep ZOOM_STEP)
(sc-key _ KEY_ShiftL (nz?
; turbo mode
(fmul pstep 5.0)
(fmul zstep 5.0)
))
(fadd zstep 1.0)
; scaled movement speed
(fdiv pstep mb_s)
(tst is_first_render (z?
; A <
(sc-key _ KEY_A (nz?
(fsub mb_x pstep)
(ld did_change 1)
(lds @cout "Pan left\n")
))
; W ^
(sc-key _ KEY_W (nz?
(fsub mb_y pstep)
(ld did_change 1)
(lds @cout "Pan up\n")
))
; S v
(sc-key _ KEY_S (nz?
(fadd mb_y pstep)
(ld did_change 1)
(lds @cout "Pan down\n")
))
; D >
(sc-key _ KEY_D (nz?
(fadd mb_x pstep)
(ld did_change 1)
(lds @cout "Pan right\n")
))
; Q +
(sc-key r0 KEY_Q (nz?
(fmul mb_s zstep)
(ld did_change 1)
(lds @cout "Zoom in\n")
))
; E -
(sc-key r0 KEY_E (nz?
(fdiv mb_s zstep)
(ld did_change 1)
(lds @cout "Zoom out\n")
))
; R iter+
(sc-key r0 KEY_R (nz?
(add maxiter 50)
(lds @cout "ITER=") (call printnum maxiter) (ld @cout '\n')
(mslp 200) ; Avoid unexpected rapid change
))
; F iter-
(sc-key r0 KEY_F (nz?
(cmp maxiter 50)
(sub.gt maxiter 50)
(lds @cout "ITER=") (call printnum maxiter) (ld @cout '\n')
(mslp 200) ; Avoid unexpected rapid change
))
; G force redraw
(sc-key r0 KEY_G (nz?
(ld did_change 1)
))
(tst did_change (nz?
; something changed...
; mark this as a first render and reset col_skip and row_skip
(ld is_first_render 1)
(ld mb_skip_index 0)
; Start from top
(ld mb_row 0)
))
))
(unsym pstep)
(unsym zstep)
(sym mb_col_skip r2)
(sym mb_row_skip r3)
(bfrd mb_col_skip @mb_skip_table_x mb_skip_index)
(bfrd mb_row_skip @mb_skip_table_y mb_skip_index)
(bfrd r1 @mb_size_table mb_skip_index)
; render row mb_row + row skip
(ld r0 mb_row)
(add r0 mb_row_skip)
(call render_row r1 mb_col_skip mb_x mb_y mb_s r0)
(unsym mb_col_skip)
(unsym mb_row_skip)
(cmp mb_row H
(lt?
; if mb_row < H
(add mb_row ROW_SKIP)
)
(else?
; otherwise, this frame is done
(ld mb_row 0)
(add mb_skip_index 1)
(ld is_first_render 0)
(cmp mb_skip_index MB_SKIP_TABLE_SIZE (ge?
; if skip index is out of bounds, go back to repeating area
(ld mb_skip_index MB_SKIP_REPEAT_INDEX)
))
)
)
(j :loop)
(proc pixel xi yi off_x off_y scale
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(fdiv x0 scale)
(fdiv y0 scale)
(fadd x0 off_x)
(fadd y0 off_y)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter maxiter)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -0,0 +1,97 @@
(
(def PX_W 1024)
(def PX_H 768)
(def UPSCALE 8)
(def MAXITER 50)
(sc-init PX_W PX_H)
(sc-opt SCREEN_AUTOBLIT 0)
(sc-opt SCREEN_UPSCALE UPSCALE)
(def W (=div PX_W UPSCALE))
(def H (=div PX_H UPSCALE))
(sym gradient r9)
(sym asciigr r10)
(mkbf gradient (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @gradient r0)
)
(else? (ld r0 0))
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(sc-blit) ; Render after every row
(cmp y H)
(j.ne :row)
;(sc-blit)
(:slp)
(sc-poll)
(mslp 10)
(j :slp)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,48 @@
::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++==============+++++++++++++++++++=:::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++========%%%%%=@%%%%%++++++++========::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++=========%%@#.&$#%%%%%%%%=============:::::::::::::::::::
:::::::::::::::::::::::::::::::::::::===+++++++++++++++++++++++++++++==========%%%%##.-##@@@%%%===============::::::::::::::::::
::::::::::::::::::::::::::::::::::::=======+++++++++++++++++++++++++==========%%%%##$&::& ||#@%%===============:::::::::::::::::
:::::::::::::::::::::::::::::::::::===========+++++++++++++++++++%%%=========@@@@@##$**:.|&$@@@@================::::::::::::::::
::::::::::::::::::::::::::::::::::=================++++++++++%%%%%%%%%%%%%@@@@@@@$$&*& ::=&$##@@@%================::::::::::::::
::::::::::::::::::::::::::::::::=========================%%%%%%%%%%%%%%%%%@@@@##$:#$.#& :-:$$@@@%%%%%%%%%=========:::::::::::::
:::::::::::::::::::::::::::::::==========================%%%%%%%%%%%%%%%%@@@@@##$*:= :&###%%%%%%%%%%=========+:::::::::::
:::::::::::::::::::::::::::::============================%%%%%%%%%@%%%%####@$$$&&|. *.&$####@%@@@@%%=========++::::::::::
:::::::::::::::::::::::::::==============================%%%%%%%%@@##$$$:*$$&&**|-:$ -**&&$##@@@#|#%========+++++::::::::
:::::::::::::::::::::::::+==============================%%%%%%%%%@@@&|:&,:|*|# , | *..#.*$&$&&: #@@=====++++++++::::::
:::::::::::::::::::::::+++============================@@%%%%%%%%@@@$&|:@ =, * + --,%% :,#%%%%%=++++++++++::::
:::::::::::::::::::::++++++==============%%%========@@@@@@@%%%#####$$*. @-*#%%%%%%++++++++++++::
:::::::::::::::::::++++++++=============%%%%%%%%%@@@@@@@@@@@@###$$&* - : :,*$@@@%%%%++++++++++++++
::::::::::::::::++++++++++==============%%%@####@@@@@#@@@@@@####$*=: -&###@%%%%++++++++++++++
::::::::::::::++++++++++++=============%%%%@$||&#$$##$$##$$$$##&&&- %-*&:@%%%%++++++++++++++
:::::::::::++++++++++++++=============%%%%##&.:**&&$&::**&&$$$&&*% =,#%%%==+++++++++++++
::::::::++++++++++++++++%%%%========@@@@@@##&&|. ,.::.+ .,.****|-&* *#@@@====++++++++++++
::::::+++++++++++++++++%%%%%%%%%%%@@@@@@@@$$$*|.& @ =::-:. |$@@======+++++++++++
:::++++++++++++++++++%%%%%%%%%%%%%@@@@####$$*--.$ , , %-#@@========+++++++++
:++++++++++++++++++==%%%%%%%%%%%%@@@@@#*-&**--= %# :$#%%%=========+++++++
+++++++++++++++++====%%%%%@@@@#####$$$$&|:. .- , $$@@%%%%==========+++++
++++++++++++++======@@@##@$*$$##$#&$$-*|.+ : #$#@@@%%%%%==========+++
+++++++++++++====%@ - |*$$#@@@%%%%%=========+++
++++++++++++++======@@@##@$*$$##$#&$$-*|.+ : #$#@@@%%%%%==========+++
+++++++++++++++++====%%%%%@@@@#####$$$$&|:. .- , $$@@%%%%==========+++++
:++++++++++++++++++==%%%%%%%%%%%%@@@@@#*-&**--= %# :$#%%%=========+++++++
:::++++++++++++++++++%%%%%%%%%%%%%@@@@####$$*--.$ , , %-#@@========+++++++++
::::::+++++++++++++++++%%%%%%%%%%%@@@@@@@@$$$*|.& @ =::-:. |$@@======+++++++++++
::::::::++++++++++++++++%%%%========@@@@@@##&&|. ,.::.+ .,.****|-&* *#@@@====++++++++++++
:::::::::::++++++++++++++=============%%%%##&.:**&&$&::**&&$$$&&*% =,#%%%==+++++++++++++
::::::::::::::++++++++++++=============%%%%@$||&#$$##$$##$$$$##&&&- %-*&:@%%%%++++++++++++++
::::::::::::::::++++++++++==============%%%@####@@@@@#@@@@@@####$*=: -&###@%%%%++++++++++++++
:::::::::::::::::::++++++++=============%%%%%%%%%@@@@@@@@@@@@###$$&* - : :,*$@@@%%%%++++++++++++++
:::::::::::::::::::::++++++==============%%%========@@@@@@@%%%#####$$*. @-*#%%%%%%++++++++++++::
:::::::::::::::::::::::+++============================@@%%%%%%%%@@@$&|:@ =, * + --,%% :,#%%%%%=++++++++++::::
:::::::::::::::::::::::::+==============================%%%%%%%%%@@@&|:&,:|*|# , | *..#.*$&$&&: #@@=====++++++++::::::
:::::::::::::::::::::::::::==============================%%%%%%%%@@##$$$:*$$&&**|-:$ -**&&$##@@@#|#%========+++++::::::::
:::::::::::::::::::::::::::::============================%%%%%%%%%@%%%%####@$$$&&|. *.&$####@%@@@@%%=========++::::::::::
:::::::::::::::::::::::::::::::==========================%%%%%%%%%%%%%%%%@@@@@##$*:= :&###%%%%%%%%%%=========+:::::::::::
::::::::::::::::::::::::::::::::=========================%%%%%%%%%%%%%%%%%@@@@##$:#$.#& :-:$$@@@%%%%%%%%%=========:::::::::::::
::::::::::::::::::::::::::::::::::=================++++++++++%%%%%%%%%%%%%@@@@@@@$$&*& ::=&$##@@@%================::::::::::::::
:::::::::::::::::::::::::::::::::::===========+++++++++++++++++++%%%=========@@@@@##$**:.|&$@@@@================::::::::::::::::
::::::::::::::::::::::::::::::::::::=======+++++++++++++++++++++++++==========%%%%##$&::& ||#@%%===============:::::::::::::::::
:::::::::::::::::::::::::::::::::::::===+++++++++++++++++++++++++++++==========%%%%##.-##@@@%%%===============::::::::::::::::::
::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++=========%%@#.&$#%%%%%%%%=============:::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++========%%%%%=@%%%%%++++++++========::::::::::::::::::::

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@ -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);

Loading…
Cancel
Save