From 846b7824934be136b3d770e1d63fcda4b30e5ad5 Mon Sep 17 00:00:00 2001 From: pjht Date: Fri, 20 Jul 2018 19:44:57 -0500 Subject: [PATCH] Initial Commit --- backend.rb | 16 +++++ compiler.rb | 16 +++++ console | 4 ++ copyprop.rb | 32 +++++++++ deadopt.rb | 49 +++++++++++++ grammar.txt | 35 +++++++++ interpreter.rb | 83 ++++++++++++++++++++++ lexer.rb | 69 ++++++++++++++++++ parser.rb | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ prg.lang | 9 +++ token.rb | 30 ++++++++ 11 files changed, 532 insertions(+) create mode 100644 backend.rb create mode 100644 compiler.rb create mode 100755 console create mode 100644 copyprop.rb create mode 100644 deadopt.rb create mode 100644 grammar.txt create mode 100644 interpreter.rb create mode 100644 lexer.rb create mode 100644 parser.rb create mode 100644 prg.lang create mode 100644 token.rb 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