nforth/nforth.asm

1281 lines
29 KiB
NASM

if 0
==============================================================================
nForth - a minimalist i386 forth
-------------------------------------------------------------------------------
Everything in this repo is subject to this license unless otherwise specified
Copyright 2022 stacksmith
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1) Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2) Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3) Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
end if
LASTHEAD = 0 ;search terminates on 0
include "config.asm"
include "macros.asm"
format ELF executable 3
entry start
segment readable executable writeable
;argc: dd 0
argv: dd 0
start:
mov [argv],esp
; pop eax ;argc
; pop dword[argv] ;argv
; pop dword[argv]
; dec eax ;one argument?
; jz noargs ;then, no arguments
; xor eax,eax
; lea ecx,[eax-1]
; repne scasb
noargs:
;-----------------------------------------------------------------------
; First brk gets the top of allocated memory; second attempts to allot
; more memory.
mov eax,45
xor ebx,ebx
int 0x80
mov [HERE+4],eax ; start of allocated memory
lea ebx,[eax+MEMSIZE] ;
mov eax,45 ; brk
int 0x80 ; eax = top of allocated memory
and eax,$FFFFF000 ; page-align memory top
lea ebp,[eax-4096] ;datastack
lea edx,[ebp-4096] ;tib
mov [TIB+4],edx
DSTACK
push 1
push 2
RSTACK
; xor ebx,ebx
mov ebx,[argv]
mov dword [PARSE.PTR+4],BOOTSTRAP
mov esi,OUTER.cold ; enter OUTER without resetting PARSE.PTR
NEXT
_promptstr: db 10,"OK> "
HEAD bye,$+4
mov eax,1
int 0x80
HEAD osclose,$+4
mov eax,6
int 0x80
NEXT
; kernel calls expect: ;eax ebx ecx edx esi edi
; our system:
;
;
HEAD oscall3,$+4 ;TOS 3 2 1
DSTACK
pop edx
_oscall2: ;TOS 2 1
pop ecx
_oscall1: ;TOS 1
pop eax
xchg eax,ebx ; load eax with TOS, call id
RSTACK
int 0x80
xchg eax,ebx ;return value
NEXT
HEAD oscall2,$+4
DSTACK
jmp _oscall2
HEAD oscall1,$+4
DSTACK
jmp _oscall1
HEAD osopen,docol ;path,flags,mode--res
dd lit,5,oscall3,return
HEAD write,docol ;buffer,count,handle
dd rot ;count,handle,buffer
dd rot ;handle,buffer,count
dd lit,4
dd oscall3
dd return
HEAD read,docol ;buffer,count,handle
dd rot,rot
dd lit,3,oscall3,return
if CFG_TIME
HEAD sys_time,$+4
DSTACK
push ebx
RSTACK
mov eax,13 ;sys_time
push ebx ;return value on stack
mov ebx,esp
int 0x80
pop ebx
NEXT
end if
;; On entry: ebx = base
_number:
;; push esi
;; mov esi,[PARSE.PTR+4]
cmp byte[esi],'$'
jne @f
inc esi
mov ebx,16
@@: DSTACK
push ebx
RSTACK
xor edi,edi
jmp .in
;; process an ASCII digit in al, in a given base; result in ebx
;;
.digit: push edi ;
mov edi,HEXTAB+4
mov ecx,[ebp] ; base
inc ecx
mov ebx,ecx ; ebx = base+1
repne scasb
pop edi
jecxz .err
sub ebx,ecx ; ebx = result + 1!
;; multiply accumulator by base, and add digit (it's +1)
;;
mov eax,edi ;eax = accumulated value
mov edx,[ebp] ;load base into edx
mul edx ;multiply by base
lea edi,[eax+ebx-1] ;add digit+1, accum in edi
;; get an ASCII character and process, unless ws
.in: lodsb
_IS_WS al
jnc .digit
.x:
add ebp,4 ;done with base
mov ebx,edi ;accumulated value
ret
.err: DSTACK
pop ebx ;get rid of base
RSTACK
mov ebx,ERRNUM_PARSE
jmp ERXIT+4
;;=============================================================================
; wsp scan past ws and return character found, or 0 if at end.
HEAD wsp,$+4
DSTACK
push ebx
RSTACK
mov ecx,[PARSE.PTR+4]
dec ecx ; will immediately increment
.loop:
inc ecx
movzx ebx,byte[ecx]
test bl,bl ; 0 means
jz .done ; at end of line, return 0
cmp bl,' ' ; space or anything below
jbe .loop ; is considered ws
.done:
mov [PARSE.PTR+4],ecx
NEXT
;;-----------------------------------------------------------------------------
; reload TIB from HANDLE.IN, up to CR. 0-terminate. On error ERXIT with EOF!
;
HEAD reload,docol
dd TIB,fetch,xdup,PARSE.PTR,xstore ; ptr
dd minus1
.loop:
dd plus1
dd xdup,one,HANDLE.IN,fetch,read
dd zbranch,.readerr
dd xdup,fetchc,lit,10,equal,zbranch,.loop
dd plus1,zero,swap,xstorec
dd return
.readerr:
dd drop,lit,ERRNUM_EOF,ERXIT
;;-----------------------------------------------------------------------------
; prompt - only when input is from STDIN!
HEAD prompt,docol
dd HANDLE.IN,fetch,nzbranch,.done
dd lit, _promptstr,lit,5,type
.done: dd return
;;-----------------------------------------------------------------------------
;; ws - skip whitespaces, reloading as needed. May erxit with EOF!
;;
HEAD ws,docol
.loop:
dd wsp,zbranch,.reload
dd return
.reload:
dd prompt,reload,branch,.loop
;;=============================================================================
; Parsing
;;;----------------------------------------------------------------------------
;;; _search for a hash, from LATEST to first. Return 0 or entry.
;;;
_search: ;(hash--0/zr or --entry/nz)
mov edx,[LATEST+4]
xchg edx,ebx ;ebx = entry; edx = hash
.loop:
cmp [ebx-4],edx ;is entry's hash = needle?
je .done ;found
mov ebx,[ebx-8] ;link
and ebx,ALIGN_MASK ;mask off control bits
jnz .loop
ret
.done:
test ebx,ebx ;0/zr = fail
ret
;;; ----------------------------------------------------------------------------
;;; parse Parse a word in TIB at PARSE.PTR, as a word, and then as a number.
;; word: -- word,1
;; number: -- num,0
;; otherwise, num will force an error.
HEAD parse,$+4
push esi
mov esi,[PARSE.PTR+4]
push esi ; keep around in case of search failure
mov cl,' '
call _FNV1a ; hash it
call _search ; try to find it (NZ=found)
jnz .found
pop esi ; restore parse position
mov ebx,[BASE+4] ; try to find as a number
call _number ; number
DSTACK
push ebx
RSTACK
xor ebx,ebx
jmp .x
.found:
pop eax ; drop ptr to name
DSTACK
push ebx ; --entry,entry ;anything nz will do
RSTACK
.x: mov [PARSE.PTR+4],esi ; update parse position
pop esi
NEXT
;;; ****************************************************************************
;;; ****************************************************************************
;;; ****************************************************************************
;;; ****************************************************************************
;;-------------------------------------------------------------------------------
;; During bootstrapping, TIB points to bootstrap Forth code appended to binary.
;; As we hit CRs, we update TIB to each line start (for error reporting).
;; At the end of the bootstrap (upon hitting a 0), TIB is
;; During normal operation (not initial load), lines are read into TIB
;;
HEAD parsereset,$+4 ;"
mov edx,[TIB+4]
mov [PARSE.PTR+4],edx ;reset parse ptr
mov byte[edx],0
NEXT
HEAD emit,docol
dd XDSP ;push char, load pointer
dd one ;length
dd HANDLE.OUT,fetch ;stdout (1)
dd write,drop,drop
dd return
;;; buf,cnt
HEAD type,docol
dd HANDLE.OUT,fetch,write,drop,return
HEADN comment,"\",$+4,1 ;"
push esi
mov esi,[PARSE.PTR+4]
@@: lodsb
test al,al
jz skipword.x
cmp al,10
jne @b
jmp skipword.x
HEAD skipword,$+4
push esi
mov esi,[PARSE.PTR+4]
@@: lodsb
_IS_WS al
jnc @b
.x: dec esi
mov [PARSE.PTR+4],esi
pop esi
NEXT
; eax=scan,mulresult
;
; ecx=temp,cnt
; edx
; esi=string
; edi=accum
; ebp=dsp
; [ebp+4]=base
; [ebp]=accum
;;; ----------------------------------------------------------------------------
;;; Hash a string at esi, advancing esi until WS. Hash returned in TOS.
;;; also, exits when cl is matched!
_FNV1a: ;from esi
DSTACK
push ebx
RSTACK
mov ebx,FNV_OFFSET_BASIS
jmp .in
.loop:
inc esi
xor eax,ebx ;eax = char xor hash
mov edx,FNV_PRIME
mul edx ;eax = (char xor hash) * FNV_PRIME
mov ebx,eax
.in:
movzx eax,byte[esi] ;eax = char
cmp al,cl
je .out
_IS_WS al ;set C if WS
jnc .loop
.out:
ret
HEADN XHASH,"HASH",$+4
push esi
mov cl,' '
mov esi,[PARSE.PTR+4]
call _FNV1a
pop esi
NEXT
;
;;-----------------------------------------------------------------------------
;; Hash an environment variable, updating the variable to 0-terminated value
;; (ptr--ptr,hash)
HEADN ENVHASH,"ENVHASH",$+4
push esi
mov cl,'='
mov esi,ebx
call _FNV1a
inc esi
mov [ebp],esi ;save updated ptr
pop esi
NEXT
HEAD strlen,$+4 ; (str--str,len)
DSTACK
push ebx
RSTACK
mov edi,ebx ;starting from string start
mov ecx,-1 ;infinite count
xor eax,eax ;seek 0
repne scasb
neg ecx
lea ebx,[ecx-2] ;negation, starting at -1, 0-term
NEXT
docol:
push esi ;stack instruction pointer
lea esi,[eax+4]
NEXT
HEADN docolcomma,"docol,",docol
dd lit,docol,comma,return
dovar:
DSTACK
push ebx
RSTACK
lea ebx,[eax+4]
NEXT
HEADN dovarcomma,"dovar,",docol
dd lit,dovar,comma,return
;;; Numeric parsing, output
HEAD HEXTAB,dovar
db "0123456789ABCDEF"
;;;
;;; Stack digits, and return count in TOS.
;;; (num--...digits,cnt)
HEAD output,$+4
mov ecx,[BASE+4] ;ecx = BASE
xchg eax,ebx ;eax = number
xor ebx,ebx ;ebx = digit counter
DSTACK
.loop:
xor edx,edx
div ecx
push edx ;push digit
inc ebx ;count it
test eax,eax
jnz .loop
RSTACK
NEXT
HEADN dot,".",docol
dd output,dotprim,return
HEAD dotprim,docol
dd xpush
@@: dd HEXTAB,plus,fetchc,emit
dd dbra,@b
dd return
HEAD LATEST,dovar
dd FINALHEAD
HEAD HANDLE.IN,dovar
dd 0
HEAD HANDLE.OUT,dovar
dd 1
;HEAD MEM.START,dovar
dd 0
HEAD STATE,dovar
dd 0 ; dictionary pointer
dd 0 ; head pointer
dd 0 ; runptr
HEAD HERE,dovar
dd 0
;HEAD MEM.END,dovar
; dd 0
;HEAD MEM.ORIG,dovar
; dd 0
HEAD ERR.FRAME,dovar
dd 0
HEAD TIB,dovar
dd 0
HEAD PARSE.PTR,dovar
dd 0
HEAD FIXUP.IF,dovar
dd 0
HEAD BASE,dovar
dd 10
HEAD RUNPTR,dovar
dd 0
HEADN HEREfetch,"HERE@",docol
dd HERE,fetch,return
;-----------------------------------------------------------------------------
; We maintain a safe STATE. At any point we are happy with the state
; of the dictionary, we can call sync to commit STATE to current state.
; In case of an error, we can rollback to the previous known-good state.
;
HEAD state.commit,$+4
; dd STATE
; dd HERE,fetch,xover,xstore
; dd LATEST,fetch,swap, lit,4,plus,xstore
; dd return
mov eax,[HERE+4]
mov [STATE+4],eax ;HERE
mov eax,[RUNPTR+4]
mov [STATE+12],eax
mov eax,[LATEST+4]
mov [STATE+8],eax
NEXT
HEAD state.rollback,$+4
mov eax,[STATE+4]
mov [HERE+4],eax
mov eax,[STATE+8]
mov [LATEST+4],eax
mov eax,[STATE+12]
mov [RUNPTR+4],eax
NEXT
HEAD state.push, $+4
push dword[STATE+4]
push dword[STATE+8]
NEXT
HEAD state.pop,$+4
pop dword[STATE+8]
pop dword[STATE+4]
NEXT
HEAD sync,docol,1
dd state.commit,return
HEAD abort,docol,1
dd state.rollback,return
if CFG_TIME
HEAD TIMEZONE,dovar
dd -14400
end if
HEAD cr,docol
dd lit
dd $A
dd emit
dd return
;-----------------------------------------------------------------------------
; 2x ..definition.. Evaluate the code block that follows twice
HEADN twox,"2x",$+4
add esi,8 ;skip the 8-byte header, to execute the next definition
push esi
NEXT
HEAD sp2,docol
dd twox
HEADN space,"sp",docol
dd lit,$20,emit,return
HEADN spaces,"sps",docol
dd xdup,zbranch,.x
dd xpush
@@: dd space
dd dbra,@b
dd return;
.x: dd drop,return
HEAD INTERP,docol
.loop:
dd HEREfetch,RUNPTR,xstore ; save HERE to run later
dd COMPILE ; compile a single unit
dd lit,return,comma ; compile a return
dd RUNPTR,fetch,xdup,HERE,xstore,eval ; restore HERE, then run
dd branch,.loop
;;;===========================================================================
; OUTER interpreter
;
; Note: initially we enter .cold without parsereset, with PARSE.PTR at code
; appended to this file, for secondary boot.
;
HEAD OUTER,docol
.in:
dd parsereset ; abort source in TIB (
.cold:
dd state.commit ; initially, commit point.
dd ERR.CATCH,xdup,nzbranch,.err ; we catch errors
dd drop ; 0 means no error
.loop:
dd INTERP
.err:
dd xdup,lit,ERRNUM_EOF,minus,zbranch,.eof ; handle EOF
dd TIB,fetch,strlen,type ; print the error line
dd PARSE.PTR,fetch,TIB,fetch,minus ; compute offset in line
dd cr,spaces,lit,'^',emit ; show wherehe
dd state.rollback ; abort to checkpoint
dd branch, .in
.eof:
dd drop,zero,HANDLE.IN,xstore ; force STDIN
dd branch, .in ; not really an error
;;; on entry, esp has 2 return addresses.
HEAD _sysp, $+4
DSTACK ; |
push ebx ;1,2,3, 3
push dword[esp+8] ;1,2,3,1, 3
push dword[esp+8] ;1,2,3,1,2, 3
RSTACK
NEXT
HEADN XDSP,"DSP",$+4
DSTACK
push ebx ;value to stack
RSTACK
mov ebx,ebp ;load pointer to value
NEXT
HEADN XRSP,"RSP",$+4
DSTACK
push ebx ;value to stack
RSTACK
mov ebx,esp
NEXT
if CFG_DEBUG
HEAD sys,docol
dd _strlit
mstring <"DSP",9," RSP",9," HERE",9," SRC">
dd type,cr
dd XDSP,hexd,space,XRSP,hexd,space
dd HEREfetch,hexd,space
dd PARSE.PTR,fetch,hexd,cr
dd _sysp
dd hexd,space,hexd,space,hexd,cr
dd return
end if
HEAD dbra,$+4
dec dword[esp] ;decrement counter
jz .done
.continue:
mov esi,[esi] ;go there
NEXT
.done:
add esp,4 ;drop 0 count
add esi,4 ;skip loop target
NEXT
;; (char--char)
HEAD printable,$+4
xor eax,eax
mov al,$7E
cmp bl,20
cmovc ebx,eax
cmp bl,$7F
cmovnc ebx,eax
NEXT
;;; ------------------------------------------------------------------------------
;;; ERROR HANDLING
;;; ERR.CATCH (--0/zr) or (--errno/nz)
HEAD ERR.CATCH,$+4
DSTACK
push ebx
RSTACK
push esi ;save IP just after catch
push dword[ERR.FRAME] ;save old frame
mov [ERR.FRAME],esp ;establish new frame
xor ebx,ebx ;return 0
NEXT
HEAD ERR.CLR,$+4
mov esp,[ERR.FRAME] ;restore stack to frame
pop dword[ERR.FRAME] ;restore previous frame
pop edx ;drop unused IP
NEXT
; ERXIT, (id--) non-zero ID.
HEAD ERXIT,$+4
mov esp,[ERR.FRAME] ;restore stack to frame
pop dword[ERR.FRAME] ;restore previous frame
pop esi ;restore IP
NEXT
HEAD gettype,$+4 ;entry--type
movzx ebx,word[ebx-8]
and ebx,ALIGN_NONCE
NEXT
;;-------------------------------------------------------------------
;; ' [srcword]
;; Parse the input stream and find the word.
;; Compile: <lit><word>
;; If srcword is a number, the number, compile lit.
HEADN TICK,"'",docol,1 ;IMMEDIATE (parses!)
dd ws,parse
dd zbranch,.err
dd lit,lit,comma ;compile <lit>
dd comma ;compile parsed value
dd return
.err:
dd lit,ERRNUM_PARSE
dd ERXIT
HEAD COMPILE.ONE,docol
dd parse ;don't forget to WS prior to this
dd zbranch,.number
.word:
dd xdup, gettype
dd zbranch,.proc
;;; Immediate word; execute now
.self: dd execute,return
;;; If a procedure, simply compile its token
.proc: dd comma,return
;;; Number? Compile <lit><number>
.number:
;dd sys
dd lit,lit,comma ;compile <LIT>
dd comma ;compile <number>
dd return
HEAD COMPILE,docol
dd ws,COMPILE.ONE
dd return
HEAD COMPILE.UNTIL,docol ;delim
dd branch,.in
.ne: dd COMPILE.ONE ;todo: fix redundant hashing
.in: dd ws
dd xdup,XHASH,equal ;is current hash = delim?
dd zbranch,.ne
dd drop,skipword ;delimiter skipped
dd return
;; Hashes for closing braces of all sorts
;;
hCBRACE = $D80C1648
hCPAREN = $2C0C9A84
hCCURLY = $F80C48A8
;; ( paren is a grouping operator; compiled expression
HEADN OPAREN,"(",docol,1 ;immediate!
dd lit,hCPAREN,COMPILE.UNTIL,return
;; { is a compiling operator (suppresses execution)
HEADN OCURLY,"{",docol,1 ;immediate!
dd lit,hCCURLY,COMPILE.UNTIL ; compile some code
dd HEREfetch,RUNPTR,xstore ; assure non-interpretation
dd return
;; Create a header with specified name hash
;; (hash--)
HEAD CREATE,docol
dd LATEST,fetch,comma ; backlink
dd comma ; hash
dd HEREfetch,LATEST,xstore ; update link chain
dd return
HEADN colon,":",docol,1
dd ws,XHASH,skipword ; hash from source
dd CREATE
dd lit,docol,comma
dd HEREfetch,RUNPTR,xstore ; compile, do not interp
dd return
HEADN XIMMEDIATE,"IMMEDIATE",$+4,1
mov eax, [LATEST+4]
or dword[eax-8],1
NEXT
; lit - ( -- num) push the value in the cell straight after lit.
;;-------------------------------------------------------------------
;; When executed, load the next slot as a literal into TOS
;;
HEADN lit,"lit",$+4
lodsd ;load next token
.load:
DSTACK
.dload:
push ebx
RSTACK
xchg eax,ebx ; into TOS
NEXT
;;; string ( -- str cnt)
HEADN zero,"zero",$+4
xor eax,eax
jmp lit.load
HEADN one,"one",$+4
mov eax,1
jmp lit.load
; rot - ( x y z -- y z x )
HEAD rot,$+4
DSTACK
pop edx
pop eax
push edx
jmp lit.dload
; # swap - ( x y -- y x ) exchange x and y
HEAD swap,$+4
DSTACK
pop eax
jmp lit.dload
HEAD hexb,$+4
shl ebx,24
mov ecx,2
jmp hexloop
HEAD hexw,$+4
shl ebx,16
mov ecx,4
jmp hexloop
HEAD hexd,$+4
mov ecx,8
hexloop:
rol ebx,4
pusha
mov eax,4
and ebx,$0000000F ;nybble
lea ecx,[4+HEXTAB+ebx] ;address of character
mov ebx,1 ;stdout
mov edx,ebx ;length 1
int 0x80
popa
loop hexloop
jmp drop.popret
;;============================================================================
;; Lambdas are unexecuted blocks of code, passed by address:
;; 0 4
;; <lambda><addr-after-lambda>....
HEAD lambdaop,$+4
DSTACK
push ebx
RSTACK
lodsd ; eax = post-lambda
mov ebx,esi ; ebx = start of lambda
xchg esi,eax ; adjust IP past lambda
NEXT
; (lambda--) evaluate a lambda and continue
HEAD eval,$+4
push esi
mov esi,ebx
jmp drop.popret
; , - ( x -- ) compile x to the current definition.
; Stores the number on the stack to the memory location currently
; pointed to by dp.
HEADN comma,",",$+4
mov edi,[HERE+4]
xchg eax,ebx
stosd
mov [HERE+4],edi
jmp drop.popret
; drop - ( x -- ) remove x from the stack.
HEAD drop,$+4
.popret:
DSTACK
pop ebx
RSTACK
NEXT
; dup - ( x -- x x ) add a copy of x to the stack
HEADN xdup,"dup",$+4
DSTACK
push ebx
RSTACK
NEXT
; -------------------
; Flow Control
; -------------------
; 0branch - ( x -- ) jump if x is zero
HEADN zbranch,"0branch",$+4
lodsd ;eax = address to maybe jump to
test ebx,ebx ;zr?
cmovz esi,eax
@@: jmp drop.popret
HEADN nzbranch,"nzbranch",$+4
lodsd ;eax = address to maybe jump to
test ebx,ebx ;zr?
cmovnz esi,eax
jmp drop.popret
; branch - ( -- ) unconditional jump
HEADN branch,"branch",$+4
mov esi,[esi]
NEXT
HEADN xpush,"push",$+4
push ebx
jmp drop.popret
HEADN xpop,"pop",$+4
DSTACK
push ebx
RSTACK
pop ebx
NEXT
; ( ..n -- nth) 0 nth = dup; 1 nth = over; etc.
HEAD nth,$+4
mov ebx,[ebp + ebx*4]
NEXT
HEADN xover,"over",$+4
DSTACK
push ebx
RSTACK
mov ebx,[ebp+4]
NEXT
; -------------------
; Maths / Logic
; -------------------
; + - ( x y -- z) calculate z=x+y then return z
HEADN plus,"+",$+4
DSTACK
pop eax
RSTACK
add ebx,eax
NEXT
; + - ( x y -- z) calculate z=x-y then return z
HEADN minus,"-",$+4
DSTACK
pop eax
RSTACK
sub eax,ebx
xchg eax,ebx
NEXT
HEADN shiftlt,"<<",$+4
mov ecx,ebx
DSTACK
pop ebx
RSTACK
shl ebx,cl
NEXT
HEADN is_negative,"neg?",$+4
DSTACK
push ebx
RSTACK
shl ebx,1
jmp equal.sbb
; = ( x y -- flag ) return true if x=y
HEADN equal,"=",$+4
DSTACK
pop eax ; if = if !=
sub ebx,eax ; 0, ZR !0, NZ
RSTACK
sub ebx,1 ;-1, C ?, NC
.sbb:
sbb ebx,ebx ; -1 0
NEXT
; < ( x y -- flag ) return true if x < y
HEADN less,"<",$+4
DSTACK
pop eax ;eax = x
RSTACK
cmp eax,ebx ;c, want true
setge al
movzx ebx,al
sub ebx,1
NEXT
HEADN minus1,"1-",$+4
dec ebx
NEXT
HEADN plus1,"1+",$+4
inc ebx
NEXT
; (val,addr--)
HEADN plusstore,"+!",$+4
DSTACK
pop eax
add [ebx],eax
pop ebx
RSTACK
NEXT
HEADN minusstore,"-!",$+4
DSTACK
pop eax
sub [ebx],eax
pop ebx
RSTACK
NEXT
;; a,b -- rem,res
HEADN smod,"/MOD",$+4
mov eax,[ebp]
xor edx,edx
idiv ebx
jmp ummod.1
;;; a,b --- rem,res
HEADN ummod,"UM/MOD",$+4
mov eax,[ebp] ;dividing this
xor edx,edx
div ebx ;by tos
.1: xchg eax,ebx ;tos is result
mov [ebp],edx ;remainder above
NEXT
;; (a,b -- a*b)
HEADN umul,"*",$+4
xchg eax,ebx
xor edx,edx
mul dword[ebp]
add ebp,4
xchg eax,ebx
NEXT
; -------------------
; Peek and Poke
; -------------------
; @ - ( addr -- x ) read x from addr
HEADN fetch,"@",$+4
mov ebx,[ebx]
NEXT
HEADN fetchc,"@c",$+4
movzx ebx,byte[ebx]
NEXT
HEADN fetchw,"@w",$+4
movzx ebx,word[ebx]
NEXT
; ! - ( x addr -- ) store x at addr
HEADN xstore,"!",$+4
DSTACK
pop dword[ebx]
.1: pop ebx
RSTACK
NEXT
HEADN xstorec,"!c",$+4
DSTACK
pop eax
mov [ebx],al
jmp xstore.1
HEADN xstorew,"!w",$+4
DSTACK
pop eax
mov [ebx],ax
jmp xstore.1
; execute - ( xt -- ) call the word's xt
HEAD execute,$+4
mov eax,ebx
DSTACK
pop ebx
RSTACK
jmp dword[eax]
if CFG_DEBUG
HEAD dumpbyte,docol
dd xdup,fetch,hexb,space,plus1,return
HEAD dumpascii,docol
dd xdup,fetch,printable,emit,plus1,return
HEAD dump16,docol ;addr
dd xdup,hexd,space,space
dd lit,16,xpush ;16 times
@@: dd dumpbyte
dd dbra,@b
dd space
dd lit,16,minus
dd lit,16,xpush ;16 times
@@: dd dumpascii
dd dbra,@b
dd cr,return
HEAD hd,docol
dd dump16,dump16,dump16,dump16,return
end if
HEADN return,";",$+4
pop esi
NEXT
;;; ============================================================================
;;; Strings (short) are stored as:
;;; <_strlit><bytesize><string...>..aligned
;;;
HEAD _strlit,$+4
lodsb ;load string size (byte)
DSTACK
push ebx
push esi ;
RSTACK
movzx ebx,al ;TOS = cnt
lea esi,[esi+ebx+1+1] ;bump IP, aligning. +1 for the 0-term
and esi,ALIGN_MASK;
NEXT
;;; primitive to copy string from source into dictionary, preceded by its byte
;;; size. Line boundary may not be crossed!
ANON _getstr,$+4
push esi
mov esi,[PARSE.PTR+4]
mov edi,[HERE+4]
mov edx,edi ;edx points at count byte
xor ecx,ecx
lodsb ;skip past space following "
.loop:
inc ecx
stosb
lodsb
test al,al
je .err
cmp al,'"' ;"
jne .loop
xor eax,eax
stosb ;null-term
dec ecx ;-1 for the byte count itself.
mov [edx],cl ;byte count, update
add edi,ALIGN_NONCE
and edi,ALIGN_MASK ;align
mov [HERE+4],edi
mov [PARSE.PTR+4],esi ;just past "
pop esi
NEXT
.err:
DSTACK
push ebx
RSTACK
mov ebx,ERRNUM_RANGE
jmp ERXIT+4
;;; " string" At compile time, compile the string that follows.
;;; at runtime, (--string,size)
HEADN stringlit,'"',docol,1 ;"
dd lit,_strlit,comma ; compile <_strlit>
dd _getstr ; copy string
dd return
;;; ." msg" At compile time, compile string followed by <type>
;;; At runtime, print the message.
HEADN prstringlit,'."',docol,1 ;"
dd stringlit
dd lit,type,comma
dd return
if 0
;;; --------------------------------------------------------------------
;;; CONTROL STRUCTURES
;;; compile: <push>|... <timesp><loopstart>
HEADN xtimes,"times",docol,1 ;(n-- n times (...)
dd lit,xpush,comma ;compile <push>. During compile,
dd HEREfetch,xpush ;save target address on RSP
dd ws,COMPILE.ONE ;compile expression
dd lit,dbra,comma ;complile <decibranz>
dd xpop,comma ;compile target
dd return
hthanx = $07596E46
HEADN xif,"if",docol,1
dd FIXUP.IF,fetch,xpush ;reentrant: push old IF
dd lit,zbranch,comma ;compile <zbranch>
dd HEREfetch,FIXUP.IF,xstore ;this is the fixup position
dd zero,comma
dd lit,hthanx,COMPILE.UNTIL ;compile to thanx
dd HEREfetch,FIXUP.IF,fetch,xstore ;fixup to here, past thanx
dd xpop,FIXUP.IF,xstore ;restore for reentrant if
dd return
HEADN xelse,"else",docol,1
dd FIXUP.IF,fetch,zbranch,.err
dd lit,branch,comma ;complie <branch> over else clause
dd HEREfetch,zero,comma ;keep next fixup position
dd HEREfetch,FIXUP.IF,fetch,xstore ;fixup if's target
dd FIXUP.IF,xstore ;and save else's fixup
dd return
.err: dd lit,$99,ERXIT
end if
if 1
;;; Loader
;;;
;;;(handle--) Load a forth file from an open file handle
HEADN xloadh,"loadh",docol
dd HANDLE.IN, fetch, xpush ; keep old input handle on stack
dd HANDLE.IN, xstore ; use provided handle
dd ERR.CATCH,xdup,nzbranch,.err
dd drop,parsereset ; will force a line load
dd INTERP ; no way out except EOF
.err:
dd HANDLE.IN,fetch,osclose,drop
dd xpop,HANDLE.IN,xstore
dd lit,ERRNUM_EOF,xover,equal,nzbranch,.err1
dd ERXIT
.err1:
dd drop,parsereset
dd return
end if
if 0
;;HEADN included,"included;",docol
;; dd uncompile
;; dd xloadh
;; dd xpop,drop ;return up
;; dd return
HEADN included,"included",docol
dd drop
dd xpush,zero,zero,xpop ;0,0,path
dd osopen
dd xloadh
dd return
;;;(name,namelen--)
HEADN included_ret,"included;",docol
dd drop
dd xpush,zero,zero,xpop ; --0,0,path
dd osopen
dd uncompile
dd xloadh
dd xpop,drop
dd return ; --handle or error..
end if
FINALHEAD = LASTHEAD
BOOTSTRAP: file "nforth.f"
db 0