#!/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;