Files
wipforth/wipforth.ws

2107 lines
33 KiB
Plaintext

.mem main 1 1 shared
.import main "emu" "mem"
;; Peripheral registers
.def TXBUF 000h
.def RXBUF 080h
.def TXHEAD 100h
.def TXTAIL 104h
.def RXHEAD 108h
.def RXTAIL 10Ch
.def DICT_START 0200h
.def RSP_INIT 10000h
.def SP_INIT F000h
;; Forth registers
.global rsp i32
.global sp i32
.global ip i32
.global cfa i32
.global fn i32
;; Trampoline control flag
.global run i32 0
;; Some little helper functions
.func push
.param x i32
global.get sp
i32.const 4
i32.sub
global.set sp
global.get sp
local.get x
i32.store 2 0
.func pop
.result i32
global.get sp
i32.load 2 0
global.get sp
i32.const 4
i32.add
global.set sp
.func pushrsp
.param x i32
global.get rsp
i32.const 4
i32.sub
global.set rsp
global.get rsp
local.get x
i32.store 2 0
.func poprsp
.result i32
global.get rsp
i32.load 2 0
global.get rsp
i32.const 4
i32.add
global.set rsp
;; The rather bizzare nature of WebAssembly means that we can't
;; actually jump to code, which makes this NEXT implemenation somewhat
;; strange. Instead of doing the jump here, we store the function
;; index (seemingly the closest you can get to a code address in
;; WebAssembly) in the fn global and return. It's then down to the
;; trampoline loop to actually run the codeword.
.func next
global.get ip
global.get ip
i32.const 4
i32.add
global.set ip
i32.load 2 0
global.set cfa
global.get cfa
i32.load 2 0
global.set fn
;; The codewords function table must contain every codeword we want to
;; be able to run, as it's indices into this table that can be ran
;; with call_indirect in the $trampoline loop.
.type codeword result
.table codewords 100
;; Our special inner interpreters <3
.func docol
.elem codewords docol DOCOL_CODEWORD
global.get ip
call pushrsp
global.get cfa
i32.const 4
i32.add
global.set ip
call next
.func doval
.elem codewords doval DOVAL_CODEWORD
global.get cfa
i32.const 4
i32.add
i32.load 2 0
call push
call next
.func dovar
.elem codewords dovar DOVAR_CODEWORD
global.get cfa
i32.const 4
i32.add
call push
call next
;; Codewords!
;;
;; Most of these are implemented here (as opposed to in the forth
;; itself) for performance rather than necessity.
;; Stack manipulation
.func dup
.elem codewords dup DUP_CODEWORD
global.get sp
i32.load 2 0
call push
call next
.func drop
.elem codewords drop DROP_CODEWORD
global.get sp
i32.const 4
i32.add
global.set sp
call next
.func swap
.elem codewords swap SWAP_CODEWORD
global.get sp
i32.const 4
i32.add
global.get sp
i32.load 2 0
global.get sp
global.get sp
i32.const 4
i32.add
i32.load 2 0
i32.store 2 0
i32.store 2 0
call next
.func rot
.elem codewords rot ROT_CODEWORD
global.get sp
i32.const 4
i32.add
global.get sp
i32.load 2 0
global.get sp
i32.const 8
i32.add
global.get sp
i32.const 4
i32.add
i32.load 2 0
global.get sp
global.get sp
i32.const 8
i32.add
i32.load 2 0
i32.store 2 0
i32.store 2 0
i32.store 2 0
call next
.func nrot
.elem codewords nrot NROT_CODEWORD
global.get sp
i32.const 8
i32.add
global.get sp
i32.load 2 0
global.get sp
global.get sp
i32.const 4
i32.add
i32.load 2 0
global.get sp
i32.const 4
i32.add
global.get sp
i32.const 8
i32.add
i32.load 2 0
i32.store 2 0
i32.store 2 0
i32.store 2 0
call next
.func over
.elem codewords over OVER_CODEWORD
global.get sp
i32.const 4
i32.add
i32.load 2 0
call push
call next
.func twodup
.elem codewords twodup TWODUP_CODEWORD
global.get sp i32.load 2 0
global.get sp i32.const 4 i32.add i32.load 2 0
call push
call push
call next
.func twodrop
.elem codewords twodrop TWODROP_CODEWORD
global.get sp
i32.const 8
i32.add
global.set sp
call next
.func twoswap
.elem codewords twoswap TWOSWAP_CODEWORD
global.get sp
i32.const 8
i32.add
global.get sp
i32.load 2 0
global.get sp
i32.const 12
i32.add
global.get sp
i32.const 4
i32.add
i32.load 2 0
global.get sp
global.get sp
i32.const 8
i32.add
i32.load 2 0
global.get sp
i32.const 4
i32.add
global.get sp
i32.const 12
i32.add
i32.load 2 0
i32.store 2 0
i32.store 2 0
i32.store 2 0
i32.store 2 0
call next
;; Arithmetic and logic
.func inc
.elem codewords inc INC_CODEWORD
global.get sp
global.get sp
i32.load 2 0
i32.const 1
i32.add
i32.store 2 0
call next
.func dec
.elem codewords dec DEC_CODEWORD
global.get sp
global.get sp
i32.load 2 0
i32.const 1
i32.sub
i32.store 2 0
call next
.func inc4
.elem codewords inc4 INC4_CODEWORD
global.get sp
global.get sp
i32.load 2 0
i32.const 4
i32.add
i32.store 2 0
call next
.func dec4
.elem codewords dec4 DEC4_CODEWORD
global.get sp
global.get sp
i32.load 2 0
i32.const 4
i32.sub
i32.store 2 0
call next
.func add
.elem codewords add ADD_CODEWORD
call pop
call pop
i32.add
call push
call next
.func sub
.elem codewords sub SUB_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.sub
call push
call next
.func mul
.elem codewords mul MUL_CODEWORD
call pop
call pop
i32.mul
call push
call next
.func div
.elem codewords div DIV_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.div_s
call push
call next
.func mod
.elem codewords mod MOD_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.rem_s
call push
call next
.func eq
.elem codewords eq EQ_CODEWORD
call pop
call pop
i32.eq
i32.const 0 i32.sub
call push
call next
.func neq
.elem codewords neq NEQ_CODEWORD
call pop
call pop
i32.ne
i32.const 0 i32.sub
call push
call next
.func lt
.elem codewords lt LT_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.lt_s
i32.const 0 i32.sub
call push
call next
.func gt
.elem codewords gt GT_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.gt_s
i32.const 0 i32.sub
call push
call next
.func lte
.elem codewords lte LTE_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.le_s
i32.const 0 i32.sub
call push
call next
.func gte
.elem codewords gte GTE_CODEWORD
.local tmp i32
call pop
local.set tmp
call pop
local.get tmp
i32.ge_s
i32.const 0 i32.sub
call push
call next
.func zeq
.elem codewords zeq ZEQ_CODEWORD
call pop
i32.eqz
i32.const 0 i32.sub
call push
call next
.func zneq
.elem codewords zneq ZNEQ_CODEWORD
call pop
i32.const 0
i32.ne
i32.const 0 i32.sub
call push
call next
.func zlt
.elem codewords zlt ZLT_CODEWORD
call pop
i32.const 0
i32.lt_s
i32.const 0 i32.sub
call push
call next
.func zgt
.elem codewords zgt ZGT_CODEWORD
call pop
i32.const 0
i32.gt_s
i32.const 0 i32.sub
call push
call next
.func zlte
.elem codewords zlte ZLTE_CODEWORD
call pop
i32.const 0
i32.le_s
i32.const 0 i32.sub
call push
call next
.func zgte
.elem codewords zgte ZGTE_CODEWORD
call pop
i32.const 0
i32.ge_s
i32.const 0 i32.sub
call push
call next
.func and
.elem codewords and AND_CODEWORD
call pop
call pop
i32.and
call push
call next
.func or
.elem codewords or OR_CODEWORD
call pop
call pop
i32.or
call push
call next
.func xor
.elem codewords xor XOR_CODEWORD
call pop
call pop
i32.xor
call push
call next
.func invert
.elem codewords invert INVERT_CODEWORD
call pop
i32.const FFFFFFFFh
i32.xor
call push
call next
;; Memory
.func store
.elem codewords store STORE_CODEWORD
call pop
call pop
i32.store 2 0
call next
.func fetch
.elem codewords fetch FETCH_CODEWORD
call pop
i32.load 2 0
call push
call next
.func addstore
.elem codewords addstore ADDSTORE_CODEWORD
.local tmp i32
call pop
local.tee tmp
local.get tmp
i32.load 2 0
call pop
i32.add
i32.store 2 0
call next
.func substore
.elem codewords substore SUBSTORE_CODEWORD
.local tmp i32
call pop
local.tee tmp
local.get tmp
i32.load 2 0
call pop
i32.sub
i32.store 2 0
call next
.func storebyte
.elem codewords storebyte STOREBYTE_CODEWORD
call pop
call pop
i32.store8 0 0
call next
.func fetchbyte
.elem codewords fetchbyte FETCHBYTE_CODEWORD
call pop
i32.load8_u 0 0
call push
call next
.func atomic-store
.elem codewords atomic-store ATOMIC_STORE_CODEWORD
call pop
call pop
i32.atomic.store 2 0
call next
.func atomic-fetch
.elem codewords atomic-fetch ATOMIC_FETCH_CODEWORD
call pop
i32.atomic.load 2 0
call push
call next
.func atomic-storebyte
.elem codewords atomic-storebyte ATOMIC_STOREBYTE_CODEWORD
call pop
call pop
i32.atomic.store8 0 0
call next
.func atomic-fetchbyte
.elem codewords atomic-fetchbyte ATOMIC_FETCHBYTE_CODEWORD
call pop
i32.atomic.load8_u 0 0
call push
call next
.func copy
.elem codewords copy COPY_CODEWORD
.local src i32 dst i32 n i32
call pop local.set dst
call pop local.set src
call pop local.set n
block done
loop iter
local.get n
i32.eqz br_if done
local.get dst
local.get src
i32.load8_u 0 0
i32.store8 0 0
local.get dst i32.const 1 i32.add local.set dst
local.get src i32.const 1 i32.add local.set src
local.get n i32.const 1 i32.sub local.set n
br iter
end
end
call next
.func wait
.elem codewords wait WAIT_CODEWORD
call pop
i32.const 0
i64.const -1
memory.atomic.wait32 2 0
call push
call next
;; Core utility words
.func exit
.elem codewords exit EXIT_CODEWORD
call poprsp
global.set ip
call next
.func lit
.elem codewords lit LIT_CODEWORD
global.get ip
i32.load 2 0
call push
global.get ip
i32.const 4
i32.add
global.set ip
call next
.func execute
.elem codewords execute EXECUTE_CODEWORD
call pop
global.set cfa
global.get cfa
i32.load 2 0
global.set fn
.func halt
.elem codewords halt HALT_CODEWORD
i32.const 0
global.set run
;; Return and parameter stack primitives
.func tor
.elem codewords tor TOR_CODEWORD
call pop
call pushrsp
call next
.func fromr
.elem codewords fromr FROMR_CODEWORD
call poprsp
call push
call next
.func rspfetch
.elem codewords rspfetch RSPFETCH_CODEWORD
global.get rsp
call push
call next
.func rspstore
.elem codewords rspstore RSPSTORE_CODEWORD
call pop
global.set rsp
call next
.func rdrop
.elem codewords rdrop RDROP_CODEWORD
global.get rsp
i32.const 4
i32.add
global.set rsp
call next
.func spfetch
.elem codewords spfetch SPFETCH_CODEWORD
global.get sp
call push
call next
.func spstore
.elem codewords spstore SPSTORE_CODEWORD
call pop
global.set sp
call next
;; Serial I/O
.func key
.elem codewords key KEY_CODEWORD
.local head i32
i32.const RXHEAD
i32.atomic.load8_u 0 0
local.set head
;; Wait for RXBUF to be non-empty
loop wait
local.get head
i32.const RXTAIL
i32.atomic.load8_u 0 0
i32.eq
if
i32.const RXTAIL
local.get head
i64.const -1
memory.atomic.wait32 2 0
br wait
end
end
;; Read byte at head position
i32.const RXBUF
local.get head
i32.add
i32.load8_u 0 0
call push
;; Advance RXHEAD
i32.const RXHEAD
local.get head
i32.const 1
i32.add
i32.const 7Fh
i32.and
i32.atomic.store8 0 0
call next
.func emit
.elem codewords emit EMIT_CODEWORD
.local tail i32 n i32
;; Wait for TXBUF to be non-full
loop wait
i32.const TXTAIL
i32.atomic.load8_u 0 0
local.tee tail
i32.const 1
i32.add
i32.const 7Fh
i32.and
local.tee n
i32.const TXHEAD
i32.atomic.load8_u 0 0
i32.eq
br_if wait
end
;; Write byte at tail position
i32.const TXBUF
local.get tail
i32.add
call pop
i32.store8 0 0
;; Advance TXTAIL
i32.const TXTAIL
local.get n
i32.atomic.store8 0 0
call next
;; Branching
.func branch
.elem codewords branch BRANCH_CODEWORD
global.get ip
i32.load 2 0
global.get ip
i32.add
global.set ip
call next
.func zbranch
.elem codewords zbranch ZBRANCH_CODEWORD
call pop
if _ i32
i32.const 4
else
global.get ip
i32.load 2 0
end
global.get ip
i32.add
global.set ip
call next
;; Dictionary time :D
.at main DICT_START
.def PREV 0
_DUP:
.word PREV
.byte 3
.utf8 "DUP"
.align
DUP:
.word DUP_CODEWORD
.def PREV _DUP
_DROP:
.word PREV
.byte 4
.utf8 "DROP"
.align
DROP:
.word DROP_CODEWORD
.def PREV _DROP
_SWAP:
.word PREV
.byte 4
.utf8 "SWAP"
.align
SWAP:
.word SWAP_CODEWORD
.def PREV _SWAP
_ROT:
.word PREV
.byte 3
.utf8 "ROT"
.align
ROT:
.word ROT_CODEWORD
.def PREV _ROT
_NROT:
.word PREV
.byte 4
.utf8 "-ROT"
.align
NROT:
.word NROT_CODEWORD
.def PREV _NROT
_OVER:
.word PREV
.byte 4
.utf8 "OVER"
.align
OVER:
.word OVER_CODEWORD
.def PREV _OVER
_TWODUP:
.word PREV
.byte 4
.utf8 "2DUP"
.align
TWODUP:
.word TWODUP_CODEWORD
.def PREV _TWODUP
_TWODROP:
.word PREV
.byte 5
.utf8 "2DROP"
.align
TWODROP:
.word TWODROP_CODEWORD
.def PREV _TWODROP
_TWOSWAP:
.word PREV
.byte 5
.utf8 "2SWAP"
.align
TWOSWAP:
.word TWOSWAP_CODEWORD
.def PREV _TWOSWAP
_INC:
.word PREV
.byte 2
.utf8 "1+"
.align
INC:
.word INC_CODEWORD
.def PREV _INC
_DEC:
.word PREV
.byte 2
.utf8 "1-"
.align
DEC:
.word DEC_CODEWORD
.def PREV _DEC
_INC4:
.word PREV
.byte 2
.utf8 "4+"
.align
INC4:
.word INC4_CODEWORD
.def PREV _INC4
_DEC4:
.word PREV
.byte 2
.utf8 "4-"
.align
DEC4:
.word DEC4_CODEWORD
.def PREV _DEC4
_ADD:
.word PREV
.byte 1
.utf8 "+"
.align
ADD:
.word ADD_CODEWORD
.def PREV _ADD
_SUB:
.word PREV
.byte 1
.utf8 "-"
.align
SUB:
.word SUB_CODEWORD
.def PREV _SUB
_MUL:
.word PREV
.byte 1
.utf8 "*"
.align
MUL:
.word MUL_CODEWORD
.def PREV _MUL
_DIV:
.word PREV
.byte 1
.utf8 "/"
.align
DIV:
.word DIV_CODEWORD
.def PREV _DIV
_MOD:
.word PREV
.byte 3
.utf8 "MOD"
.align
MOD:
.word MOD_CODEWORD
.def PREV _MOD
_EQ:
.word PREV
.byte 1
.utf8 "="
.align
EQ:
.word EQ_CODEWORD
.def PREV _EQ
_NEQ:
.word PREV
.byte 2
.utf8 "<>"
.align
NEQ:
.word NEQ_CODEWORD
.def PREV _NEQ
_LT:
.word PREV
.byte 1
.utf8 "<"
.align
LT:
.word LT_CODEWORD
.def PREV _LT
_GT:
.word PREV
.byte 1
.utf8 ">"
.align
GT:
.word GT_CODEWORD
.def PREV _GT
_LTE:
.word PREV
.byte 2
.utf8 "<="
.align
LTE:
.word LTE_CODEWORD
.def PREV _LTE
_GTE:
.word PREV
.byte 2
.utf8 ">="
.align
GTE:
.word GTE_CODEWORD
.def PREV _GTE
_ZEQ:
.word PREV
.byte 2
.utf8 "0="
.align
ZEQ:
.word ZEQ_CODEWORD
.def PREV _ZEQ
_ZNEQ:
.word PREV
.byte 3
.utf8 "0<>"
.align
ZNEQ:
.word ZNEQ_CODEWORD
.def PREV _ZNEQ
_ZLT:
.word PREV
.byte 2
.utf8 "0<"
.align
ZLT:
.word ZLT_CODEWORD
.def PREV _ZLT
_ZGT:
.word PREV
.byte 2
.utf8 "0>"
.align
ZGT:
.word ZGT_CODEWORD
.def PREV _ZGT
_ZLTE:
.word PREV
.byte 3
.utf8 "0<="
.align
ZLTE:
.word ZLTE_CODEWORD
.def PREV _ZLTE
_ZGTE:
.word PREV
.byte 3
.utf8 "0>="
.align
ZGTE:
.word ZGTE_CODEWORD
.def PREV _ZGTE
_AND:
.word PREV
.byte 3
.utf8 "AND"
.align
AND:
.word AND_CODEWORD
.def PREV _AND
_OR:
.word PREV
.byte 2
.utf8 "OR"
.align
OR:
.word OR_CODEWORD
.def PREV _OR
_XOR:
.word PREV
.byte 3
.utf8 "XOR"
.align
XOR:
.word XOR_CODEWORD
.def PREV _XOR
_INVERT:
.word PREV
.byte 6
.utf8 "INVERT"
.align
INVERT:
.word INVERT_CODEWORD
.def PREV _INVERT
_STORE:
.word PREV
.byte 1
.utf8 "!"
.align
STORE:
.word STORE_CODEWORD
.def PREV _STORE
_FETCH:
.word PREV
.byte 1
.utf8 "@"
.align
FETCH:
.word FETCH_CODEWORD
.def PREV _FETCH
_ADDSTORE:
.word PREV
.byte 2
.utf8 "+!"
.align
ADDSTORE:
.word ADDSTORE_CODEWORD
.def PREV _ADDSTORE
_SUBSTORE:
.word PREV
.byte 2
.utf8 "-!"
.align
SUBSTORE:
.word SUBSTORE_CODEWORD
.def PREV _SUBSTORE
_STOREBYTE:
.word PREV
.byte 2
.utf8 "C!"
.align
STOREBYTE:
.word STOREBYTE_CODEWORD
.def PREV _STOREBYTE
_FETCHBYTE:
.word PREV
.byte 2
.utf8 "C@"
.align
FETCHBYTE:
.word FETCHBYTE_CODEWORD
.def PREV _FETCHBYTE
_ATOMIC_STORE:
.word PREV
.byte 2
.utf8 "A!"
.align
ATOMIC_STORE:
.word ATOMIC_STORE_CODEWORD
.def PREV _ATOMIC_STORE
_ATOMIC_FETCH:
.word PREV
.byte 2
.utf8 "A@"
.align
ATOMIC_FETCH:
.word ATOMIC_FETCH_CODEWORD
.def PREV _ATOMIC_FETCH
_ATOMIC_STOREBYTE:
.word PREV
.byte 3
.utf8 "AC!"
.align
ATOMIC_STOREBYTE:
.word ATOMIC_STOREBYTE_CODEWORD
.def PREV _ATOMIC_STOREBYTE
_ATOMIC_FETCHBYTE:
.word PREV
.byte 3
.utf8 "AC@"
.align
ATOMIC_FETCHBYTE:
.word ATOMIC_FETCHBYTE_CODEWORD
.def PREV _ATOMIC_FETCHBYTE
_COPY:
.word PREV
.byte 4
.utf8 "COPY"
.align
COPY:
.word COPY_CODEWORD
.def PREV _COPY
_WAIT:
.word PREV
.byte 4
.utf8 "WAIT"
.align
WAIT:
.word WAIT_CODEWORD
.def PREV _WAIT
_EXIT:
.word PREV
.byte 4
.utf8 "EXIT"
.align
EXIT:
.word EXIT_CODEWORD
.def PREV _EXIT
_LIT:
.word PREV
.byte 3
.utf8 "LIT"
.align
LIT:
.word LIT_CODEWORD
.def PREV _LIT
_EXECUTE:
.word PREV
.byte 7
.utf8 "EXECUTE"
.align
EXECUTE:
.word EXECUTE_CODEWORD
.def PREV _EXECUTE
_HALT:
.word PREV
.byte 4
.utf8 "HALT"
.align
HALT:
.word HALT_CODEWORD
.def PREV _HALT
_TOR:
.word PREV
.byte 2
.utf8 ">R"
.align
TOR:
.word TOR_CODEWORD
.def PREV _TOR
_FROMR:
.word PREV
.byte 2
.utf8 "R>"
.align
FROMR:
.word FROMR_CODEWORD
.def PREV _FROMR
_RSPFETCH:
.word PREV
.byte 4
.utf8 "RSP@"
.align
RSPFETCH:
.word RSPFETCH_CODEWORD
.def PREV _RSPFETCH
_RSPSTORE:
.word PREV
.byte 4
.utf8 "RSP!"
.align
RSPSTORE:
.word RSPSTORE_CODEWORD
.def PREV _RSPSTORE
_RDROP:
.word PREV
.byte 5
.utf8 "RDROP"
.align
RDROP:
.word RDROP_CODEWORD
.def PREV _RDROP
_SPFETCH:
.word PREV
.byte 3
.utf8 "SP@"
.align
SPFETCH:
.word SPFETCH_CODEWORD
.def PREV _SPFETCH
_SPSTORE:
.word PREV
.byte 3
.utf8 "SP!"
.align
SPSTORE:
.word SPSTORE_CODEWORD
.def PREV _SPSTORE
_KEY:
.word PREV
.byte 3
.utf8 "KEY"
.align
KEY:
.word KEY_CODEWORD
.def PREV _KEY
_EMIT:
.word PREV
.byte 4
.utf8 "EMIT"
.align
EMIT:
.word EMIT_CODEWORD
.def PREV _EMIT
_BRANCH:
.word PREV
.byte 6
.utf8 "BRANCH"
.align
BRANCH:
.word BRANCH_CODEWORD
.def PREV _BRANCH
_ZBRANCH:
.word PREV
.byte 7
.utf8 "0BRANCH"
.align
ZBRANCH:
.word ZBRANCH_CODEWORD
.def PREV _ZBRANCH
;; Built-in values and variables
_DOCOL:
.word PREV
.byte 5
.utf8 "DOCOL"
.align
DOCOL:
.word DOVAL_CODEWORD
.word DOCOL_CODEWORD
.def PREV _DOCOL
_DOVAL:
.word PREV
.byte 5
.utf8 "DOVAL"
.align
DOVAL:
.word DOVAL_CODEWORD
.word DOVAL_CODEWORD
.def PREV _DOVAL
_DOVAR:
.word PREV
.byte 5
.utf8 "DOVAR"
.align
DOVAR:
.word DOVAL_CODEWORD
.word DOVAR_CODEWORD
.def PREV _DOVAR
_R0:
.word PREV
.byte 2
.utf8 "R0"
.align
R0:
.word DOVAL_CODEWORD
.word RSP_INIT
.def PREV _R0
_S0:
.word PREV
.byte 2
.utf8 "S0"
.align
S0:
.word DOVAL_CODEWORD
.word SP_INIT
.def PREV _S0
_FALSE:
.word PREV
.byte 5
.utf8 "FALSE"
.align
FALSE:
.word DOVAL_CODEWORD
.word 0
.def PREV _FALSE
_TRUE:
.word PREV
.byte 4
.utf8 "TRUE"
.align
TRUE:
.word DOVAL_CODEWORD
.word FFFFFFFFh
.def PREV _TRUE
_STATE:
.word PREV
.byte 5
.utf8 "STATE"
.align
STATE:
.word DOVAR_CODEWORD
.word 0
.def PREV _STATE
_BASE:
.word PREV
.byte 4
.utf8 "BASE"
.align
BASE:
.word DOVAR_CODEWORD
.word 10
.def PREV _BASE
_HERE:
.word PREV
.byte 4
.utf8 "HERE"
.align
HERE:
.word DOVAR_CODEWORD
.word KERNEL_DEFS_END
.def PREV _HERE
_LATEST:
.word PREV
.byte 6
.utf8 "LATEST"
.align
LATEST:
.word DOVAR_CODEWORD
.word KERNEL_DEFS_LAST
.def PREV _LATEST
_WORDBUF:
.word PREV
.byte 7
.utf8 "WORDBUF"
.align
WORDBUF:
.word DOVAR_CODEWORD
.zero 20h
.def PREV _WORDBUF
_WNFHOOK:
.word PREV
.byte 7
.utf8 "WNFHOOK"
.align
WNFHOOK:
.word DOVAL_CODEWORD
.word WNF_HANDLER
.def PREV _WNFHOOK
;; And now, it's time for some colon words!
;; : TUCK DUP -ROT ;
_TUCK:
.word PREV
.byte 4
.utf8 "TUCK"
.align
TUCK:
.word DOCOL_CODEWORD
.word DUP NROT
.word EXIT
.def PREV _TUCK
;; : NSPACE ( byte -- bool )
;; DUP 9 <> SWAP \ Compare to horizontal tab
;; DUP 10 <> SWAP \ Compare to line-feed
;; DUP 13 <> SWAP \ Compare to carriage return
;; 32 <> \ Compare to space
;; AND AND AND \ And all results together
;; ;
_NSPACE:
.word PREV
.byte 6
.utf8 "NSPACE"
.align
NSPACE:
.word DOCOL_CODEWORD
.word DUP LIT 9 NEQ SWAP
.word DUP LIT 10 NEQ SWAP
.word DUP LIT 13 NEQ SWAP
.word LIT 32 NEQ
.word AND AND AND
.word EXIT
.def PREV _NSPACE
;; : WORD ( -- len )
;; 0 \ Initial length
;;
;; KEY \ Get byte from input
;; TUCK NSPACE 0BRANCH [60] \ Check if whitespace
;; TUCK WORDBUF + C! \ Append byte to WORDBUF
;; 1+ \ Increment length
;; DUP 32 >= 0BRANCH [8] EXIT \ Exit if at max length
;; BRANCH [-72] \ Loop back to KEY
;;
;; \ Byte is whitespace
;; SWAP DROP
;; DUP 0<> 0BRANCH [-96] \ Loop back to KEY if zero length
;; ;
_WORD:
.word PREV
.byte 4
.utf8 "WORD"
.align
WORD:
.word DOCOL_CODEWORD
.word LIT 0
.word KEY
.word TUCK NSPACE ZBRANCH 60
.word TUCK WORDBUF ADD STOREBYTE
.word INC
.word DUP LIT 32 GTE ZBRANCH 8 EXIT
.word BRANCH -72
.word SWAP DROP
.word DUP ZNEQ ZBRANCH -96
.word EXIT
.def PREV _WORD
;; : STRING= ( len str1 str2 -- bool )
;; 2DUP C@ SWAP C@ \ Load a byte from each address
;; = 0BRANCH [48] \ Check for byte mismatch
;; 1+ -ROT 1+ -ROT 1- \ Increment addresses, decrement length
;; DUP 0BRANCH [32] \ Check for zero remaining bytes
;; -ROT BRANCH [-68] \ Loop
;;
;; 2DROP DROP FALSE EXIT \ Strings not equal
;; 2DROP DROP TRUE \ Strings equal
;; ;
_STRINGEQ:
.word PREV
.byte 7
.utf8 "STRING="
.align
STRINGEQ:
.word DOCOL_CODEWORD
.word TWODUP FETCHBYTE SWAP FETCHBYTE
.word EQ ZBRANCH 48
.word INC NROT INC NROT DEC
.word DUP ZBRANCH 32
.word NROT BRANCH -68
.word TWODROP DROP FALSE EXIT
.word TWODROP DROP TRUE
.word EXIT
.def PREV _STRINGEQ
;; : FIND-NAME-LEN 4+ C@ 63 AND ;
;; The 3Fh mask we use here includes the hidden flag, so that FIND
;; never matches a hidden entry as its length will appear to be
;; greater than the maximum length returned by WORD.
_FIND_NAME_LEN:
.word PREV
.byte 13
.utf8 "FIND-NAME-LEN"
.align
FIND_NAME_LEN:
.word DOCOL_CODEWORD
.word INC4 FETCHBYTE LIT 3Fh AND
.word EXIT
.def PREV _FIND_NAME_LEN
;; : FIND ( len -- entry )
;; LATEST @ \ Initial entry
;;
;; TUCK FIND-NAME-LEN \ Get name length
;; OVER = 0BRANCH [52] \ Check for length mismatch
;; OVER 5 + \ Get name address
;; OVER SWAP WORDBUF STRING= \ Check if name matches
;; 0BRANCH [12]
;; DROP EXIT
;;
;; \ Name doesn't match
;; OVER @ 0<> 0BRANCH [20] \ Check for nil link
;; SWAP @ BRANCH [-104] \ Follow link and loop
;; 2DROP FALSE \ Return false
;; ;
_FIND:
.word PREV
.byte 4
.utf8 "FIND"
.align
FIND:
.word DOCOL_CODEWORD
.word LATEST FETCH
.word TUCK FIND_NAME_LEN
.word OVER EQ ZBRANCH 52
.word OVER LIT 5 ADD
.word OVER SWAP WORDBUF STRINGEQ
.word ZBRANCH 12
.word DROP EXIT
.word OVER FETCH ZNEQ ZBRANCH 20
.word SWAP FETCH BRANCH -104
.word TWODROP FALSE
.word EXIT
.def PREV _FIND
;; : NAME-LEN 4+ C@ 31 AND ;
_NAME_LEN:
.word PREV
.byte 8
.utf8 "NAME-LEN"
.align
NAME_LEN:
.word DOCOL_CODEWORD
.word INC4 FETCHBYTE LIT 31 AND
.word EXIT
.def PREV _NAME_LEN
;; : >CFA ( entry -- cfa )
;; DUP NAME-LEN \ Get name length
;; 5 + + \ Increment address to end of name
;; DUP 3 AND 0BRANCH [16] \ Check if aligned
;; 1+ BRANCH [-32] \ Increment address, loop to align check
;; ;
_TOCFA:
.word PREV
.byte 4
.utf8 ">CFA"
.align
TOCFA:
.word DOCOL_CODEWORD
.word DUP NAME_LEN
.word LIT 5 ADD ADD
.word DUP LIT 3 AND ZBRANCH 16
.word INC BRANCH -32
.word EXIT
.def PREV _TOCFA
;; : DIGIT ( byte -- value )
;; DUP DUP 48 >= SWAP 57 <= AND \ Test if 0-9
;; 0BRANCH [24] \ Jump to A-Z test if not
;; 48 - \ Get digit value
;; BRANCH [64] \ Go to range check
;;
;; DUP DUP 65 >= SWAP 90 <= AND \ Test if A-Z
;; 0BRANCH [60] \ Jump to invalid digit if not
;; 55 - \ Get digit value
;;
;; DUP DUP 0>= SWAP BASE @ < AND \ Test if 0 <= value < BASE
;; 0BRANCH [8] \ Jump to invalid digit if not
;; EXIT
;;
;; DROP TRUE \ Return -1 for an invalid digit
;; ;
_DIGIT:
.word PREV
.byte 5
.utf8 "DIGIT"
.align
DIGIT:
.word DOCOL_CODEWORD
.word DUP DUP LIT 48 GTE SWAP LIT 57 LTE AND
.word ZBRANCH 24
.word LIT 48 SUB
.word BRANCH 64
.word DUP DUP LIT 65 GTE SWAP LIT 90 LTE AND
.word ZBRANCH 60
.word LIT 55 SUB
.word DUP DUP ZGTE SWAP BASE FETCH LT AND
.word ZBRANCH 8
.word EXIT
.word DROP TRUE
.word EXIT
.def PREV _DIGIT
;; : NEGATE INVERT 1+ ;
_NEGATE:
.word PREV
.byte 6
.utf8 "NEGATE"
.align
NEGATE:
.word DOCOL_CODEWORD
.word INVERT INC
.word EXIT
.def PREV _NEGATE
;; : NUMBER? ( len -- value TRUE | FALSE )
;; WORDBUF \ Initial address
;;
;; DUP C@ 45 = 0BRANCH [60] \ Check for minus sign
;; 1+ \ Increment address
;; SWAP 1- \ Decrement length
;; DUP 0BRANCH [24] \ Check if any characters remain
;; TRUE -ROT \ Set negate flag true
;; SWAP BRANCH [24] \ Jump to setting initial value
;;
;; \ No characters after minus, exit with failure
;; 2DROP FALSE EXIT
;;
;; \ No leading minus, set negate flag false
;; FALSE -ROT
;;
;; \ Set initial value
;; 0
;;
;; \ Main loop
;; OVER C@ DIGIT \ Get byte's digit value
;; DUP 0>= 0BRANCH [68] \ Check if digit valid
;;
;; SWAP BASE @ * + \ Incorporate digit
;; SWAP 1+ \ Increment address
;; ROT 1- \ Decrement length
;; DUP 0BRANCH [40] \ Go to end if length zero
;; -ROT SWAP BRANCH [-88] \ Loop if characters remain
;;
;; \ Invalid digit
;; 2DROP 2DROP DROP FALSE EXIT \ Exit with failure
;;
;; \ Reached end of word
;; 2DROP \ Discard pointer and length
;; SWAP 0BRANCH [8] NEGATE \ Negate if -ve flag set
;; TRUE \ Exit with success
;; ;
_NUMBER:
.word PREV
.byte 7
.utf8 "NUMBER?"
.align
NUMBER:
.word DOCOL_CODEWORD
.word WORDBUF
.word DUP FETCHBYTE LIT 45 EQ ZBRANCH 60
.word INC
.word SWAP DEC
.word DUP ZBRANCH 24
.word TRUE NROT
.word SWAP BRANCH 24
.word TWODROP FALSE EXIT
.word FALSE NROT
.word LIT 0
.word OVER FETCHBYTE DIGIT
.word DUP ZGTE ZBRANCH 68
.word SWAP BASE FETCH MUL ADD
.word SWAP INC
.word ROT DEC
.word DUP ZBRANCH 40
.word NROT SWAP BRANCH -88
.word TWODROP TWODROP DROP FALSE EXIT
.word TWODROP
.word SWAP ZBRANCH 8 NEGATE
.word TRUE
.word EXIT
.def PREV _NUMBER
;; : , HERE @ ! 4 HERE +! ;
_COMMA:
.word PREV
.byte 1
.utf8 ","
.align
COMMA:
.word DOCOL_CODEWORD
.word HERE FETCH STORE LIT 4 HERE ADDSTORE
.word EXIT
.def PREV _COMMA
;; : IMMEDIATE? 4+ @ 128 AND 0BRANCH [12] TRUE EXIT FALSE ;
_IS_IMMEDIATE:
.word PREV
.byte 10
.utf8 "IMMEDIATE?"
.align
IS_IMMEDIATE:
.word DOCOL_CODEWORD
.word INC4 FETCH LIT 128 AND ZBRANCH 12 TRUE EXIT FALSE
.word EXIT
.def PREV _IS_IMMEDIATE
;; : INTERPRET
;; WORD \ Read a word
;; DUP FIND \ Look it up
;; DUP 0BRANCH [72] \ Branch to number handling if not found
;; SWAP DROP \ Discard word length
;;
;; \ Handle word
;; DUP IMMEDIATE? \ Check if the word is immediate
;; INVERT 0BRANCH [32] \ Jump straight to executing if so
;; STATE @ 0BRANCH [16] \ Check state
;; >CFA , EXIT \ Compile word
;; >CFA EXECUTE EXIT \ Execute word
;;
;; \ Handle number
;; DROP \ Discard nil entry
;; DUP NUMBER? 0BRANCH [48] \ Convert to number
;; SWAP DROP \ Discard word length
;; STATE @ 0BRANCH [20] \ Check state
;; LIT LIT , , \ If compiling, append LIT and the value
;; EXIT
;;
;; \ Word was not found
;; WNFHOOK EXECUTE
;; ;
_INTERPRET:
.word PREV
.byte 9
.utf8 "INTERPRET"
.align
INTERPRET:
.word DOCOL_CODEWORD
.word WORD
.word DUP FIND
.word DUP ZBRANCH 72
.word SWAP DROP
.word DUP IS_IMMEDIATE
.word INVERT ZBRANCH 32
.word STATE FETCH ZBRANCH 16
.word TOCFA COMMA EXIT
.word TOCFA EXECUTE EXIT
.word DROP
.word DUP NUMBER ZBRANCH 48
.word SWAP DROP
.word STATE FETCH ZBRANCH 20
.word LIT LIT COMMA COMMA
.word EXIT
.word WNFHOOK EXECUTE
.word EXIT
.def PREV _INTERPRET
;; : QUIT R0 RSP! INTERPRET BRANCH [-8] ;
_QUIT:
.word PREV
.byte 4
.utf8 "QUIT"
.align
QUIT:
.word DOCOL_CODEWORD
.word R0 RSPSTORE
.word INTERPRET BRANCH -8
.word EXIT
.def PREV _QUIT
;; And now to make myself obsolete as the compiler (finally)
;; : [ 0 STATE ! ; IMMEDIATE
_LBRAC:
.word PREV
.byte 81h
.utf8 "["
.align
LBRAC:
.word DOCOL_CODEWORD
.word LIT 0 STATE STORE
.word EXIT
.def PREV _LBRAC
;; : ] 1 STATE ! ;
_RBRAC:
.word PREV
.byte 1
.utf8 "]"
.align
RBRAC:
.word DOCOL_CODEWORD
.word LIT 1 STATE STORE
.word EXIT
.def PREV _RBRAC
;; : ALIGN
;; DUP 3 AND 0BRANCH [32]
;; 0 OVER C! 1+ BRANCH [-48]
;; ;
_ALIGN:
.word PREV
.byte 5
.utf8 "ALIGN"
.align
ALIGN:
.word DOCOL_CODEWORD
.word DUP LIT 3 AND ZBRANCH 32
.word LIT 0 OVER STOREBYTE INC BRANCH -48
.word EXIT
.def PREV _ALIGN
;; : HIDDEN 4+ DUP C@ 32 XOR SWAP C! ;
_HIDDEN:
.word PREV
.byte 6
.utf8 "HIDDEN"
.align
HIDDEN:
.word DOCOL_CODEWORD
.word INC4 DUP FETCHBYTE LIT 32 XOR SWAP STOREBYTE
.word EXIT
.def PREV _HIDDEN
;; : IMMEDIATE
;; LATEST @
;; 4+ DUP C@ 128 XOR SWAP C!
;; ; IMMEDIATE
_IMMEDIATE:
.word PREV
.byte 89h
.utf8 "IMMEDIATE"
.align
IMMEDIATE:
.word DOCOL_CODEWORD
.word LATEST FETCH
.word INC4 DUP FETCHBYTE LIT 128 XOR SWAP STOREBYTE
.word EXIT
.def PREV _IMMEDIATE
;; : CREATE
;; HERE @ \ Get initial address
;; LATEST @ OVER ! 4+ \ Write link
;; 2DUP C! 1+ \ Write length
;; 2DUP WORDBUF SWAP COPY + \ Write name
;; ALIGN \ Pad to alignment
;;
;; HERE @ LATEST ! \ Update LATEST
;; HERE ! \ Update HERE
;; ;
_CREATE:
.word PREV
.byte 6
.utf8 "CREATE"
.align
CREATE:
.word DOCOL_CODEWORD
.word HERE FETCH
.word LATEST FETCH OVER STORE INC4
.word TWODUP STOREBYTE INC
.word TWODUP WORDBUF SWAP COPY ADD
.word ALIGN
.word HERE FETCH LATEST STORE
.word HERE STORE
.word EXIT
.def PREV _CREATE
;; : :
;; WORD
;; CREATE
;; DOCOL ,
;; LATEST @ HIDDEN
;; ]
;; ;
_COLON:
.word PREV
.byte 1
.utf8 ":"
.align
COLON:
.word DOCOL_CODEWORD
.word WORD
.word CREATE
.word DOCOL COMMA
.word LATEST FETCH HIDDEN
.word RBRAC
.word EXIT
.def PREV _COLON
;; : ;
;; LIT EXIT ,
;; LATEST @ HIDDEN
;; [
;; ; IMMEDIATE
_SEMICOLON:
.word PREV
.byte 81h
.utf8 ";"
.align
SEMICOLON:
.word DOCOL_CODEWORD
.word LIT EXIT COMMA
.word LATEST FETCH HIDDEN
.word LBRAC
.word EXIT
.def PREV _SEMICOLON
;; : WNF-HANDLER
;; DROP \ Discard word length
;; 87 EMIT 78 EMIT 70 EMIT 10 EMIT \ Print WNF
;; QUIT \ Reset return stack
;; ;
_WNF_HANDLER:
.word PREV
.byte 11
.utf8 "WNF-HANDLER"
.align
WNF_HANDLER:
.word DOCOL_CODEWORD
.word DROP
.word LIT 87 EMIT LIT 78 EMIT LIT 70 EMIT LIT 10 EMIT
.word QUIT
.word EXIT
.def PREV _WNF_HANDLER
COLD_START:
.word QUIT
.def KERNEL_DEFS_LAST PREV
KERNEL_DEFS_END:
;; The trampoline is a workaround for WebAssembly's lack of indirect
;; jumps and code addresses. Instead of jumping into the next
;; codeword, NEXT sets the fn global to the function index of the next
;; codeword and returns to the trampoline.
;;
;; This way, we can simulate jumping to the code within WebAssembly's
;; restrictions without the WebAssembly call stack growing unbounded
;; as would happen if we used call_indirect inside NEXT.
.func trampoline
loop iter
global.get fn call_indirect codeword codewords
global.get run br_if iter
end
.func reset
i32.const RSP_INIT global.set rsp ;; Set the return stack pointer
i32.const SP_INIT global.set sp ;; Set the stack pointer
i32.const COLD_START global.set ip ;; Set the IP to the cold start
call next
i32.const 1 global.set run
call trampoline
.export reset