diff --git a/README.md b/README.md index f32235e..2decd74 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ These keywords (among others) are used in conditional branches to specify flag t - `nneg` … NonNegative, - `c` … Carry, - `nc` … NotCarry, -- `valid` … Valid, -- `inval` … Invalid, +- `val`, `valid`, `ok` … Valid, +- `inval`, `nok` … Invalid, - `ov` … Overflow, - `nov` … NotOverflow, @@ -127,19 +127,50 @@ Instructions are written like this: ### Conditional instructions -All instructions can be made conditional by appending `.` to the keyword, i.e. `j.ne` means "jump if not equal". -This is used internally by the assembler when translating conditional branches to executable code. +All instructions can be made conditional by appending `.` to the keyword, i.e. `(j.ne :LABEL)` means "jump if not equal". +These modifiers are mainly used by the assembler when translating conditional branches to executable code. + +Note that the flags can only be tested immediately after the instruction that produced them, or after instructions that do not +affect flags (pseudo-instructions like `def` and `sym`, `nop`, `j`, `fj`, `s`, `call` etc). Instructions that can set flags first +clear all flags to make the result predictable. + +Status flags can be saved to and restored from a register using the `stf` and `ldf` instructions. This can also be used to set +or test flags manually, but the binary format may change ### Instruction arguments -Args are either: -- One of the registers (`reg0`, `arg3` etc) -- Names of constants defined earlier in the program (e.g. `SCREEN_WIDTH`) -- Symbols defined as register aliases (e.g. `x`) -- The "discard register" `_` to discard an output value. That is used when you only care about side effects or status flags. -- Literal values (decimal, hex or binary) -- Label or routine name (e.g. `factorial`, `:again`) -- ...or anything else an installed crsn extension supports +Arguments are always ordered writes-first, reads-last. + +This document uses the following notation for arguments: +- `REG` - one of the registers (`regX`, `argX`, `resX`) +- `SYM` - a symbol defined as a register alias (e.g. `(sym x r0)`) +- `@REG` / `@SYM` - access an object referenced by a handle. Handle is simply a numeric value stored in a register of some kind. +- `_` - a special "register" that discards anything written to it. + The "discard register" is used when you do not need the value and only care about side effects or status flags. +- `CONST` - name of a constant defined earlier in the program (e.g. `(def SCREEN_WIDTH 640)`) +- `NUM` - literal values + - unsigned `123` + - signed `-123` + - float `-45.6789` + - hex `0xabcd`, `#abcd` + - binary `0b0101` + - character `'a'`, `'🐁'`. Supports unicode and C-style escapes. Use `\\` for a literal backslash. +- `"str"` - a double-quoted string (`"ahoj\n"`). Supports unicode and C-style escapes. Use `\\` for a literal backslash. +- `:LABEL` - label name +- `PROC` - routine name +- `PROC/A` - routine name with arity (number of arguments) + +The different ways to specify a value can be grouped as "reads" and "writes": + +- `Rd` - read: `REG`, `SYM`, `@REG`, `@SYM`, `VALUE`, `CONST` +- `Wr` - writes: `REG`, `SYM`, `@REG`, `@SYM`, `_` +- `RW` - intersection of the two sets, capable of reading and writing: `REG`, `SYM`, `@REG`, `@SYM` + +Objects (`@reg`, `@sym`) can be read or written as if they were a register, but only if the referenced object supports it. +Other objects may produce a runtime fault or set the INVALID flag. + +In the instruction lists below, I will use the symbols `Rd` for reads, `Wr` for writes, `RW` for read-writes, and `@Obj` for object handles, +with optional description after a colon, such as: `(add Wr:dst Rd:a Rd:b)`. ### Conditional branches @@ -236,7 +267,7 @@ Jumping to a label is always safer than a manual skip. ; Mark a jump target. (:LABEL) ; Numbered labels -(:#NUMBER) +(:#NUM) ; Mark a far jump target (can be jumped to from another routine). ; This label is preserved in optimized code. @@ -249,46 +280,50 @@ Jumping to a label is always safer than a manual skip. (fj :LABEL) ; Skip backward or forward -(s COUNT) +(s Rd) ; Mark a routine entry point (call target). -(routine NAME) -(routine NAME/ARITY) +(routine PROC) +(routine PROC/A) ; Call a routine with arguments. ; The arguments are passed as argX. Return values are stored in resX registers. -(call ROUTINE ARGUMENTS...) +(call PROC Rd...) ; Exit the current routine with return values -(ret VALUES...) +(ret Rd...) ; Deny jumps, skips and run across this address, producing a run-time fault with a message. (barrier) +(barrier message) (barrier "message text") ; Block barriers are used for routines. They are automatically skipped in execution -; and the whole pair can be jumped *across* +; and the whole pair can be jumped *across*. +; The label can be a numeric or string label, its sole purpose is tying the two together. They must be unique in the program. (barrier-open LABEL) (barrier-close LABEL) ; Generate a run-time fault with a debugger message (fault) +(fault message) (fault "message text") ; Copy value -(ld DST SRC) +(ld Wr Rd) ; Store status flags to a register -(sst DST) +(stf Wr) ; Load status flags from a register -(sld SRC) +(ldf Rd) ; Define a register alias. The alias is only valid in the current routine or in the root of the program. -(sym ALIAS REGISTER) +(sym SYM REG) ; Define a constant. These are valid in the whole program. -(def NAME VALUE) +; Value must be known at compile time. +(def CONST VALUE) ``` ## Arithmetic Module @@ -304,101 +339,152 @@ Many instructions have two forms: (tst SRC) ; Compare two values -(cmp A B) +(cmp Rd Rd) ; Add A+B -(add DST A B) -(add DST B) +(add Wr Rd Rd) +(add RW Rd) ; Subtract A-B -(sub DST A B) -(sub DST B) +(sub Wr Rd Rd) +(sub RW Rd) ; Multiply A*B -(mul DST A B) -(mul DST B) +(mul Wr Rd Rd) +(mul RW Rd) ; Divide A/B -(div DST A B) -(div DST B) +(div Wr Rd Rd:divider) +(div RW Rd:divider) ; Divide and get remainder ; Both DST and REM are output registers -(divr DST REM A B) -(divr DST REM B) +(divr Wr:result Wr:remainder Rd Rd:divider) +(divr RW Wr:remainder Rd:divider) ; Get remainder A%B ; This is equivalent to (divr _ REM A B), ; except status flags are updated by the remainder value -(mod DST A B) -(mod DST B) +(mod Wr Rd Rd-divider) +(mod RW Rd-divider) ; AND A&B -(and DST A B) -(and DST B) +(and Wr Rd Rd) +(and RW Rd) ; OR A|B -(or DST A B) -(or DST B) +(or Wr Rd Rd) +(or RW Rd) ; XOR A&B -(xor DST A B) -(xor DST B) +(xor Wr Rd Rd) +(xor RW Rd) ; CPL ~A (negate all bits) (cpl DST A) (cpl DST) ; Rotate right (wrap around) -(ror DST A B) -(ror DST B) +(ror Wr Rd Rd) +(ror RW Rd) ; Rotate left (wrap around) -(rol DST A B) -(rol DST B) +(rol Wr Rd:value Rd:count) +(rol RW Rd:count) ; Logical shift right (fill with zeros) -(lsr DST A B) -(lsr DST B) +(lsr Wr Rd Rd:count) +(lsr RW Rd:count) ; Logical shift left (fill with zeros) -(lsl DST A B) -(lsl DST B) +(lsl Wr Rd Rd:count) +(lsl RW Rd:count) ; Arithmetic shift right (copy sign bit) -(asr DST A B) -(asr DST B) +(asr Wr Rd Rd:count) +(asr RW Rd:count) ; Arithmetic shift left (this is identical to `lsl`, added for completeness) -(asl DST A B) -(asl DST B) +(asl Wr Rd Rd:count) +(asl RW Rd:count) ; Delete an object by its handle. Objects are used by some extensions. -(del @REG) +(del @Rd) +``` + +## Buffers Module + +This module defines dynamic size integer buffers. + +A buffer needs to be created using one of the init instructions: + +``` +; Create an empty buffer and store its handle into a register +(mkbf Wr) + +; Create a buffer of a certain size, filled with zeros. +; COUNT may be a register or an immediate value +(mkbf Wr Rd:count) + +; Create a buffer and fill it with characters from a string (unicode code points) +(mkbf Wr "string") + +; Create a buffer and fill it with values. +(mkbf Wr (Rd...)) +``` + +Primitive buffer ops (position is always 0-based) + +``` +; Get buffer size +(bfsz Wr @Obj) + +; Read from a position +(bfrd Wr @Obj Rd:index) + +; Write to a position +(bfwr @Obj Rd:index Rd) + +; Insert at a position, shifting the rest to the right +(bfins @Obj Rd:index Rd) + +; Remove item at a position, shifting the rest to the left to fill the empty space +(bfrm Wr @Obj Rd:index) +``` + +Whole buffer manipulation: + ``` +; Resize the buffer. Removes trailing elements or inserts zero to match the new size. +(bfrsz @Obj Rd:len) -## Stacks Module +; Reverse a buffer +(bfrev @Obj) -This module defines data stacks. Stacks can be shared by routines by passing a handle. +; Append a buffer +(bfapp @Obj @Obj:other) +; Prepend a buffer +(bfprep @Obj @Obj:other) ``` -; Create a stack. The register then contains the stack handle. -(stack REG) -; Push to a stack (insert to the end) -(push @REG VALUE) +Stack-style buffer ops: + +``` +; Push (insert at the end) +(bfpush @Obj Rd) -; Pop from a stack (remove from the end) -(pop DST @REG) +; Pop (remove from the end) +(bfpop Wr @Obj) -; Reverse push to a stack (insert to the beginning) -(rpush @REG VALUE) +; Reverse push (insert to the beginning) +(bfrpush @Obj Rd) -; Reverse pop from a stack (remove from the beginning) -(rpop DST @REG) +; Reverse pop (remove from the beginning) +(bfrpop Wr @Obj) ``` -To delete a stack, use the `del` instruction - `(del @REG)` +To delete a buffer, use the `del` instruction - `(del @Obj)` ## Screen module @@ -456,7 +542,7 @@ such as animations. ## Stdio module -- This module currently defines two global handles: `@stdin` and `@stdout`. +- This module currently defines two global handles (resp. constants): `@stdin` and `@stdout`. - You can think of these handles as streams or SFRs (special function registers). To use them, simply load data to or from the handles (e.g. `(ld r0 @stdin)`). - They operate over unicode code points, which are a superset of ASCII. diff --git a/crsn/src/asm/instr/cond.rs b/crsn/src/asm/instr/cond.rs index 7be6b31..b489e43 100644 --- a/crsn/src/asm/instr/cond.rs +++ b/crsn/src/asm/instr/cond.rs @@ -71,12 +71,12 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result { "nneg" | "0+" | ">=0" | "≤0" => Cond::NonNegative, "c" => Cond::Carry, "nc" => Cond::NotCarry, - "em" => Cond::Empty, - "nem" => Cond::NotEmpty, - "f" => Cond::Full, - "nf" => Cond::NotFull, - "valid" => Cond::Valid, - "inval" => Cond::Invalid, + "em" | "empty" => Cond::Empty, + "nem" | "nempty" => Cond::NotEmpty, + "f" | "full" => Cond::Full, + "nf" | "nfull" => Cond::NotFull, + "ok" | "val" | "valid" => Cond::Valid, + "inval" | "invalid" | "nval" | "nok" => Cond::Invalid, "ov" => Cond::Overflow, "nov" => Cond::NotOverflow, _ => { diff --git a/crsn/src/builtin/defs.rs b/crsn/src/builtin/defs.rs index 2769b9c..9c18cf8 100644 --- a/crsn/src/builtin/defs.rs +++ b/crsn/src/builtin/defs.rs @@ -71,7 +71,9 @@ pub enum BuiltinOp { /// The object is released and the handle becomes invalid. Delete(RdObj), /// Copy value - Move { dst: Wr, src: Rd }, + MoveValue { dst: Wr, src: Rd }, + /// Swap two registers + SwapValues { a: Wr, b: Wr }, /// Store runtime status to a register StoreFlags { dst: Wr }, /// Load runtime status from a register diff --git a/crsn/src/builtin/exec.rs b/crsn/src/builtin/exec.rs index a3ad142..bad66b5 100644 --- a/crsn/src/builtin/exec.rs +++ b/crsn/src/builtin/exec.rs @@ -103,12 +103,18 @@ impl OpTrait for BuiltinOp { let pc = state.get_pc(); program.validate_jump(pc, Addr((pc.0 as i64 + res.advance) as u64))?; } - BuiltinOp::Move { dst, src } => { + BuiltinOp::MoveValue { dst, src } => { state.clear_status(); let val = state.read(*src)?; state.update_status(val); state.write(*dst, val)?; } + BuiltinOp::SwapValues { a, b } => { + let aa = state.read(a.as_rd())?; + let bb = state.read(b.as_rd())?; + state.write(*a, bb)?; + state.write(*b, aa)?; + } BuiltinOp::StoreFlags { dst } => { let packed = state.frame.status.store(); state.write(*dst, packed)?; diff --git a/crsn/src/builtin/parse.rs b/crsn/src/builtin/parse.rs index 445f2f2..101d130 100644 --- a/crsn/src/builtin/parse.rs +++ b/crsn/src/builtin/parse.rs @@ -177,12 +177,19 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok } "ld" => { - BuiltinOp::Move { + BuiltinOp::MoveValue { dst: args.next_wr()?, src: args.next_rd()?, } } + "swap" => { + BuiltinOp::SwapValues { + a: args.next_wr()?, + b: args.next_wr()?, + } + } + "stf" => { BuiltinOp::StoreFlags { dst: args.next_wr()?, @@ -295,7 +302,8 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp { } } BuiltinOp::Delete(obj) => sexp::list(&[A("del"), A(obj)]), - BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]), + BuiltinOp::MoveValue { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]), + BuiltinOp::SwapValues { a, b } => sexp::list(&[A("swp"), A(a), A(b)]), BuiltinOp::StoreFlags { dst } => sexp::list(&[A("stf"), A(dst)]), BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]) } @@ -364,6 +372,7 @@ mod test { ("(fault)", "(fault)"), ("(fault kur*a)", "(fault kur*a)"), ("(fault \"do pr*ele\")", "(fault \"do pr*ele\")"), + ("(swap r0 r1)", "(swap r0 r1)"), ("(ld r0 r0)", "(ld r0 r0)"), ("(ld r0 156)", "(ld r0 156)"), ("(ld _ -32767)", "(ld _ -32767)"), diff --git a/crsn_buf/src/parse.rs b/crsn_buf/src/parse.rs index 336be9c..0eb70c2 100644 --- a/crsn_buf/src/parse.rs +++ b/crsn_buf/src/parse.rs @@ -12,40 +12,32 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar Ok(ParseRes::ext(match keyword { "mkbf" => { let dst = args.next_wr()?; - let len = if args.have_more() { - args.next_rd()? - } else { - Rd::immediate(0) - }; - BufOps::New { dst, value: BufValue::Zeros(len) } - } - - "mkbfv" => { - let dst = args.next_wr()?; - - let next = args.next_or_err()?; - match next { - Sexp::Atom(Atom::QS(s), _) => { - BufOps::New { - dst, - value: BufValue::Chars(s), - } + let value = match args.next() { + None => { + BufValue::Zeros(Rd::immediate(0)) } - Sexp::List(list, _) => { + Some(tok @ Sexp::Atom(Atom::S(_), _)) | + Some(tok @ Sexp::Atom(Atom::I(_), _)) | + Some(tok @ Sexp::Atom(Atom::U(_), _)) => { + BufValue::Zeros(parse_rd(tok, args.pcx)?) + } + Some(Sexp::Atom(Atom::QS(s), _)) => { + BufValue::Chars(s) + } + Some(Sexp::List(list, _)) => { let mut vals = vec![]; for v in list { vals.push(parse_rd(v, args.pcx)?); } - BufOps::New { - dst, - value: BufValue::Values(vals), - } + BufValue::Values(vals) } - other => { + Some(other) => { return Err(CrsnError::Parse("Expected quoted string or a tuple of values".into(), other.pos().clone())); } - } + }; + + BufOps::New { dst, value } } "bfrd" => { diff --git a/examples/number_array.csn b/examples/number_array.csn index 17e594b..146e6df 100644 --- a/examples/number_array.csn +++ b/examples/number_array.csn @@ -1,7 +1,7 @@ ( (ld r0 65) (sym buf r7) - (mkbfv buf (r0 66 67 68 '\n')) + (mkbf buf (r0 66 67 68 '\n')) (bfrpop @stdout @buf) (s.nem -1) diff --git a/examples/stdio.csn b/examples/stdio.csn index 10808eb..5a52cbe 100644 --- a/examples/stdio.csn +++ b/examples/stdio.csn @@ -4,9 +4,9 @@ (def T_HELLO 0) (def T_UNK 1) (mkbf TXT) - (mkbfv r0 "*** Type ascii to uppercase. Press q to quit. ***\n") + (mkbf r0 "*** Type ascii to uppercase. Press q to quit. ***\n") (bfins @TXT T_HELLO r0) - (mkbfv r0 "🐈") + (mkbf r0 "🐈") (bfins @TXT T_UNK r0) ; Print string from the table