Skip to content

Instantly share code, notes, and snippets.

@madmo
Created October 15, 2012 23:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save madmo/3896213 to your computer and use it in GitHub Desktop.
Save madmo/3896213 to your computer and use it in GitHub Desktop.
Some forth-like interpreter/hack
#!/usr/bin/ruby
require 'thread'
class Lexer
attr_accessor :start
attr_accessor :pos
attr_accessor :tokens
def initialize (input, output)
@input = input
@start = 0
@pos = 0
@tokens = output
@line = 0
end
def lexCode
r = nxt
case r
when "\n"
@line += 1
ignore
when "", " ", "\t"
ignore
when "#"
return method :lexComment
when "\""
return method :lexString
when "+", "-"
if ("0".."9").include? peek
return method :lexNumber
else
return method :lexMethod
end
when ("0".."9")
return method :lexNumber
when -1
return nil
else
return method :lexMethod
end
method :lexCode
end
def lexMethod
while not [" ", "\t", "\n", -1].include? nxt
end
backup
emit "method"
nxt
ignore
return method :lexCode
end
def lexString
ignore
while true
case nxt
when "\\"
if nxt == -1
error "wrong string escape"
end
when "\n", -1
error "undelimited string"
when "\""
break
end
end
backup
emit "string"
nxt
ignore
return method :lexCode
end
def lexComment
ignore
while true
r = nxt
if r == -1 || r == "\n"
break
end
end
backup
emit "comment"
return method :lexCode
end
def lexNumber
backup
unless scanNumber
error "bad number format"
end
if ["+", "-"].include? peek
if ! scanNumber or @input[@pos-1,1] != "i"
error "bad number format"
end
emit "complex"
else
emit "number"
end
return method :lexCode
end
def scanNumber ()
accept "+-"
digits = "0123456789"
if accept "0" and accept "xX"
digits = "0123456789abcdefABCDEF"
end
acceptRun digits
if accept "."
acceptRun digits
end
if accept "eE"
accept "+-"
acceptRun "0123456789"
end
accept "i"
unless ["", " ", "\t", "\n", -1].include? peek
nxt
return false
end
return true
end
def nxt
if @pos >= @input.length
@pos += 1
return -1
end
res = @input[@pos,1]
@pos += 1
return res
end
def peek
res = nxt
backup
return res
end
def ignore
@start = @pos
end
def backup
@pos -= 1
end
def accept (valid)
if valid.index nxt
return true
end
backup
return false
end
def acceptRun (valid)
while valid.index nxt
end
backup
end
def error(message)
tokens.push(["error", @line, message])
Thread.current.exit
end
def emit(type)
@tokens.push([type, @input[@start..@pos-1]])
@start = @pos
end
def debug
puts "dbg '#{@input[@start..@pos-1]}'"
end
def run()
state = lexCode()
while state
state = state.call()
end
end
end
class Compiler
attr_accessor :symtable
attr_accessor :code
def initialize (tokens)
@code = []
@symtable = {}
@tokens = tokens
@cstack = []
end
def compile
state = compToken
while state
state = state.call
end
end
def compToken
t = nxt
case t[0]
when "number"
return method :compNumber
when "method"
return method :compMethod
when "string"
return method :compString
when nil
return nil
end
return method :compToken
end
def compNumber
@code.push "p"
if @current[1].index(".") != nil
@code.push @current[1].to_f
else
@code.push @current[1].to_i
end
return method :compToken
end
def compString
@code.push "p"
@code.push @current[1]
return method :compToken
end
def compMethod
meth = @current[1]
case meth
when "+", "-", "."
@code.push meth
when "def"
name = nxt[1]
@code.push "rj"
@code.push nil
@symtable[name] = @code.length
@cstack.push(@code.length - 1)
when "end"
@code.push "r"
pos = @cstack.pop
@code[pos] = (@code.length) - pos
else
if pos = @symtable[meth]
@code.push "c"
@code.push pos
end
end
return method :compToken
end
def nxt
@current = @tokens.pop rescue [nil, nil]
end
end
class Interpreter
attr_accessor :code
attr_accessor :ip
attr_accessor :ds
attr_accessor :rs
def initialize(code)
@code = code
@ip = 0
@ds = []
@rs = []
end
def run
while cmd = @code[@ip]
case cmd
when "c"
@rs.push @ip+1
@ip = @code[@ip+1]
@ip -= 1
when "r"
@ip = @rs.pop
when "j"
@ip = @code[@ip+1]
@ip -= 1 # decerase ip it is incereased after each loop
when "rj"
@ip += @code[@ip+1]
when "p"
@ds.push @code[@ip+1]
@ip += 1 # skip parameter
when "+"
b = @ds.pop
a = @ds.pop
@ds.push a + b
when "-"
b = @ds.pop
a = @ds.pop
@ds.push a - b
when "."
puts @ds.pop
else
puts "bad cmd #{cmd}"
return
end
@ip += 1
end
end
end
if __FILE__ == $0
q = Queue.new
l = Lexer.new(STDIN.read, q)
Thread.new do
l.run()
end
c = Compiler.new(q)
c.compile
i = Interpreter.new c.code
i.run
puts i.ds
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment