# # intp # class Intp::Parser prechigh nonassoc UMINUS left '*' '/' left '+' '-' nonassoc EQ preclow rule program : stmt_list { result = RootNode.new( val[0] ) } stmt_list : { result = [] } | stmt_list stmt EOL { result.push val[1] } | stmt_list EOL stmt : expr | assign | IDENT realprim { result = FuncallNode.new( @fname, val[0][0], val[0][1], [val[1]] ) } | if_stmt | while_stmt | defun if_stmt : IF stmt THEN EOL stmt_list else_stmt END { result = IfNode.new( @fname, val[0][0], val[1], val[4], val[5] ) } else_stmt : ELSE EOL stmt_list { result = val[2] } | { result = nil } while_stmt: WHILE stmt DO EOL stmt_list END { result = WhileNode.new(@fname, val[0][0], val[1], val[4]) } defun : DEF IDENT param EOL stmt_list END { result = DefNode.new(@fname, val[0][0], val[1][1], Function.new(@fname, val[0][0], val[2], val[4])) } param : '(' name_list ')' { result = val[1] } | '(' ')' { result = [] } | { result = [] } name_list : IDENT { result = [ val[0][1] ] } | name_list ',' IDENT { result.push val[2][1] } assign : IDENT '=' expr { result = AssignNode.new(@fname, val[0][0], val[0][1], val[2]) } expr : expr '+' expr { result = FuncallNode.new(@fname, val[0].lineno, '+', [val[0], val[2]]) } | expr '-' expr { result = FuncallNode.new(@fname, val[0].lineno, '-', [val[0], val[2]]) } | expr '*' expr { result = FuncallNode.new(@fname, val[0].lineno, '*', [val[0], val[2]]) } | expr '/' expr { result = FuncallNode.new(@fname, val[0].lineno, '/', [val[0], val[2]]) } | expr EQ expr { result = FuncallNode.new(@fname, val[0].lineno, '==', [val[0], val[2]]) } | primary primary : realprim | '(' expr ')' { result = val[1] } | '-' expr =UMINUS { result = FuncallNode.new(@fname, val[0][0], '-@', [val[1]]) } realprim : IDENT { result = VarRefNode.new(@fname, val[0][0], val[0][1]) } | NUMBER { result = LiteralNode.new(@fname, *val[0]) } | STRING { result = StringNode.new(@fname, *val[0]) } | TRUE { result = LiteralNode.new(@fname, *val[0]) } | FALSE { result = LiteralNode.new(@fname, *val[0]) } | NIL { result = LiteralNode.new(@fname, *val[0]) } | funcall funcall : IDENT '(' args ')' { result = FuncallNode.new(@fname, val[0][0], val[0][1], val[2]) } | IDENT '(' ')' { result = FuncallNode.new(@fname, val[0][0], val[0][1], []) } args : expr { result = val } | args ',' expr { result.push val[2] } end ---- header # # intp/parser.rb # ---- inner def initialize @scope = {} end RESERVED = { 'if' => :IF, 'else' => :ELSE, 'while' => :WHILE, 'then' => :THEN, 'do' => :DO, 'def' => :DEF, 'true' => :TRUE, 'false' => :FALSE, 'nil' => :NIL, 'end' => :END } RESERVED_V = { 'true' => true, 'false' => false, 'nil' => nil } def parse(f, fname) @q = [] @fname = fname lineno = 1 f.each do |line| line.strip! until line.empty? case line when /\A\s+/, /\A\#.*/ ; when /\A[a-zA-Z_]\w*/ word = $& @q.push [(RESERVED[word] || :IDENT), [lineno, RESERVED_V.key?(word) ? RESERVED_V[word] : word.intern]] when /\A\d+/ @q.push [:NUMBER, [lineno, $&.to_i]] when /\A"(?:[^"\\]+|\\.)*"/, /\A'(?:[^'\\]+|\\.)*'/ @q.push [:STRING, [lineno, eval($&)]] when /\A==/ @q.push [:EQ, [lineno, '==']] when /\A./ @q.push [$&, [lineno, $&]] else raise RuntimeError, 'must not happen' end line = $' end @q.push [:EOL, [lineno, nil]] lineno += 1 end @q.push [false, '$'] do_parse end def next_token @q.shift end def on_error(t, v, values) if v line = v[0] v = v[1] else line = 'last' end raise Racc::ParseError, "#{@fname}:#{line}: syntax error on #{v.inspect}" end ---- footer # intp/node.rb module Intp class IntpError < StandardError; end class IntpArgumentError < IntpError; end class Core def initialize @ftab = {} @obj = Object.new @stack = [] @stack.push Frame.new '(toplevel)' end def frame @stack[-1] end def define_function(fname, node) raise IntpError, "function #{fname} defined twice" if @ftab.key?(fname) @ftab[fname] = node end def call_function_or(fname, args) call_intp_function_or(fname, args) { call_ruby_toplevel_or(fname, args) { yield } } end def call_intp_function_or(fname, args) if func = @ftab[fname] frame = Frame.new(fname) @stack.push frame func.call self, frame, args @stack.pop else yield end end def call_ruby_toplevel_or(fname, args) if @obj.respond_to? fname, true @obj.send fname, *args else yield end end end class Frame def initialize(fname) @fname = fname @lvars = {} end attr :fname def lvar?(name) @lvars.key? name end def [](key) @lvars[key] end def []=(key, val) @lvars[key] = val end end class Node def initialize(fname, lineno) @filename = fname @lineno = lineno end attr_reader :filename attr_reader :lineno def exec_list(intp, nodes) v = nil nodes.each {|i| v = i.evaluate(intp) } v end def intp_error!(msg) raise IntpError, "in #{filename}:#{lineno}: #{msg}" end def inspect "#{self.class.name}/#{lineno}" end end class RootNode < Node def initialize(tree) super nil, nil @tree = tree end def evaluate exec_list Core.new, @tree end end class DefNode < Node def initialize(file, lineno, fname, func) super file, lineno @funcname = fname @funcobj = func end def evaluate(intp) intp.define_function @funcname, @funcobj end end class FuncallNode < Node def initialize(file, lineno, func, args) super file, lineno @funcname = func @args = args end def evaluate(intp) args = @args.map {|i| i.evaluate intp } begin intp.call_intp_function_or(@funcname, args) { if args.empty? or not args[0].respond_to?(@funcname) intp.call_ruby_toplevel_or(@funcname, args) { intp_error! "undefined function #{@funcname.id2name}" } else recv = args.shift recv.send @funcname, *args end } rescue IntpArgumentError, ArgumentError intp_error! $!.message end end end class Function < Node def initialize(file, lineno, params, body) super file, lineno @params = params @body = body end def call(intp, frame, args) unless args.size == @params.size raise IntpArgumentError, "wrong # of arg for #{frame.fname}() (#{args.size} for #{@params.size})" end args.each_with_index do |v,i| frame[@params[i]] = v end exec_list intp, @body end end class IfNode < Node def initialize(fname, lineno, cond, tstmt, fstmt) super fname, lineno @condition = cond @tstmt = tstmt @fstmt = fstmt end def evaluate(intp) if @condition.evaluate(intp) exec_list intp, @tstmt else exec_list intp, @fstmt if @fstmt end end end class WhileNode < Node def initialize(fname, lineno, cond, body) super fname, lineno @condition = cond @body = body end def evaluate(intp) while @condition.evaluate(intp) exec_list intp, @body end end end class AssignNode < Node def initialize(fname, lineno, vname, val) super fname, lineno @vname = vname @val = val end def evaluate(intp) intp.frame[@vname] = @val.evaluate(intp) end end class VarRefNode < Node def initialize(fname, lineno, vname) super fname, lineno @vname = vname end def evaluate(intp) if intp.frame.lvar?(@vname) intp.frame[@vname] else intp.call_function_or(@vname, []) { intp_error! "unknown method or local variable #{@vname.id2name}" } end end end class StringNode < Node def initialize(fname, lineno, str) super fname, lineno @val = str end def evaluate(intp) @val.dup end end class LiteralNode < Node def initialize(fname, lineno, val) super fname, lineno @val = val end def evaluate(intp) @val end end end # module Intp begin tree = nil fname = 'src.intp' File.open(fname) {|f| tree = Intp::Parser.new.parse(f, fname) } tree.evaluate rescue Racc::ParseError, Intp::IntpError, Errno::ENOENT raise #### $stderr.puts "#{File.basename $0}: #{$!}" exit 1 end