tests/acceptance/packrat/minibasic/minibasic.rb in rockit-0.7.1 vs tests/acceptance/packrat/minibasic/minibasic.rb in rockit-0.7.2

- old
+ new

@@ -32,106 +32,124 @@ # H1G 5G1 Canada ############################################################################# require 'packrat/grammar' module MiniBasic - class Interpreter - def initialize - # For variables and their values. Default value is 0. - @vars = Hash.new(0) - end - - def eval(sexpr) - case sexpr.first - when :Statements - sexpr.statements.each {|stmt| mb_eval(stmt)} - when "If" - if mb_eval(sexpr.condition) # What is true and false in basic? - mb_eval(sexpr.statements) - elsif sexpr.optelse - mb_eval(sexpr.optelse[2]) - end - when "For" - for i in (mb_eval(sexpr.from)..mb_eval(sexpr.to)) - $vars[sexpr.ident.id] = i - mb_eval(sexpr.statements) - end - when "Read" - print "? "; STDOUT.flush - $vars[sexpr.ident.id] = STDIN.gets.to_i # Error catching?! - when "Print" - print mb_eval(sexpr.message); STDOUT.flush - when "PrintLn" - print "\n"; STDOUT.flush - when "Assignment" - $vars[sexpr.ident.id] = mb_eval(sexpr.expression) - when "Condition" - map = {">" => :>, "<" => :<, "=" => :==} - mb_eval(sexpr.left).send(map[sexpr.op], mb_eval(sexpr.right)) - when "BinExpr" - map = {"+"=>:+, "-"=>:-, "*"=>:*, "/"=>"/".intern, "MOD"=>"%".intern } - mb_eval(sexpr.left).send(map[sexpr.op], mb_eval(sexpr.right)) - when "String" - sexpr.value[1..-2] # Skip leading and trailing double quotes - when "Identifier" - $vars[sexpr.id] - when "Number" - sexpr.lexeme.to_i - end - end - end - Grammar = Packrat::Grammar.new do start_symbol :Program # Spacing (S) and Forced Spacing (FS) S = hidden(/\s*/) FS = hidden(/\s\s*/) - prod :Program, [S, :Statements, eos(), lift(0)] + prod :Program, [S, :Statements, eos(), lift(1)] prod :Statements, [plus(:Statement), lift(0)] - rule :Statement, [ - ['IF', FS, :Condition, FS, 'THEN', FS, - :Statements, FS, - maybe(:OptElse), S, - 'ENDIF', S, ast(:If)], + rule( :Statement, + ['IF', FS, :Condition, FS, 'THEN', FS, + :Statements, S, + maybe(:OptElse), S, + 'ENDIF', S, ast(:If)], - ['FOR', FS, :Identifier, S, ':=', S, :Expr, FS, 'TO', FS, :Expr, S, - :Statements, S, - 'NEXT', S, ast(:For)], + ['FOR', FS, :Identifier, S, ':=', S, :Expr, FS, 'TO', FS, :Expr, S, + :Statements, S, + 'NEXT', S, ast(:For, :expr1 => :from, :expr2 => :to)], - ['READ', FS, :Identifier, S, ast(:Read)], + ['READ', FS, :Identifier, S, ast(:Read)], - ['PRINTLN', S, ast(:PrintLn)], + ['PRINTLN', S, ast(:PrintLn)], - ['PRINT', FS, any(:Expr, :String), S, ast(:Print)], + ['PRINT', FS, any(:Expr, :String), S, ast(:Print)], - [:Identifier, S, ':=', S, :Expr, S, ast(:Assign)], - ] + [:Identifier, S, ':=', S, :Expr, S, ast(:Assign)] + ) - prod :OptElse, ['ELSE', FS, :Statements, lift(1)] + prod :OptElse, ['ELSE', FS, :Statements, lift(2)] - prod :Condition, [:Expr, S, any('<', '>', '='), S, :Expr, ast(:Cond)] + prod :Condition, [:Expr, S, any('<', '>', '='), S, :Expr, + ast(:Cond, :expr1 => :left, :expr2 => :right)] # This is crude! No precedence levels or handling of associativity. - rule :Expr, [ - [:BaseExpr, S, any('+', '-', '*', '/', 'MOD'), S, :BaseExpr, - ast(:OpExpr) - ], - [:BaseExpr, lift(0)], - ] + rule( :Expr, + [:BaseExpr, S, any('+', '-', '*', '/', 'MOD'), S, :BaseExpr, + ast(:BinExpr, :base_expr1 => :left, :base_expr2 => :right)], + [:BaseExpr, lift(0)] + ) - rule :BaseExpr, [ - [:Number, lift(0)], - [:Identifier, lift(0)], - ['(', S, :Expr, S, ')', lift(2)], - ] + rule( :BaseExpr, + [:Number, lift(0)], + [:Identifier, lift(0)], + [:String, lift(0)], + ['(', S, :Expr, S, ')', lift(2)] + ) - prod :String, [/"[^"]*"/, lift(0)] + prod :String, ['"', /[^"]*/, '"', lift(1)] #" prod :Identifier, [/[A-Z]([A-Z0-9])*/, lift(0) {|r| r.intern}] prod :Number, [/[0-9]+/, lift(0) {|r| r.to_i}] end Parser = Grammar.interpreting_parser + + class Interpreter + attr_reader :vars + + def initialize(options = {}) + # For variables and their values. Default value is 0. + @vars = Hash.new(0) + @stdout = options[:stdout] || STDOUT + @stdin = options[:stdin] || STDIN + end + + include MiniBasic::Grammar::ASTs + + def interpret_program(str) + ast = MiniBasic::Parser.parse_string(str) + interpret(ast) + end + + def interpret(ast) + case ast + when Array + ast.each {|stmt| interpret(stmt)} + when If + if interpret(ast.condition) # What is true and false in basic? + interpret(ast.statements) + elsif ast[4] + interpret(ast[4]) + end + when For + for i in (interpret(ast.from)..interpret(ast.to)) + @vars[ast.identifier] = i + interpret(ast.statements) + end + when Read + @stdout.print "? " + @stdout.flush + @vars[ast.identifier] = @stdin.gets.to_i # Error catching?! + when Print + @stdout.print(interpret(ast[1]).to_s) + @stdout.flush + when PrintLn + @stdout.print "\n" + @stdout.flush + when Assign + @vars[ast.identifier] = interpret(ast.expr) + when Cond + map = {">" => :>, "<" => :<, "=" => :==} + interpret(ast.left).send(map[ast[1]], interpret(ast.right)) + when BinExpr + map = {"+"=>:+, "-"=>:-, "*"=>:*, "/"=>"/".intern, "MOD"=>"%".intern } + interpret(ast.left).send(map[ast[1]], interpret(ast.right)) + when Symbol + @vars[ast] + else + ast # Return the value itself + end + end + end +end + +if $0 == __FILE__ + prg = File.open(ARGV[0]) {|fh| fh.read} + MiniBasic::Interpreter.new.interpret_program(prg) end