Removed use of deprecated parse() in favour of Meta.parse().
[forth.jl.git] / src / forth.jl
1 module forth
2
3 import REPL.REPLCompletions, Base.invokelatest, Pkg
4
5 # VM mem size
6 size_mem = 1000000 # 1 mega-int
7
8 # Buffer sizes
9 size_RS = 1000   # Return stack size
10 size_PS = 1000   # Parameter stack size
11 size_TIB = 1000  # Terminal input buffer size
12 size_FIB = 1000  # File input buffer size
13
14 # Memory arrays
15 mem = Array{Int64,1}(undef,size_mem)
16 primitives = Array{Function,1}(undef, 0)
17 primNames = Array{AbstractString,1}(undef, 0)
18
19 # Memory geography and built-in variables
20
21 nextVarAddr = 1
22 H = nextVarAddr; nextVarAddr += 1              # Next free memory address
23 FORTH_LATEST = nextVarAddr; nextVarAddr += 1   # FORTH dict latest
24 CURRENT = nextVarAddr; nextVarAddr += 1        # Current compilation dict
25
26 RSP0 = nextVarAddr                  # bottom of RS
27 PSP0 = RSP0 + size_RS               # bottom of PS
28 TIB = PSP0 + size_PS                # address of terminal input buffer
29 FIB = TIB + size_TIB                # address of terminal input buffer
30 mem[H] = FIB + size_FIB             # location of bottom of dictionary
31 mem[FORTH_LATEST] = 0               # zero FORTH dict latest (no previous def)
32 mem[CURRENT] = FORTH_LATEST-1       # Compile words to system dict initially
33
34 DICT = mem[H] # Save bottom of dictionary as constant
35
36 # VM registers
37 mutable struct Reg
38     RSP::Int64  # Return stack pointer
39     PSP::Int64  # Parameter/data stack pointer
40     IP::Int64   # Instruction pointer
41     W::Int64    # Working register
42 end
43 reg = Reg(RSP0, PSP0, 0, 0)
44
45 # Stack manipulation functions
46
47 function ensurePSDepth(depth::Int64)
48     if reg.PSP - PSP0 < depth
49         error("Parameter stack underflow.")
50     end
51 end
52
53 function ensurePSCapacity(toAdd::Int64)
54     if reg.PSP + toAdd >= PSP0 + size_PS
55         error("Parameter stack overflow.")
56     end
57 end
58
59 function ensureRSDepth(depth::Int64)
60     if reg.RSP - RSP0 < depth
61         error("Return stack underflow.")
62     end
63 end
64
65 function ensureRSCapacity(toAdd::Int64)
66     if reg.RSP + toAdd >= RSP0 + size_RS
67         error("Return stack overflow.")
68     end
69 end
70
71 function pushRS(val::Int64)
72     ensureRSCapacity(1)
73     mem[reg.RSP+=1] = val
74 end
75
76 function popRS()
77     ensureRSDepth(1)
78
79     val = mem[reg.RSP]
80     reg.RSP -= 1
81     return val
82 end
83
84 function pushPS(val::Int64)
85     ensurePSCapacity(1)
86
87     mem[reg.PSP += 1] = val
88 end
89
90 function popPS()
91     ensurePSDepth(1)
92
93     val = mem[reg.PSP]
94     reg.PSP -= 1
95     return val
96 end
97
98 # Handy functions for adding/retrieving strings to/from memory.
99
100 getString(addr::Int64, len::Int64) = String([Char(c) for c in mem[addr:(addr+len-1)]])
101
102 function putString(str::AbstractString, addr::Int64)
103     mem[addr:(addr+length(str)-1)] = [Int64(c) for c in str]
104 end
105
106 function putString(str::AbstractString, addr::Int64, maxLen::Int64)
107     len = min(length(str), maxLen)
108     mem[addr:(addr+len-1)] = [Int64(c) for c in str]
109 end
110
111 stringAsInts(str::AbstractString) = [Int(c) for c in collect(str)]
112
113 # Primitive creation and calling functions
114
115 function defPrim(f::Function; name="nameless")
116     push!(primitives, f)
117     push!(primNames, replace(name, "\004" => "EOF"))
118
119     return -length(primitives)
120 end
121
122 function callPrim(addr::Int64)
123     if addr >=0 || -addr>length(primitives)
124         error("Attempted to execute non-existent primitive at address $addr.")
125     else
126                 invokelatest(primitives[-addr])
127     end
128 end
129 getPrimName(addr::Int64) = primNames[-addr]
130
131 # Word creation functions
132
133 F_LENMASK = 31
134 F_IMMED = 32
135 F_HIDDEN = 64
136 NFA_MARK = 128
137
138 function dictWrite(ints::Array{Int64,1})
139     mem[mem[H]:(mem[H]+length(ints)-1)] = ints
140     mem[H] += length(ints)
141 end
142 dictWrite(int::Int64) = dictWrite([int])
143 dictWriteString(string::AbstractString) = dictWrite([Int64(c) for c in string])
144
145 function createHeader(name::AbstractString, flags::Int64)
146     mem[mem[H]] = mem[mem[CURRENT]+1]
147     mem[mem[CURRENT]+1] = mem[H]
148     mem[H] += 1
149
150     dictWrite(length(name) | flags | NFA_MARK)
151     dictWriteString(name)
152 end
153
154 function defPrimWord(name::AbstractString, f::Function; flags::Int64=0)
155     createHeader(name, flags)
156
157     codeWordAddr = mem[H]
158     dictWrite(defPrim(f, name=name))
159
160     return codeWordAddr
161 end
162
163 function defWord(name::AbstractString, wordAddrs::Array{Int64,1}; flags::Int64=0)
164     createHeader(name, flags)
165
166     addr = mem[H]
167     dictWrite(DOCOL)
168
169     dictWrite(wordAddrs)
170
171     return addr
172 end
173
174 # Variable creation functions
175
176 function defExistingVar(name::AbstractString, varAddr::Int64; flags::Int64=0)
177
178     defPrimWord(name, eval(:(() -> begin
179         pushPS($(varAddr))
180         return NEXT
181     end)))
182 end
183
184 function defNewVar(name::AbstractString, initial::Array{Int64,1}; flags::Int64=0)
185     createHeader(name, flags)
186     
187     codeWordAddr = mem[H]
188     varAddr = mem[H] + 1
189
190     dictWrite(DOVAR)
191     dictWrite(initial)
192
193     return varAddr, codeWordAddr
194 end
195
196 defNewVar(name::AbstractString, initial::Int64; flags::Int64=0) =
197     defNewVar(name, [initial]; flags=flags)
198
199 function defConst(name::AbstractString, val::Int64; flags::Int64=0)
200     createHeader(name, flags)
201
202     codeWordAddr = mem[H]
203
204     dictWrite(DOCON)
205     dictWrite(val)
206
207     return codeWordAddr
208 end
209
210 # Threading Primitives (inner interpreter)
211
212 NEXT = defPrim(() -> begin
213     reg.W = mem[reg.IP]
214     reg.IP += 1
215     return mem[reg.W]
216 end, name="NEXT")
217
218 DOCOL = defPrim(() -> begin
219     pushRS(reg.IP)
220     reg.IP = reg.W + 1
221     return NEXT
222 end, name="DOCOL")
223
224 DOVAR = defPrim(() -> begin
225     pushPS(reg.W + 1)
226     return NEXT
227 end, name="DOVAR")
228
229 DOCON = defPrim(() -> begin
230     pushPS(mem[reg.W + 1])
231     return NEXT
232 end, name="DOVAR")
233
234 EXIT_CFA = defPrimWord("EXIT", () -> begin
235     reg.IP = popRS()
236     return NEXT
237 end)
238
239 # Dictionary entries for core built-in variables, constants
240
241 H_CFA = defExistingVar("H", H)
242
243 PSP0_CFA = defConst("PSP0", PSP0)
244 RSP0_CFA = defConst("RSP0", RSP0)
245
246 defConst("DOCOL", DOCOL)
247 defConst("DOCON", DOCON)
248 defConst("DOVAR", DOVAR)
249
250 defConst("DICT", DICT)
251 defConst("MEMSIZE", size_mem)
252
253 F_IMMED_CFA = defConst("F_IMMED", F_IMMED)
254 F_HIDDEN_CFA = defConst("F_HIDDEN", F_HIDDEN)
255 F_LENMASK_CFA = defConst("F_LENMASK", F_LENMASK)
256 NFA_MARK_CFA = defConst("NFA_MARK", NFA_MARK)
257
258 # Basic forth primitives
259
260 DROP_CFA = defPrimWord("DROP", () -> begin
261     popPS()
262     return NEXT
263 end)
264
265 SWAP_CFA = defPrimWord("SWAP", () -> begin
266     a = popPS()
267     b = popPS()
268     pushPS(a)
269     pushPS(b)
270     return NEXT
271 end)
272
273 DUP_CFA = defPrimWord("DUP", () -> begin
274     ensurePSDepth(1)
275     pushPS(mem[reg.PSP])
276     return NEXT
277 end)
278
279 OVER_CFA = defPrimWord("OVER", () -> begin
280     ensurePSDepth(2)
281     pushPS(mem[reg.PSP-1])
282     return NEXT
283 end)
284
285 ROT_CFA = defPrimWord("ROT", () -> begin
286     a = popPS()
287     b = popPS()
288     c = popPS()
289     pushPS(b)
290     pushPS(a)
291     pushPS(c)
292     return NEXT
293 end)
294
295 NROT_CFA = defPrimWord("-ROT", () -> begin
296     a = popPS()
297     b = popPS()
298     c = popPS()
299     pushPS(a)
300     pushPS(c)
301     pushPS(b)
302     return NEXT
303 end)
304
305
306 TWODROP_CFA = defPrimWord("2DROP", () -> begin
307     popPS()
308     popPS()
309     return NEXT
310 end)
311
312 TWODUP_CFA = defPrimWord("2DUP", () -> begin
313     ensurePSDepth(2)
314     a = mem[reg.PSP-1]
315     b = mem[reg.PSP]
316     pushPS(a)
317     pushPS(b)
318     return NEXT
319 end)
320
321 TWOSWAP_CFA = defPrimWord("2SWAP", () -> begin
322     a = popPS()
323     b = popPS()
324     c = popPS()
325     d = popPS()
326     pushPS(b)
327     pushPS(a)
328     pushPS(d)
329     pushPS(c)
330     return NEXT
331 end)
332
333 TWOOVER_CFA = defPrimWord("2OVER", () -> begin
334     ensurePSDepth(4)
335     a = mem[reg.PSP-3]
336     b = mem[reg.PSP-2]
337     pushPS(a)
338     pushPS(b)
339     return NEXT
340 end)
341
342 QDUP_CFA = defPrimWord("?DUP", () -> begin
343     ensurePSDepth(1)
344     val = mem[reg.PSP]
345     if val != 0
346         pushPS(val)
347     end
348     return NEXT
349 end)
350
351 INCR_CFA = defPrimWord("1+", () -> begin
352     ensurePSDepth(1)
353     mem[reg.PSP] += 1
354     return NEXT
355 end)
356
357 DECR_CFA = defPrimWord("1-", () -> begin
358     ensurePSDepth(1)
359     mem[reg.PSP] -= 1
360     return NEXT
361 end)
362
363 INCR2_CFA = defPrimWord("2+", () -> begin
364     ensurePSDepth(1)
365     mem[reg.PSP] += 2
366     return NEXT
367 end)
368
369 DECR2_CFA = defPrimWord("2-", () -> begin
370     ensurePSDepth(1)
371     mem[reg.PSP] -= 2
372     return NEXT
373 end)
374
375 ADD_CFA = defPrimWord("+", () -> begin
376     b = popPS()
377     a = popPS()
378     pushPS(a+b)
379     return NEXT
380 end)
381
382 SUB_CFA = defPrimWord("-", () -> begin
383     b = popPS()
384     a = popPS()
385     pushPS(a-b)
386     return NEXT
387 end)
388
389 MUL_CFA = defPrimWord("*", () -> begin
390     b = popPS()
391     a = popPS()
392     pushPS(a*b)
393     return NEXT
394 end)
395
396 DIVMOD_CFA = defPrimWord("/MOD", () -> begin
397     b = popPS()
398     a = popPS()
399     q,r = divrem(a,b)
400     pushPS(r)
401     pushPS(q)
402     return NEXT
403 end)
404
405 TWOMUL_CFA = defPrimWord("2*", () -> begin
406     pushPS(popPS() << 1)
407     return NEXT
408 end)
409
410 TWODIV_CFA = defPrimWord("2/", () -> begin
411     pushPS(popPS() >> 1)
412     return NEXT
413 end)
414
415 EQ_CFA = defPrimWord("=", () -> begin
416     b = popPS()
417     a = popPS()
418     pushPS(a==b ? -1 : 0)
419     return NEXT
420 end)
421
422 NE_CFA = defPrimWord("<>", () -> begin
423     b = popPS()
424     a = popPS()
425     pushPS(a!=b ? -1 : 0)
426     return NEXT
427 end)
428
429 LT_CFA = defPrimWord("<", () -> begin
430     b = popPS()
431     a = popPS()
432     pushPS(a<b ? -1 : 0)
433     return NEXT
434 end)
435
436 GT_CFA = defPrimWord(">", () -> begin
437     b = popPS()
438     a = popPS()
439     pushPS(a>b ? -1 : 0)
440     return NEXT
441 end)
442
443 LE_CFA = defPrimWord("<=", () -> begin
444     b = popPS()
445     a = popPS()
446     pushPS(a<=b ? -1 : 0)
447     return NEXT
448 end)
449
450 GE_CFA = defPrimWord(">=", () -> begin
451     b = popPS()
452     a = popPS()
453     pushPS(a>=b ? -1 : 0)
454     return NEXT
455 end)
456
457 ZE_CFA = defPrimWord("0=", () -> begin
458     pushPS(popPS() == 0 ? -1 : 0)
459     return NEXT
460 end)
461
462 ZNE_CFA = defPrimWord("0<>", () -> begin
463     pushPS(popPS() != 0 ? -1 : 0)
464     return NEXT
465 end)
466
467 ZLT_CFA = defPrimWord("0<", () -> begin
468     pushPS(popPS() < 0 ? -1 : 0)
469     return NEXT
470 end)
471
472 ZGT_CFA = defPrimWord("0>", () -> begin
473     pushPS(popPS() > 0 ? -1 : 0)
474     return NEXT
475 end)
476
477 ZLE_CFA = defPrimWord("0<=", () -> begin
478     pushPS(popPS() <= 0 ? -1 : 0)
479     return NEXT
480 end)
481
482 ZGE_CFA = defPrimWord("0>=", () -> begin
483     pushPS(popPS() >= 0 ? -1 : 0)
484     return NEXT
485 end)
486
487 AND_CFA = defPrimWord("AND", () -> begin
488     b = popPS()
489     a = popPS()
490     pushPS(a & b)
491     return NEXT
492 end)
493
494 OR_CFA = defPrimWord("OR", () -> begin
495     b = popPS()
496     a = popPS()
497     pushPS(a | b)
498     return NEXT
499 end)
500
501 XOR_CFA = defPrimWord("XOR", () -> begin
502     b = popPS()
503     a = popPS()
504     pushPS(xor(a, b))
505     return NEXT
506 end)
507
508 INVERT_CFA = defPrimWord("INVERT", () -> begin
509     pushPS(~popPS())
510     return NEXT
511 end)
512
513 # Literals
514
515 LIT_CFA = defPrimWord("LIT", () -> begin
516     pushPS(mem[reg.IP])
517     reg.IP += 1
518     return NEXT
519 end)
520
521 # Memory primitives
522
523 STORE_CFA = defPrimWord("!", () -> begin
524     addr = popPS()
525     dat = popPS()
526     mem[addr] = dat
527     return NEXT
528 end)
529
530 FETCH_CFA = defPrimWord("@", () -> begin
531     addr = popPS()
532     pushPS(mem[addr])
533     return NEXT
534 end)
535
536 ADDSTORE_CFA = defPrimWord("+!", () -> begin
537     addr = popPS()
538     toAdd = popPS()
539     mem[addr] += toAdd
540     return NEXT
541 end)
542
543 SUBSTORE_CFA = defPrimWord("-!", () -> begin
544     addr = popPS()
545     toSub = popPS()
546     mem[addr] -= toSub
547     return NEXT
548 end)
549
550
551 # Return Stack
552
553 TOR_CFA = defPrimWord(">R", () -> begin
554     pushRS(popPS())
555     return NEXT
556 end)
557
558 FROMR_CFA = defPrimWord("R>", () -> begin
559     pushPS(popRS())
560     return NEXT
561 end)
562
563 RFETCH_CFA = defPrimWord("R@", () -> begin
564     pushPS(mem[reg.RSP])
565     return NEXT
566 end)
567
568 RSPFETCH_CFA = defPrimWord("RSP@", () -> begin
569     pushPS(reg.RSP)
570     return NEXT
571 end)
572
573 RSPSTORE_CFA = defPrimWord("RSP!", () -> begin
574     reg.RSP = popPS()
575     return NEXT
576 end)
577
578 RDROP_CFA = defPrimWord("RDROP", () -> begin
579     popRS()
580     return NEXT
581 end)
582
583 # Parameter Stack
584
585 PSPFETCH_CFA = defPrimWord("PSP@", () -> begin
586     pushPS(reg.PSP)
587     return NEXT
588 end)
589
590 PSPSTORE_CFA = defPrimWord("PSP!", () -> begin
591     reg.PSP = popPS()
592     return NEXT
593 end)
594
595 # Working Register
596
597 WFETCH_CFA = defPrimWord("W@", () -> begin
598     pushPS(reg.W)
599     return NEXT
600 end)
601
602 WSTORE_CFA = defPrimWord("W!", () -> begin
603     reg.W = popPS()
604     return NEXT
605 end)
606
607 # I/O
608
609 openFiles = Dict{Int64,IOStream}()
610 nextFileID = 1
611
612
613 ## File access modes
614 FAM_RO = 0
615 FAM_WO = 1
616 FAM_RO_CFA = defConst("R/O", FAM_RO)
617 FAM_WO_CFA = defConst("W/O", FAM_WO)
618
619 function fileOpener(create::Bool)
620     fam = popPS()
621     fnameLen = popPS()
622     fnameAddr = popPS()
623
624     fname = getString(fnameAddr, fnameLen)
625
626     if create && !isfile(fname)
627         pushPS(0)
628         pushPS(-1) # error
629         return NEXT
630     end
631
632     if (fam == FAM_RO)
633         mode = "r"
634     else
635         mode = "w"
636     end
637
638     global nextFileID
639     openFiles[nextFileID] = open(fname, mode)
640     pushPS(nextFileID)
641     pushPS(0)
642     
643     nextFileID += 1
644 end
645
646 OPEN_FILE_CFA = defPrimWord("OPEN-FILE", () -> begin
647     fileOpener(false)
648     return NEXT
649 end);
650
651 CREATE_FILE_CFA = defPrimWord("CREATE-FILE", () -> begin
652     fileOpener(true)
653     return NEXT
654 end);
655
656 CLOSE_FILE_CFA = defPrimWord("CLOSE-FILE", () -> begin
657     fid = popPS()
658     close(openFiles[fid])
659     delete!(openFiles, fid)
660
661     pushPS(0) # Result code 0
662     return NEXT
663 end)
664
665 CLOSE_FILES_CFA = defPrimWord("CLOSE-FILES", () -> begin
666     for fh in values(openFiles)
667         close(fh)
668     end
669     empty!(openFiles)
670
671     pushPS(0) # Result code 0
672     return NEXT
673 end)
674
675 READ_LINE_CFA = defPrimWord("READ-LINE", () -> begin
676     fid = popPS()
677     maxSize = popPS()
678     addr = popPS()
679
680     if !(fid in keys(openFiles))
681         error(string("Invalid FID ", fid, "."))
682     end
683
684     fh = openFiles[fid]
685     line = readline(fh, keep=true)
686
687     eofFlag = endswith(line, '\n') ? 0 : -1
688     line = chomp(line)
689
690     putString(line, addr, maxSize)
691
692     pushPS(length(line))
693     pushPS(eofFlag)
694     pushPS(0)
695
696     return NEXT
697 end)
698
699 READ_FILE_CFA = defPrimWord("READ-FILE", () -> begin
700     fid = popPS()
701     size = popPS()
702     addr = popPS()
703
704     fh = openFiles[fid]
705
706     string = join(map(x -> Char(x), read(fh, size)), "")
707
708     eofFlag = length(string) == size ? 0 : -1 ;
709
710     putString(string, addr, length(string))
711
712     pushPS(length(string))
713     pushPS(eofFlag)
714
715     return NEXT
716 end)
717
718
719 EMIT_CFA = defPrimWord("EMIT", () -> begin
720     print(Char(popPS()))
721     return NEXT
722 end)
723
724 function raw_mode!(mode::Bool)
725     if ccall(:jl_tty_set_mode, Int32, (Ptr{Nothing}, Int32), stdin.handle, mode) != 0
726         throw("FATAL: Terminal unable to enter raw mode.")
727     end
728 end
729
730 function getKey()
731     raw_mode!(true)
732     byte = read(stdin, 1)[1]
733     raw_mode!(false)
734
735     if byte == 0x0d
736         return 0x0a
737     elseif byte == 127
738         return 0x08
739     else
740         return byte
741     end
742 end
743
744 KEY_CFA = defPrimWord("KEY", () -> begin
745     pushPS(Int(getKey()))
746     return NEXT
747 end)
748
749 function getLineFromSTDIN()
750
751     function getFrag(s)
752         chars = collect(s)
753         slashIdx = findlast(isequal('\\'), chars)
754
755         if slashIdx != nothing
756             return join(chars[slashIdx:length(chars)])
757         else
758             return nothing
759         end
760     end
761
762     function backspaceStr(s, bsCount)
763         oldLen = length(s)
764         newLen = max(0, oldLen - bsCount)
765         return join(collect(s)[1:newLen])
766     end
767
768     line = ""
769     while true
770         key = Char(getKey())
771
772         if key == '\n'
773             print(" ")
774             return String(line)
775
776         elseif key == '\x04'
777             if isempty(line)
778                 return string("\x04")
779             end
780
781         elseif key == '\b'
782             if !isempty(line)
783                 print("\b\033[K")
784                 line = backspaceStr(line, 1)
785             end
786
787         elseif key == '\e'
788             # Strip ANSI escape sequence
789             nextKey = Char(getKey())
790             if nextKey == '['
791                 while true
792                     nextKey = Char(getKey())
793                     if nextKey >= '@' || nextKey <= '~'
794                         break
795                     end
796                 end
797             end
798
799         elseif key == '\t'
800             # Currently do nothing
801
802             frag = getFrag(line)
803             if frag != nothing
804                 if haskey(REPLCompletions.latex_symbols, frag)
805                     print(repeat("\b", length(frag)))
806                     print("\033[K")
807                     comp = REPLCompletions.latex_symbols[frag]
808                     line = string(backspaceStr(line, length(frag)), comp)
809                     print(comp)
810                 end
811             end
812
813         else
814             print(key)
815             line = string(line, key)
816         end
817     end
818 end
819
820 SPAN, SPAN_CFA = defNewVar("SPAN", 0)
821 EXPECT_CFA = defPrimWord("EXPECT", () -> begin
822     maxLen = popPS()
823     addr = popPS()
824
825     line = getLineFromSTDIN()
826
827     mem[SPAN] = min(length(line), maxLen)
828     putString(line, addr, maxLen)
829
830     return NEXT
831 end)
832
833 BASE, BASE_CFA = defNewVar("BASE", 10)
834 NUMBER_CFA = defPrimWord("NUMBER", () -> begin
835     wordAddr = popPS()+1
836     wordLen = mem[wordAddr-1]
837
838     s = getString(wordAddr, wordLen)
839
840     pushPS(parse(Int64, s, base=mem[BASE]))
841
842     return NEXT
843 end)
844
845 # Dictionary searches
846
847 FROMLINK_CFA = defPrimWord("LINK>", () -> begin
848
849     addr = popPS()
850     lenAndFlags = mem[addr+1]
851     len = lenAndFlags & F_LENMASK
852
853     pushPS(addr + 2 + len)
854
855     return NEXT
856 end)
857
858 NUMCONTEXT, NUMCONTEXT_CFA = defNewVar("#CONTEXT", 1)
859
860 createHeader("FORTH", 0)
861 FORTH_CFA = mem[H]
862 dictWrite(defPrim(() -> begin
863     mem[CONTEXT + mem[NUMCONTEXT] - 1] = reg.W
864     return NEXT
865 end, name="FORTH"))
866 dictWrite(0) # cell for latest
867
868 CURRENT_CFA = defExistingVar("CURRENT", CURRENT)
869
870 # Switch to new FORTH vocabulary cfa
871 mem[FORTH_CFA+1] = mem[mem[CURRENT]+1]
872 mem[CURRENT] = FORTH_CFA
873
874 CONTEXT, CONTEXT_CFA = defNewVar("CONTEXT", zeros(Int64, 10))
875 mem[CONTEXT] = FORTH_CFA
876
877 FINDVOCAB_CFA = defPrimWord("FINDVOCAB", () -> begin
878     vocabCFA = popPS()
879     countedAddr = popPS()
880
881     wordAddr = countedAddr + 1
882     wordLen = mem[countedAddr]
883     word = lowercase(getString(wordAddr, wordLen))
884
885     lfa = vocabCFA+1
886     lenAndFlags = 0
887
888     while (lfa = mem[lfa]) > 0
889
890         lenAndFlags = mem[lfa+1]
891         len = lenAndFlags & F_LENMASK
892         hidden = (lenAndFlags & F_HIDDEN) == F_HIDDEN
893
894         if hidden || len != wordLen
895             continue
896         end
897
898         thisWord = lowercase(getString(lfa+2, len))
899
900         if thisWord == word
901             break
902         end
903     end
904
905     if lfa > 0
906         pushPS(lfa)
907         callPrim(mem[FROMLINK_CFA])
908         if (lenAndFlags & F_IMMED) == F_IMMED
909             pushPS(1)
910         else
911             pushPS(-1)
912         end
913     else
914         pushPS(countedAddr)
915         pushPS(0)
916     end
917
918     return NEXT
919 end)
920
921 FIND_CFA = defPrimWord("FIND", () -> begin
922
923     countedAddr = popPS()
924     context = mem[CONTEXT:(CONTEXT+mem[NUMCONTEXT]-1)]
925
926     for vocabCFA in reverse(context)
927         pushPS(countedAddr)
928         pushPS(vocabCFA)
929         callPrim(mem[FINDVOCAB_CFA])
930
931         callPrim(mem[DUP_CFA])
932         if popPS() != 0
933             return NEXT
934         else
935             popPS()
936             popPS()
937         end
938     end
939
940     pushPS(countedAddr)
941     pushPS(0)
942
943     return NEXT
944 end)
945
946
947 # Branching
948
949 BRANCH_CFA = defPrimWord("BRANCH", () -> begin
950     reg.IP += mem[reg.IP]
951     return NEXT
952 end)
953
954 ZBRANCH_CFA = defPrimWord("0BRANCH", () -> begin
955     if (popPS() == 0)
956         reg.IP += mem[reg.IP]
957     else
958         reg.IP += 1
959     end
960
961     return NEXT
962 end)
963
964 # Strings
965
966 LITSTRING_CFA = defPrimWord("LITSTRING", () -> begin
967     len = mem[reg.IP]
968     reg.IP += 1
969     pushPS(reg.IP)
970     pushPS(len)
971     reg.IP += len
972
973     return NEXT
974 end)
975
976 TYPE_CFA = defPrimWord("TYPE", () -> begin
977     len = popPS()
978     addr = popPS()
979     str = getString(addr, len)
980     print(str)
981     return NEXT
982 end)
983
984 # Interpreter/Compiler-specific I/O
985
986 TIB_CFA = defConst("TIB", TIB)
987 NUMTIB, NUMTIB_CFA = defNewVar("#TIB", 0)
988
989 FIB_CFA = defConst("FIB", FIB)
990 NUMFIB, NUMFIB_CFA = defNewVar("#FIB", 0)
991
992 IB_CFA = defPrimWord("IB", () -> begin
993     pushPS(mem[SOURCE_ID_VAR] == 0 ? TIB : FIB)
994     return NEXT
995 end)
996
997 NUMIB_CFA = defPrimWord("#IB", () -> begin
998     pushPS(mem[SOURCE_ID_VAR] == 0 ? NUMTIB : NUMFIB)
999     return NEXT
1000 end)
1001
1002 TOIN, TOIN_CFA = defNewVar(">IN", 0)
1003
1004 SOURCE_ID_VAR, SOURCE_ID_VAR_CFA = defNewVar("SOURCE-ID-VAR", 0)
1005
1006 QUERY_CFA = defWord("QUERY",
1007     [TIB_CFA, LIT_CFA, 160, EXPECT_CFA,
1008     SPAN_CFA, FETCH_CFA, NUMTIB_CFA, STORE_CFA,
1009     LIT_CFA, 0, TOIN_CFA, STORE_CFA,
1010     EXIT_CFA])
1011
1012 EOF_FLAG, EOF_FLAG_CFA = defNewVar("EOF-FLAG", 0)
1013
1014 # ( fid -- )
1015 # EOF-FLAG set to true if EOF is reached
1016 QUERY_FILE_CFA = defWord("QUERY-FILE",
1017     [FIB_CFA, LIT_CFA, 160, ROT_CFA, READ_LINE_CFA,
1018     DROP_CFA, EOF_FLAG_CFA, STORE_CFA,
1019     NUMFIB_CFA, STORE_CFA,
1020     LIT_CFA, 0, TOIN_CFA, STORE_CFA,
1021     EXIT_CFA])
1022
1023 WORD_CFA = defPrimWord("WORD", () -> begin
1024     delim = popPS()
1025
1026     if mem[SOURCE_ID_VAR] == 0
1027         bufferAddr = TIB
1028         sizeAddr = NUMTIB
1029     else
1030         bufferAddr = FIB
1031         sizeAddr = NUMFIB
1032     end
1033
1034     # Chew up initial occurrences of delim
1035     while (mem[TOIN]<mem[sizeAddr] && mem[bufferAddr+mem[TOIN]] == delim)
1036         mem[TOIN] += 1
1037     end
1038
1039     countAddr = mem[H]
1040     addr = mem[H]+1
1041
1042     # Start reading in word
1043     count = 0
1044     while (mem[TOIN]<mem[sizeAddr])
1045         mem[addr] = mem[bufferAddr+mem[TOIN]]
1046         mem[TOIN] += 1
1047
1048         if (mem[addr] == delim)
1049             break
1050         end
1051
1052         count += 1
1053         addr += 1
1054     end
1055
1056     # Record count
1057     mem[countAddr] = count
1058     pushPS(countAddr)
1059
1060     #println("Processing word: '$(getString(countAddr+1,mem[countAddr]))' (state $(mem[STATE]))")
1061
1062     return NEXT
1063 end)
1064
1065 # Compilation
1066
1067 STATE, STATE_CFA = defNewVar("STATE", 0)
1068
1069 COMMA_CFA = defPrimWord(",", () -> begin
1070     mem[mem[H]] = popPS()
1071     mem[H] += 1
1072
1073     return NEXT
1074 end)
1075
1076 HERE_CFA = defWord("HERE",
1077     [H_CFA, FETCH_CFA, EXIT_CFA])
1078
1079 HEADER_CFA = defPrimWord("HEADER", () -> begin
1080     wordAddr = popPS()+1
1081     wordLen = mem[wordAddr-1]
1082     word = getString(wordAddr, wordLen)
1083
1084     createHeader(word, 0)
1085
1086     return NEXT
1087 end)
1088
1089 CREATE_CFA = defWord("CREATE",
1090     [LIT_CFA, 32, WORD_CFA, HEADER_CFA,
1091     LIT_CFA, DOVAR, COMMA_CFA,
1092     EXIT_CFA])
1093
1094 DODOES = defPrim(() -> begin
1095     pushRS(reg.IP)
1096     reg.IP = popPS()
1097     pushPS(reg.W + 1)
1098     return NEXT
1099 end, name="DODOES")
1100
1101 DOES_HELPER_CFA = defPrimWord("(DOES>)", () -> begin
1102
1103     pushPS(mem[mem[CURRENT]+1])
1104     callPrim(mem[FROMLINK_CFA])
1105     cfa = popPS()
1106
1107     runtimeAddr = popPS()
1108
1109     mem[cfa] = defPrim(eval(:(() -> begin
1110         pushPS($(runtimeAddr))
1111         return DODOES
1112     end)), name="doesPrim")
1113
1114     return NEXT
1115 end, flags=F_IMMED | F_HIDDEN)
1116
1117 DOES_CFA = defWord("DOES>",
1118     [LIT_CFA, LIT_CFA, COMMA_CFA, HERE_CFA, LIT_CFA, 3, ADD_CFA, COMMA_CFA,
1119     LIT_CFA, DOES_HELPER_CFA, COMMA_CFA, LIT_CFA, EXIT_CFA, COMMA_CFA, EXIT_CFA],
1120     flags=F_IMMED)
1121
1122 LBRAC_CFA = defPrimWord("[", () -> begin
1123     mem[STATE] = 0
1124     return NEXT
1125 end, flags=F_IMMED)
1126
1127 RBRAC_CFA = defPrimWord("]", () -> begin
1128     mem[STATE] = 1
1129     return NEXT
1130 end, flags=F_IMMED)
1131
1132 HIDDEN_CFA = defPrimWord("HIDDEN", () -> begin
1133     lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
1134     mem[lenAndFlagsAddr] = xor(mem[lenAndFlagsAddr], F_HIDDEN)
1135     return NEXT
1136 end)
1137
1138 COLON_CFA = defWord(":",
1139     [LIT_CFA, 32, WORD_CFA,
1140     HEADER_CFA,
1141     LIT_CFA, DOCOL, COMMA_CFA,
1142     HIDDEN_CFA,
1143     RBRAC_CFA,
1144     EXIT_CFA])
1145
1146 SEMICOLON_CFA = defWord(";",
1147     [LIT_CFA, EXIT_CFA, COMMA_CFA,
1148     HIDDEN_CFA,
1149     LBRAC_CFA,
1150     EXIT_CFA], flags=F_IMMED)
1151
1152 IMMEDIATE_CFA = defPrimWord("IMMEDIATE", () -> begin
1153     lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
1154     mem[lenAndFlagsAddr] = xor(mem[lenAndFlagsAddr],  F_IMMED)
1155     return NEXT
1156 end, flags=F_IMMED)
1157
1158 # ( addr n -- primAddr )
1159 CREATE_PRIM_CFA = defPrimWord("CREATE-PRIM", () -> begin
1160     len = popPS()
1161     addr = popPS()
1162     
1163     exprString = string("() -> begin\n",
1164                         getString(addr, len), "\n",
1165                         "return NEXT\n",
1166                         "end")
1167     func = eval(Meta.parse(exprString))
1168
1169     pushPS(defPrim(func))
1170     return NEXT
1171 end)
1172
1173 # Outer Interpreter
1174
1175 EXECUTE_CFA = defPrimWord("EXECUTE", () -> begin
1176     reg.W = popPS()
1177     return mem[reg.W]
1178 end)
1179
1180 INTERPRET_CFA = defWord("INTERPRET",
1181     [LIT_CFA, 32, WORD_CFA, # Read next space-delimited word
1182
1183     DUP_CFA, FETCH_CFA, ZE_CFA, ZBRANCH_CFA, 3,
1184         DROP_CFA, EXIT_CFA, # Exit if input buffer is exhausted
1185
1186     STATE_CFA, FETCH_CFA, ZBRANCH_CFA, 24,
1187         # Compiling
1188         FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 13,
1189
1190             # Found word. 
1191             LIT_CFA, -1, EQ_CFA, INVERT_CFA, ZBRANCH_CFA, 4,
1192
1193                 # Immediate: Execute!
1194                 EXECUTE_CFA, BRANCH_CFA, -26,
1195
1196                 # Not immediate: Compile!
1197                 COMMA_CFA, BRANCH_CFA, -29,
1198
1199             # No word found, parse number
1200             NUMBER_CFA, LIT_CFA, LIT_CFA, COMMA_CFA, COMMA_CFA, BRANCH_CFA, -36,
1201         
1202        # Interpreting
1203         FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 5,
1204
1205             # Found word. Execute!
1206             DROP_CFA, EXECUTE_CFA, BRANCH_CFA, -44,
1207
1208             # No word found, parse number and leave on stack
1209             NUMBER_CFA, BRANCH_CFA, -47,
1210     EXIT_CFA])
1211
1212 PROMPT_CFA = defPrimWord("PROMPT", () -> begin
1213     if mem[STATE] == 0
1214         print(" ok")
1215     end
1216     println()
1217
1218     return NEXT
1219 end)
1220
1221 QUIT_CFA = defWord("QUIT",
1222     [LIT_CFA, 0, STATE_CFA, STORE_CFA,      # Set mode to interpret
1223     LIT_CFA, 0, SOURCE_ID_VAR_CFA, STORE_CFA,   # Set terminal as input stream
1224     LIT_CFA, 0, NUMTIB_CFA, STORE_CFA,      # Clear the input buffer
1225     RSP0_CFA, RSPSTORE_CFA,                 # Clear the return stack
1226     QUERY_CFA,                              # Read line of input
1227     INTERPRET_CFA, PROMPT_CFA,              # Interpret line
1228     BRANCH_CFA,-4])                         # Loop
1229
1230 INCLUDED_CFA = defWord("INCLUDED",
1231     [LIT_CFA, 0, STATE_CFA, STORE_CFA,          # Set mode to interpret
1232     FAM_RO_CFA, OPEN_FILE_CFA, DROP_CFA,        # Open the file
1233     SOURCE_ID_VAR_CFA, FETCH_CFA, SWAP_CFA,         # Store current source on stack
1234     SOURCE_ID_VAR_CFA, STORE_CFA,                   # Mark this as the current source
1235     SOURCE_ID_VAR_CFA, FETCH_CFA, QUERY_FILE_CFA,   # Read line from file
1236     EOF_FLAG_CFA, FETCH_CFA,
1237     NUMFIB_CFA, FETCH_CFA, ZE_CFA, AND_CFA,     # Test for EOF and empty line
1238     INVERT_CFA, ZBRANCH_CFA, 4,                 # Break out if EOF
1239     INTERPRET_CFA,                              # Interpret line
1240     BRANCH_CFA, -14,                            # Loop
1241     SOURCE_ID_VAR_CFA, FETCH_CFA,
1242     CLOSE_FILE_CFA, DROP_CFA,                   # Close file
1243     SOURCE_ID_VAR_CFA, STORE_CFA,               # Restore input source
1244     LIT_CFA, 0, NUMIB_CFA, STORE_CFA,           # Zero #IB
1245     LIT_CFA, 0, TOIN_CFA, STORE_CFA,            # Zero >IN
1246     EXIT_CFA])
1247
1248 INCLUDE_CFA = defWord("INCLUDE", [LIT_CFA, 32, WORD_CFA,
1249     DUP_CFA, INCR_CFA,
1250     SWAP_CFA, FETCH_CFA,
1251     INCLUDED_CFA, EXIT_CFA]);
1252
1253 ABORT_CFA = defWord("ABORT",
1254     [CLOSE_FILES_CFA, DROP_CFA, PSP0_CFA, PSPSTORE_CFA, QUIT_CFA])
1255
1256 BYE_CFA = defPrimWord("BYE", () -> begin
1257     if mem[SOURCE_ID_VAR] == 0
1258         println("\nBye!")
1259     end
1260     return 0
1261 end)
1262
1263 EOF_CFA = defPrimWord("\x04", () -> begin
1264     return 0
1265 end)
1266
1267 ### Library loading ###
1268
1269 oldCWD = ""
1270 SETLIBCWD_CFA = defPrimWord("SETLIBCWD", () -> begin
1271     global oldCWD = pwd()
1272     if !isfile("lib.4th") # Exception for debugging.
1273         cd(@__DIR__) # Macro expands to CWD at compile time
1274     end
1275     return NEXT
1276 end)
1277
1278 RESTORECWD_CFA = defPrimWord("RESTORECWD", () -> begin
1279     cd(oldCWD)
1280     return NEXT
1281 end)
1282
1283 INCLUDED_LIB_CFA = defWord("INCLUDED-LIB",
1284     [SETLIBCWD_CFA, INCLUDED_CFA, RESTORECWD_CFA, EXIT_CFA])
1285
1286 INCLUDE_LIB_CFA = defWord("INCLUDE-LIB", [LIT_CFA, 32, WORD_CFA,
1287     DUP_CFA, INCR_CFA,
1288     SWAP_CFA, FETCH_CFA,
1289     INCLUDED_LIB_CFA, EXIT_CFA]);
1290
1291 SKIP_WELCOME, SKIP_WELCOME_CFA = defNewVar("SKIP-WELCOME", 0)
1292
1293 #### VM loop ####
1294
1295 initialized = false
1296 libFileName = "lib.4th"
1297
1298 function run(fileName=nothing; initialize=true)
1299
1300     # Start with IP pointing to first instruction of outer interpreter
1301     pushRS(QUIT_CFA+1)
1302
1303     # Include optional file
1304     if fileName != nothing
1305         putString(fileName, mem[H])
1306         pushPS(mem[H])
1307         mem[H] += length(fileName)
1308         pushPS(length(fileName))
1309         pushRS(INCLUDED_CFA+1)
1310
1311         mem[SKIP_WELCOME] = -1
1312     end
1313
1314     # Load library files
1315     global initialized, libFileName
1316     if !initialized && initialize
1317         if libFileName != nothing
1318             #print("Including definitions from $libFileName...")
1319
1320             putString(libFileName, mem[H])
1321             pushPS(mem[H])
1322             pushPS(length(libFileName))
1323             pushRS(INCLUDED_LIB_CFA+1)
1324
1325             initialized = true
1326         else
1327             println("No library file found. Only primitive words available.")
1328         end
1329     end
1330
1331
1332     # Primitive processing loop.
1333     # Everyting else is simply a consequence of this loop!
1334     jmp = mem[EXIT_CFA]
1335     while jmp != 0
1336         try
1337             #print("Entering prim $(getPrimName(jmp)), PS: ")
1338             #printPS()
1339
1340             jmp = callPrim(jmp)
1341
1342         catch ex
1343             println(string("Error in primitive '", getPrimName(jmp), "' at address ", jmp))
1344             showerror(stdout, ex)
1345             println()
1346
1347             # QUIT
1348             reg.IP = ABORT_CFA + 1
1349             jmp = NEXT
1350         end
1351     end
1352 end
1353
1354 # Debugging tools
1355
1356 TRACE_CFA = defPrimWord("TRACE", () -> begin
1357     println("reg.W: $(reg.W) reg.IP: $(reg.IP)")
1358     print("PS: "); printPS()
1359     print("RS: "); printRS()
1360     print("[paused]")
1361     readline()
1362
1363     return NEXT
1364 end)
1365
1366 function dump(startAddr::Int64; count::Int64 = 100, cellsPerLine::Int64 = 10)
1367     chars = Array{Char,1}(undef, cellsPerLine)
1368
1369     lineStartAddr = cellsPerLine*div((startAddr-1),cellsPerLine) + 1
1370     endAddr = startAddr + count - 1
1371
1372     q, r = divrem((endAddr-lineStartAddr+1), cellsPerLine)
1373     numLines = q + (r > 0 ? 1 : 0)
1374
1375     i = lineStartAddr
1376     for l in 1:numLines
1377         print(i,":")
1378
1379         for c in 1:cellsPerLine
1380             if i >= startAddr && i <= endAddr
1381                 print("\t",mem[i])
1382                 if mem[i]>=32 && mem[i]<128
1383                     chars[c] = Char(mem[i])
1384                 else
1385                     chars[c] = '.'
1386                 end
1387             else
1388                 print("\t")
1389                 chars[c] = ' '
1390             end
1391
1392             i += 1
1393         end
1394
1395         println("\t", String(chars))
1396     end
1397 end
1398
1399 function printPS()
1400     count = reg.PSP - PSP0
1401
1402     if count > 0
1403         print("<$count>")
1404         for i in (PSP0+1):reg.PSP
1405             print(" $(mem[i])")
1406         end
1407         println()
1408     else
1409         println("Parameter stack empty")
1410     end
1411 end
1412
1413 function printRS()
1414     count = reg.RSP - RSP0
1415
1416     if count > 0
1417         print("<$count>")
1418         for i in (RSP0+1):reg.RSP
1419             print(" $(mem[i])")
1420         end
1421         println()
1422     else
1423         println("Return stack empty")
1424     end
1425 end
1426
1427 DUMP = defPrimWord("DUMP", () -> begin
1428     count = popPS()
1429     addr = popPS()
1430
1431     println()
1432     dump(addr, count=count)
1433
1434     return NEXT
1435 end)
1436
1437 end