#!/usr/bin/perl # -------- description + version history -------- #FOLD00 =head1 PerlForth an incremental compiler and interactive interpreter, based on a virtual machine, executing indirect threaded code. =cut my $version = 27; # to do: improved file interface. can only read source files to compile from now. # time of last change: # 20110428,ls 0.27 arithmetic on addresses may result in negative mem array indici. changed some memory primitives to unsign addresses # 20110427,ls problem with 64 bit. forcing to 32 bit for now. # 20110420,ls 0.26 string packing and unpacking using W type strings. # 20091001,ls 0.25 initialising catchframe in empty avoids undefined error handler when quitting in site-forth.4th # 20090930,ls 0.24 loading /usr/local/share/perlforth/site-forth.4th at start # 20090106,ls 0.23 fixes for 64bit Perl versions # 20090106,ls 0.22 can compile source from included file. # 20090103,ls 0.21 vocabularies. # 20090101,ls 0.20 prepared for vocabularies. # 20081228,ls 0.19 radix prefixes # 20081228,ls 0.18 catch, throw, top level error handler, fixed bug in hash which rendered does> defective # 20081228,ls 0.17 experimentally connected Perl exception handler to interpreter errors # 20081223,ls 0.16 does>, keymap lister, linked vars, defers, constants, arrays. # 20081223,ls 0.15 hilevelized/deperled many words. key is now deferred. cleanup. stuff added. # 20081221,ls 0.14 simulated disk loaded during boot, extending interpreter. # better compile-time word defining macros. # branching version which moves definitions to simulated disk. # 20081221,ls 0.13 simulated disk for testing compiling from file. # 20081221,ls 0.12 some string support: ." s" ," /string move$ # 20081220,ls 0.11 added move fill for next leave ?leave i j do ?do loop # 20081217,ls 0.10 numbers, if else then begin while repeat until again. # 20081217,ls 0.09 added [ ], create, variable, : ; colon definitions work. # 20081217,ls 0.08 input line is parsed now. "real" interpreter connected, # but compilation and numbers are stubs. # 20081217,ls 0.07 bit logic, comparison, keymap customizer, hide/reveal, skip/scan # 20081215,ls 0.06 debugging and cleanup # 20081215,ls 0.05 rudimentary command execution loop # 20081214,ls 0.04 rudimentary buffered line input, more primitives. # 20081213,ls 0.03 more run time words, primitives, flow control # 20081211,ls 0.02 added run time words, constants, minimal flow control # 20081210,ls 0.01 ITC inner interpreter executes lo- and hilevel use strict; use warnings; use Term::ReadKey; #use Term::ANSIColor; # -------- configuration items -------- #FOLD00 my $tibsize = 256; # size of terminal input buffer my $cell; # override. uses perl compilation width if undefined. # $cell = 0; # bits per cell determined by size perl has been compiled for # $cell = 0xffff; # 16 bit override $cell = 0xffffffff; # 32 bit override # $cell = 0xffffffffffffffff; # 64 bit override # 2011apr27,ls problem with 64 bit. forcing to 32 bit for now. # -------- simulated sources disk -------- #FOLD00 # simulated source disk, contents are loaded and compiled during boot my @disk = ( "forth only", "forth definitions", '#10 base !', ': binary 2 base ! ;', # ( -- ) ': octal 8 base ! ;', # ( -- ) ': decimal 10 base ! ;', # ( -- ) ': hex 16 base ! ;', # ( -- ) ": align ; immediate", # ( -- ) ": aligned ; immediate", # ( a1 -- a2 ) ": pad here 256 + ;", # ( -- a ) ": latest last @ ;", # ( -- a ) ": recurse latest , ; immediate", # ( -- ) "also hidden", ": compile r> skim , >r ;", # ( -- ) ": postpone ' , ; immediate", # ( -- ) ": literal ?comp (lit) (lit) , , ; immediate", # ( x -- ) ( -- x ) ": ['] ' postpone literal ; immediate", # ( -- ) ( -- a ) "previous", # --- chars and strings --- ': char bl parse drop c@ ;', # ( -- c ) ': [char] char postpone literal ; immediate', # ( -- ) ( -- c ) ': ctrl char $1F and ;', # ( -- c ) ': [ctrl] ctrl postpone literal ; immediate', # ( -- ) ( -- c ) ': \ 0 parse 2drop ; immediate', # ( -- ) ': // postpone \ ; immediate', # ( -- ) ': s( [char] ) parse ;', # ( -- a n ) ': ( s( 2drop ; immediate', # ( -- ) ': .( s( type ;', # ( -- ) "also hidden", ': move$ 2dup c! 1+ swap move ;', # ( a1 n a2 -- ) ': ,s here over 1+ allot move$ ;', # ( a n -- ) ': ," [char] " parse ,s ;', # ( -- ) ': s" ?comp [\'] (slit) , ," ; immediate', # ( -- ) ( -- a n ) ': ." ?comp [\'] (.") , ," ; immediate', # ( -- ) ( -- ) # --- flow control --- "definitions", ': resolve here - , ;', # ( a -- ) ': <resolve here over - swap ! ;', # ( a -- ) ": ?clause compile (0branch) ;", # ( -- ) ": clause compile (branch) ;", # ( -- ) ": mark here 0 , ;", # ( -- a ) "previous definitions", "also hidden", ': if ?comp ?clause mark 1 ; immediate', ': else ?comp 1 structured clause mark swap <resolve 2 ; immediate', ': then ?comp dup 2 = + 1 structured <resolve ; immediate', ': endif postpone then ; immediate', ': begin ?comp here 3 ; immediate', ': while ?comp 3 structured ?clause mark 4 ; immediate', ': repeat ?comp 4 structured swap clause resolve <resolve ; immediate', ': until ?comp 3 structured ?clause resolve ; immediate', ': again ?comp 3 structured clause resolve ; immediate', "definitions", ": docompiler create , , immediate", " does> ?comp skim , @ >r", " here innerloop exchange", " mark r> ;", ": loopcompiler create , , immediate", " does> ?comp skim >r @ structured r> , dup 1+ resolve <resolve innerloop ! ;", "previous definitions", "also hidden", "5 ' (do) docompiler do", "5 ' (?do) docompiler ?do", "6 ' (for) docompiler for", "5 ' (loop) loopcompiler loop", "5 ' (+loop) loopcompiler +loop", "6 ' (next) loopcompiler next", ": leave, ?comp innerloop @ ?dup 0= -26 and throw swap , , ;", ": leave ['] (leave) leave, ; immediate", ": ?leave ['] (?leave) leave, ; immediate", ": unloop ?comp innerloop @ 0= -26 and throw compile (unloop) ; immediate", # --- ': tuck swap over ;', # ( x1 x2 -- x2 x1 x2 ) ': pluck 2 pick ;', # ( x1 x2 x3 -- x1 x2 x3 x1 ) ': max 2dup < if swap then drop ;', # ( x1 x2 -- x1|x2 ) ': min 2dup > if swap then drop ;', # ( x1 x2 -- x1|x2 ) ': -rot rot rot ;', # ( x1 x2 x3 -- x3 x1 x2 ) ': (abort") if -2 dup r> count newerror throw then', ' r> count + >r ;', ': abort" ?comp compile (abort") ," ; immediate', # ( f -- ) ': link here swap exchange , ;', ': unlink dup @ ?dup if @ over ! then drop ;', # tricky: 'make new constants behave like "true" (which is a constant)' # tricky: 'make new deferred words behave like "key" (which is a deferred word)' # tricky: 'make new arrays behave like "keytable" (which is an array)' # tricky: 'make new vocabularies behave like "forth" (which is a vocabulary)' ": constant constants link create , [ ' true @ ] literal use ;", '1 constant cell', ": defer defers link create cell allot [ ' key @ ] literal use ;", ": array arrays link create dup , allot [ ' keytable @ ] literal use ;", ": vocabulary vocabularies link create 0 , 0 , [ ' forth @ ] literal use ;", ': variable variables link create cell allot ;', ": value constant ;", # values behave like constants. for now. ': vocs vocabularies begin @ ?dup while dup 1+ .name space repeat ;', 'previous', ': cell+ 1+ ; : char+ 1+ ;', ': cell- 1- ; : char- 1- ;', ': cells ; : chars ; ', ': range over + swap ;', # ( x1 n -- x2 x1 ) ': erase 0 fill ;', # ( a n -- ) ': blank bl fill ;', # ( a n -- ) ': c, 255 and , ;', # ( c -- ) ': within pluck < >r < r> or 0= ;', # ( n1 n2 n3 -- f ) ': printable bl 127 within ;', # ( c -- f ) ': emits swap dup 0> and', # ( u c -- ) ' 0 ?do dup emit loop drop ;', ': spaces bl emits ;', # ( u -- ) ': >body cell+ ; ', # ( a1 -- a2 ) ': body> cell- ; ', # ( a1 -- a2 ) ': word here >r parse r@ move$ r> ;', # ( c -- a ) ': lines >r', # ( a -- ) ' bl word count', ' fileopen', ' begin fileread', ' while r@ execute', ' repeat', ' fileclose rdrop ;', ": from fileopen", # ( a n -- ) " begin fileread", " while evaluate", " repeat fileclose ;", ': from" [char] " parse from ;', # ( -- ) # is and to identical yet but they will check to make sure the target is of proper type # therefore no factoring in these words, as these are in transition. "also hidden definitions", ": (was) r> dup cell+ >r @ >body @ ;", ": (is) r> dup cell+ >r @ >body ! ;", ": (to) r> dup cell+ >r @ >body ! ;", "previous definitions", "also hidden", ": was compiling if compile (was) exit then ' >body @ ; immediate", ": is compiling if compile (is) exit then ' >body ! ; immediate", ": to compiling if compile (to) exit then ' >body ! ; immediate", "previous", # --- obsolescent input parsing and vocabulary search. required by dpans94 --- ': find dup count hunt', # ( a1 -- a2 0 | a2 1 | a2 -1 ) ' dup if', # 1: immediate. -1: non-immediate ' nip dup name>', ' swap ?imm invert 1 or', ' then ;', # --- pictured number output conversion --- ': s>d dup 0< ;', # ( x -- d ) ': <# swap >r pad tuck r> ;', # ( d -- a x a x ) ': #> drop nip tuck - ;', # ( a x a x -- a x ) ': hold rot 1-', # ( a x c -- a x ) ' dup here < -17 and throw', ' -rot pluck c! ;', ': cipher dup 9 > if 7 + then [char] 0 + ;', # ( n -- c ) ': # base @ u/mod swap cipher hold ;', # ( a x -- a x ) ': #s begin # dup 0= until ;', # ( a x -- a x ) ': sign pluck 0< if [char] - hold then ;', # ( x a x -- x a x ) ': string <# #s sign #> ;', # ( n -- a n ) ': (.) s>d >r abs r> string ;', # ( n1 -- a n2 ) ': (u.) 0 string ;', # ( u -- a n ) ': . (.) type space ;', # ( n -- ) ': u. (u.) type space ;', # ( u -- ) ': (.r) over - spaces type ;', # ( a n1 n2 -- ) ': .r >r (.) r> (.r) ;', # ( n u -- ) ': u.r >r (u.) r> (.r) ;', # ( u1 u2 -- ) ': .b base exchange swap u. base ! ;', # ( n base -- ) ': .% 2 .b ;', # ( n -- ) ': .# 10 .b ;', # ( n -- ) ': .$ 16 .b ;', # ( n -- ) ': .s depth ?dup if', # ( -- ) ' for i pick . next', ' else ." stack empty"', ' then ;', ': number ?number 0= -24 and throw ;', # ( a n1 -- n2 ) # --- string words ': /string over min tuck 2>r + 2r> - ;', # ( a n1 n2 -- ) ': -trailing dup if dup for 1- 2dup + c@ bl <> ?leave next 1+ then ;', # left/right boundary, centered type ': typer ( a n1 n2 -- ) over - 0 max spaces type ;', ': typel ( a n1 n2 -- ) over - 0 max >r type r> spaces ;', ': typec ( a n1 n2 -- ) over - 0 max dup 2/ dup spaces - >r type r> spaces ;', # --- 'char, ^ctrlchar, >shellcommand input "also hidden definitions", ': toshell 1 /string drop 0 parse + over -', # ( a n -- ) ' compiling if compile (slit) ,s compile then shell ;', ": andchar nip swap 1+ c@ and compiling if postpone literal then ;", # ( a n -- c ) ": tochar 255 andchar ;", # ( a n -- c ) ": toctrl 31 andchar ;", # ( a n -- c ) ': dispatchable s" \'^>" rot scan nip ;', # ( c -- u ) " create action ] toshell toctrl tochar [", ': dispatch action + @ execute ;', # ( a n1 n2 -- ? ) ": prefixes over c@ dispatchable ?dup if 1- dispatch exit then", #" 2dup analyze_input if process_input exit then", " (notfound) ;", "' prefixes is notfound", # ( a n -- ? ) ': .? dup defined if . else drop ." undefined" then ;', ': .linknames', ' >r begin @ ?dup', ' while dup 1+', ' dup cr .name ." : "', ' >body @ r@ execute', ' repeat rdrop cr ;', ": .variables variables ['] .? .linknames ;", ": .constants constants ['] . .linknames ;", ": .arrays arrays ['] . .linknames ;", ": .defers defers ['] .name .linknames ;", ": .vocs vocabularies ['] . .linknames ;", ": user_interrupt -28 throw ;", "previous definitions", ': shell: create ," does> count shell ;', 'shell: page clear', 'shell: ps ps auxf|pager', 'shell: sh bash', ': command: create does> drop source shell postpone \ ;', 'command: ls', ': cls page ;', ': commandline ." (ctrl-D to exit)" sh ." ok" cr ;', ': .keys -1 keytable @ 0 do', ' i keytable @', ' ?dup if', " dup ['] nop <> if", ' cr ." ctrl-" i \'@ + emit', ' 3 spaces dup .name', ' then', ' drop', ' then', ' loop cr ;', # "0 keytable -1 keytable @ ' nop fill", "0 keytable bl ' nop fill", ": bindkey keytable ! ;", "also hidden", "' .arrays ^A bindkey", "' .constants ^B bindkey", "' user_interrupt ^C bindkey", "' commandline ^D bindkey", "' .defers ^E bindkey", " 0 ^H bindkey", " 0 ^I bindkey", " 0 ^J bindkey", "' .keys ^K bindkey", "' page ^L bindkey", "' order ^O bindkey", "' bye ^Q bindkey", "' .variables ^V bindkey", "' words ^W bindkey", "' .vocs ^X bindkey", ': fkey', ' begin (key)', ' dup bl < 0= unless', ' dup keytable @', ' ?dup while', ' execute drop', ' repeat ;', ': accept >r 0', # ( a n1 -- n2 ) ' begin dup r@ <>', ' while fkey dup 10 =', ' if r> 2drop dup >r', ' else decode', ' then', ' repeat swap r> 2drop ;', ': query tib dup tibsize accept dup #tib ! pushsource space ;', "definitions", ": (quit) empty postpone [ begin query interpret prompt again ;", "' (quit) is quit", ': (prompt) compiling 0= if ." ok" depth 0 ?do \'. emit loop then cr ;', "' (prompt) is prompt", "previous definitions", #": recent context @ >body @ ;", ": :noname ?exec here [ latest @ ] literal , ] ; immediate", ': up s" ./up" shell ;', ': doc s" ./doc" shell ;', #": bogo 1000000 0 do loop bye ; bogo", #' from hexdump.4th', # -- time ": time ( -- secs ) epoch 86400 mod ;", ": ##: ( u1 -- u2 ) base @ >r decimal # 6 base ! # ': hold r> base ! ;", ": .now ( -- ) time s>d <# ##: ##: #s #> type ;", ": now ( -- s m h ) time 60 /mod 60 /mod ;", # load site-forth.4th at start ' :noname ( -- ) s" /usr/local/share/perlforth/site-forth.4th" from ; catch drop', # ' \ dup -38 <> and throw', '.( Threaded Code Interpreter in Perl, version )', " version s>d <# # # '. hold #s #> type", " '. emit here . cr space", ); # -------- virtual machine data -------- # VM memory my @m; # main memory my @s; # user stack my @r; # return stack # global VM registers my $sp; # user stack pointer my $rp; # return stack pointer my $w; # word pointer my $ip; # instruction pointer # global interpreter/compiler variables my $dp = 0; # pointer to free VM mem my $wc = 0; # word count, analog the name field address my @header; # word headers my @body; # pointers to word code fields my @voclink; # pointer to index of next word of same vocabulary my @precedence; # reveal/precedence flags per word my $parsebuf; # pointer to current source buffer my $parsebuflen; # size of current source buffer my @sourcestack; # holds nested source buffer my %does; # helper hash for create .. does> simplification my $catchframe = 0; # pointer to prev catch/throw context (or 0) my $maxu = (-1|0); # determine cell size in bits $maxu = $cell if ($cell); # or use override my $wrap = $maxu+1; # modulo for trimming results to fit into cell my $msb = 1; # value with only the most significant bit set my $bits = 1; for (;$msb<$wrap/2;$msb+=$msb) {$bits++} #print "$msb, $bits"; my $revealbit = 1; my $precedencebit = 2; # variables residing in interpreter virtual memory space. sub comma { $m[$dp] = shift(@_); return $dp++; } my @vocstack; my $xlaststore = comma 0; my $xcurrentstore = comma 0; my $xcontextstore = comma 0; # -------- virtual machine -------- #$meow = $model ? sub { 'purr' } : sub { q/=^_^=/ }; $meow->(); sub nest { $r[++$rp] = $ip; $ip = $w+1; } sub unnest { $ip = $r[$rp--]; } my $unnest = $dp; $m[$dp++] = \&unnest; sub doconst { $s[++$sp] = $m[$w+1]; } sub dovar { $s[++$sp] = $w+1; } sub dodefer { $w = $m[$w+1]; $m[$w](); } sub dovoc { $m[$xcontextstore] = $w; } # -------- vocabularies -------- sub reveal { $precedence[$wc-1] |= $revealbit; } sub immediate { $precedence[$wc-1] |= $precedencebit; } sub hide { $precedence[$wc-1] &= ~$revealbit; } sub header { $header[$wc] = shift(@_); $body[$wc] = $dp; $precedence[$wc] = 0; $voclink[$wc] = $m[$m[$xcurrentstore]+2]; $m[$xlaststore] = $dp; $m[$m[$xcurrentstore]+1] = $dp; $m[$m[$xcurrentstore]+2] = $wc; $wc++; return $dp; } sub xlink { my $anchor = (shift(@_))+1; ($m[$anchor], $m[$dp]) = ($dp, $m[$anchor]); $dp++; } sub allot { $dp += shift(@_); } my $xvocabularies = comma \&dovar; comma 0; # a hand-built variable, needed early.(for linking # vocabularies needed to contain the link anchors # of variables, constant, vocabularies...) sub vocabulary { xlink $xvocabularies; my $addr = comma \&dovoc; comma 0; comma 0; # last cfa, last wc. return $addr; } my $xonlyvoc = vocabulary; sub only { $m[$xcontextstore] = $xonlyvoc; } my $xforth = vocabulary; sub forth { $m[$xcontextstore] = $xforth; } my $xhidden = vocabulary; sub hidden { $m[$xcontextstore] = $xhidden; } sub definitions { $m[$xcurrentstore] = $m[$xcontextstore] } hidden; definitions; header ""; # must be header 0 (0 represents end # of chain, common for all vocabularies) # to do: # hand-craft a link anchor, used as link anchor for list of link anchors here. # link "vocabularies" link anchor to this link anchor. later, create a header, # link "anchors" to itself. moala - mother of all link anchors. header "vocabularies"; reveal; # header for vocabularies link anchor. $body[$wc-1] = $xvocabularies; only; definitions; header "forth"; reveal; $body[$wc-1] = $xforth; forth; definitions; header "only"; reveal; $body[$wc-1] = $xonlyvoc; header "hidden"; reveal; $body[$wc-1] = $xhidden; # -------- macros: defining words -------- #FOLD00 sub compile { my $addr = $dp; foreach my $i (0..$#_) { comma $_[$i]; } return $addr; } sub colon { header shift(@_); return compile \&nest; } sub semicolon { compile $unnest; reveal; } sub unnamedprimitive { return compile shift(@_); } sub primitive { header shift(@_); reveal; return compile shift(@_); } sub create { header shift(@_); reveal; return compile \&dovar; } sub xnop { } my $xnop = primitive "nop", \&xnop; hidden; definitions; my $xconstants = create "constants"; comma 0; sub constant { xlink $xconstants; header shift(@_); reveal; return compile \&doconst, shift(@_); } my $xvariables = create "variables"; comma 0; sub variable { xlink $xvariables; header shift(@_); reveal; return compile \&dovar, shift(@_); } sub alias { my $cfa = $body[$wc-1]; header shift(@_); reveal; $body[$wc-1] = $cfa; return $cfa; } my $xdefers = create "defers"; comma 0; sub defer { xlink $xdefers; header shift(@_); reveal; return compile \&dodefer, shift(@_); } # ( a n -- ) packs chars at $m[$a..$a+n-1] into string which is returned. sub string { my $x2 = $s[$sp--]&$cell; my $x1 = $s[$sp--]&$cell; return pack "W*",@m[$x1..$x1+$x2-1]; } # ( a -- n ) unpacks chars of string par to $m[$a..] sub unstring { my @arg = unpack "W*", $_[0]; $w = @arg; (my $addr, $s[$sp]) = ($s[$sp], $w); @m[$addr..$addr+$w-1] = @arg; } # -------- vocabularies search order -------- #FOLD00 only; definitions; sub xalso { push @vocstack, $m[$xcontextstore]; } primitive "also", \&xalso; forth; definitions; my $xlast = constant "last", $xlaststore; constant "context", $xcontextstore; constant "current", $xcurrentstore; sub xprevious { $m[$xcontextstore] = pop @vocstack if ($#vocstack >= 0); } primitive "previous", \&xprevious; sub xonly { $m[$xcontextstore] = $xonlyvoc; @vocstack = $xonlyvoc; } my $xonly = primitive "only", \&xonly; sub xdefinitions { $m[$xcurrentstore] = $m[$xcontextstore]; } primitive "definitions", \&xdefinitions; # -------- error handling -------- #FOLD00 my %throwmessage = ( -1 => "aborted", -2 => "aborted", -3 => "stack overflow", -4 => "stack underflow", -5 => "return stack overflow", -6 => "return stack underflow", # -7 => "do loops nested too deeply", # -8 => "dictionary overflow", -9 => "invalid memory address", -10 => "division by zero", -11 => "result out of range", -12 => "argument type mismatch", -13 => "word not found", -14 => "use only during compilation", -15 => "invalid forget", -16 => "attempt to use zero-length string as name", -17 => "pictured numeric output string overflow", -18 => "parsed string overflow", # -19 => "word name too long", -20 => "write to a read-only location", -21 => "unsupported operation", -22 => "unstructured", # -23 => "address alignment exception", -24 => "invalid numeric argument", -25 => "return stack imbalance", -26 => "loop parameters unavailable", -27 => "invalid recursion", -28 => "user interrupt", -29 => "compiler nesting", -30 => "obsolescent feature", -31 => ">BODY used on non-CREATEd definition", -32 => "invalid name argument", -33 => "Block read exception", -34 => "Block write exception", -35 => "Invalid block number", -36 => "Invalid file position", -37 => "File I/O exception", -38 => "File not found", # additional error messages: -64 => "use only while interpreting", -65 => "executed BODY> on a non-body address", -67 => "TO must be used on a VALUE", -72 => "Invalid memory region specifier, or heap corrupted", ); # used by abort" to introduce new abort messages sub xnewerror { # ( n1 a n2 -- ) $throwmessage{$s[$sp--]} = string; } primitive "newerror", \&xnewerror; # executed at the end of word executed by catch. sub xbrthrow0 { ($ip, $sp, $catchframe) = @r[$rp-2..$rp]; # restore previous catch context $rp -= 3; $s[$sp] = 0; # throw value 0 } my $xbrthrow0 = compile unnamedprimitive \&xbrthrow0; # not a primitive - returning to. sub xexecute { $w = $s[$sp--]; $m[$w](); } my $xexecute = primitive "execute", \&xexecute; # ( a -- x ) sub xcatch { $rp += 3; # room for new catch frame @r[$rp-2..$rp] = ($ip, $sp, $catchframe); # save previous catch context $catchframe = $rp; # point to this catch frame $r[++$rp] = $xbrthrow0; # inject return address to throw0 xexecute; # call word running under catch } my $xcatch = primitive "catch", \&xcatch; # ( err -- ) sub throw { my $exception = shift; # throw value other than 0? if ($exception) { if ($catchframe) { # does previous catch frame exist? $rp = $catchframe; # yes: point to prev catch frame ($ip, $sp, $catchframe) = @r[$rp-2..$rp]; # restore previous catch context $rp -= 3; $s[$sp] = $exception; # return throw value } else { # throw without catch: top level die $exception; } } } sub xthrow { throw $s[$sp--]; } my $xthrow = primitive "throw", \&xthrow; hidden; definitions; sub xbrerror { throw -1; } sub xstackunderflow { throw -4; } sub xbrnotfound { throw -13; } my $xbrerror = primitive "(error)", \&xbrerror; my $xstackunderflow = unnamedprimitive \&xstackunderflow; my $xbrnotfound = primitive "(notfound)", \&xbrnotfound; my $xnotfound = defer "notfound", $xbrnotfound; my $xlastword = create "lastword"; allot 2; forth; definitions; my $xerror = defer "error", $xbrerror; # -------- run time words: literals and flow control -------- hidden; definitions; sub xlit { $s[++$sp] = $m[$ip++]; } my $xlit = primitive "(lit)", \&xlit; sub xslit { my $count = $m[$ip++]; $sp += 2; @s[$sp-1..$sp] = ($ip, $count); $ip += $count; } my $xslit = primitive '(slit)', \&xslit; sub xbrdotquote { xslit; print string; } my $xbrdotquote = primitive '(.")', \&xbrdotquote; sub xbranch { $ip += $m[$ip]; } my $xbranch = primitive "(branch)", \&xbranch; sub xbranch0 { if ($s[$sp--]) { $ip++; } else { $ip += $m[$ip]; } } my $xbranch0 = primitive "(0branch)", \&xbranch0; sub xbrfor { $r[++$rp] = $s[$sp]-1; $r[++$rp] = $s[$sp--]-1; $ip++; } my $xbrfor = primitive "(for)", \&xbrfor; sub xbrnext { if ($r[$rp]--) { $ip += $m[$ip]; } else { $rp -= 2; $ip++; } } my $xbrnext = primitive "(next)", \&xbrnext; sub xbrdo { $rp += 2; @r[$rp-1..$rp] = @s[$sp-1..$sp]; $sp -= 2; $ip++; } my $xbrdo = primitive "(do)", \&xbrdo; sub xbrqdo { if ($s[$sp] == $s[$sp-1]) { $ip += $m[$ip]; } else { $rp += 2; @r[$rp-1..$rp] = @s[$sp-1..$sp]; $ip++ ; } $sp -= 2; } my $xbrqdo = primitive "(?do)", \&xbrqdo; sub xbrleave { $rp -= 2; $ip = $m[$ip]; $ip += $m[$ip]; } my $xbrleave = primitive "(leave)", \&xbrleave; sub xbrqleave { if ($s[$sp--]) { xbrleave; } else { $ip++; } } my $xbrqleave = primitive "(?leave)", \&xbrqleave; sub xbrloop { if (++$r[$rp] != $r[$rp-1]) { # index+1 != limit $ip += $m[$ip]; # add branch offset to instruction pointer } else { $rp -= 2; # discard loop parameters $ip++; # skip branch offset } } my $xbrloop = primitive "(loop)", \&xbrloop; sub xbrplusloop { # determine loop exit condition by simulating sign overflow: $w = $r[$rp] - $r[$rp-1]; # temp = index-limit $r[$rp] += $s[$sp--]; # index += loop increment if ((($r[$rp] - $r[$rp-1]) ^ $w) < $msb) { # sign change of index-limit before and after? $ip += $m[$ip]; # no: add branch offset to instruction pointer } else { $rp -= 2; # yes: exit loop: discard loop parameters $ip++; # skip branch offset } } my $xbrplusloop = primitive "(+loop)", \&xbrplusloop; sub xbrunloop { $rp -= 2; } my $xbrunloop = primitive "(unloop)", \&xbrunloop; sub doarray { if (($s[$sp] < $m[$w+1]) && ($s[$sp] >= -1)) { # legal index. -1 addresses array size $s[$sp] += ($w+2); # index > address } else { throw -24; } } my $xarrays = create "arrays"; comma 0; sub array { xlink $xarrays; header shift(@_); reveal; my $cfa = compile \&doarray; my $count = shift(@_); comma $count; allot $count; return $cfa; } forth; definitions; # -------- constants, variables -------- #FOLD00 my $xesc = constant "esc", 27; my $xbl = constant "bl", 32; my $xfalse = constant "false", 0; my $xzero = alias "0"; my $xtrue = constant "true", -1; my $xminusone = alias "-1"; constant "msb", $msb; constant "maxu", $maxu; my $xstate = variable "state", 0; my $xbase = variable "base", 10; my $xhashtib = variable "#tib", 0; my $xtoin = variable ">in", 0; my $xinnerloop = variable "innerloop", 0; my $xtib = create "tib"; allot $tibsize; constant "version", int $version; constant "tibsize", $tibsize; my $xkeytable = array "keytable", 32; # -------- stack handling -------- sub xdrop { $sp--; } sub xrdrop { $rp--; } sub x2drop { $sp -= 2; } sub xsp { $s[++$sp] = $sp; } sub xrp { $s[++$sp] = $rp; } sub xdup { $s[++$sp] = $s[$sp]; } sub xqdup { $s[++$sp] = $s[$sp] if ( $s[$sp]); } sub xover { $s[++$sp] = $s[$sp-1]; } sub xnip { $s[$sp] = $s[$sp--]; } sub xpick { $s[$sp] = $s[$sp-$s[$sp]-1]; } sub xdepth { $s[++$sp] = $sp; } sub xswap { @s[$sp-1..$sp] = ($s[$sp], $s[$sp-1]); } sub xrot { @s[$sp-2, $sp-1, $sp] = @s[$sp-1, $sp, $sp-2]; } sub x2dup { $sp += 2; @s[$sp-1..$sp] = @s[$sp-3..$sp-2]; } sub x2over { $sp += 2; @s[$sp-1..$sp] = @s[$sp-5..$sp-4]; } sub x2swap { @s[$sp-3..$sp] = (@s[$sp-1..$sp], @s[$sp-3..$sp-2]);} sub xtor { $r[++$rp] = $s[$sp--]; } sub xrfrom { $s[++$sp] = $r[$rp--]; } sub xrfetch { $s[++$sp] = $r[$rp]; } sub x2tor { $r[++$rp] = $s[$sp--]; $r[++$rp] = $s[$sp--]; } sub x2rfrom { $s[++$sp] = $r[$rp--]; $s[++$sp] = $r[$rp--]; } sub x2rfetch { $s[++$sp] = $r[$rp]; $s[++$sp] = $r[$rp-1]; } my $xdup = primitive "dup", \&xdup; my $xqdup = primitive "?dup", \&xqdup; my $xdrop = primitive "drop", \&xdrop; my $xover = primitive "over", \&xover; my $xswap = primitive "swap", \&xswap; my $xrot = primitive "rot", \&xrot; my $xnip = primitive "nip", \&xnip; my $x2dup = primitive "2dup", \&x2dup; my $x2drop = primitive "2drop", \&x2drop; my $x2swap = primitive "2swap", \&x2swap; my $x2over = primitive "2over", \&x2over; my $xpick = primitive "pick", \&xpick; my $xdepth = primitive "depth", \&xdepth; my $xtor = primitive ">r", \&xtor; my $xrfrom = primitive "r>", \&xrfrom; my $xrfetch = primitive "r@", \&xrfetch; my $xrdrop = primitive "rdrop", \&xrdrop; my $x2tor = primitive "2>r", \&x2tor; my $x2rfrom = primitive "2r>", \&x2rfrom; my $x2rfetch = primitive "2r@", \&x2rfetch; primitive "rp", \&xrp; primitive "sp", \&xsp; # -------- flow control -------- #FOLD00 my $xexit = primitive "exit", \&unnest; sub xi { $s[++$sp] = $r[$rp]; } my $xi = primitive "i", \ξ sub xj { $s[++$sp] = $r[$rp-2]; } my $xj = primitive "j", \&xj; sub xuse { $m[$body[$wc-1]] = $s[$sp--]; } my $xuse = primitive "use", \&xuse; sub xunless { $ip = $r[$rp--] if ($s[$sp--]) } my $xunless = primitive "unless", \&xunless; sub xbye { print "\n"; exit; } my $xbye = primitive "bye", \&xbye; sub XIF { comma $xbranch0; $s[++$sp] = $dp++; } sub XELSE { comma $xbranch; my $offs = $s[$sp]; $s[$sp] = $dp++; $m[$offs] = $dp-$offs; } sub XTHEN { $m[$s[$sp]] = $dp-$s[$sp]; $sp--; } sub XBEGIN { $s[++$sp] = $dp; } sub XAGAIN { comma $xbranch; comma $s[$sp--]-$dp; } sub XUNTIL { comma $xbranch0; comma $s[$sp--]-$dp; } sub XWHILE { XIF; } sub XREPEAT { xswap; XAGAIN; XTHEN; } # -------- bitwise logic -------- #FOLD00 sub xand { $s[$sp-1] &= ($s[$sp--] % $wrap); $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $xand = primitive "and", \&xand; sub xor { $s[$sp-1] |= ($s[$sp--] % $wrap); $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $xor = primitive "or", \&xor; sub xxor { $s[$sp-1] ^= ($s[$sp--] % $wrap); $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $xxor = primitive "xor", \&xxor; sub xinvert { $s[$sp] ^= -1; $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $xinvert = primitive "invert", \&xinvert; sub x2mul { $s[$sp] <<= 1; $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $x2mul = primitive "2*", \&x2mul; sub x2div { $s[$sp] >>= 1; } my $x2div = primitive "2/", \&x2div; sub xrshift { $s[$sp-1] >>= ($s[$sp--] & $bits-1); } my $xrshift = primitive "rshift", \&xrshift; alias ">>"; sub xlshift { $s[$sp-1] <<= ($s[$sp--] & $bits-1); $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp]>=$msb; } my $xlshift = primitive "lshift", \&xlshift; alias "<<"; # -------- comparison -------- sub xequals { $s[--$sp] = -($s[$sp] == $s[$sp-1]); } my $xequals = primitive "=", \&xequals; sub xnotequals { $s[--$sp] = -($s[$sp] != $s[$sp-1]); } my $xnotequals = primitive "<>", \&xnotequals; sub xless { my $tos = $s[$sp--]; $s[$sp] = -($s[$sp] < $tos); } my $xless = primitive "<", \&xless; sub xuless { my $tos = $s[$sp--]|0; $s[$sp] = -(($s[$sp]|0) < $tos); } my $xuless = primitive "u<", \&xuless; sub xgreater { my $tos = $s[$sp--]; $s[$sp] = -($s[$sp] > $tos); } my $xgreater = primitive ">", \&xgreater; sub xugreater { my $tos = $s[$sp--]|0; $s[$sp] = -(($s[$sp]|0) > $tos); } my $xugreater = primitive "u>", \&xugreater; sub xzeroequals { $s[$sp] = -(!$s[$sp]); } my $xzeroequals = primitive "0=", \&xzeroequals; sub xzeronotequals { $s[$sp] = -(!!$s[$sp]); } my $xzeronotequals = primitive "0<>", \&xzeronotequals; sub xzeroless { $s[$sp] = -($s[$sp] < 0); } my $xzeroless = primitive "0<", \&xzeroless; sub xzeromore { $s[$sp] = -($s[$sp] > 0); } my $xzeromore = primitive "0>", \&xzeromore; # -------- arithmetic -------- sub xoneplus { $s[$sp]++; $s[$sp] -= $wrap if $s[$sp] >= $msb; } my $xoneplus = primitive "1+", \&xoneplus; sub xoneminus { $s[$sp]--; $s[$sp] += $wrap if $s[$sp] < -$msb; } my $xoneminus = primitive "1-", \&xoneminus; sub xplus { $s[$sp-1] += $s[$sp--]; $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp] >= $msb; } my $xplus = primitive "+", \&xplus; sub xminus { $s[$sp-1] -= $s[$sp--]; $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp] >= $msb; } my $xminus = primitive "-", \&xminus; sub xmul { $s[$sp-1] *= $s[$sp--]; $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp] >= $msb; } my $xmul = primitive "*", \&xmul; sub xdiv { if (!$s[$sp]) { throw -10; } $s[$sp-1] /= $s[$sp--]; } my $xdiv = primitive "/", \&xdiv; sub xmod { if (!$s[$sp]) { throw -10; } $s[$sp-1] %= $s[$sp--]; } my $xmod = primitive "mod", \&xmod; sub xstarslash { if (!$s[$sp]) { throw -10; } $s[$sp-2] *= $s[$sp-1]; $s[$sp-2] /= $s[$sp]; $sp -= 2; $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp] >= $msb; } my $xstarslash = primitive "*/", \&xstarslash; # ( n1 n2 -- n3 n4 ) sub xslashmod { @s[$sp-1..$sp] = ($s[$sp-1]%$s[$sp], int $s[$sp-1]/$s[$sp]); } my $xslashmod = primitive "/mod", \&xslashmod; # ( n1 n2 -- n3 n4 ) sub xuslashmod { $s[$sp-1]%=$wrap; @s[$sp-1..$sp] = ($s[$sp-1]%$s[$sp], int $s[$sp-1]/$s[$sp]); } my $xuslashmod = primitive "u/mod", \&xuslashmod; sub xstarslashmod { if (!$s[$sp]) { throw -10; } $s[$sp-2] *= $s[$sp-1]; @s[$sp-2..$sp-1] = ($s[$sp-2]%$s[$sp], int $s[$sp-2]/$s[$sp]); $s[$sp]%=$wrap; $s[$sp]-=$wrap if $s[$sp] >= $msb; $sp--; } my $xstarslashmod = primitive "*/mod", \&xstarslashmod; sub xabs { $s[$sp] = abs($s[$sp]); } my $xabs = primitive "abs", \&xabs; sub xnegate { $s[$sp] = -$s[$sp]; } my $xnegate = primitive "negate", \&xnegate; # -------- memory access -------- #FOLD00 sub xfetch { $s[$sp] = $m[$s[$sp]&$cell]; } my $xfetch = primitive "@", \&xfetch; sub xcfetch { $s[$sp] = $m[$s[$sp]&$cell] & 255; } my $xcfetch = primitive "c@", \&xcfetch; # ( a -- d ) sub x2fetch { my $addr = $s[$sp++]&$cell; @s[$sp-1..$sp] = @m[$addr..$addr+1]; } my $x2fetch = primitive "2@", \&x2fetch; sub xstore { $m[$s[$sp]&$cell] = $s[$sp-1]; $sp-=2; } my $xstore = primitive "!", \&xstore; sub xcstore { $m[$s[$sp]&$cell] = $s[$sp-1] & 255; $sp-=2; } my $xcstore = primitive "c!", \&xcstore; # ( d a -- ) sub x2store { my $addr = $s[$sp--]&$cell; @m[$addr..$addr+1] = @s[$sp-1..$sp]; $sp -= 2 } my $x2store = primitive "2!", \&x2store; sub xplusstore { $m[$s[$sp]&$cell] += $s[$sp-1]; $sp-=2; } my $xplusstore = primitive "+!", \&xplusstore; sub xcount { $s[++$sp] = $m[$s[$sp]++&$cell] & 255; } my $xcount = primitive "count", \&xcount; sub xskim { $s[++$sp] = $m[$s[$sp]++&$cell]; } my $xskim = primitive "skim", \&xskim; sub xon { $m[$s[$sp--]&$cell] = -1; } my $xon = primitive "on", \&xon; sub xoff { $m[$s[$sp--]&$cell] = 0; } my $xoff = primitive "off", \&xoff; # ( x1 a -- x2 ) sub xexchange { my $addr = $s[$sp--]&$cell; ($m[$addr], $s[$sp]) = ($s[$sp], $m[$addr]); } my $xexchange = primitive "exchange", \&xexchange; # ( a1 n1 c -- a2 n2 ) sub xskip { my $char = $s[$sp--]; (my $addr, my $len) = @s[$sp-1..$sp]; while (($m[$addr&$cell] == $char) && ($len)) { $addr++; $len--; } @s[$sp-1..$sp] = ($addr, $len); } my $xskip = primitive "skip", \&xskip; # ( a1 n1 c -- a2 n2 ) sub xscan { my $char = $s[$sp--]; (my $addr, my $len) = @s[$sp-1..$sp]; while (($m[$addr&$cell] != $char) && ($len)) { $addr++; $len--; } @s[$sp-1..$sp] = ($addr, $len); } my $xscan = primitive "scan", \&xscan; # ( src dst n -- ) sub xmove { (my $src, my $dest, my $count) = @s[$sp-2..$sp]; @m[$dest..$dest+$count-1] = @m[$src..$src+$count-1]; $sp-=3; } my $xmove = primitive "move", \&xmove; # ( a n c -- ) sub xfill { (my $dest, my $count, my $char) = @s[$sp-2..$sp]; @m[$dest..$dest+$count-1] = ($char) x $count; $sp-=3; } my $xfill = primitive "fill", \&xfill; # -------- number conversion -------- #FOLD00 my %radixprefix = ( '%' => 2, '&' => 8, '#' => 10, '$' => 16, '_' => 36, ); # ( a n -- x -1 | 0 ) sub xqnumber { my $sign = 0; my $accu = 0; # accumulator my $valid = -1; # assume valid number my $i = $s[$sp--]; # number of digits to test/convert $w = $s[$sp--]; # addr of next digit if ($m[$w] == 45) { # leading - $sign--; $w++; # strip $i--; } my $radix = $m[$xbase+1]; # assume radix from base if (defined $radixprefix{chr $m[$w]}) { # but if radix prefix, $radix = $radixprefix{chr $m[$w]}; # use radix for prefix $w++; # strip prefix $i--; } for (; $i; $i--) { # for all digits my $digit = $m[$w++] - 48; # read digit if (($digit < 0) || (($digit > 9) && ($digit < 17))) { $valid = 0; last; } $digit -= 7 if ($digit > 9 ); # remove gap between 9 and A $digit -= 32 if ($digit > 41); # a..z -> A..Z if (($digit < 0) || ($digit >= $radix)) { $valid = 0; last; } ($accu *= $radix) += $digit; } if ($valid) { $accu = -$accu if ($sign); $accu %= $wrap; $accu -= $wrap if $accu >= $msb; $s[++$sp] = $accu; } $s[++$sp] = $valid; } my $xqnumber = primitive "?number", \&xqnumber; # -------- output -------- #FOLD00 sub xcr { print "\n"; } my $xcr = primitive "cr", \&xcr; sub xemit { printf "%c",$s[$sp--]; } my $xemit = primitive "emit", \&xemit; sub xdotslit { print $m[$ip++]; } my $xdotslit = unnamedprimitive \&xdotslit; sub xspace { print " "; } my $xspace = primitive "space", \&xspace; # ( a n -- ) sub xtype { print string; } my $xtype = primitive "type", \&xtype; # -------- character input -------- #FOLD00 my $keybuffer; # ( -- c ) lowest level key input word sub xbrkey { my $key = $keybuffer; $keybuffer = 0; if (!$key) { ReadMode 4; $key = ReadKey(0); ReadMode 0; } $s[++$sp] = ord $key; } my $xbrkey = primitive "(key)", \&xbrkey; my $xkey = defer "key", $xbrkey; sub xqkey { if ($keybuffer) { $s[++$sp] = -1; } else { ReadMode 4; $keybuffer = ReadKey(-1); # possible race condition resulting in occasional echoing ReadMode 0; $s[++$sp] = -(defined $keybuffer); } } my $xqkey = primitive "key?", \&xqkey; # -------- buffered I/O -------- #FOLD00 # read string, delimited by c. return address and len # updates source # ( c -- a n ) sub xparse { my $delimiter = $s[$sp]; my $bufend = $parsebuf + $parsebuflen; # first non-buf address $w = $m[$xtoin+1] + $parsebuf; # parse address my $nxtchar = $m[$w]; if ($delimiter == 32) { for (; $w < $bufend;) { last if (!(defined $nxtchar)); last if ($nxtchar != $delimiter); $w++; $nxtchar = $m[$w]; } } $s[$sp] = $w; for (; $w < $bufend;) { last if (!(defined $nxtchar) || ($nxtchar == $delimiter)); $nxtchar = $m[++$w]; } $s[++$sp] = $w - $s[$sp]; $w++ if ((defined $nxtchar) && ($nxtchar == $delimiter)); $m[$xtoin+1] = $w - $parsebuf; } my $xparse = primitive "parse", \&xparse; sub xsource { $sp += 2; @s[$sp-1..$sp] = ($parsebuf, $parsebuflen); } my $xsource = primitive "source", \&xsource; hidden; definitions; # ( addr len offs -- ) sub xpushsource { push @sourcestack, $m[$xtoin+1], $parsebuf, $parsebuflen; $m[$xtoin+1] = 0; ($parsebuf, $parsebuflen) = @s[$sp-1..$sp]; $sp -= 2; } my $xpushsource = primitive "pushsource", \&xpushsource; sub xpopsource { $parsebuflen = pop @sourcestack; $parsebuf = pop @sourcestack; $m[$xtoin+1] = pop @sourcestack; } my $xpopsource = primitive "popsource", \&xpopsource; # ( a n1 asc -- a n2 ) my $xdecode = colon "decode"; compile $xdup, $xlit, 127, $xequals; # Del/BS: remove previous compile $xover, $xlit, 8, $xequals, $xor; XIF; compile $xdrop; compile $xdup; XIF; compile $xdotslit, "\b \b", $xoneminus; XTHEN; compile $xexit; XTHEN; compile $xdup, $xlit, 9, $xequals; # Tab: convert to space XIF; compile $xdrop, $xbl; XTHEN; compile $xdup, $xemit; # echo char compile $xtor, $x2dup, $xplus; # calc buffer address compile $xrfrom, $xswap, $xstore; # buffer char compile $xoneplus; # count semicolon; forth; definitions; # -------- dictionary and compilation -------- #FOLD00 sub xhere { $s[++$sp] = $dp; } my $xhere = primitive "here", \&xhere; sub xallot { $dp += $s[$sp--]; } my $xallot = primitive "allot", \&xallot; sub xcomma { $m[$dp++&$cell] = $s[$sp--]; } my $xcomma = primitive ",", \&xcomma; my $xstateoff = colon '['; immediate; compile $xstate, $xoff; semicolon; my $xstateon = colon "]"; compile $xstate, $xon; semicolon; my $xcompiling = colon "compiling"; compile $xstate, $xfetch; semicolon; my $xqcomp = colon "?comp"; compile $xcompiling, $xzeroequals; compile $xlit, -14, $xand, $xthrow; semicolon; my $xqexec = colon "?exec"; compile $xcompiling; compile $xlit, -64, $xand, $xthrow; semicolon; # -------- vocabulary/wordlist -------- #FOLD00 sub xheader { header string } my $xheader = primitive "header", \&xheader; my $xhide = primitive "hide", \&hide; my $xreveal = primitive "reveal" , \&reveal; my $ximmediate = primitive "immediate", \&immediate; # ( header -- f ) sub xqimm { $s[$sp] = -(!!($precedence[$s[$sp]] & $precedencebit)); } my $xqimm = primitive "?imm", \&xqimm; sub xwords { my $nfa = $m[$m[$xcontextstore]+2]; while ($nfa) { print "$header[$nfa] "; $nfa = $voclink[$nfa]; } xcr; } only; definitions; my $xwords = primitive "words", \&xwords; forth; definitions; sub xnamefrom { $s[$sp] = $body[$s[$sp]]; } my $xnamefrom = primitive "name>", \&xnamefrom; hidden; definitions; # returns matching header index, aka nfa, (or 0) # ( a1 n -- a2 | 0 ) sub xbrhunt { my $name = string; $s[++$sp] = 0; my $last = $m[$m[$xcontextstore]+2]; while ($last) { if ($precedence[$last] & $revealbit) { if ($header[$last] eq $name) { $s[$sp] = $last; last; } } $last = $voclink[$last] } } my $xbrhunt = primitive "(hunt)", \&xbrhunt; forth; definitions; sub xhunt { x2dup; xbrhunt; if (!($s[$sp])) { my $prevcontext = $m[$xcontextstore]; my $vocstackdepth = $#vocstack; for my $voc (0..$vocstackdepth) { my $tempcontext = $vocstack[$vocstackdepth-$voc]; if ($tempcontext != $prevcontext) { xdrop; $m[$xcontextstore] = $tempcontext; x2dup; xbrhunt; last if ($s[$sp]); } } $m[$xcontextstore] = $prevcontext; } xnip; xnip; } my $xhunt = primitive "hunt", \&xhunt; # returns matching header index, aka nfa, (or 0) # ( cfa -- a | 0 ) sub xtoname { my $cfa = $s[$sp]; $s[$sp] = 0; for (my $i=$wc-1; $i; --$i) { if ($body[$i] eq $cfa) { $s[$sp] = $i; last; } } } my $xtoname = primitive ">name", \&xtoname; # ( cfa -- a n ) sub xname { xtoname; my $nfa = $s[$sp]; $s[$sp] = $dp; $s[++$sp] = 0; if ($nfa) { $s[$sp] = $dp; unstring $header[$nfa]; } } my $xname = primitive "name", \&xname; # ( cfa -- ) sub xdotname { xtoname; print $header[$s[$sp]] if ($s[$sp]); $sp--; } my $xdotname = primitive ".name", \&xdotname; sub xorder { print "\ncontext: "; $s[++$sp] = $m[$xcontextstore]; xdotname; xspace; xspace; my $vocstackdepth = $#vocstack; for my $voc (0..$vocstackdepth) { $s[++$sp] = $vocstack[$vocstackdepth-$voc]; xdotname; xspace; } $s[++$sp] = $m[$xcurrentstore]; print "\ncurrent: "; xdotname; xspace; xcr; } only; definitions; my $xorder = primitive "order", \&xorder; forth; definitions; my $xtick = colon "'"; compile $xbl, $xparse; compile $x2dup, $xlastword, $x2store; compile $xhunt; compile $xqdup; XIF; compile $xnamefrom; XELSE; compile $xnotfound; XTHEN; semicolon; my $xcreate = colon "create"; compile $xbl, $xparse; compile $xqdup, $xzeroequals, $xlit, -16, $xand, $xthrow; compile $xheader, $xlit, \&dovar, $xcomma; compile $xreveal; semicolon; my $xcolon = colon ":"; immediate; compile $xcompiling, $xlit, -29, $xand, $xthrow; compile $xcreate, $xhide; compile $xlit, \&nest, $xuse; compile $xstateon; semicolon; my $xsemicolon = colon ";"; immediate; compile $xqcomp, $xlit, $xexit, $xcomma, $xstateoff, $xreveal; semicolon; # -------- misc -------- #FOLD00 sub xepoch { $s[++$sp] = time; } my $xepoch = primitive "epoch", \&xepoch; my $xstructured = colon "structured"; compile $x2dup, $xnotequals; compile $xlit, -22, $xand, $xthrow; compile $x2drop; semicolon; sub xdefined { $s[$sp] = -(defined $s[$sp]); } my $xdefined = primitive "defined", \&xdefined; # ( a n -- x ) sub xshell { print "\n"; system string; } primitive "shell", \&xshell; # -------- does> -------- #FOLD00 sub xdodoes { # cfa of created word revectored here. $s[++$sp] = $w+1; # push data address of created word $r[++$rp] = $ip; # nest to hilevel code behind does> $ip = $does{$w}; } sub xdoes { $m[$body[$wc-1]] = \&xdodoes; # revector created word to point to dodoes $does{$body[$wc-1]} = $ip; # does> code pointer hashed to key "body address" $ip = $r[$rp--]; # unnest, preventing execution of does> code now } primitive "does>", \&xdoes; # -------- interpreter/compiler -------- #FOLD00 # ( a n -- x -1 | d -1 | r -1 | -1 | 0 ) sub xinterpretnumber { xqnumber; if ($s[$sp] && $m[$xstate+1]) { # number valid while compiling? $dp += 2; @m[$dp-2..$dp-1] = ($xlit, $s[--$sp]); # yes: compile number as literal $s[$sp] = -1; # and remove from stack. } } my $xinterpretnumber = unnamedprimitive \&xinterpretnumber; hidden; definitions; # ( -- ) my $xbrinterpret = colon "(interpret)"; XBEGIN; compile $xbl, $xparse; # pull in string from buffered input compile $xdup; XWHILE; compile $x2dup, $xlastword, $x2store; # keep copy for literal or error compile $xhunt, $xqdup; # got string, look up in dictionary XIF; # found in dictionary: compile $xdup, $xqimm; # immediate word? XIF; compile $xnamefrom, $xexecute; # execute immediate words always XELSE; compile $xnamefrom, $xcompiling; # non-immediate words depend on compile state: XIF; compile $xcomma; # postponed execution when compiling XELSE; compile $xexecute; # immediate execution when interpreting XTHEN; XTHEN; compile $xdepth, $xzeroless; # test for stack underflow XIF; compile $xstackunderflow; XTHEN; # throw exception in case of XELSE; compile $xlastword, $x2fetch; compile $xinterpretnumber, $xzeroequals; # word not found: try as number XIF; compile $xlastword, $x2fetch, $xnotfound; # neither, try user hook XTHEN; XTHEN; XREPEAT; compile $x2drop; semicolon; forth; definitions; my $xinterpret = defer "interpret", $xbrinterpret; # ( a n -- ) my $xevaluate = colon "evaluate"; compile $xpushsource; compile $xinterpret; compile $xpopsource; semicolon; # -------- disk I/O -------- my $line; sub publish { if (defined $line) { $s[++$sp] = $dp; $s[++$sp] = $dp; chomp($line); unstring $line; } $s[++$sp] = -(defined $line); } # ( a -- u ) sub xread { ($line, @disk) = @disk; publish; } my $xread = unnamedprimitive \&xread; # ( a n -- ) sub fileopen { open(file1, "< ".string) or throw(-38); } my $xfileopen = primitive "fileopen", \&fileopen; sub fileclose { close(file1); } my $xfileclose = primitive "fileclose", \&fileclose; # ( -- a n -1 | 0 ) sub fileread { $line = <file1>; publish; } my $xfileread = primitive "fileread", \&fileread; # -------- entry point, init, and VM main loop -------- #FOLD00 my $xprompt = defer "prompt", $xnop; my $xquit = defer "quit", $xbye; sub xempty { $rp = -1; # init return stack $sp = -1; # init data stack $catchframe = 0; @sourcestack = (); # drop any nested input source } my $xempty = primitive "empty", \&xempty; my $xabort = colon "abort"; compile $xquit; semicolon; my $xcold = compile $xonly, $xempty; XBEGIN; compile $xread; XWHILE; compile $xevaluate; XREPEAT; my $xwarm = compile $xabort; sub exceptionhandler { my $exception = $@; my $exceptionnr = $@; $exceptionnr =~ s/ .*\n//; my $err0 = pack "C*", @m[$parsebuf..$parsebuf+$m[$xtoin+1]-1]; # collect source line from virtual memory print "\n", $err0; # print the line containing the error $err0 =~ s/ *$//; # strip trailing spaces my $all = length($err0); # determine length of whole line $err0 =~ s/[^ ]*$//; # strip last space delimited string my $ok = length($err0); # determine length of part without error print "\n", " " x $ok, "^" x ($all-$ok); # underscore error with carets print "\n", $throwmessage{$exceptionnr} if (defined $throwmessage{$exceptionnr}); print "\nexception ", $exception; } #sub xcolor { # my $string = string; # print $string; # print color($string); #} #primitive "fg", \&xcolor; sub main { $ip = $xcold; # set instruction pointer to coldstart until (0) { eval { until (0) { # virtual machine execution loop: $w = $m[$ip++]; # instruction fetch # $s[++$sp] = $w; xdotname; xspace; # xcr if $w == $xexit; $m[$w](); # instruction execute } }; # interpreter error exit exceptionhandler; $ip = $xwarm; # reenter at warmstart } } main;