commit 846b7824934be136b3d770e1d63fcda4b30e5ad5 Author: pjht Date: Fri Jul 20 19:44:57 2018 -0500 Initial Commit diff --git a/backend.rb b/backend.rb new file mode 100644 index 0000000..88ebbda --- /dev/null +++ b/backend.rb @@ -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 diff --git a/compiler.rb b/compiler.rb new file mode 100644 index 0000000..902b5bf --- /dev/null +++ b/compiler.rb @@ -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 diff --git a/console b/console new file mode 100755 index 0000000..f38cda1 --- /dev/null +++ b/console @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require "pry" +require_relative "parser.rb" +Pry.start() diff --git a/copyprop.rb b/copyprop.rb new file mode 100644 index 0000000..9a627aa --- /dev/null +++ b/copyprop.rb @@ -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 diff --git a/deadopt.rb b/deadopt.rb new file mode 100644 index 0000000..ddf60de --- /dev/null +++ b/deadopt.rb @@ -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 diff --git a/grammar.txt b/grammar.txt new file mode 100644 index 0000000..6db4401 --- /dev/null +++ b/grammar.txt @@ -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 diff --git a/interpreter.rb b/interpreter.rb new file mode 100644 index 0000000..b780a6f --- /dev/null +++ b/interpreter.rb @@ -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 diff --git a/lexer.rb b/lexer.rb new file mode 100644 index 0000000..01050d9 --- /dev/null +++ b/lexer.rb @@ -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 diff --git a/parser.rb b/parser.rb new file mode 100644 index 0000000..b4855ea --- /dev/null +++ b/parser.rb @@ -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 diff --git a/prg.lang b/prg.lang new file mode 100644 index 0000000..682fafb --- /dev/null +++ b/prg.lang @@ -0,0 +1,9 @@ +def hi { + x=10; + puts(x); + return x; +} +x=15; +y=hi(); +puts(x); +puts(y); diff --git a/token.rb b/token.rb new file mode 100644 index 0000000..58737cd --- /dev/null +++ b/token.rb @@ -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