add "swap" instr, document buffer ops

pull/21/head
Ondřej Hruška 4 years ago
parent aae9db0598
commit d5821e3552
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 226
      README.md
  2. 12
      crsn/src/asm/instr/cond.rs
  3. 4
      crsn/src/builtin/defs.rs
  4. 8
      crsn/src/builtin/exec.rs
  5. 13
      crsn/src/builtin/parse.rs
  6. 42
      crsn_buf/src/parse.rs
  7. 2
      examples/number_array.csn
  8. 4
      examples/stdio.csn

@ -76,8 +76,8 @@ These keywords (among others) are used in conditional branches to specify flag t
- `nneg` … NonNegative, - `nneg` … NonNegative,
- `c` … Carry, - `c` … Carry,
- `nc` … NotCarry, - `nc` … NotCarry,
- `valid` … Valid, - `val`, `valid`, `ok` … Valid,
- `inval` … Invalid, - `inval`, `nok` … Invalid,
- `ov` … Overflow, - `ov` … Overflow,
- `nov` … NotOverflow, - `nov` … NotOverflow,
@ -127,19 +127,50 @@ Instructions are written like this:
### Conditional instructions ### Conditional instructions
All instructions can be made conditional by appending `.<cond>` to the keyword, i.e. `j.ne` means "jump if not equal". All instructions can be made conditional by appending `.<cond>` to the keyword, i.e. `(j.ne :LABEL)` means "jump if not equal".
This is used internally by the assembler when translating conditional branches to executable code. 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 ### Instruction arguments
Args are either: Arguments are always ordered writes-first, reads-last.
- One of the registers (`reg0`, `arg3` etc)
- Names of constants defined earlier in the program (e.g. `SCREEN_WIDTH`) This document uses the following notation for arguments:
- Symbols defined as register aliases (e.g. `x`) - `REG` - one of the registers (`regX`, `argX`, `resX`)
- The "discard register" `_` to discard an output value. That is used when you only care about side effects or status flags. - `SYM` - a symbol defined as a register alias (e.g. `(sym x r0)`)
- Literal values (decimal, hex or binary) - `@REG` / `@SYM` - access an object referenced by a handle. Handle is simply a numeric value stored in a register of some kind.
- Label or routine name (e.g. `factorial`, `:again`) - `_` - a special "register" that discards anything written to it.
- ...or anything else an installed crsn extension supports 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 ### Conditional branches
@ -236,7 +267,7 @@ Jumping to a label is always safer than a manual skip.
; Mark a jump target. ; Mark a jump target.
(:LABEL) (:LABEL)
; Numbered labels ; Numbered labels
(:#NUMBER) (:#NUM)
; Mark a far jump target (can be jumped to from another routine). ; Mark a far jump target (can be jumped to from another routine).
; This label is preserved in optimized code. ; This label is preserved in optimized code.
@ -249,46 +280,50 @@ Jumping to a label is always safer than a manual skip.
(fj :LABEL) (fj :LABEL)
; Skip backward or forward ; Skip backward or forward
(s COUNT) (s Rd)
; Mark a routine entry point (call target). ; Mark a routine entry point (call target).
(routine NAME) (routine PROC)
(routine NAME/ARITY) (routine PROC/A)
; Call a routine with arguments. ; Call a routine with arguments.
; The arguments are passed as argX. Return values are stored in resX registers. ; 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 ; 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. ; Deny jumps, skips and run across this address, producing a run-time fault with a message.
(barrier) (barrier)
(barrier message)
(barrier "message text") (barrier "message text")
; Block barriers are used for routines. They are automatically skipped in execution ; 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-open LABEL)
(barrier-close LABEL) (barrier-close LABEL)
; Generate a run-time fault with a debugger message ; Generate a run-time fault with a debugger message
(fault) (fault)
(fault message)
(fault "message text") (fault "message text")
; Copy value ; Copy value
(ld DST SRC) (ld Wr Rd)
; Store status flags to a register ; Store status flags to a register
(sst DST) (stf Wr)
; Load status flags from a register ; 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. ; 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. ; 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 ## Arithmetic Module
@ -304,101 +339,152 @@ Many instructions have two forms:
(tst SRC) (tst SRC)
; Compare two values ; Compare two values
(cmp A B) (cmp Rd Rd)
; Add A+B ; Add A+B
(add DST A B) (add Wr Rd Rd)
(add DST B) (add RW Rd)
; Subtract A-B ; Subtract A-B
(sub DST A B) (sub Wr Rd Rd)
(sub DST B) (sub RW Rd)
; Multiply A*B ; Multiply A*B
(mul DST A B) (mul Wr Rd Rd)
(mul DST B) (mul RW Rd)
; Divide A/B ; Divide A/B
(div DST A B) (div Wr Rd Rd:divider)
(div DST B) (div RW Rd:divider)
; Divide and get remainder ; Divide and get remainder
; Both DST and REM are output registers ; Both DST and REM are output registers
(divr DST REM A B) (divr Wr:result Wr:remainder Rd Rd:divider)
(divr DST REM B) (divr RW Wr:remainder Rd:divider)
; Get remainder A%B ; Get remainder A%B
; This is equivalent to (divr _ REM A B), ; This is equivalent to (divr _ REM A B),
; except status flags are updated by the remainder value ; except status flags are updated by the remainder value
(mod DST A B) (mod Wr Rd Rd-divider)
(mod DST B) (mod RW Rd-divider)
; AND A&B ; AND A&B
(and DST A B) (and Wr Rd Rd)
(and DST B) (and RW Rd)
; OR A|B ; OR A|B
(or DST A B) (or Wr Rd Rd)
(or DST B) (or RW Rd)
; XOR A&B ; XOR A&B
(xor DST A B) (xor Wr Rd Rd)
(xor DST B) (xor RW Rd)
; CPL ~A (negate all bits) ; CPL ~A (negate all bits)
(cpl DST A) (cpl DST A)
(cpl DST) (cpl DST)
; Rotate right (wrap around) ; Rotate right (wrap around)
(ror DST A B) (ror Wr Rd Rd)
(ror DST B) (ror RW Rd)
; Rotate left (wrap around) ; Rotate left (wrap around)
(rol DST A B) (rol Wr Rd:value Rd:count)
(rol DST B) (rol RW Rd:count)
; Logical shift right (fill with zeros) ; Logical shift right (fill with zeros)
(lsr DST A B) (lsr Wr Rd Rd:count)
(lsr DST B) (lsr RW Rd:count)
; Logical shift left (fill with zeros) ; Logical shift left (fill with zeros)
(lsl DST A B) (lsl Wr Rd Rd:count)
(lsl DST B) (lsl RW Rd:count)
; Arithmetic shift right (copy sign bit) ; Arithmetic shift right (copy sign bit)
(asr DST A B) (asr Wr Rd Rd:count)
(asr DST B) (asr RW Rd:count)
; Arithmetic shift left (this is identical to `lsl`, added for completeness) ; Arithmetic shift left (this is identical to `lsl`, added for completeness)
(asl DST A B) (asl Wr Rd Rd:count)
(asl DST B) (asl RW Rd:count)
; Delete an object by its handle. Objects are used by some extensions. ; 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) Stack-style buffer ops:
(push @REG VALUE)
```
; Push (insert at the end)
(bfpush @Obj Rd)
; Pop from a stack (remove from the end) ; Pop (remove from the end)
(pop DST @REG) (bfpop Wr @Obj)
; Reverse push to a stack (insert to the beginning) ; Reverse push (insert to the beginning)
(rpush @REG VALUE) (bfrpush @Obj Rd)
; Reverse pop from a stack (remove from the beginning) ; Reverse pop (remove from the beginning)
(rpop DST @REG) (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 ## Screen module
@ -456,7 +542,7 @@ such as animations.
## Stdio module ## 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). - 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)`). 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. - They operate over unicode code points, which are a superset of ASCII.

