# # 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 if_stmt : expr assign IDENT realprim result = FuncallNode.new( @fname, val[0][0], val[0][1], [val[1]] ) if_stmt while_stmt defun : IF stmt THEN EOL stmt_list _stmt END result = IfNode.new( @fname, val[0][0], val[1], val[4], val[5] ) _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 param : 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])) : '(' 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 val[2]]) val[2]]) : expr '+' expr result = FuncallNode.new(@fname, val[0].lineno, '+', [val[0], expr '-' expr result = FuncallNode.new(@fname, val[0].lineno, '-', [val[0], 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 [2]) args : IDENT '(' args ')' result = FuncallNode.new(@fname, val[0][0], val[0][1], val IDENT '(' ')' result = FuncallNode.new(@fname, val[0][0], val[0][1], []) : expr result = val args ',' expr result.push val[2] ---- header # # intp/parser.rb # ---- inner def initialize @scope = RESERVED = 'if' => :IF, '' => :ELSE, 'while' => :WHILE, 'then' => :THEN, 'do' => :DO, 'def' => :DEF, 'true' => :TRUE, 'false' => :FALSE, 'nil' => :NIL, '' => :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, $&]] raise RuntimeError, 'must not happen' line = $' @q.push [:EOL, [lineno, nil]] lineno += 1 @q.push [false, '$'] do_parse def next_token @q.shift def on_error(t, v, values) if v line = v[0] v = v[1] line = 'last' raise Racc::ParseError, "#@fname:#line: syntax error on #v.inspect" ---- footer # intp/node.rb module Intp class IntpError < StandardError; class IntpArgumentError < IntpError; class Core def initialize @ftab = @obj = Object.new @stack = [] @stack.push Frame.new '(toplevel)' def frame @stack[-1]
def define_function(fname, node) raise IntpError, "function #fname defined twice" if @ftab.key?(fname) @ftab[fname] = node def call_function_or(fname, args) call_intp_function_or(fname, args) call_ruby_toplevel_or(fname, args) yield 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 yield def call_ruby_toplevel_or(fname, args) if @obj.respond_to? fname, true @obj.s fname, *args yield class Frame def initialize(fname) @fname = fname @lvars = attr :fname def lvar?(name) @lvars.key? name def [](key) @lvars[key] def []=(key, val) @lvars[key] = val class Node def initialize(fname, lineno) @filename = fname @lineno = lineno attr_reader :filename attr_reader :lineno def exec_list(intp, nodes) v = nil
nodes.each i v = i.evaluate(intp) v def intp_error!(msg) raise IntpError, "in #filename:#lineno: #msg" def inspect "#self.class.name/#lineno" class RootNode < Node def initialize(tree) super nil, nil @tree = tree def evaluate exec_list Core.new, @tree class DefNode < Node def initialize(file, lineno, fname, func) super file, lineno @funcname = fname @funcobj = func intp.define_function @funcname, @funcobj class FuncallNode < Node def initialize(file, lineno, func, args) super file, lineno @funcname = func @args = args 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" recv = args.shift recv.s @funcname, *args rescue IntpArgumentError, ArgumentError intp_error! $!.message
class Function < Node def initialize(file, lineno, params, body) super file, lineno @params = params @body = body def call(intp, frame, args) unless args.size == @params.size raise IntpArgumentError, "wrong # of arg for #frame.fname() (#args.size for # @params.size)" args.each_with_index do v,i frame[@params[i]] = v exec_list intp, @body class IfNode < Node def initialize(fname, lineno, cond, tstmt, fstmt) @condition = cond @tstmt = tstmt @fstmt = fstmt if @condition.evaluate(intp) exec_list intp, @tstmt exec_list intp, @fstmt if @fstmt class WhileNode < Node def initialize(fname, lineno, cond, body) @condition = cond @body = body while @condition.evaluate(intp) exec_list intp, @body class AssignNode < Node def initialize(fname, lineno, vname, val) @vname = vname @val = val intp.frame[@vname] = @val.evaluate(intp)
class VarRefNode < Node def initialize(fname, lineno, vname) @vname = vname if intp.frame.lvar?(@vname) intp.frame[@vname] intp.call_function_or(@vname, []) intp_error! "unknown method or local variable #@vname.id2name" class StringNode < Node def initialize(fname, lineno, str) @val = str @val.dup class LiteralNode < Node def initialize(fname, lineno, val) @val = val @val # 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