Initial Commit
This commit is contained in:
commit
846b782493
16
backend.rb
Normal file
16
backend.rb
Normal file
@ -0,0 +1,16 @@
|
||||
class Backend
|
||||
attr_writer :dbg
|
||||
def initialize(code)
|
||||
@code=code
|
||||
@dbg=false
|
||||
@pc=0
|
||||
end
|
||||
def run
|
||||
while true
|
||||
op=@code[@pc]
|
||||
break if op==nil
|
||||
inc=execute(op)
|
||||
@pc+=1 if inc
|
||||
end
|
||||
end
|
||||
end
|
16
compiler.rb
Normal file
16
compiler.rb
Normal file
@ -0,0 +1,16 @@
|
||||
require_relative "parser.rb"
|
||||
require_relative "interpreter.rb"
|
||||
require_relative "copyprop.rb"
|
||||
require_relative "deadopt.rb"
|
||||
if ARGV[0]!=nil
|
||||
parser=Parser.new(File.read(ARGV[0]))
|
||||
else
|
||||
parser=Parser.new(File.read("prg.lang"))
|
||||
end
|
||||
parser.parse
|
||||
opt=CopyProp.new(parser.output)
|
||||
opt=DeadOpt.new(opt.optimize)
|
||||
out=opt.optimize
|
||||
p out
|
||||
interp=Interpreter.new(out)
|
||||
interp.run
|
4
console
Executable file
4
console
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env ruby
|
||||
require "pry"
|
||||
require_relative "parser.rb"
|
||||
Pry.start()
|
32
copyprop.rb
Normal file
32
copyprop.rb
Normal file
@ -0,0 +1,32 @@
|
||||
class CopyProp
|
||||
def initialize(code)
|
||||
@code=code
|
||||
end
|
||||
def optimize()
|
||||
table={}
|
||||
i=0
|
||||
@code.clone.each do |quad|
|
||||
if quad[3]==nil and quad[1]=="="
|
||||
table[quad[0]]=quad[2]
|
||||
else
|
||||
if table.has_key? quad[2]
|
||||
while true
|
||||
replace=table[quad[2]]
|
||||
break if replace==nil
|
||||
quad[2]=replace
|
||||
end
|
||||
end
|
||||
if table.has_key? quad[3]
|
||||
while true
|
||||
replace=table[quad[3]]
|
||||
break if replace==nil
|
||||
quad[3]=replace
|
||||
end
|
||||
end
|
||||
end
|
||||
@code[i]=quad
|
||||
i+=1
|
||||
end
|
||||
return @code
|
||||
end
|
||||
end
|
49
deadopt.rb
Normal file
49
deadopt.rb
Normal file
@ -0,0 +1,49 @@
|
||||
require "set"
|
||||
class DeadOpt
|
||||
def initialize(code)
|
||||
@code=code
|
||||
@always=["call","func"]
|
||||
@ignore=["call","func"]
|
||||
end
|
||||
def optimize()
|
||||
optimized=false
|
||||
while true
|
||||
table=Set.new
|
||||
@code.each do |quad|
|
||||
next if @ignore.include? quad[1]
|
||||
if quad[2]
|
||||
table.add quad[2] if quad[2].is_a? String
|
||||
end
|
||||
if quad[3]
|
||||
table.add quad[3] if quad[2].is_a? String
|
||||
end
|
||||
end
|
||||
dupcode=@code.clone
|
||||
@code=[]
|
||||
dupcode.each do |quad|
|
||||
if @always.include? quad[1]
|
||||
if quad[0]
|
||||
if !(table.include? quad[0])
|
||||
quad[0]=nil
|
||||
optimized=true
|
||||
end
|
||||
end
|
||||
@code.push quad
|
||||
else
|
||||
if quad[0]
|
||||
if table.include? quad[0]
|
||||
@code.push quad
|
||||
else
|
||||
optimized=true
|
||||
end
|
||||
else
|
||||
@code.push quad
|
||||
end
|
||||
end
|
||||
end
|
||||
break if optimized==false
|
||||
optimized=false
|
||||
end
|
||||
return @code
|
||||
end
|
||||
end
|
35
grammar.txt
Normal file
35
grammar.txt
Normal file
@ -0,0 +1,35 @@
|
||||
tokens:
|
||||
\s+ # Do nothing
|
||||
( OPEN_PAREN
|
||||
) CLOSE_PAREN
|
||||
{ OPEN_CURL
|
||||
} CLOSE_CURL
|
||||
+ ADD
|
||||
- SUB
|
||||
* MUL
|
||||
/ DIV
|
||||
= EQUALS
|
||||
, COMMA
|
||||
; SEMICOLON
|
||||
"[^"]*" STRING
|
||||
def DEF
|
||||
return RETURN
|
||||
\d+ NUMBER
|
||||
\w+ IDENT
|
||||
grammar:
|
||||
program: block
|
||||
block: { statement }
|
||||
statement: IDENT EQUALS expression SEMICOLON # ident=expression; : x=10;
|
||||
| DEF IDENT OPEN_CURL block CLOSE_CURL # def ident { block } : def hi{return 10}
|
||||
| RETURN expression SEMICOLON # return expression; : return 10;
|
||||
| expression SEMICOLON # expression; : 1+2;
|
||||
expression: term { addop term } # 1+2-3+400-67...
|
||||
term: factor { mulop factor } # 7*3*67/4*8*3...
|
||||
factor: IDENT OPEN_PAREN CLOSE_PAREN # ident() : hi()
|
||||
| IDENT # ident : hi
|
||||
| OPEN_PAREN expression CLOSE_PAREN # (expression): (1+2)
|
||||
| SUB NUMBER # -number : -10
|
||||
| NUMBER # number : 10
|
||||
| STRING # string : "hi"
|
||||
addop: ADD | SUB
|
||||
mulop: MUL | DIV
|
83
interpreter.rb
Normal file
83
interpreter.rb
Normal file
@ -0,0 +1,83 @@
|
||||
require_relative "backend.rb"
|
||||
class Interpreter < Backend
|
||||
def initialize(code)
|
||||
@vartable={}
|
||||
@paramtable=[]
|
||||
@functable={}
|
||||
@retstack=[]
|
||||
@envstack=[]
|
||||
@noinc=["call","return"]
|
||||
@infunc=false
|
||||
super(code)
|
||||
end
|
||||
def execute(op)
|
||||
if @infunc
|
||||
if op[1]=="funcend"
|
||||
@infunc=false
|
||||
end
|
||||
return true
|
||||
end
|
||||
puts "Running #{op}"
|
||||
case op[1]
|
||||
when "="
|
||||
@vartable[op[0]]=toval(op[2])
|
||||
when "+","-","*","/"
|
||||
op[2]=toval(op[2])
|
||||
op[3]=toval(op[3])
|
||||
@vartable[op[0]]=op[2].send(op[1],op[3])
|
||||
when "param"
|
||||
@paramtable.push toval(op[2])
|
||||
when "call"
|
||||
result=nil
|
||||
case op[2]
|
||||
when "puts"
|
||||
puts @paramtable.shift
|
||||
result=1
|
||||
@pc+=1
|
||||
when "gets"
|
||||
print "Enter number:"
|
||||
result=gets.chomp!.to_i
|
||||
@pc+=1
|
||||
else
|
||||
if @functable.has_key? op[2]
|
||||
@retstack.push([@pc+1,op[0]])
|
||||
@pc=@functable[op[2]]
|
||||
@envstack.push @vartable
|
||||
@vartable={}
|
||||
end
|
||||
end
|
||||
if result and op[0]
|
||||
@vartable[op[0]]=result
|
||||
end
|
||||
when "func"
|
||||
@functable[op[2]]=@pc+1
|
||||
@infunc=true
|
||||
when "return"
|
||||
info=@retstack.pop()
|
||||
@pc=info[0]
|
||||
if op[2]
|
||||
rval=toval(op[2])
|
||||
else
|
||||
rval=nil
|
||||
end
|
||||
@vartable=@envstack.pop()
|
||||
@vartable[info[1]]=rval
|
||||
end
|
||||
puts "Vars:#{@vartable}"
|
||||
puts "Params:#{@paramtable}"
|
||||
puts "Funcs:#{@functable}"
|
||||
return !(@noinc.include? op[1])
|
||||
end
|
||||
private
|
||||
def toval(obj)
|
||||
if obj.is_a? Integer
|
||||
return obj
|
||||
elsif /^\d+/.match obj
|
||||
return obj.to_i
|
||||
elsif /^"([^"]+)"/.match obj
|
||||
return $1
|
||||
else
|
||||
return @vartable[obj]
|
||||
end
|
||||
end
|
||||
end
|
69
lexer.rb
Normal file
69
lexer.rb
Normal file
@ -0,0 +1,69 @@
|
||||
require "strscan"
|
||||
require_relative "token.rb"
|
||||
class Lexer
|
||||
def initialize(string)
|
||||
@scan=StringScanner.new(string)
|
||||
@line=0
|
||||
end
|
||||
def next_token()
|
||||
return nil if @scan.eos?
|
||||
lexeme=@scan.scan(/\n/)
|
||||
if lexeme
|
||||
@line+=1
|
||||
return next_token
|
||||
end
|
||||
lexeme=@scan.scan(/\s+/)
|
||||
return next_token if lexeme
|
||||
lexeme=@scan.scan(/\(/)
|
||||
return Token.new(:OPEN_PAREN) if lexeme
|
||||
lexeme=@scan.scan(/\)/)
|
||||
return Token.new(:CLOSE_PAREN) if lexeme
|
||||
lexeme=@scan.scan(/\{/)
|
||||
return Token.new(:OPEN_CURL) if lexeme
|
||||
lexeme=@scan.scan(/\}/)
|
||||
return Token.new(:CLOSE_CURL) if lexeme
|
||||
lexeme=@scan.scan(/\+/)
|
||||
return Token.new(:ADD) if lexeme
|
||||
lexeme=@scan.scan(/\-/)
|
||||
return Token.new(:SUB) if lexeme
|
||||
lexeme=@scan.scan(/\*/)
|
||||
return Token.new(:MUL) if lexeme
|
||||
lexeme=@scan.scan(/\//)
|
||||
return Token.new(:DIV) if lexeme
|
||||
lexeme=@scan.scan(/=/)
|
||||
return Token.new(:EQUALS) if lexeme
|
||||
lexeme=@scan.scan(/,/)
|
||||
return Token.new(:COMMA) if lexeme
|
||||
lexeme=@scan.scan(/;/)
|
||||
return Token.new(:SEMICOLON) if lexeme
|
||||
lexeme=@scan.scan(/"/)
|
||||
return string() if lexeme
|
||||
lexeme=@scan.scan(/def/)
|
||||
return Token.new(:DEF) if lexeme
|
||||
lexeme=@scan.scan(/return/)
|
||||
return Token.new(:RETURN) if lexeme
|
||||
lexeme=@scan.scan(/\d+/)
|
||||
return Number.new(lexeme) if lexeme
|
||||
lexeme=@scan.scan(/\w+/)
|
||||
return Ident.new(lexeme) if lexeme
|
||||
puts "Error: Cannot match #{@scan.rest}"
|
||||
exit
|
||||
end
|
||||
def tokenize()
|
||||
prg=[]
|
||||
while true
|
||||
token=next_token
|
||||
return prg if token==nil
|
||||
prg.push token
|
||||
end
|
||||
end
|
||||
private
|
||||
def string()
|
||||
lexeme=@scan.scan(/\[^"]*/)
|
||||
ok=@scan.scan(/"/)
|
||||
if ok==nil
|
||||
puts "Error: Unterminated string \"#{lexeme} on line #{@line}"
|
||||
end
|
||||
return StrTok.new('"'+lexeme+'"')
|
||||
end
|
||||
end
|
189
parser.rb
Normal file
189
parser.rb
Normal file
@ -0,0 +1,189 @@
|
||||
require_relative "lexer.rb"
|
||||
require_relative "token.rb"
|
||||
require "pry"
|
||||
class Parser
|
||||
@@tag_to_op={:ADD=>"+",:SUB=>"-",:MUL=>"*",:DIV=>"/"}
|
||||
attr_reader :output
|
||||
def initialize(string)
|
||||
@debug=false
|
||||
@tokens=Lexer.new(string).tokenize
|
||||
p @tokens if @debug
|
||||
@token=@tokens.shift
|
||||
@tmpid=1
|
||||
@output=[]
|
||||
end
|
||||
def parse()
|
||||
block()
|
||||
expected("EOF") if @token != nil
|
||||
end
|
||||
private
|
||||
def expected(str)
|
||||
puts "Error: #{str} Expected"
|
||||
begin
|
||||
raise StandardError
|
||||
rescue StandardError => e
|
||||
e.backtrace.shift
|
||||
puts e.backtrace
|
||||
end
|
||||
exit
|
||||
end
|
||||
def get_numb()
|
||||
expected("Number") if @token.class!=Number
|
||||
tok=@token
|
||||
@token=@tokens.shift
|
||||
p tok if @debug
|
||||
p [@token,@tokens].flatten if @debug
|
||||
return tok.val
|
||||
end
|
||||
def get_name()
|
||||
expected("Name") if @token.class!=Ident
|
||||
tok=@token
|
||||
@token=@tokens.shift
|
||||
p tok if @debug
|
||||
p [@token,@tokens].flatten if @debug
|
||||
return tok.val
|
||||
end
|
||||
def get_str()
|
||||
expected("String") if @token.class!=StrTok
|
||||
tok=@token
|
||||
@token=@tokens.shift
|
||||
p tok if @debug
|
||||
p [@token,@tokens].flatten if @debug
|
||||
return tok.strval
|
||||
end
|
||||
def match(tag)
|
||||
expected(tag) if @token==nil
|
||||
expected(tag) if @token.tag!=tag
|
||||
@token=@tokens.shift
|
||||
p tok if @debug
|
||||
p [@token,@tokens].flatten if @debug
|
||||
end
|
||||
def make_tmp()
|
||||
tmp="t#{@tmpid}"
|
||||
@tmpid+=1
|
||||
return tmp
|
||||
end
|
||||
def factor()
|
||||
case @token.tag
|
||||
when :IDENT
|
||||
name=get_name()
|
||||
tmp=make_tmp()
|
||||
if @token.tag==:OPEN_PAREN
|
||||
params=[]
|
||||
match(:OPEN_PAREN)
|
||||
while true
|
||||
break if @token.tag==:CLOSE_PAREN
|
||||
params.push expression()
|
||||
break if @token.tag!=:COMMA
|
||||
match(:COMMA)
|
||||
end
|
||||
match(:CLOSE_PAREN)
|
||||
params.each do |tmpvar|
|
||||
@output.push [nil,"param",tmpvar,nil]
|
||||
end
|
||||
@output.push [tmp,"call",name,nil]
|
||||
else
|
||||
@output.push [tmp,"=",name,nil]
|
||||
end
|
||||
when :OPEN_PAREN
|
||||
match(:OPEN_PAREN)
|
||||
tmp=expression()
|
||||
match(:CLOSE_PAREN)
|
||||
when :SUB
|
||||
match(:SUB)
|
||||
tmp=make_tmp()
|
||||
num=get_numb()
|
||||
@output.push [tmp,"=","-#{num}",nil]
|
||||
else
|
||||
tmp=make_tmp()
|
||||
if @token.class==StrTok
|
||||
val=get_str()
|
||||
else
|
||||
val=get_numb()
|
||||
end
|
||||
@output.push [tmp,"=",val,nil]
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
def term()
|
||||
ops=[:MUL,:DIV]
|
||||
tmp=factor()
|
||||
oldtmp=tmp
|
||||
return tmp if @token==nil
|
||||
while ops.include? @token.tag
|
||||
tmp=make_tmp()
|
||||
op=@token.tag
|
||||
match(op)
|
||||
op=@@tag_to_op[op]
|
||||
fact=factor()
|
||||
@output.push [tmp,op,oldtmp,fact]
|
||||
oldtmp=tmp
|
||||
break if @token==nil
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
def expression()
|
||||
ops=[:ADD,:SUB]
|
||||
tmp=term()
|
||||
oldtmp=tmp
|
||||
return tmp if @token==nil
|
||||
while ops.include? @token.tag
|
||||
tmp=make_tmp()
|
||||
op=@token.tag
|
||||
match(op)
|
||||
op=@@tag_to_op[op]
|
||||
trm=term()
|
||||
@output.push [tmp,op,oldtmp,trm]
|
||||
oldtmp=tmp
|
||||
break if @token==nil
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
def assingment()
|
||||
name=get_name()
|
||||
match(:EQUALS)
|
||||
tmpvar=expression()
|
||||
@output.push [name,"=",tmpvar,nil]
|
||||
end
|
||||
def func()
|
||||
match(:DEF)
|
||||
name=@token.val
|
||||
match(:IDENT)
|
||||
match(:OPEN_CURL)
|
||||
@output.push [nil,"func",name,nil]
|
||||
block(:CLOSE_CURL)
|
||||
@output.push [nil,"return",nil,nil]
|
||||
@output.push [nil,"funcend",nil,nil]
|
||||
end
|
||||
def ret()
|
||||
match(:RETURN)
|
||||
tmpvar=expression()
|
||||
@output.push [nil,"return",tmpvar,nil]
|
||||
end
|
||||
def statement()
|
||||
if @token.tag==:IDENT and @tokens[0].tag==:EQUALS
|
||||
assingment()
|
||||
match(:SEMICOLON)
|
||||
elsif @token.tag==:DEF
|
||||
func()
|
||||
elsif @token.tag==:RETURN
|
||||
ret()
|
||||
match(:SEMICOLON)
|
||||
else
|
||||
expression()
|
||||
match(:SEMICOLON)
|
||||
end
|
||||
end
|
||||
def block(end_tok=nil)
|
||||
if end_tok
|
||||
while @token.tag!=end_tok
|
||||
statement()
|
||||
end
|
||||
match(end_tok)
|
||||
else
|
||||
while @token!=nil
|
||||
statement()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
9
prg.lang
Normal file
9
prg.lang
Normal file
@ -0,0 +1,9 @@
|
||||
def hi {
|
||||
x=10;
|
||||
puts(x);
|
||||
return x;
|
||||
}
|
||||
x=15;
|
||||
y=hi();
|
||||
puts(x);
|
||||
puts(y);
|
30
token.rb
Normal file
30
token.rb
Normal file
@ -0,0 +1,30 @@
|
||||
class Token
|
||||
attr_reader :tag
|
||||
def initialize(tag)
|
||||
@tag=tag
|
||||
end
|
||||
end
|
||||
|
||||
class Number < Token
|
||||
attr_reader :val
|
||||
def initialize(lexeme)
|
||||
super(:NUMB)
|
||||
@val=lexeme.to_i
|
||||
end
|
||||
end
|
||||
|
||||
class Ident < Token
|
||||
attr_reader :val
|
||||
def initialize(lexeme)
|
||||
super(:IDENT)
|
||||
@val=lexeme
|
||||
end
|
||||
end
|
||||
|
||||
class StrTok < Token
|
||||
attr_reader :strval
|
||||
def initialize(lexeme)
|
||||
super(:STRING)
|
||||
@strval=lexeme
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user