FossilOrigin-Name: 5d014cbe85c713fbe1fccf2a053db4b4474c0eee5e909d7cfd7de58fcbb03f77
private
alpha 2 years ago
parent cf72deef8b
commit 22a126dd0b

@ -1,12 +1,7 @@
require_relative "visitable"
module Lox module Lox
module Expr module Expr
module Visitable
def accept(visitor)
klass = self.class.to_s.split("::").last.downcase
visitor.send("visit_#{klass}", self)
end
end
Binary = Struct.new(:left, :op, :right) do Binary = Struct.new(:left, :op, :right) do
include Visitable include Visitable
end end

@ -1,14 +1,24 @@
module Lox module Lox
class Interpreter class Interpreter
# The book does printing and error catching here, but # The book does printing and error catching here, but
# we're going to do it in the runner instead. # we're going to do it in the runner instead.
def interpret(expr) def interpret(stmts)
value = evaluate(expr) stmts.each do |stmt|
stringify(value) evaluate(stmt)
end
end
def evaluate(stmt) = stmt.accept(self)
def visit_expr(expr)
evaluate(expr.expr)
nil
end end
def evaluate(expr) = expr.accept(self) def visit_print(expr)
puts stringify(evaluate(expr.expr))
nil
end
def visit_grouping(expr) = evaluate(expr.expr) def visit_grouping(expr) = evaluate(expr.expr)
def visit_literal(expr) = expr.value def visit_literal(expr) = expr.value

@ -1,135 +1,156 @@
require_relative "error" require_relative "error"
require_relative "expr" require_relative "expr"
require_relative "stmt"
module Lox module Lox
class Parser class Parser
class State < Struct.new(:tokens, :current)
def initialize(tokens) def initialize(tokens)
super(tokens, 0) @tokens = tokens
@current = 0
end
def parse!
statements = []
until eot?
statements << statement
end end
statements
end
def expression = equality private
def equality def statement
expr = comparison return print if match?(:PRINT)
while match?(:BANG_EQUAL, :EQUAL_EQUAL) expressionStmt
op = prev end
right = comparison
expr = Expr::Binary.new(expr, op, right)
end
expr def print
end value = expression
consume!(:SEMICOLON, "Expect ';' after value.")
Stmt::Print.new(value)
end
def comparison def expressionStmt
expr = term value = expression
consume!(:SEMICOLON, "Expect ';' after value.")
Stmt::Expr.new(value)
end
while match?(:GREATER, :GREATER_EQUAL, :LESS, :LESS_EQUAL) def expression = equality
op = prev
right = term
expr = Expr::Binary.new(expr, op, right)
end
expr def equality
end expr = comparison
while match?(:BANG_EQUAL, :EQUAL_EQUAL)
op = prev
right = comparison
expr = Expr::Binary.new(expr, op, right)
end
def term expr
expr = factor end
while match?(:MINUS, :PLUS) def comparison
op = prev expr = term
right = factor
expr = Expr::Binary.new(expr, op, right)
end
expr while match?(:GREATER, :GREATER_EQUAL, :LESS, :LESS_EQUAL)
op = prev
right = term
expr = Expr::Binary.new(expr, op, right)
end end
def factor expr
expr = unary end
while match?(:SLASH, :STAR) def term
op = prev expr = factor
right = unary
expr = Expr::Binary.new(expr, op, right)
end
expr while match?(:MINUS, :PLUS)
op = prev
right = factor
expr = Expr::Binary.new(expr, op, right)
end end
def unary expr
return primary unless match?(:BANG, :MINUS) end
def factor
expr = unary
while match?(:SLASH, :STAR)
op = prev op = prev
right = unary right = unary
Expr::Unary.new(op, right) expr = Expr::Binary.new(expr, op, right)
end end
def primary expr
return Expr::Literal.new(false) if match?(:FALSE) end
return Expr::Literal.new(true) if match?(:TRUE)
return Expr::Literal.new(nil) if match?(:NIL)
return Expr::Literal.new(prev.literal) if match?(:NUMBER, :STRING)
if match?(:LEFT_PAREN)
expr = expression
consume!(:RIGHT_PAREN, "Expect ')' after expression.")
return Expr::Grouping.new(expr)
end
raise ParseError.new(peek, "Expect expression.") def unary
end return primary unless match?(:BANG, :MINUS)
private op = prev
right = unary
Expr::Unary.new(op, right)
end
def match?(*types) def primary
return false unless check?(*types) return Expr::Literal.new(false) if match?(:FALSE)
return Expr::Literal.new(true) if match?(:TRUE)
return Expr::Literal.new(nil) if match?(:NIL)
return Expr::Literal.new(prev.literal) if match?(:NUMBER, :STRING)
advance! if match?(:LEFT_PAREN)
return true expr = expression
consume!(:RIGHT_PAREN, "Expect ')' after expression.")
return Expr::Grouping.new(expr)
end end
def consume!(type, message) raise ParseError.new(peek, "Expect expression.")
raise ParseError.new(peek, message) unless check?(type) end
advance! private
end
def check?(*types) def match?(*types)
return false if eot? return false unless check?(*types)
types.include?(peek.type) advance!
end return true
end
def advance! def consume!(type, message)
self.current += 1 unless eot? raise ParseError.new(peek, message) unless check?(type)
prev
end
def eot? = peek.type === :EOF advance!
def peek = tokens.fetch(current) end
def prev = tokens.fetch(current - 1)
def synchronize! def check?(*types)
advance! return false if eot?
until eot? types.include?(peek.type)
return if prev.type == :SEMICOLON end
return if %i[CLASS FUN VAR FOR IF WHILE PRINT RETURN].include?(peek.type)
advance! def advance!
end @current += 1 unless eot?
end prev
end end
# In the book, this returns nil when there's an error, but def eot? = peek.type === :EOF
# that feels weird so let's move that error handling up the def peek = @tokens.fetch(@current)
# stack for now. def prev = @tokens.fetch(@current - 1)
def parse(tokens)
state = State.new(tokens) def synchronize!
state.expression advance!
until eot?
return if prev.type == :SEMICOLON
return if %i[CLASS FUN VAR FOR IF WHILE PRINT RETURN].include?(peek.type)
advance!
end
end end
end end
end end

