add "swap" instr, document buffer ops
This commit is contained in:
@@ -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)
|
||||
```
|
||||
|
||||
## Stacks Module
|
||||
## Buffers Module
|
||||
|
||||
This module defines data stacks. Stacks can be shared by routines by passing a handle.
|
||||
This module defines dynamic size integer buffers.
|
||||
|
||||
A buffer needs to be created using one of the init instructions:
|
||||
|
||||
```
|
||||
; Create a stack. The register then contains the stack handle.
|
||||
(stack REG)
|
||||
; Create an empty buffer and store its handle into a register
|
||||
(mkbf Wr)
|
||||
|
||||
; Push to a stack (insert to the end)
|
||||
(push @REG VALUE)
|
||||
; Create a buffer of a certain size, filled with zeros.
|
||||
; COUNT may be a register or an immediate value
|
||||
(mkbf Wr Rd:count)
|
||||
|
||||
; Pop from a stack (remove from the end)
|
||||
(pop DST @REG)
|
||||
; Create a buffer and fill it with characters from a string (unicode code points)
|
||||
(mkbf Wr "string")
|
||||
|
||||
; Reverse push to a stack (insert to the beginning)
|
||||
(rpush @REG VALUE)
|
||||
|
||||
; Reverse pop from a stack (remove from the beginning)
|
||||
(rpop DST @REG)
|
||||
; Create a buffer and fill it with values.
|
||||
(mkbf Wr (Rd...))
|
||||
```
|
||||
|
||||
To delete a stack, use the `del` instruction - `(del @REG)`
|
||||
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)
|
||||
|
||||
; Reverse a buffer
|
||||
(bfrev @Obj)
|
||||
|
||||
; Append a buffer
|
||||
(bfapp @Obj @Obj:other)
|
||||
|
||||
; Prepend a buffer
|
||||
(bfprep @Obj @Obj:other)
|
||||
```
|
||||
|
||||
Stack-style buffer ops:
|
||||
|
||||
```
|
||||
; Push (insert at the end)
|
||||
(bfpush @Obj Rd)
|
||||
|
||||
; Pop (remove from the end)
|
||||
(bfpop Wr @Obj)
|
||||
|
||||
; Reverse push (insert to the beginning)
|
||||
(bfrpush @Obj Rd)
|
||||
|
||||
; Reverse pop (remove from the beginning)
|
||||
(bfrpop Wr @Obj)
|
||||
```
|
||||
|
||||
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)"),
|
||||
|
||||
+16
-24
@@ -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)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user