rendered paste body256 constant input-buffer-size
assemble-start
\ word structure: (xt is address of type flag)
\ pointer to previous word or 0 for first word
\ counted-string name
\ type flag
\ primitive-byte or program
\ type:
\ 1: immediate
\ 2: not immediate
\ 3: not immediate, xt is a primitive
:program-name
.s' Virtual Forth 0.1'
:input-buffer
.dw 0
\ .org [ data-index input-buffer-size + ]
:>in
.db 0
:input-count
.db 0
:in-index
.dw 0
:immediate-flag
.dw -1
:state
.dw :count
.s' state'
.db 2
:state-code
lit :immediate-flag ret
:+
.dw 0
.s' +'
.db 3 +
:count ( c-addr1 -- c-addr2 u )
.dw :+
.s' count'
.db 2
:count-code
dup c@ swap 1+ swap ret
:min ( n1 n2 -- n3 )
.dw :count
.s' min'
.db 2
:min-code
over over
> jz :min-less
swap drop
ret
:min-less
drop ret
:compare-count
.db 0
:compare-addr1
.dw 0
:compare-count1
.db 0
:compare-addr2
.dw 0
:compare-count2
.db 0
:compare ( c-addr1 u1 c-addr2 u2 -- n )
.dw :min
.s' compare'
.db 2
:compare-code
lit :compare-count2 c! lit :compare-addr2 !
lit :compare-count1 c! lit :compare-addr1 !
lit :compare-count1 c@ lit :compare-count2 c@ call :min-code
lit :compare-count c!
:compare-next
lit :compare-count c@ jnz :compare-char
:compare-length
lit :compare-count1 c@ lit :compare-count2 c@ < jnz :compare-less
lit :compare-count1 c@ lit :compare-count2 c@ > jnz :compare-greater
jump :compare-equal
:compare-char
lit :compare-addr1 @ c@ lit :compare-addr2 @ c@ < jnz :compare-less
lit :compare-addr1 @ c@ lit :compare-addr2 @ c@ > jnz :compare-less
lit :compare-addr1 dup @ 1+ swap !
lit :compare-addr2 dup @ 1+ swap !
lit :compare-count c@ 1- lit :compare-count c!
jump :compare-next
:compare-less
lit -1 ret
:compare-greater
lit 1 ret
:compare-equal
lit 0 ret
:find-word-address
.dw :compare
:find-word-count
.db 0
:find-word ( c-addr u -- false | xt true )
.dw :compare
.s' find-word'
.db 2
:find-word-code
lit :find-word-count c!
lit :find-word-address !
lit :last-word @
:find-word-next
dup lit 2 + call :count-code
lit :find-word-address @ lit :find-word-count c@ call :compare-code
jnz :find-word-step
lit 2 + call :count-code + lit -1 ret
:find-word-step
@ dup jnz :find-word-next
ret
:type ( c-addr u -- )
.dw :find-word
.s' type'
.db 2
:type-code
dup jz :type-end
swap dup c@ emit 1+ swap 1-
jump :type-code
:type-end
drop
drop
ret
:move-count
.dw 0
:move-addr1
.dw 0
:move-addr2
.dw 0
:move ( addr1 addr2 count -- )
.dw :type
.s' move'
.db 2
:move-code
lit :move-count !
lit :move-addr2 !
lit :move-addr1 !
:move-start
lit :move-count @ jz :move-end
lit :move-addr1 @ c@ lit :move-addr2 @ c!
lit :move-addr1 dup @ 1+ swap !
lit :move-addr2 dup @ 1+ swap !
lit :move-count dup @ 1+ swap !
jump :move-start
:move-end
ret
:execute ( xt -- )
.dw :move
.s' execute'
.db 2
:execute-code
dup c@ lit 3 = jnz :execute-primitive
1+ pop-call ret
:execute-primitive
1+ c@ execute ret
:is-whitespace
lit 32 =
ret
:skip-while-whitespace
lit :>in c@ lit :input-count c@ = jnz :skip-while-whitespace-end
lit :>in c@ lit :input-buffer @ + c@
call :is-whitespace jz :skip-while-whitespace-end
lit :>in dup c@ 1+ swap c!
jump :skip-while-whitespace
:skip-while-whitespace-end
ret
:skip-until-whitespace
lit :>in c@ lit :input-count c@ = jnz :skip-until-whitespace-end
lit :>in c@ lit :input-buffer @ + c@
call :is-whitespace jnz :skip-until-whitespace-end
lit :>in dup c@ 1+ swap c!
jump :skip-until-whitespace
:skip-until-whitespace-end
ret
:parse-number-neg
.db 0
:parse-number-result
.dw 0
:parse-number-word
.dw 0
:parse-number-count
.db 0
:parse-number ( addr u -- number -1 | 0 )
\ check for empty string
dup jnz :parse-number-at-least-one-char
swap drop ret
:parse-number-at-least-one-char
\ init variables
lit :parse-number-count c!
lit :parse-number-word !
lit 0 lit :parse-number-result !
lit 0 lit :parse-number-neg c!
\ if first character is "-", then increment pointer and set flag
lit :parse-number-word @ c@ lit 45 = jz :parse-number-positive
lit -1 lit :parse-number-neg c!
lit :parse-number-count dup c@ 1- swap c!
lit :parse-number-word dup @ 1+ swap !
:parse-number-positive
\ check for empty string after possible "-"
lit :parse-number-count c@ jnz :parse-number-positive2
lit 0 ret
:parse-number-positive2
\ shift current result
lit :parse-number-result dup @ lit 10 * swap !
\ check next digit
lit :parse-number-word @ c@
lit 48 - dup lit 0 < over lit 9 > or jz :parse-number-valid-digit
drop lit 0 ret
:parse-number-valid-digit
\ add digit
lit :parse-number-result @ + lit :parse-number-result !
\ increment pointer
lit :parse-number-count dup c@ 1- swap c!
lit :parse-number-word dup @ 1+ swap !
\ test for last character
lit :parse-number-count c@ jnz :parse-number-positive2
\ was last character, test negate flag
lit :parse-number-result @ lit :parse-number-neg c@ jz :parse-number-valid
negate
:parse-number-valid
lit -1 ret
:next-word ( -- 0 | c-addr u -1 )
call :skip-while-whitespace
lit :>in c@ lit :input-count c@ = jz :next-word-until-whitespace
lit 0 ret
:next-word-until-whitespace
lit :>in c@ dup lit :input-buffer @ + swap
call :skip-until-whitespace
lit :>in c@ swap - lit -1 ret
:,
.dw :execute
.s' ,'
.db 2
:,-code
here ! lit 2 allot ret
:c,
.dw :,
.s' c,'
.db 2
:c,-code
here c! lit 1 allot ret
::
.dw :c,
.s' :'
.db 2
::-code
\ store pointer to previous word and update last word pointer
here lit :last-word @ call :,-code lit :last-word !
\ read word name and copy it
call :next-word jz ::-error
dup call :c,-code swap over here swap call :move-code allot
\ set type flag
lit 2 call :c,-code
\ set compile mode
lit 0 lit :immediate-flag !
ret
::-error
\ TODO: handle EOF errors
ret
:;
.dw ::
.s' ;'
.db 1
:;-code
\ finish word
lit ret call :c,-code
\ set immediate mode
lit -1 lit :immediate-flag !
ret
:evaluate-number ( n -- n | )
lit :immediate-flag @ jz :compile-number
ret
:compile-number
lit lit call :c, call :,
ret
:evaluate-word ( xt -- )
lit :immediate-flag @ jz :compile-word
call :execute-code
ret
:compile-word
dup c@ lit 1 = jz :compile-word-no-immediate
1+ c@ pop-call ret
:compile-word-no-immediate
dup c@ lit 2 = jz :compile-word-primtive
lit call call :c,
1+ call :, ret
:compile-word-primtive
c@ call :c, ret
:evaluate ( c-addr u -- )
.dw :;
.s' evaluate'
.db 2
:evaluate-code
\ set input buffer
lit :input-count c! lit :input-buffer ! lit 0 lit :>in c!
:evaluate-next
\ get next word
call :next-word jz :evaluate-end
\ try to evaluate it as a word
over over call :find-word-code jz :evaluate-try-number
swap drop swap drop call :evaluate-word jump :evaluate-next
:evaluate-try-number
\ try to evaluate it as a number
call :parse-number jz :evaluate-error
call :evaluate-number
:evaluate-error
\ evaluate error: ignore
jump :evaluate-next
:evaluate-end
ret
:last-word
.dw :evaluate
:data-start
.end