FossilOrigin-Name: ae1ca40694300eff97d7ee3961f0e8e0b1f61f275c8226872ac1b502c5343b07private
parent
dd97e96aca
commit
b651c84d9b
@ -1,10 +1,23 @@
|
||||
module Lox
|
||||
class Error < StandardError
|
||||
def initialize(line, where="", message)
|
||||
end
|
||||
|
||||
class ParseError < Error
|
||||
def initialize(token, message)
|
||||
where = token.type == :EOF ? "end" : "'#{token.lexeme}'"
|
||||
|
||||
error = "Error"
|
||||
error << " #{where}" unless where.empty?
|
||||
error << " at #{where}" unless where.empty?
|
||||
super("[line #{token.line}] #{error}: #{message}")
|
||||
end
|
||||
end
|
||||
|
||||
class RuntimeError < Error
|
||||
attr_reader :token
|
||||
|
||||
super("[line #{line}] #{error}: #{message}")
|
||||
def initialize(token, message)
|
||||
@token = token
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,88 @@
|
||||
module Lox
|
||||
class Interpreter
|
||||
|
||||
# The book does printing and error catching here, but
|
||||
# we're going to do it in the runner instead.
|
||||
def interpret(expr)
|
||||
value = evaluate(expr)
|
||||
stringify(value)
|
||||
end
|
||||
|
||||
def evaluate(expr) = expr.accept(self)
|
||||
|
||||
def visit_grouping(expr) = evaluate(expr.expr)
|
||||
def visit_literal(expr) = expr.value
|
||||
|
||||
def visit_unary(expr)
|
||||
right = evaluate(expr.right)
|
||||
|
||||
case expr.op.type
|
||||
when :MINUS
|
||||
check_number_operand!(expr.op, right)
|
||||
-right
|
||||
when :BANG then !truthy?(right)
|
||||
else fail
|
||||
end
|
||||
end
|
||||
|
||||
def visit_binary(expr)
|
||||
left = evaluate(expr.left)
|
||||
right = evaluate(expr.right)
|
||||
|
||||
case expr.op.type
|
||||
when :GREATER
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left > right
|
||||
when :GREATER_EQUAL
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left >= right
|
||||
when :LESS
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left < right
|
||||
when :LESS_EQUAL
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left <= right
|
||||
when :BANG_EQUAL then left != right
|
||||
when :EQUAL_EQUAL then left == right
|
||||
when :MINUS
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left - right
|
||||
when :PLUS
|
||||
unless left.is_a?(Float) && right.is_a?(Float) || left.is_a?(String) && right.is_a?(String)
|
||||
raise RuntimeError.new(expr.op, "Operands must be two numbers or two strings.")
|
||||
end
|
||||
left + right
|
||||
when :SLASH
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left / right
|
||||
when :STAR
|
||||
check_number_operands!(expr.op, left, right)
|
||||
left * right
|
||||
else fail
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def truthy?(value) = !!value
|
||||
|
||||
def check_number_operand!(token, operand)
|
||||
return if operand.is_a?(Float)
|
||||
|
||||
raise RuntimeError.new(token, "Operand must be a number.")
|
||||
end
|
||||
|
||||
def check_number_operands!(token, left, right)
|
||||
return if left.is_a?(Float) && right.is_a?(Float)
|
||||
|
||||
raise RuntimeError.new(token, "Operands must be numbers.")
|
||||
end
|
||||
|
||||
def stringify(value)
|
||||
return "nil" if value.nil?
|
||||
return value.to_s.sub(/\.0$/, "") if value.is_a?(Float)
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,104 @@
|
||||
require_relative "../test_helper"
|
||||
|
||||
require "lox/interpreter"
|
||||
require "lox/parser"
|
||||
require "lox/scanner"
|
||||
|
||||
class TestInterpreter < Lox::Test
|
||||
def setup
|
||||
@scanner = Lox::Scanner.new
|
||||
@parser = Lox::Parser.new
|
||||
@interpreter = Lox::Interpreter.new
|
||||
end
|
||||
|
||||
def test_literal
|
||||
assert_interpreted(42.0, "42")
|
||||
end
|
||||
|
||||
def test_grouping
|
||||
assert_interpreted(42.0, "(42)")
|
||||
end
|
||||
|
||||
def test_unary
|
||||
assert_interpreted(-42.0, "-42")
|
||||
assert_interpreted(false, "!42")
|
||||
assert_interpreted(false, "!true")
|
||||
assert_interpreted(true, "!false")
|
||||
assert_interpreted(true, "!nil")
|
||||
end
|
||||
|
||||
def test_binary
|
||||
assert_interpreted(42.0, "100 - 58")
|
||||
assert_interpreted(42.0, "84 / 2")
|
||||
assert_interpreted(42.0, "21 * 2")
|
||||
|
||||
# precedence
|
||||
assert_interpreted(42.0, "2 * 25 - 8")
|
||||
|
||||
assert_interpreted(42.0, "40 + 2")
|
||||
assert_interpreted("42", "\"4\" + \"2\"")
|
||||
|
||||
assert_interpreted(true, "1 > 0")
|
||||
assert_interpreted(false, "0 > 0")
|
||||
assert_interpreted(false, "0 > 1")
|
||||
|
||||
assert_interpreted(true, "1 >= 0")
|
||||
assert_interpreted(true, "0 >= 0")
|
||||
assert_interpreted(false, "0 >= 1")
|
||||
|
||||
assert_interpreted(false, "1 < 0")
|
||||
assert_interpreted(false, "0 < 0")
|
||||
assert_interpreted(true, "0 < 1")
|
||||
|
||||
assert_interpreted(false, "1 <= 0")
|
||||
assert_interpreted(true, "0 <= 0")
|
||||
assert_interpreted(true, "0 <= 1")
|
||||
|
||||
assert_interpreted(true, "0 != 1")
|
||||
assert_interpreted(false, "0 != 0")
|
||||
assert_interpreted(false, "nil != nil")
|
||||
assert_interpreted(true, "nil != 1")
|
||||
|
||||
assert_interpreted(false, "0 == 1")
|
||||
assert_interpreted(true, "0 == 0")
|
||||
assert_interpreted(true, "nil == nil")
|
||||
assert_interpreted(false, "nil == 1")
|
||||
end
|
||||
|
||||
def test_errors
|
||||
[
|
||||
"-true",
|
||||
"12 > true",
|
||||
"true < 23",
|
||||
"false * 23",
|
||||
"false + 23",
|
||||
].each do |src|
|
||||
assert_raises Lox::RuntimeError do
|
||||
evaluate(src)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_stringify
|
||||
assert_equal "nil", interpret("nil")
|
||||
assert_equal "42", interpret("42")
|
||||
assert_equal "42.1", interpret("42.1")
|
||||
assert_equal "foo", interpret("\"foo\"")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def evaluate(src)
|
||||
expr = @parser.parse(@scanner.scan(src))
|
||||
@interpreter.evaluate(expr)
|
||||
end
|
||||
|
||||
def interpret(src)
|
||||
expr = @parser.parse(@scanner.scan(src))
|
||||
@interpreter.interpret(expr)
|
||||
end
|
||||
|
||||
def assert_interpreted(expected, src)
|
||||
assert_equal expected, evaluate(src)
|
||||
end
|
||||
end
|
Loading…
Reference in new issue