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. 40
      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,
- `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 `.<cond>` 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 `.<cond>` 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.

@ -71,12 +71,12 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
"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,
_ => {

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

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

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

@ -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))
}
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)?)
}
Sexp::List(list, _) => {
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" => {

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

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

Loading…
Cancel
Save