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