module forth
+import Base.REPLCompletions
+
# VM mem size
size_mem = 1000000 # 1 mega-int
size_RS = 1000 # Return stack size
size_PS = 1000 # Parameter stack size
size_TIB = 1000 # Terminal input buffer size
+size_FIB = 1000 # File input buffer size
# Memory arrays
mem = Array{Int64,1}(size_mem)
primitives = Array{Function,1}()
-primNames = Array{ASCIIString,1}()
+primNames = Array{AbstractString,1}()
# Memory geography and built-in variables
RSP0 = nextVarAddr # bottom of RS
PSP0 = RSP0 + size_RS # bottom of PS
TIB = PSP0 + size_PS # address of terminal input buffer
-mem[H] = TIB + size_TIB # location of bottom of dictionary
+FIB = TIB + size_TIB # address of terminal input buffer
+mem[H] = FIB + size_FIB # location of bottom of dictionary
mem[FORTH_LATEST] = 0 # zero FORTH dict latest (no previous def)
mem[CURRENT] = FORTH_LATEST-1 # Compile words to system dict initially
end
end
+function ensurePSCapacity(toAdd::Int64)
+ if reg.PSP + toAdd >= PSP0 + size_PS
+ error("Parameter stack overflow.")
+ end
+end
+
function ensureRSDepth(depth::Int64)
if reg.RSP - RSP0 < depth
error("Return stack underflow.")
end
end
+function ensureRSCapacity(toAdd::Int64)
+ if reg.RSP + toAdd >= RSP0 + size_RS
+ error("Return stack overflow.")
+ end
+end
+
function pushRS(val::Int64)
+ ensureRSCapacity(1)
mem[reg.RSP+=1] = val
end
end
function pushPS(val::Int64)
+ ensurePSCapacity(1)
+
mem[reg.PSP += 1] = val
end
# Handy functions for adding/retrieving strings to/from memory.
-getString(addr::Int64, len::Int64) = ASCIIString([Char(c) for c in mem[addr:(addr+len-1)]])
+getString(addr::Int64, len::Int64) = AbstractString([Char(c) for c in mem[addr:(addr+len-1)]])
-function putString(str::ASCIIString, addr::Int64)
- mem[addr:(addr+length(str)-1)] = [Int64(c) for c in str]
+function putString(str::AbstractString, addr::Int64, maxLen::Int64)
+ len = min(length(str), maxLen)
+ mem[addr:(addr+len-1)] = [Int64(c) for c in str]
end
-stringAsInts(str::ASCIIString) = [Int(c) for c in collect(str)]
+stringAsInts(str::AbstractString) = [Int(c) for c in collect(str)]
# Primitive creation and calling functions
mem[H] += length(ints)
end
dictWrite(int::Int64) = dictWrite([int])
-dictWriteString(string::ASCIIString) = dictWrite([Int64(c) for c in string])
+dictWriteString(string::AbstractString) = dictWrite([Int64(c) for c in string])
function createHeader(name::AbstractString, flags::Int64)
mem[mem[H]] = mem[mem[CURRENT]+1]
# I/O
-sources = Array{Any,1}()
-currentSource() = sources[length(sources)]
+openFiles = Dict{Int64,IOStream}()
+nextFileID = 1
+SOURCE_ID, SOURCE_ID_CFA = defNewVar("SOURCE-ID", 0)
+
+
+## File access modes
+FAM_RO = 0
+FAM_WO = 1
+FAM_RO_CFA = defConst("R/O", FAM_RO)
+FAM_WO_CFA = defConst("W/O", FAM_WO)
+
+function fileOpener(create::Bool)
+ fnameLen = popPS()
+ fnameAddr = popPS()
+ fam = popPS()
+
+ fname = getString(fnameAddr, fnameLen)
-EOF_CFA = defPrimWord("\x04", () -> begin
- if currentSource() != STDIN
- close(pop!(sources))
+ if create && !isfile(fname)
+ pushPS(0)
+ pushPS(-1) # error
return NEXT
+ end
+
+ if (fam == FAM_RO)
+ mode = "r"
else
- return 0
+ mode = "w"
+ end
+
+ openFiles[nextFileID] = open(fname, mode)
+ pushPS(nextFileID)
+ pushPS(0)
+
+ nextFileID += 1
+end
+
+OPEN_FILE_CFA = defPrimWord("OPEN-FILE", () -> begin
+ fileOpener(false)
+ return NEXT
+end);
+
+CREATE_FILE_CFA = defPrimWord("CREATE-FILE", () -> begin
+ fileOpener(true)
+ return NEXT
+end);
+
+CLOSE_FILE_CFA = defPrimWord("CLOSE-FILE", () -> begin
+ fid = popPS()
+ close(openFiles[fid])
+ delete!(openFiles, fid)
+ return NEXT
+end)
+
+CLOSE_FILES_CFA = defPrimWord("CLOSE-FILES", () -> begin
+ for fh in values(openFiles)
+ close(fh)
end
+ empty!(openFiles)
+
+ return NEXT
end)
+READ_LINE_CFA = defPrimWord("READ-LINE", () -> begin
+ return NEXT
+end)
+
+
EMIT_CFA = defPrimWord("EMIT", () -> begin
print(Char(popPS()))
return NEXT
end)
function getLineFromSTDIN()
+
+ function getFrag(s)
+ chars = collect(s)
+ slashIdx = findlast(chars, '\\')
+
+ if slashIdx > 0
+ return join(chars[slashIdx:length(chars)])
+ else
+ return nothing
+ end
+ end
+
+ function backspaceStr(s, bsCount)
+ oldLen = length(s)
+ newLen = max(0, oldLen - bsCount)
+ return join(collect(s)[1:newLen])
+ end
+
line = ""
while true
key = Char(getKey())
if key == '\n'
print(" ")
- return ASCIIString(line)
+ return AbstractString(line)
elseif key == '\x04'
if isempty(line)
elseif key == '\b'
if !isempty(line)
- line = line[1:length(line)-1]
- print("\b \b")
+ print("\b\033[K")
+ line = backspaceStr(line, 1)
end
elseif key == '\e'
end
end
+ elseif key == '\t'
+ # Currently do nothing
+
+ frag = getFrag(line)
+ if frag != nothing
+ if haskey(REPLCompletions.latex_symbols, frag)
+ print(repeat("\b", length(frag)))
+ print("\033[K")
+ comp = REPLCompletions.latex_symbols[frag]
+ line = string(backspaceStr(line, length(frag)), comp)
+ print(comp)
+ end
+ end
+
else
print(key)
line = string(line, key)
maxLen = popPS()
addr = popPS()
- if currentSource() == STDIN
- line = getLineFromSTDIN()
- else
- if !eof(currentSource())
- line = chomp(readline(currentSource()))
- else
- line = "\x04" # eof
- end
- end
+ line = getLineFromSTDIN()
mem[SPAN] = min(length(line), maxLen)
- putString(line[1:mem[SPAN]], addr)
+ putString(line, addr, maxLen)
return NEXT
end)
TIB_CFA = defConst("TIB", TIB)
NUMTIB, NUMTIB_CFA = defNewVar("#TIB", 0)
+
+FIB_CFA = defConst("FIB", TIB)
+NUMFIB, NUMFIB_CFA = defNewVar("#FIB", 0)
+
TOIN, TOIN_CFA = defNewVar(">IN", 0)
QUERY_CFA = defWord("QUERY",
LIT_CFA, 0, TOIN_CFA, STORE_CFA,
EXIT_CFA])
+QUERY_FILE_CFA = defWord("QUERY-FILE",
+ [FIB_CFA, LIT_CFA, 160, ROT_CFA, READ_LINE_CFA,
+ DROP_CFA, SWAP_CFA,
+ NUMFIB_CFA, STORE_CFA,
+ EXIT_CFA])
+
WORD_CFA = defPrimWord("WORD", () -> begin
delim = popPS()
+ if mem[SOURCE_ID] == 0
+ bufferAddr = TIB
+ sizeAddr = NUMTIB
+ else
+ bufferAddr = FIB
+ sizeAddr = NUMFIB
+ end
+
# Chew up initial occurrences of delim
- while (mem[TOIN]<mem[NUMTIB] && mem[TIB+mem[TOIN]] == delim)
+ while (mem[TOIN]<mem[sizeAddr] && mem[bufferAddr+mem[TOIN]] == delim)
mem[TOIN] += 1
end
# Start reading in word
count = 0
- while (mem[TOIN]<mem[NUMTIB])
- mem[addr] = mem[TIB+mem[TOIN]]
+ while (mem[TOIN]<mem[sizeAddr])
+ mem[addr] = mem[bufferAddr+mem[TOIN]]
mem[TOIN] += 1
if (mem[addr] == delim)
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
EXIT_CFA])
PROMPT_CFA = defPrimWord("PROMPT", () -> begin
- if currentSource() == STDIN
- if mem[STATE] == 0
- print(" ok")
- end
- println()
+ if mem[STATE] == 0
+ print(" ok")
end
+ println()
return NEXT
end)
BRANCH_CFA,-4])
ABORT_CFA = defWord("ABORT",
- [PSP0_CFA, PSPSTORE_CFA, QUIT_CFA])
+ [CLOSE_FILES_CFA, PSP0_CFA, PSPSTORE_CFA, QUIT_CFA])
BYE_CFA = defPrimWord("BYE", () -> begin
println("\nBye!")
return 0
end)
-# File I/O
-
-INCLUDE_CFA = defPrimWord("INCLUDE", () -> begin
- pushPS(32)
- callPrim(mem[WORD_CFA])
- wordAddr = popPS()+1
- wordLen = mem[wordAddr-1]
- word = getString(wordAddr, wordLen)
-
- fname = word
- if !isfile(fname)
- fname = Pkg.dir("forth","src",word)
- if !isfile(fname)
- error("No file named $word found in current directory or package source directory.")
- end
- end
- push!(sources, open(fname, "r"))
-
- # Clear input buffer
- mem[NUMTIB] = 0
-
- return NEXT
-end)
-
-
#### VM loop ####
initialized = false
end
function run(;initialize=true)
- # Begin with STDIN as source
- push!(sources, STDIN)
global initialized, initFileName
if !initialized && initialize
if initFileName != nothing
print("Including definitions from $initFileName...")
- push!(sources, open(initFileName, "r"))
+
+ # TODO
+
initialized = true
else
println("No library file found. Only primitive words available.")
showerror(STDOUT, ex)
println()
- while !isempty(sources) && currentSource() != STDIN
- close(pop!(sources))
- end
-
# QUIT
reg.IP = ABORT_CFA + 1
jmp = NEXT
i += 1
end
- println("\t", ASCIIString(chars))
+ println("\t", AbstractString(chars))
end
end