Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- -*- coding: raw-text -*-
-- A direct translation of Marc Simpson' RubyForth to Lua.
-- (find-angg "rubyforth/README.too")
-- Edrx, 2008nov07



-- «.stack.rb»		(to "stack.rb")
-- «.vocabulary.rb»	(to "vocabulary.rb")
-- «.kernel.rb»		(to "kernel.rb")

-- (find-lua50file "etc/compat.lua")
strchar = string.char
strfind = string.find
eval   = function (str) return assert(loadstring(str))() end
-- (find-es "rubyforth" "lrstrip")
lstrip = function (str) return str:match("^[ \t]*(.*)$") end
 strip = function (str) return str:match("^[ \t]*(.-)[ \t]*$") end
rstrip = function (str) return str:match(      "^(.-)[ \t]*$") end
string.lstrip = lstrip
string.strip  =  strip
string.rstrip = rstrip
string.split  =  split

-- «stack.rb»  (to ".stack.rb")
-- (find-rubyforthfile "stack.rb")
Stack           = {}
Stack_metatable = {__index = Stack}
Stack.new = function ()
      return setmetatable({n = 0}, Stack_metatable)
    end
Stack.push = function (s, obj)
    s.n = s.n + 1
    s[s.n] = obj
    return s
  end
Stack.pop = function (s)
    if s.n < 1 then error "Stack underflow!" end
    s.n = s.n - 1
    return s[s.n + 1]
  end
Stack.tos = function (s)
    if s.n < 1 then error "Stack underflow!" end
    return s[s.n]
  end
Stack.swap = function (s)
    local x, y = s:pop(), s:pop()
    s:push(x); s:push(y)
  end
Stack.contents = function (s)
    return s   -- This is a hack - beware!
  end
Stack.nth = function (s, n)
    return s[n + 1]  -- no checks yet
  end
Stack.depth = function (s)
    return s.n
  end




-- «vocabulary.rb»  (to ".vocabulary.rb")
-- (find-rubyforthfile "vocabulary.rb" "-[ Vocabulary Class ]-")
Vocabulary           = {}
Vocabulary_metatable = {__index = Vocabulary}
Vocabulary.new = function (name)
    local v = {vocab_name = name, [0] = {}, [1] = {}}
    return setmetatable(v, Vocabulary_metatable)
  end
Vocabulary.to_s = function (v)
    return "vocab{" .. v.vocab_name .. "}"
  end

-- (find-rubyforthfile "vocabulary.rb" "-[ Vocabulary Storage ]-")
vocabularies = Stack.new()

-- (find-rubyforthfile "vocabulary.rb" "-[ Core Words ]-")
current_vocab    = function () return vocabularies:tos() end
vocabulary_order = function () return vocabularies:contents() end
push_vocab       = function (v) vocabularies:push(v) end  
pop_vocab        = function () return vocabularies:pop() end  

-- Search for 'name' in all vocabularies, with the specified wordlist index.
find_word = function (name, wordlist_type)
    local found_xt = nil
    for i=0,vocabularies:depth()-1 do
      local vocab = vocabularies:nth(i)
      local wid = vocab[wordlist_type]
      local found_xt = wid[name]
      if found_xt then return found_xt end
    end
  end
array_index = function (arr, key, s, e)  -- like Ruby's <array>.index(key)
    for i=s or 1,e or #arr do
      if arr[i] == key then return i end
    end
  end
c_index = function (str, c)    -- like Ruby's <str>.index(c)  (c is a number)
    return str:find(strchr(c), 1, true)
  end
find_xt = function (xt, wordlist_type)
    local found_name = nil
    for i=0,vocabularies:depth()-1 do
      local vocab = vocabularies:nth(i)
      local wid = vocab[wordlist_type]
      -- ?? found_name = wid.index(xt)
      local found_name = array_index(wid, xt)
      if found_name then return found_name end
    end
  end

forth_words     = function () return current_vocab()[0] end
compiler_words  = function () return current_vocab()[1] end
forth_word      = function (name) return find_word(name, 0) end
compiler_word   = function (name) return find_word(name, 1) end
forth_header    = function (xt) return find_xt(xt, 0) end
compiler_header = function (xt) return find_xt(xt, 1) end




