#!/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", \&xi;

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;