+HEADER_CFA = defPrimWord("HEADER", () -> begin
+ wordAddr = popPS()+1
+ wordLen = mem[wordAddr-1]
+ word = getString(wordAddr, wordLen)
+
+ createHeader(word, 0)
+
+ return NEXT
+end)
+
+CREATE_CFA = defWord("CREATE",
+ [LIT_CFA, 32, WORD_CFA, HEADER_CFA,
+ LIT_CFA, DOVAR, COMMA_CFA,
+ EXIT_CFA])
+
+DODOES = defPrim(() -> begin
+ pushRS(reg.IP)
+ reg.IP = popPS()
+ pushPS(reg.W + 1)
+ return NEXT
+end, name="DODOES")
+
+DOES_HELPER_CFA = defPrimWord("(DOES>)", () -> begin
+
+ pushPS(mem[mem[CURRENT]+1])
+ callPrim(mem[FROMLINK_CFA])
+ cfa = popPS()
+
+ runtimeAddr = popPS()
+
+ mem[cfa] = defPrim(eval(:(() -> begin
+ pushPS($(runtimeAddr))
+ return DODOES
+ end)), name="doesPrim")
+
+ return NEXT
+end, flags=F_IMMED | F_HIDDEN)
+
+DOES_CFA = defWord("DOES>",
+ [LIT_CFA, LIT_CFA, COMMA_CFA, HERE_CFA, LIT_CFA, 3, ADD_CFA, COMMA_CFA,
+ LIT_CFA, DOES_HELPER_CFA, COMMA_CFA, LIT_CFA, EXIT_CFA, COMMA_CFA, EXIT_CFA],
+ flags=F_IMMED)
+
+LBRAC_CFA = defPrimWord("[", () -> begin
+ mem[STATE] = 0
+ return NEXT
+end, flags=F_IMMED)
+
+RBRAC_CFA = defPrimWord("]", () -> begin
+ mem[STATE] = 1
+ return NEXT
+end, flags=F_IMMED)
+
+HIDDEN_CFA = defPrimWord("HIDDEN", () -> begin
+ lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
+ mem[lenAndFlagsAddr] = mem[lenAndFlagsAddr] $ F_HIDDEN
+ return NEXT
+end)
+
+COLON_CFA = defWord(":",
+ [LIT_CFA, 32, WORD_CFA,
+ HEADER_CFA,
+ LIT_CFA, DOCOL, COMMA_CFA,
+ HIDDEN_CFA,
+ RBRAC_CFA,
+ EXIT_CFA])
+
+SEMICOLON_CFA = defWord(";",
+ [LIT_CFA, EXIT_CFA, COMMA_CFA,
+ HIDDEN_CFA,
+ LBRAC_CFA,
+ EXIT_CFA], flags=F_IMMED)
+
+IMMEDIATE_CFA = defPrimWord("IMMEDIATE", () -> begin
+ lenAndFlagsAddr = mem[mem[CURRENT]+1] + 1
+ mem[lenAndFlagsAddr] = mem[lenAndFlagsAddr] $ F_IMMED
+ return NEXT
+end, flags=F_IMMED)
+
+CODE_CFA = defPrimWord("CODE", () -> begin
+ pushPS(32)
+ callPrim(mem[WORD_CFA])
+ callPrim(mem[HEADER_CFA])
+
+ exprString = "() -> begin\n"
+ while true
+ if mem[TOIN] >= mem[NUMTIB]
+ exprString = string(exprString, "\n")
+ if currentSource() == STDIN
+ println()
+ end
+
+ pushPS(TIB)
+ pushPS(160)
+ callPrim(mem[EXPECT_CFA])
+ mem[NUMTIB] = mem[SPAN]
+ mem[TOIN] = 0
+ end
+
+ pushPS(32)
+ callPrim(mem[WORD_CFA])
+ cAddr = popPS()
+ thisWord = getString(cAddr+1, mem[cAddr])
+
+ if uppercase(thisWord) == "END-CODE"
+ break
+ end
+
+ exprString = string(exprString, " ", thisWord)
+ end
+ exprString = string(exprString, "\nreturn NEXT\nend")
+
+ func = eval(parse(exprString))
+ dictWrite(defPrim(func))
+
+ return NEXT
+end)
+
+# Outer Interpreter
+
+EXECUTE_CFA = defPrimWord("EXECUTE", () -> begin
+ reg.W = popPS()
+ return mem[reg.W]
+end)
+
+INTERPRET_CFA = defWord("INTERPRET",
+ [LIT_CFA, 32, WORD_CFA, # Read next space-delimited word
+
+ DUP_CFA, FETCH_CFA, ZE_CFA, ZBRANCH_CFA, 3,
+ DROP_CFA, EXIT_CFA, # Exit if input buffer is exhausted
+
+ STATE_CFA, FETCH_CFA, ZBRANCH_CFA, 24,
+ # Compiling
+ FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 13,
+
+ # Found word.
+ LIT_CFA, -1, EQ_CFA, INVERT_CFA, ZBRANCH_CFA, 4,
+
+ # Immediate: Execute!
+ EXECUTE_CFA, BRANCH_CFA, -26,
+
+ # Not immediate: Compile!
+ COMMA_CFA, BRANCH_CFA, -29,
+
+ # No word found, parse number
+ NUMBER_CFA, LIT_CFA, LIT_CFA, COMMA_CFA, COMMA_CFA, BRANCH_CFA, -36,
+
+ # Interpreting
+ FIND_CFA, QDUP_CFA, ZBRANCH_CFA, 5,
+
+ # Found word. Execute!
+ DROP_CFA, EXECUTE_CFA, BRANCH_CFA, -44,
+
+ # No word found, parse number and leave on stack
+ NUMBER_CFA, BRANCH_CFA, -47,
+ EXIT_CFA])
+
+PROMPT_CFA = defPrimWord("PROMPT", () -> begin
+ if mem[STATE] == 0
+ print(" ok")
+ end
+ println()
+
+ return NEXT
+end)
+
+QUIT_CFA = defWord("QUIT",
+ [LIT_CFA, 0, STATE_CFA, STORE_CFA, # Set mode to interpret
+ LIT_CFA, 0, SOURCE_ID_CFA, STORE_CFA, # Set terminal as input stream
+ LIT_CFA, 0, NUMTIB_CFA, STORE_CFA, # Clear the input buffer
+ RSP0_CFA, RSPSTORE_CFA, # Clear the return stack
+ QUERY_CFA, # Read line of input
+ INTERPRET_CFA, PROMPT_CFA, # Interpret line
+ BRANCH_CFA,-4]) # Loop
+
+INCLUDED_CFA = defWord("INCLUDED",
+ [LIT_CFA, 0, STATE_CFA, STORE_CFA, # Set mode to interpret
+ FAM_RO_CFA, OPEN_FILE_CFA, DROP_CFA, # Open the file
+ SOURCE_ID_CFA, FETCH_CFA, SWAP_CFA, # Store current source on stack
+ SOURCE_ID_CFA, STORE_CFA, # Mark this as the current source
+ SOURCE_ID_CFA, FETCH_CFA, QUERY_FILE_CFA, # Read line from file
+ INTERPRET_CFA, # Interpret line
+ EOF_FLAG_CFA, FETCH_CFA, ZBRANCH_CFA, -7, # Loop if not EOF
+ SOURCE_ID_CFA, FETCH_CFA,
+ CLOSE_FILE_CFA, DROP_CFA, # Close file
+ SOURCE_ID_CFA, STORE_CFA, # Restore input source
+ EXIT_CFA])
+
+INCLUDE_CFA = defWord("INCLUDE", [LIT_CFA, 32, WORD_CFA,
+ DUP_CFA, INCR_CFA,
+ SWAP_CFA, FETCH_CFA,
+ INCLUDED_CFA, EXIT_CFA]);
+
+
+ABORT_CFA = defWord("ABORT",
+ [CLOSE_FILES_CFA, DROP_CFA, PSP0_CFA, PSPSTORE_CFA, QUIT_CFA])
+
+BYE_CFA = defPrimWord("BYE", () -> begin
+ println("\nBye!")
+ return 0
+end)
+
+EOF_CFA = defPrimWord("\x04", () -> begin
+ return 0
+end)
+
+#### VM loop ####
+
+initialized = false
+initFileName = nothing
+if isfile("lib.4th")
+ initFileName = "lib.4th"
+elseif isfile(Pkg.dir("forth","src", "lib.4th"))
+ initFileName = Pkg.dir("forth","src","lib.4th")
+end
+
+function run(;initialize=true)
+
+ # Start with IP pointing to first instruction of outer interpreter
+ pushRS(QUIT_CFA+1)
+
+ # Load library files
+ global initialized, initFileName
+ if !initialized && initialize
+ if initFileName != nothing
+ print("Including definitions from $initFileName...")
+
+ putString(initFileName, mem[H])
+ pushPS(mem[H])
+ pushPS(length(initFileName))
+ pushRS(INCLUDED_CFA+1)
+
+ initialized = true
+ else
+ println("No library file found. Only primitive words available.")
+ end
+ end
+
+
+ # Primitive processing loop.
+ # Everyting else is simply a consequence of this loop!
+ jmp = mem[EXIT_CFA]
+ while jmp != 0
+ try
+ print("Entering prim $(getPrimName(jmp)), PS: ")
+ printPS()
+
+ jmp = callPrim(jmp)
+
+ catch ex
+ showerror(STDOUT, ex)
+ println()
+
+ # QUIT
+ reg.IP = ABORT_CFA + 1
+ jmp = NEXT
+ end
+ end
+end
+
+# Debugging tools
+
+TRACE_CFA = defPrimWord("TRACE", () -> begin
+ println("reg.W: $(reg.W) reg.IP: $(reg.IP)")
+ print("PS: "); printPS()
+ print("RS: "); printRS()
+ print("[paused]")
+ readline()
+
+ return NEXT
+end)
+
+function dump(startAddr::Int64; count::Int64 = 100, cellsPerLine::Int64 = 10)
+ chars = Array{Char,1}(cellsPerLine)
+
+ lineStartAddr = cellsPerLine*div((startAddr-1),cellsPerLine) + 1
+ endAddr = startAddr + count - 1
+
+ q, r = divrem((endAddr-lineStartAddr+1), cellsPerLine)
+ numLines = q + (r > 0 ? 1 : 0)
+
+ i = lineStartAddr
+ for l in 1:numLines
+ print(i,":")
+
+ for c in 1:cellsPerLine
+ if i >= startAddr && i <= endAddr
+ print("\t",mem[i])
+ if mem[i]>=32 && mem[i]<128
+ chars[c] = Char(mem[i])
+ else
+ chars[c] = '.'
+ end
+ else
+ print("\t")
+ chars[c] = ' '
+ end
+
+ i += 1
+ end
+
+ println("\t", AbstractString(chars))
+ end
+end
+
+function printPS()
+ count = reg.PSP - PSP0
+
+ if count > 0
+ print("<$count>")
+ for i in (PSP0+1):reg.PSP
+ print(" $(mem[i])")
+ end
+ println()
+ else
+ println("Parameter stack empty")
+ end
+end
+
+function printRS()
+ count = reg.RSP - RSP0
+
+ if count > 0
+ print("<$count>")
+ for i in (RSP0+1):reg.RSP
+ print(" $(mem[i])")
+ end
+ println()
+ else
+ println("Return stack empty")