-- «kernel.rb»  (to ".kernel.rb")
-- (find-rubyforthfile "kernel.rb" "-[ Globals ]-")
data_stack = Stack.new()
retn_stack = Stack.new()
data_space = {n = 0}
scratch    = 0
here       = 100
ip         = 0
header_ip  = 0

rubyforth = Vocabulary.new("rubyforth")
push_vocab(rubyforth)

context     = "forth"
compiling   = false
this_header = nil

parsed       = nil
current_line = nil

-- (find-rubyforthfile "kernel.rb" "-[ Stack operations ]-")
push = function (obj) return data_stack:push(obj) end
pop  = function ()    return data_stack:pop() end
tos  = function ()    return data_stack:tos() end
rot  = function ()
    local x,y,z = pop(), pop(), pop()
    push(y); push(x); push(z)
  end
swap    = function () data_stack:swap() end
fetch   = function (addr) return data_space[addr] end
save_ip    = function () retn_stack:push(ip) end
restore_ip = function () ip = retn_stack:pop() end

-- (find-rubyforthfile "kernel.rb" "-[ Predicates ]-")
int_re   = "^-?[0-9]+$"
float_re = "^-?[0-9]*%.[0-9]+$"
word_re  = "[ \t]"
is_number  = function (str) return str:match(int_re) or str:match(float_re) end
is_primitive = function (elt) return type(elt) == "string" end

-- (find-rubyforthfile "kernel.rb" "-[ Wordlists ]-")
active_wordlist = function ()
    if context == "compiler"
    then return compiler_words()
    else return forth_words()
    end
  end
print_wordlist = function (w)   -- Pretty wordlist printing.
  local ks = sort(keys(w))
  local col = 4
  for _,k in ipairs(ks) do
    if col > 3 then col = 0; print() end
    printf("%20s", k)
    col = col + 1
  end
  print("\n")
end
print_words = function (v)      -- Print Forth then Compiler words in vocab v.
    print("FORTH\n=====\n")
    print_wordlist(v[0])
    print("COMPILER\n========\n")
    print_wordlist(v[1])
  end

-- (find-rubyforthfile "kernel.rb" "-[ String Conversion ]-")
-- We used to use traditional Forth-like strings, with conversion overhead.
-- These routines take an address/length pair from the stack and return a ruby
-- string.
_ruby_string = function (len, addr)
    local arr = {}
    for i=1,len do arr[i] = string.char(data_space[addr+i-1]) end 
    return table.concat(arr) 
  end
ruby_string = function ()                -- convert ( a u -- ) into a ruby string
    return _ruby_string(pop(), pop())
  end

-- (find-rubyforthfile "kernel.rb" "-[ Primitives ]-")
-- Our interpreter will treat strings as primitives; here we define some of the
-- lengthier operations to keep the code clear, and clean.
comma = function (n)
    data_space[here] = n; here = here + 1
  end
place = function ()
    local length, offset = pop(), pop()
    for i=0,length-1 do comma(data_space[offset+i]) end
  end
to_scratch = function (str)  -- Write a ruby string to scratch.
    local length = #str
    if length > 99 then length = 99 end
    for i=0,length-1 do data_space[scratch+i] = strbyte(str, i+1) end -- check
    push(scratch); push(length)
  end
null_parse = function ()   -- return '' and skip over the delimiter token
    parsed = ""
    current_line = current_line:sub(2)
    return parsed
  end
process_parse = function (index)  -- alter the input stream; return $parsed data
    if index == 0 then return null_parse end
    parsed       = current_line:sub(0, index)     -- check this
    current_line = current_line:sub(index)        -- check this
    return parsed
  end
parse = function ()   -- ( c -- )
    local index = c_index(current_line, pop()) or #current_line
    process_parse(index)
  end
peek = function ()    -- ( c -- )
    local index = c_index(current_line, pop()) or #current_line
    if index == 0 then return null_parse() end
    return current_line:sub(1, index-1)  -- check
  end
scratch_parse = function ()              -- perform parse, write to scratch.
    to_scratch(parse())
  end
next_word = function ()            -- fetch the index of next token separator.
    return current_line:find("()[ \t]") or #current_line
  end
parse_word = function ()           -- parse using next_word (up to separator)
    return process_parse(next_word())
  end
peek_word = function ()            -- peek using next_word
    local index = next_word()
    if index == 0 then return null_parse() end
    return current_line:sub(1, index)
  end