@ -71,12 +71,12 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
"nneg" | "0+" | ">=0" | "≤0" => Cond::NonNegative, "nneg" | "0+" | ">=0" | "≤0" => Cond::NonNegative,
"c" => Cond::Carry, "c" => Cond::Carry,
"nc" => Cond::NotCarry, "nc" => Cond::NotCarry,
"em" => Cond::Empty, "em" | "empty" => Cond::Empty,
"nem" => Cond::NotEmpty, "nem" | "nempty" => Cond::NotEmpty,
"f" => Cond::Full, "f" | "full" => Cond::Full,
"nf" => Cond::NotFull, "nf" | "nfull" => Cond::NotFull,
"valid" => Cond::Valid, "ok" | "val" | "valid" => Cond::Valid,
"inval" => Cond::Invalid, "inval" | "invalid" | "nval" | "nok" => Cond::Invalid,
"ov" => Cond::Overflow, "ov" => Cond::Overflow,
"nov" => Cond::NotOverflow, "nov" => Cond::NotOverflow,
_ => { _ => {

@ -71,7 +71,9 @@ pub enum BuiltinOp {
/// The object is released and the handle becomes invalid. /// The object is released and the handle becomes invalid.
Delete(RdObj), Delete(RdObj),
/// Copy value /// 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 /// Store runtime status to a register
StoreFlags { dst: Wr }, StoreFlags { dst: Wr },
/// Load runtime status from a register /// Load runtime status from a register

@ -103,12 +103,18 @@ impl OpTrait for BuiltinOp {
let pc = state.get_pc(); let pc = state.get_pc();
program.validate_jump(pc, Addr((pc.0 as i64 + res.advance) as u64))?; program.validate_jump(pc, Addr((pc.0 as i64 + res.advance) as u64))?;
} }
BuiltinOp::Move { dst, src } => { BuiltinOp::MoveValue { dst, src } => {
state.clear_status(); state.clear_status();
let val = state.read(*src)?; let val = state.read(*src)?;
state.update_status(val); state.update_status(val);
state.write(*dst, 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 } => { BuiltinOp::StoreFlags { dst } => {
let packed = state.frame.status.store(); let packed = state.frame.status.store();
state.write(*dst, packed)?; state.write(*dst, packed)?;

@ -177,12 +177,19 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
} }
"ld" => { "ld" => {
BuiltinOp::Move { BuiltinOp::MoveValue {
dst: args.next_wr()?, dst: args.next_wr()?,
src: args.next_rd()?, src: args.next_rd()?,
} }
} }
"swap" => {
BuiltinOp::SwapValues {
a: args.next_wr()?,
b: args.next_wr()?,
}
}
"stf" => { "stf" => {
BuiltinOp::StoreFlags { BuiltinOp::StoreFlags {
dst: args.next_wr()?, 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::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::StoreFlags { dst } => sexp::list(&[A("stf"), A(dst)]),
BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]) BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)])
} }
@ -364,6 +372,7 @@ mod test {
("(fault)", "(fault)"), ("(fault)", "(fault)"),
("(fault kur*a)", "(fault kur*a)"), ("(fault kur*a)", "(fault kur*a)"),
("(fault \"do pr*ele\")", "(fault \"do pr*ele\")"), ("(fault \"do pr*ele\")", "(fault \"do pr*ele\")"),
("(swap r0 r1)", "(swap r0 r1)"),
("(ld r0 r0)", "(ld r0 r0)"), ("(ld r0 r0)", "(ld r0 r0)"),
("(ld r0 156)", "(ld r0 156)"), ("(ld r0 156)", "(ld r0 156)"),
("(ld _ -32767)", "(ld _ -32767)"), ("(ld _ -32767)", "(ld _ -32767)"),

@ -12,40 +12,32 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
Ok(ParseRes::ext(match keyword { Ok(ParseRes::ext(match keyword {
"mkbf" => { "mkbf" => {
let dst = args.next_wr()?; 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 { let value = match args.next() {
Sexp::Atom(Atom::QS(s), _) => { None => {
BufOps::New { BufValue::Zeros(Rd::immediate(0))
dst,
value: BufValue::Chars(s),
}
} }
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![]; let mut vals = vec![];
for v in list { for v in list {
vals.push(parse_rd(v, args.pcx)?); vals.push(parse_rd(v, args.pcx)?);
} }
BufOps::New { BufValue::Values(vals)
dst,
value: BufValue::Values(vals),
}
} }
other => { Some(other) => {
return Err(CrsnError::Parse("Expected quoted string or a tuple of values".into(), other.pos().clone())); return Err(CrsnError::Parse("Expected quoted string or a tuple of values".into(), other.pos().clone()));
} }
} };
BufOps::New { dst, value }
} }
"bfrd" => { "bfrd" => {

@ -1,7 +1,7 @@
( (
(ld r0 65) (ld r0 65)
(sym buf r7) (sym buf r7)
(mkbfv buf (r0 66 67 68 '\n')) (mkbf buf (r0 66 67 68 '\n'))
(bfrpop @stdout @buf) (bfrpop @stdout @buf)
(s.nem -1) (s.nem -1)

@ -4,9 +4,9 @@
(def T_HELLO 0) (def T_HELLO 0)
(def T_UNK 1) (def T_UNK 1)
(mkbf TXT) (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) (bfins @TXT T_HELLO r0)
(mkbfv r0 "🐈") (mkbf r0 "🐈")
(bfins @TXT T_UNK r0) (bfins @TXT T_UNK r0)
; Print string from the table ; Print string from the table

Loading…
Cancel
Save