628f3798e70bb78ba74b82d8647eb38504672c43
[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 SOURCE_ID, SOURCE_ID_CFA = defNewVar("SOURCE-ID", 0)
608
609
610 ## File access modes
611 FAM_RO = 0
612 FAM_WO = 1
613 FAM_RO_CFA = defConst("R/O", FAM_RO)
614 FAM_WO_CFA = defConst("W/O", FAM_WO)
615
616 function fileOpener(create::Bool)
617     fnameLen = popPS()
618     fnameAddr = popPS()
619     fam = popPS()
620
621     fname = getString(fnameAddr, fnameLen)
622
623     if create && !isfile(fname)
624         pushPS(0)
625         pushPS(-1) # error
626         return NEXT
627     end
628
629     if (fam == FAM_RO)
630         mode = "r"
631     else
632         mode = "w"
633     end
634
635     openFiles[nextFileID] = open(fname, mode)
636     pushPS(nextFileID)
637     pushPS(0)
638     
639     nextFileID += 1
640 end
641
642 OPEN_FILE_CFA = defPrimWord("OPEN-FILE", () -> begin
643     fileOpener(false)
644     return NEXT
645 end);
646
647 CREATE_FILE_CFA = defPrimWord("CREATE-FILE", () -> begin
648     fileOpener(true)
649     return NEXT
650 end);
651
652 CLOSE_FILE_CFA = defPrimWord("CLOSE-FILE", () -> begin
653     fid = popPS()
654     close(openFiles[fid])
655     delete!(openFiles, fid)
656     return NEXT
657 end)
658
659 CLOSE_FILES_CFA = defPrimWord("CLOSE-FILES", () -> begin
660     for fh in values(openFiles)
661         close(fh)
662     end
663     empty!(openFiles)
664
665     return NEXT
666 end)
667
668 READ_LINE_CFA = defPrimWord("READ-LINE", () -> begin
669     return NEXT
670 end)
671
672
673 EMIT_CFA = defPrimWord("EMIT", () -> begin
674     print(Char(popPS()))
675     return NEXT
676 end)
677
678 function raw_mode!(mode::Bool)
679     if ccall(:jl_tty_set_mode, Int32, (Ptr{Void}, Int32), STDIN.handle, mode) != 0
680         throw("FATAL: Terminal unable to enter raw mode.")
681     end
682 end
683
684 function getKey()
685     raw_mode!(true)
686     byte = readbytes(STDIN, 1)[1]
687     raw_mode!(false)
688
689     if byte == 0x0d
690         return 0x0a
691     elseif byte == 127
692         return 0x08
693     else
694         return byte
695     end
696 end
697
698 KEY_CFA = defPrimWord("KEY", () -> begin
699     pushPS(Int(getKey()))
700     return NEXT
701 end)
702
703 function getLineFromSTDIN()
704
705     function getFrag(s)
706         chars = collect(s)
707         slashIdx = findlast(chars, '\\')
708
709         if slashIdx > 0
710             return join(chars[slashIdx:length(chars)])
711         else
712             return nothing
713         end
714     end
715
716     function backspaceStr(s, bsCount)
717         oldLen = length(s)
718         newLen = max(0, oldLen - bsCount)
719         return join(collect(s)[1:newLen])
720     end
721
722     line = ""
723     while true
724         key = Char(getKey())
725
726         if key == '\n'
727             print(" ")
728             return AbstractString(line)
729
730         elseif key == '\x04'
731             if isempty(line)
732                 return string("\x04")
733             end
734
735         elseif key == '\b'
736             if !isempty(line)
737                 print("\b\033[K")
738                 line = backspaceStr(line, 1)
739             end
740
741         elseif key == '\e'
742             # Strip ANSI escape sequence
743             nextKey = Char(getKey())
744             if nextKey == '['
745                 while true
746                     nextKey = Char(getKey())
747                     if nextKey >= '@' || nextKey <= '~'
748                         break
749                     end
750                 end
751             end
752
753         elseif key == '\t'
754             # Currently do nothing
755
756             frag = getFrag(line)
757             if frag != nothing
758                 if haskey(REPLCompletions.latex_symbols, frag)
759                     print(repeat("\b", length(frag)))
760                     print("\033[K")
761                     comp = REPLCompletions.latex_symbols[frag]
762                     line = string(backspaceStr(line, length(frag)), comp)
763                     print(comp)
764                 end
765             end
766
767         else
768             print(key)
769             line = string(line, key)
770         end
771     end
772 end
773
774 SPAN, SPAN_CFA = defNewVar("SPAN", 0)
775 EXPECT_CFA = defPrimWord("EXPECT", () -> begin
776     maxLen = popPS()
777     addr = popPS()
778
779     line = getLineFromSTDIN()
780
781     mem[SPAN] = min(length(line), maxLen)
782     putString(line, addr, maxLen)
783
784     return NEXT
785 end)
786
787 BASE, BASE_CFA = defNewVar("BASE", 10)
788 NUMBER_CFA = defPrimWord("NUMBER", () -> begin
789     wordAddr = popPS()+1
790     wordLen = mem[wordAddr-1]
791
792     s = getString(wordAddr, wordLen)
793
794     pushPS(parse(Int64, s, mem[BASE]))
795
796     return NEXT
797 end)
798
799 # Dictionary searches
800
801 FROMLINK_CFA = defPrimWord("LINK>", () -> begin
802
803     addr = popPS()
804     lenAndFlags = mem[addr+1]
805     len = lenAndFlags & F_LENMASK
806
807     pushPS(addr + 2 + len)
808
809     return NEXT
810 end)
811
812 NUMCONTEXT, NUMCONTEXT_CFA = defNewVar("#CONTEXT", 1)
813
814 createHeader("FORTH", 0)
815 FORTH_CFA = mem[H]
816 dictWrite(defPrim(() -> begin
817     mem[CONTEXT + mem[NUMCONTEXT] - 1] = reg.W
818     return NEXT
819 end, name="FORTH"))
820 dictWrite(0) # cell for latest
821
822 CURRENT_CFA = defExistingVar("CURRENT", CURRENT)
823
824 # Switch to new FORTH vocabulary cfa
825 mem[FORTH_CFA+1] = mem[mem[CURRENT]+1]
826 mem[CURRENT] = FORTH_CFA
827
828 CONTEXT, CONTEXT_CFA = defNewVar("CONTEXT", zeros(Int64, 10))
829 mem[CONTEXT] = FORTH_CFA
830
831 FINDVOCAB_CFA = defPrimWord("FINDVOCAB", () -> begin
832     vocabCFA = popPS()
833     countedAddr = popPS()
834
835     wordAddr = countedAddr + 1
836     wordLen = mem[countedAddr]
837     word = lowercase(getString(wordAddr, wordLen))
838
839     lfa = vocabCFA+1
840     lenAndFlags = 0
841
842     while (lfa = mem[lfa]) > 0
843
844         lenAndFlags = mem[lfa+1]
845         len = lenAndFlags & F_LENMASK
846         hidden = (lenAndFlags & F_HIDDEN) == F_HIDDEN
847
848         if hidden || len != wordLen
849             continue
850         end
851
852         thisWord = lowercase(getString(lfa+2, len))
853
854         if thisWord == word
855             break
856         end
857     end
858
859     if lfa > 0
860         pushPS(lfa)
861         callPrim(mem[FROMLINK_CFA])
862         if (lenAndFlags & F_IMMED) == F_IMMED
863             pushPS(1)
864         else
865             pushPS(-1)
866         end
867     else
868         pushPS(countedAddr)
869         pushPS(0)
870     end
871
872     return NEXT
873 end)
874
875 FIND_CFA = defPrimWord("FIND", () -> begin
876
877     countedAddr = popPS()
878     context = mem[CONTEXT:(CONTEXT+mem[NUMCONTEXT]-1)]
879
880     for vocabCFA in reverse(context)
881         pushPS(countedAddr)
882         pushPS(vocabCFA)
883         callPrim(mem[FINDVOCAB_CFA])
884
885         callPrim(mem[DUP_CFA])
886         if popPS() != 0
887             return NEXT
888         else
889             popPS()
890             popPS()
891         end
892     end
893
894     pushPS(countedAddr)
895     pushPS(0)
896
897     return NEXT
898 end)
899
900
901 # Branching
902
903 BRANCH_CFA = defPrimWord("BRANCH", () -> begin
904     reg.IP += mem[reg.IP]
905     return NEXT
906 end)
907
908 ZBRANCH_CFA = defPrimWord("0BRANCH", () -> begin
909     if (popPS() == 0)
910         reg.IP += mem[reg.IP]
911     else
912         reg.IP += 1
913     end
914
915     return NEXT
916 end)
917
918 # Strings
919
920 LITSTRING_CFA = defPrimWord("LITSTRING", () -> begin
921     len = mem[reg.IP]
922     reg.IP += 1
923     pushPS(reg.IP)
924     pushPS(len)
925     reg.IP += len
926
927     return NEXT
928 end)
929
930 TYPE_CFA = defPrimWord("TYPE", () -> begin
931     len = popPS()
932     addr = popPS()
933     str = getString(addr, len)
934     print(str)
935     return NEXT
936 end)
937
938 # Interpreter/Compiler-specific I/O
939
940 TIB_CFA = defConst("TIB", TIB)
941 NUMTIB, NUMTIB_CFA = defNewVar("#TIB", 0)
942
943 FIB_CFA = defConst("FIB", TIB)
944 NUMFIB, NUMFIB_CFA = defNewVar("#FIB", 0)
945
946 TOIN, TOIN_CFA = defNewVar(">IN", 0)
947
948 QUERY_CFA = defWord("QUERY",
949     [TIB_CFA, LIT_CFA, 160, EXPECT_CFA,
950     SPAN_CFA, FETCH_CFA, NUMTIB_CFA, STORE_CFA,
951     LIT_CFA, 0, TOIN_CFA, STORE_CFA,
952     EXIT_CFA])
953
954 QUERY_FILE_CFA = defWord("QUERY-FILE",
955     [FIB_CFA, LIT_CFA, 160, ROT_CFA, READ_LINE_CFA,
956     DROP_CFA, SWAP_CFA,
957     NUMFIB_CFA, STORE_CFA,
958     EXIT_CFA])
959
960 WORD_CFA = defPrimWord("WORD", () -> begin
961     delim = popPS()
962
963     if mem[SOURCE_ID] == 0
964         bufferAddr = TIB
965         sizeAddr = NUMTIB
966     else
967         bufferAddr = FIB
968         sizeAddr = NUMFIB
969     end
970
971     # Chew up initial occurrences of delim
972     while (mem[TOIN]<mem[sizeAddr] && mem[bufferAddr+mem[TOIN]] == delim)
973         mem[TOIN] += 1
974     end
975
976     countAddr = mem[H]
977     addr = mem[H]+1
978
979     # Start reading in word
980     count = 0
981     while (mem[TOIN]<mem[sizeAddr])
982         mem[addr] = mem[bufferAddr+mem[TOIN]]
983         mem[TOIN] += 1
984
985         if (mem[addr] == delim)
986             break
987         end
988
989         count += 1
990         addr += 1
991     end
992
993     # Record count
994     mem[countAddr] = count
995     pushPS(countAddr)
996
997     return NEXT
998 end)
999
1000 # Compilation
1001
1002 STATE, STATE_CFA = defNewVar("STATE", 0)
1003
1004 COMMA_CFA = defPrimWord(",", () -> begin
1005     mem[mem[H]] = popPS()
1006     mem[H] += 1
1007
1008     return NEXT
1009 end)
1010
1011 HERE_CFA = defWord("HERE",
1012     [H_CFA, FETCH_CFA, EXIT_CFA])
1013
1014 HEADER_CFA = defPrimWord("HEADER", () -> begin
1015     wordAddr = popPS()+1
1016     wordLen = mem[wordAddr-1]
1017     word = getString(wordAddr, wordLen)
1018
1019     createHeader(word, 0)
1020
1021     return NEXT
1022 end)
1023
1024 CREATE_CFA = defWord("CREATE",
1025     [LIT_CFA, 32, WORD_CFA, HEADER_CFA,
1026     LIT_CFA, DOVAR, COMMA_CFA,
1027     EXIT_CFA])
1028
1029 DODOES = defPrim(() -> begin
1030     pushRS(reg.IP)
1031     reg.IP = popPS()
1032     pushPS(reg.W + 1)
1033     return NEXT
1034 end, name="DODOES")
1035
1036 DOES_HELPER_CFA = defPrimWord("(DOES>)", () -> begin
1037
1038     pushPS(mem[mem[CURRENT]+1])
1039     callPrim(mem[FROMLINK_CFA])
1040     cfa = popPS()
1041
1042     runtimeAddr = popPS()
1043
1044     mem[cfa] = defPrim(eval(:(() -> begin
1045         pushPS($(runtimeAddr))
1046         return DODOES
1047     end)), name="doesPrim")
1048
1049     return NEXT
1050 end, flags=F_IMMED | F_HIDDEN)
1051
1052 DOES_CFA = defWord("DOES>",
1053     [LIT_CFA, LIT_CFA, COMMA_CFA, HERE_CFA, LIT_CFA, 3, ADD_CFA, COMMA_CFA,
1054     LIT_CFA, DOES_HELPER_CFA, COMMA_CFA, LIT_CFA, EXIT_CFA, COMMA_CFA, EXIT_CFA],
1055     flags=F_IMMED)
1056
1057 LBRAC_CFA = defPrimWord("[", () -> begin
1058     mem[STATE] = 0
1059     return NEXT
1060 end, flags=F_IMMED)
1061
1062 RBRAC_CFA = defPrimWord("]", () -> begin
1063     mem[STATE] = 1
1064     return NEXT
1065 end, flags=F_IMMED)
1066
1067 HIDDEN_CFA = defPrimWord("HIDDEN", () -> begin
1068     lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
1069     mem[lenAndFlagsAddr] = mem[lenAndFlagsAddr] $ F_HIDDEN
1070     return NEXT
1071 end)
1072
1073 COLON_CFA = defWord(":",
1074     [LIT_CFA, 32, WORD_CFA,
1075     HEADER_CFA,
1076     LIT_CFA, DOCOL, COMMA_CFA,
1077     HIDDEN_CFA,
1078     RBRAC_CFA,
1079     EXIT_CFA])
1080
1081 SEMICOLON_CFA = defWord(";",
1082     [LIT_CFA, EXIT_CFA, COMMA_CFA,
1083     HIDDEN_CFA,
1084     LBRAC_CFA,
1085     EXIT_CFA], flags=F_IMMED)
1086
1087 IMMEDIATE_CFA = defPrimWord("IMMEDIATE", () -> begin
1088     lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
1089     mem[lenAndFlagsAddr] = mem[lenAndFlagsAddr] $ F_IMMED
1090     return NEXT
1091 end, flags=F_IMMED)
1092
1093 CODE_CFA = defPrimWord("CODE", () -> begin
1094     pushPS(32)
1095     callPrim(mem[WORD_CFA])
1096     callPrim(mem[HEADER_CFA])
1097
1098     exprString = "() -> begin\n"
1099     while true
1100         if mem[TOIN] >= mem[NUMTIB]
1101             exprString = string(exprString, "\n")
1102             if currentSource() == STDIN
1103                 println()
1104             end
1105
1106             pushPS(TIB)
1107             pushPS(160)
1108             callPrim(mem[EXPECT_CFA])
1109             mem[NUMTIB] = mem[SPAN]
1110             mem[TOIN] = 0
1111         end
1112
1113         pushPS(32)
1114         callPrim(mem[WORD_CFA])
1115         cAddr = popPS()
1116         thisWord = getString(cAddr+1, mem[cAddr])
1117
1118         if uppercase(thisWord) == "END-CODE"
1119             break
1120         end
1121
1122         exprString = string(exprString, " ", thisWord)
1123     end
1124     exprString = string(exprString, "\nreturn NEXT\nend")
1125
1126     func = eval(parse(exprString))
1127     dictWrite(defPrim(func))
1128
1129     return NEXT
1130 end)
1131
1132 # Outer Interpreter
1133
1134 EXECUTE_CFA = defPrimWord("EXECUTE", () -> begin
1135     reg.W = popPS()
1136     return mem[reg.W]
1137 end)
1138
1139 INTERPRET_CFA = defWord("INTERPRET",
1140     [LIT_CFA, 32, WORD_CFA, # Read next space-delimited word
1141
1142     DUP_CFA, FETCH_CFA, ZE_CFA, ZBRANCH_CFA, 3,
1143         DROP_CFA, EXIT_CFA, # Exit if TIB is exhausted
1144
1145     STATE_CFA, FETCH_CFA, ZBRANCH_CFA, 24,
1146         # Compiling
1147         FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 13,
1148
1149             # Found word. 
1150             LIT_CFA, -1, EQ_CFA, INVERT_CFA, ZBRANCH_CFA, 4,
1151
1152                 # Immediate: Execute!
1153                 EXECUTE_CFA, BRANCH_CFA, -26,
1154
1155                 # Not immediate: Compile!
1156                 COMMA_CFA, BRANCH_CFA, -29,
1157
1158             # No word found, parse number
1159             NUMBER_CFA, LIT_CFA, LIT_CFA, COMMA_CFA, COMMA_CFA, BRANCH_CFA, -36,
1160         
1161        # Interpreting
1162         FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 5,
1163
1164             # Found word. Execute!
1165             DROP_CFA, EXECUTE_CFA, BRANCH_CFA, -44,
1166
1167             # No word found, parse number and leave on stack
1168             NUMBER_CFA, BRANCH_CFA, -47,
1169     EXIT_CFA])
1170
1171 PROMPT_CFA = defPrimWord("PROMPT", () -> begin
1172     if mem[STATE] == 0
1173         print(" ok")
1174     end
1175     println()
1176
1177     return NEXT
1178 end)
1179
1180 QUIT_CFA = defWord("QUIT",
1181     [LIT_CFA, 0, STATE_CFA, STORE_CFA,
1182     LIT_CFA, 0, NUMTIB_CFA, STORE_CFA,
1183     RSP0_CFA, RSPSTORE_CFA,
1184     QUERY_CFA,
1185     INTERPRET_CFA, PROMPT_CFA,
1186     BRANCH_CFA,-4])
1187
1188 ABORT_CFA = defWord("ABORT",
1189     [CLOSE_FILES_CFA, PSP0_CFA, PSPSTORE_CFA, QUIT_CFA])
1190
1191 BYE_CFA = defPrimWord("BYE", () -> begin
1192     println("\nBye!")
1193     return 0
1194 end)
1195
1196 #### VM loop ####
1197
1198 initialized = false
1199 initFileName = nothing
1200 if isfile("lib.4th")
1201     initFileName = "lib.4th"
1202 elseif isfile(Pkg.dir("forth","src", "lib.4th"))
1203     initFileName = Pkg.dir("forth","src","lib.4th")
1204 end
1205
1206 function run(;initialize=true)
1207
1208     global initialized, initFileName
1209     if !initialized && initialize
1210         if initFileName != nothing
1211             print("Including definitions from $initFileName...")
1212
1213             # TODO
1214
1215             initialized = true
1216         else
1217             println("No library file found. Only primitive words available.")
1218         end
1219     end
1220
1221     # Start with IP pointing to first instruction of outer interpreter
1222     reg.IP = QUIT_CFA + 1
1223
1224     # Primitive processing loop.
1225     # Everyting else is simply a consequence of this loop!
1226     jmp = NEXT
1227     while jmp != 0
1228         try
1229             #println("Entering prim $(getPrimName(jmp))")
1230             jmp = callPrim(jmp)
1231
1232         catch ex
1233             showerror(STDOUT, ex)
1234             println()
1235
1236             # QUIT
1237             reg.IP = ABORT_CFA + 1
1238             jmp = NEXT
1239         end
1240     end
1241 end
1242
1243 # Debugging tools
1244
1245 TRACE_CFA = defPrimWord("TRACE", () -> begin
1246     println("reg.W: $(reg.W) reg.IP: $(reg.IP)")
1247     print("PS: "); printPS()
1248     print("RS: "); printRS()
1249     print("[paused]")
1250     readline()
1251
1252     return NEXT
1253 end)
1254
1255 function dump(startAddr::Int64; count::Int64 = 100, cellsPerLine::Int64 = 10)
1256     chars = Array{Char,1}(cellsPerLine)
1257
1258     lineStartAddr = cellsPerLine*div((startAddr-1),cellsPerLine) + 1
1259     endAddr = startAddr + count - 1
1260
1261     q, r = divrem((endAddr-lineStartAddr+1), cellsPerLine)
1262     numLines = q + (r > 0 ? 1 : 0)
1263
1264     i = lineStartAddr
1265     for l in 1:numLines
1266         print(i,":")
1267
1268         for c in 1:cellsPerLine
1269             if i >= startAddr && i <= endAddr
1270                 print("\t",mem[i])
1271                 if mem[i]>=32 && mem[i]<128
1272                     chars[c] = Char(mem[i])
1273                 else
1274                     chars[c] = '.'
1275                 end
1276             else
1277                 print("\t")
1278                 chars[c] = ' '
1279             end
1280
1281             i += 1
1282         end
1283
1284         println("\t", AbstractString(chars))
1285     end
1286 end
1287
1288 function printPS()
1289     count = reg.PSP - PSP0
1290
1291     if count > 0
1292         print("<$count>")
1293         for i in (PSP0+1):reg.PSP
1294             print(" $(mem[i])")
1295         end
1296         println()
1297     else
1298         println("Parameter stack empty")
1299     end
1300 end
1301
1302 function printRS()
1303     count = reg.RSP - RSP0
1304
1305     if count > 0
1306         print("<$count>")
1307         for i in (RSP0+1):reg.RSP
1308             print(" $(mem[i])")
1309         end
1310         println()
1311     else
1312         println("Return stack empty")
1313     end
1314 end
1315
1316 DUMP = defPrimWord("DUMP", () -> begin
1317     count = popPS()
1318     addr = popPS()
1319
1320     println()
1321     dump(addr, count=count)
1322
1323     return NEXT
1324 end)
1325
1326 end