parse_header = function ()
    parse_word()                -- grab the next token 
    this_header = parsed        -- set most recent dictionary header
    return header(parsed)       -- write the header
  end
colon_body = function ()
    comma("doCOL")              -- begin a colon definition
    compiling = true            -- switch to compiling mode
  end
colon = function ()
    parse_header()              -- write header information
    colon_body()                -- begin doCOL
  end
noname = function ()
    push(here)                  -- leave xt on the stack
    colon_body()
  end
forth_alias = function ()       -- ( "new word" "old word" -- )
    local new_word = parse_word()
    local old_word = parse_word()
    local old_xt = xt(old_word)
    if old_xt
    then active_wordlist[new_word] = old_xt
    else print("Error: could not create an alias; '"..old_word.."' not found.")
    end
  end
variable = function ()           -- create a variable
    parse_header()
    comma("doVAR")
    comma(0)                     -- Variables default to value 0
  end
constant = function ()           -- create a constant
    parse_header()
    comma("doCONSTANT")
    comma(pop())
  end
create_body = function ()
    comma("doCREATE")
    comma(here+2)
    last_does = here
    comma(0)
  end
create = function ()            -- create a 'create' word
    parse_header()
    create_body()
  end
make = function ()              -- like create, but ( header$ -- )
    header(pop())
    create_body()
  end
dodoes = function ()            -- called at run time from a create/does> word
    -- Points to doCOL...
    data_space[last_does] = ip + 2
  end
does = function ()              -- written by does>
    comma("dodoes")
    comma("exit")
    comma("doCOL")
  end
forth_exit = function ()        -- Written by semi-colon
    comma("exit")
    compiling = false
  end
lit = function ()               -- For numerical and string literals
    ip = ip + 1
    push(fetch(ip))
  end
compile = function ()           -- Compile the next word in the input stream.
    local context_ = context    --> regardless of context, search FORTH
    context = "forth"
    push(xt(parse_word()))
    context = context_
    comma("lit")
    comma(pop())
    comma(xt(","))
  end
forth_equal = function ()
    if pop() == pop() then push(-1) else push(0) end
  end
forth_flag = function (flag)    -- convert a ruby/lua bool into a Forth flag
    if flag then return -1 else return 0 end
  end
forth_true = function ()
    return pop() ~= 0 
  end
branch = function ()  -- Branch to the next cell (take into account the NEXT call)
    ip = data_space[ip + 1] - 1
  end
qbranch = function () -- Conditional branching ('?branch'), see 'branch'
    if not forth_true() then branch() else ip = ip + 1 end
  end

-- (find-rubyforthfile "kernel.rb" "-[ Headers and XTs ]-")
header_with = function (name, address)
    active_wordlist()[name] = address
  end
header = function (name)              -- store $here into the active wordlist
    header_with(name, here)
  end
remove_header = function (header)
    active_wordlist:delete(header)    -- wrong!
  end
prim = function (name, code)    -- no threading; 'name' points to a ruby string
    header(name)
    comma(code)
  end
forth_prim = function ()        -- one-line primitive, called from Forth
    local name = parse_word()
    push(0)
    local code = parse()
    prim(name, code)
  end
-- Return the execution token for header 'name'. If local is 'true', then only
-- search the current vocabulary.
lookup_xt = function (name, locl)
    local token = nil
    if context == "compiler" then
      if locl
      then token = compiler_words[name]
      else token = compiler_word(name)
      end
      if token then return token end
    end
    if locl 
    then token = forth_words[name]
    else token = forth_word(name)
    end
    if token then return token end
    return 0
  end
xt       = function (name) lookup_xt(name, false) end
local_xt = function (name) lookup_xt(name, true)  end

-- (find-rubyforthfile "kernel.rb" "-[ Inner Interpreter ]-")
-- In this section, we define the heart of our interpreter -- forth_execute(),
-- interpret_token(), compile_token(). Tokenisation is left to the outer
-- interpreter [see the next section].
token_re = "^[ \t]*[^ \t]+[ \t]+"
doCOL = function ()
    save_ip()
    ip = header_ip + 1                   -- perform jump
    while data_space[ip] ~= "exit" do    -- hack!!!
      forth_execute(ip); ip = ip + 1
    end
    restore_ip()
  end
