Initial Commit

This commit is contained in:
pjht 2018-07-20 19:44:57 -05:00
commit 846b782493
11 changed files with 532 additions and 0 deletions

16
backend.rb Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
#!/usr/bin/env ruby
require "pry"
require_relative "parser.rb"
Pry.start()

32
copyprop.rb Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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