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