doVAR = function ()
    push(header_ip + 1)
  end
doCONSTANT = function ()
    push(fetch(header_ip + 1))
  end
doCREATE = function ()
    local address = fetch(header_ip + 1)
    local does_xt = fetch(header_ip + 2)
    push(address)
    forth_execute(does_xt)
  end
forth_execute = function (xt)
    local resolved = data_space[xt]     -- resolve the xt.
    if type(resolved) == "string" then  -- primitive?
      header_ip = xt                    -- can be used if necessary
      eval(resolved)                    -- *WRONG* - uses a ruby trick
    else                                -- address...
      if xt == 0 then return end        -- NOOP
      forth_execute(resolved)
    end
  end
return_to_toplevel = function () -- in case of error, clean up and reset system
    current_line = ""
    data_stack:reset()                  -- not implemented
    compiling = false
    throw("toplevel")                   -- not implemented
  end
interpret_token = function (token)
    local xt = forth_word(token)
    if xt then return forth_execute(xt) end
    if is_number(token) then return push(tonumber(token)) end
    -- Print error message and return to toplevel...
    print("'"..token.."' not found.")
    return_to_toplevel()
  end
compile_token = function (token)
    local xt = compiler_word(token)
    if xt then return forth_execute(xt) end
    xt = forth_word(token)
    if xt then return comma(xt) end
    if is_number(token) then
      comma("lit")
      comma(eval(token))                 -- not implemented
    else
      remove_header(this_header)
      print("'#"..token.."' not found during compilation of '"..this_header.."'.")
      return_to_toplevel()
    end
  end

-- (find-rubyforthfile "kernel.rb" "-[ Outer Interpreter ]-")
-- Here we define operations for reading input from strings, tokenising, and
-- dispatching these tokens to the inner interpreter.
forth_eval_token = function (token)
    if compiling
    then compile_token(token)
    else interpret_token(token)
    end
  end