@ -0,0 +1,13 @@
require_relative "visitable"
module Lox
module Stmt
Expr = Struct.new(:expr) do
include Visitable
end
Print = Struct.new(:expr) do
include Visitable
end
end
end

@ -0,0 +1,8 @@
module Lox
module Visitable
def accept(visitor)
klass = self.class.to_s.split("::").last.downcase
visitor.send("visit_#{klass}", self)
end
end
end

@ -7,62 +7,61 @@ require "lox/scanner"
class TestInterpreter < Lox::Test class TestInterpreter < Lox::Test
def setup def setup
@scanner = Lox::Scanner.new @scanner = Lox::Scanner.new
@parser = Lox::Parser.new
@interpreter = Lox::Interpreter.new @interpreter = Lox::Interpreter.new
end end
def test_literal # def test_literal
assert_interpreted(42.0, "42") # assert_evaluated(42.0, "42")
end # end
def test_grouping def test_grouping
assert_interpreted(42.0, "(42)") assert_evaluated "42", "(42)"
end end
def test_unary def test_unary
assert_interpreted(-42.0, "-42") assert_evaluated "-42", "-42"
assert_interpreted(false, "!42") assert_evaluated "false", "!42"
assert_interpreted(false, "!true") assert_evaluated "false", "!true"
assert_interpreted(true, "!false") assert_evaluated "true", "!false"
assert_interpreted(true, "!nil") assert_evaluated "true", "!nil"
end end
def test_binary def test_binary
assert_interpreted(42.0, "100 - 58") assert_evaluated "42", "100 - 58"
assert_interpreted(42.0, "84 / 2") assert_evaluated "42", "84 / 2"
assert_interpreted(42.0, "21 * 2") assert_evaluated "42", "21 * 2"
# precedence # precedence
assert_interpreted(42.0, "2 * 25 - 8") assert_evaluated "42", "2 * 25 - 8"
assert_interpreted(42.0, "40 + 2") assert_evaluated "42", "40 + 2"
assert_interpreted("42", "\"4\" + \"2\"") assert_evaluated "42", "\"4\" + \"2\""
assert_interpreted(true, "1 > 0") assert_evaluated "true", "1 > 0"
assert_interpreted(false, "0 > 0") assert_evaluated "false", "0 > 0"
assert_interpreted(false, "0 > 1") assert_evaluated "false", "0 > 1"
assert_interpreted(true, "1 >= 0") assert_evaluated "true", "1 >= 0"
assert_interpreted(true, "0 >= 0") assert_evaluated "true", "0 >= 0"
assert_interpreted(false, "0 >= 1") assert_evaluated "false", "0 >= 1"
assert_interpreted(false, "1 < 0") assert_evaluated "false", "1 < 0"
assert_interpreted(false, "0 < 0") assert_evaluated "false", "0 < 0"
assert_interpreted(true, "0 < 1") assert_evaluated "true", "0 < 1"
assert_interpreted(false, "1 <= 0") assert_evaluated "false", "1 <= 0"
assert_interpreted(true, "0 <= 0") assert_evaluated "true", "0 <= 0"
assert_interpreted(true, "0 <= 1") assert_evaluated "true", "0 <= 1"
assert_interpreted(true, "0 != 1") assert_evaluated "true", "0 != 1"
assert_interpreted(false, "0 != 0") assert_evaluated "false", "0 != 0"
assert_interpreted(false, "nil != nil") assert_evaluated "false", "nil != nil"
assert_interpreted(true, "nil != 1") assert_evaluated "true", "nil != 1"
assert_interpreted(false, "0 == 1") assert_evaluated "false", "0 == 1"
assert_interpreted(true, "0 == 0") assert_evaluated "true", "0 == 0"
assert_interpreted(true, "nil == nil") assert_evaluated "true", "nil == nil"
assert_interpreted(false, "nil == 1") assert_evaluated "false", "nil == 1"
end end
def test_errors def test_errors
@ -74,31 +73,53 @@ class TestInterpreter < Lox::Test
"false + 23", "false + 23",
].each do |src| ].each do |src|
assert_raises Lox::RuntimeError do assert_raises Lox::RuntimeError do
evaluate(src) assert_evaluated nil, src
end end
end end
end end
def test_stringify def test_stringify
assert_equal "nil", interpret("nil") assert_evaluated "nil", "nil"
assert_equal "42", interpret("42") assert_evaluated "42", "42"
assert_equal "42.1", interpret("42.1") assert_evaluated "42.1", "42.1"
assert_equal "foo", interpret("\"foo\"") assert_evaluated "foo", "\"foo\""
end
def test_multiple_statements
assert_interpreted <<~EXPECTED.chomp, <<~SRC
one
true
3
EXPECTED
print "one";
print true;
print 2 + 1;
SRC
end end
private private
def evaluate(src) def assert_interpreted(expected, src)
expr = @parser.parse(@scanner.scan(src)) output = with_stdout {
@interpreter.evaluate(expr) stmts = Lox::Parser.new(@scanner.scan(src)).parse!
@interpreter.interpret(stmts)
}
assert_equal expected, output
end end
def interpret(src) def assert_evaluated(expected, src)
expr = @parser.parse(@scanner.scan(src)) assert_interpreted(expected, "print #{src};")
@interpreter.interpret(expr)
end end
def assert_interpreted(expected, src) def with_stdout
assert_equal expected, evaluate(src) original = $stdout
$stdout = StringIO.new
yield
output = $stdout.string.chomp
$stdout = original
output
end end
end end

@ -56,7 +56,7 @@ class TestParser < Lox::Test
def parse(src, name) def parse(src, name)
tokens = @scanner.scan(src) tokens = @scanner.scan(src)
Lox::Parser::State.new(tokens).send(name) Lox::Parser.new(tokens).send(name)
end end
def assert_parsed(expected, name, src) def assert_parsed(expected, name, src)

Loading…
Cancel
Save