Created
October 15, 2012 23:02
-
-
Save madmo/3896213 to your computer and use it in GitHub Desktop.
Some forth-like interpreter/hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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