forth_process_line = function () -- fetch the next token from the input stream
    current_line = current_line:strip()
    local  token = current_line:split()[1]
    if token
    then current_line = current_line:sub(#token + 1)
    else current_line = current_line:strip()
    end
    return token
  end
_forth_eval_line = function ()   -- silently evaluate the line
    while current_line do
      local token = forth_process_line()
      if not token then break end
      forth_eval_token(token)
    end
  end
forth_eval_line = function () -- as above, but print confirmation of action
    _forth_eval_line()
    if compiling 
    then print "\tcompiled"
    else print "\tok"
    end 
  end
forth_eval = function (str)
    current_line = str
    forth_eval_line()
  end
code = function (str)         -- for inlining Forth in Ruby scripts
    current_line = str
    _forth_eval_line()
  end

-- (find-rubyforthfile "kernel.rb" "-[ Toplevel ]-")
alive = true
enter_forth = function ()     -- our REPL loop
    while alive do
      current_line = io.read()
      if not current_line then break end
      -- catch ("toplevel") do
      -- begin
      forth_eval_line()
      -- rescue Exception
      -- puts "Error: Ruby has encountered the following error:\n#{$!}"
      -- end
    end
  end
compiler = function () context = "compiler" end
forth    = function () context = "forth"    end




-- (find-rubyforthfile "")
-- (find-rubyforthfile "primitives.rb")
Prim = function (str) prim(str:match("^[ \t]*([^ \t]+)[ \t]+(.*)")) end

compiler()                     -- COMPILER words.

Prim          ";       forth_exit()"
Prim    "compile       compile()"
Prim    "literal       comma('lit'); comma(pop())"
Prim      "does>       does()"
Prim          "[       compiling = false"
Prim         "\"       push(34); comma('lit'); comma(parse())"

forth()                        -- FORTH words.

Prim   ".context       puts 'Context is: \"'..context..'\"'"

Prim     "branch       branch()"
Prim    "?branch       qbranch()"

Prim          ".       printf('%s ', pop())"
Prim          ">       push(forth_flag(pop() < pop()))"
Prim          "<       push(forth_flag(pop() > pop()))"
Prim          "*       swap() ; push(pop() * pop())"
Prim          "+       swap() ; push(pop() + pop())"
Prim          "-       swap() ; push(pop() - pop())"
Prim          "/       swap() ; push(pop() / pop())"
Prim          "=       forth_equal()"
Prim          ",       comma(pop())"
Prim          "@       push(data_space[pop()])"
Prim          "!       data_space[pop()] = pop()"
Prim         "\"       push(34); push(parse())"
Prim         "1-       push(pop() - 1)"
Prim         "1+       push(pop() + 1)"
Prim         "**       swap(); push(pop()^pop())"
Prim         ".r       printf('%'..pop()..'i', pop())"

Prim         ">r       retn_stack:push(pop())"
Prim         "r>       push(retn_stack:pop())"
Prim         "r@       push(retn_stack:tos())"

Prim     "invert       push(binv(pop()))"
Prim        "and       push(band(pop(), pop()))"
Prim         "or       push(bor(pop(), pop()))"

Prim         "bl       push(32)"
Prim        "dup       push(tos())"
Prim        "rot       rot()"
Prim       "drop       pop()"
Prim       "swap       swap()"
Prim       "over       push(data_stack:nth(1))"

Prim       "here       push(here)"
Prim      "allot       here = here + pop()"
Prim      "place       place()"
Prim   "variable       variable()"
Prim   "constant       constant()"

Prim       "peek       push(peek())"    -- parse, non-destructive
Prim      "parse       push(parse())"   -- parse, returning a string
Prim       "word       scratch_parse()" -- parse, scratch: ( -- addr len )
Prim "parse-word       push(parse_word())"
Prim  "peek-word       push(peek_word())"
Prim       "type       print(pop())"
Prim       "emit       printf('%c', pop())"

Prim       "page       system('clear')"

Prim  "rubyforth       push(rubyforth)"
Prim      "vocab       push(Vocabulary.new(pop()))"
Prim     "expose       push_vocab(pop())"
Prim     "shield       pop_vocab()"
Prim      "order       vocabulary_order()"
Prim      "words       print_words(current_vocab())"
Prim     ".vocab       print_words(pop())"

Prim        "see       see()"
Prim         "cr       print()"
Prim         ".s       data_stack:contents()"
Prim        ".ds       ds_print()"      -- dump data-space to stdout

Prim        "bye       alive = false"
Prim       "quit       throw('toplevel')"
Prim      "chdir       Dir.chdir(pop())"
Prim         "cd       push(0); Dir.chdir(parse())"

Prim          ":       colon()"
Prim          "]       compiling = true"
Prim       "prim       forth_prim()"
Prim      "alias       forth_alias()"
Prim       "make       make()"
Prim     "create       create()"
Prim    ":noname       noname()"
Prim     "header       header(pop())"
Prim    "header,       swap(); header_with(pop(), pop())"

Prim   "compiler       context = 'compiler'"
Prim      "forth       context = 'forth'"

Prim          "'       push(xt(parse_word()))"
Prim        "xt?       push(xt(pop()))"
Prim      "name?       push(lookup_name(pop()))"
Prim     "local'       push(local_xt(parse_word()))"
Prim     "search       push_vocab(pop()); push(local_xt(pop())); pop_vocab()"
Prim   "local-xt       push(local_xt(pop()))"
Prim "local-name?      push(lookup_local_name(pop()))"

Prim    "execute       forth_execute(pop())"
Prim   "evaluate       code(pop())"
Prim  "ruby-eval       eval(pop())"
Prim   "lua-eval       eval(pop())"

Prim        "nip       swap(); pop()"
Prim       "tuck       swap(); push(data_stack.nth(1))"
Prim       "2dup       push($data_stack.nth(1)); push(data_stack.nth(1))"
Prim      "2swap       rot();retn_stack:push(pop());rot();push(retn_stack:pop())"
Prim      "2drop       pop(); pop()"
Prim      "space       printf ' '"
Prim     "spaces       printf(strrep(' ', pop()))"

--[ High Level ]-----------------------------------------------------------
-- # 
-- # We define comments, then load the rest of the system from a Forth script.
-- 
-- compiler
-- 
-- code  ': \ 0  parse drop ;'     # comment out the rest of the line
-- code  ': ( 41 parse drop ;'     # comment until )
-- 
-- forth
-- 
-- code  ': \ 0  parse drop ;'
-- code  ': ( 41 parse drop ;'
-- 
-- code  'include high-level.fs'   # include!