FossilOrigin-Name: 53d2e006949bbca6adb34110c64c3288c1077e2cb8ba71ef24803b81aedccb12private
parent
a2a685bc47
commit
18be1863dd
@ -1,20 +1,25 @@
|
|||||||
#!/usr/bin/env ruby -w
|
#!/usr/bin/env ruby -w
|
||||||
|
|
||||||
|
require_relative "lox/ast_printer"
|
||||||
require_relative "lox/error"
|
require_relative "lox/error"
|
||||||
require_relative "lox/expr"
|
require_relative "lox/expr"
|
||||||
|
require_relative "lox/parser"
|
||||||
require_relative "lox/scanner"
|
require_relative "lox/scanner"
|
||||||
require_relative "lox/token"
|
require_relative "lox/token"
|
||||||
|
|
||||||
module Lox
|
module Lox
|
||||||
class Runner
|
class Runner
|
||||||
def initialize(scanner: Scanner.new)
|
def initialize(scanner=Scanner.new, parser=Parser.new)
|
||||||
@scanner = scanner
|
@scanner, @parser = scanner, parser
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(src)
|
def run(src)
|
||||||
@scanner.scan(src).each do |token|
|
tokens = @scanner.scan(src)
|
||||||
puts token
|
expr = @parser.parse(tokens)
|
||||||
end
|
|
||||||
|
puts AstPrinter.new.print(expr)
|
||||||
|
rescue ParseError => e
|
||||||
|
puts e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
module Lox
|
module Lox
|
||||||
class Error < StandardError
|
class Error < StandardError
|
||||||
def initialize(line:, where: "", message:)
|
def initialize(line, where="", message)
|
||||||
@line, @where, @message = line, where, message
|
error = "Error"
|
||||||
end
|
error << " #{where}" unless where.empty?
|
||||||
|
|
||||||
def to_s
|
super("[line #{line}] #{error}: #{message}")
|
||||||
"[line #@line] Error#@where: #@message"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,143 @@
|
|||||||
|
require_relative "error"
|
||||||
|
require_relative "expr"
|
||||||
|
|
||||||
|
module Lox
|
||||||
|
class ParseError < Error
|
||||||
|
def initialize(token, message)
|
||||||
|
at = token.type == :EOF ? "end" : "'#{token.lexeme}'"
|
||||||
|
|
||||||
|
super(token.line, "at #{at}", message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
class State < Struct.new(:tokens, :current)
|
||||||
|
def initialize(tokens)
|
||||||
|
super(tokens, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def expression = equality
|
||||||
|
|
||||||
|
def equality
|
||||||
|
expr = comparison
|
||||||
|
|
||||||
|
while match?(:BANG_EQUAL, :EQUAL_EQUAL)
|
||||||
|
op = prev
|
||||||
|
right = comparison
|
||||||
|
expr = Expr::Binary.new(expr, op, right)
|
||||||
|
end
|
||||||
|
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
|
||||||
|
def comparison
|
||||||
|
expr = term
|
||||||
|
|
||||||
|
while match?(:GREATER, :GREATER_EQUAL, :LESS, :LESS_EQUAL)
|
||||||
|
op = prev
|
||||||
|
right = term
|
||||||
|
expr = Expr::Binary.new(expr, op, right)
|
||||||
|
end
|
||||||
|
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def term
|
||||||
|
expr = factor
|
||||||
|
|
||||||
|
while match?(:MINUS, :PLUS)
|
||||||
|
op = prev
|
||||||
|
right = factor
|
||||||
|
expr = Expr::Binary.new(expr, op, right)
|
||||||
|
end
|
||||||
|
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
|
||||||
|
def factor
|
||||||
|
expr = unary
|
||||||
|
|
||||||
|
while match?(:SLASH, :STAR)
|
||||||
|
op = prev
|
||||||
|
right = unary
|
||||||
|
expr = Expr::Binary.new(expr, op, right)
|
||||||
|
end
|
||||||
|
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
|
||||||
|
def unary
|
||||||
|
return primary unless match?(:BANG, :MINUS)
|
||||||
|
|
||||||
|
op = prev
|
||||||
|
right = unary
|
||||||
|
Expr::Unary.new(op, right)
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary
|
||||||
|
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)
|
||||||
|
|
||||||
|
if match?(:LEFT_PAREN)
|
||||||
|
expr = expression
|
||||||
|
consume!(:RIGHT_PAREN, "Expect ')' after expression.")
|
||||||
|
return Expr::Grouping.new(expr)
|
||||||
|
end
|
||||||
|
|
||||||
|
raise ParseError.new(peek, "Expect expression.")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def match?(*types)
|
||||||
|
return false unless check?(*types)
|
||||||
|
|
||||||
|
advance!
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
def consume!(type, message)
|
||||||
|
raise ParseError.new(peek, message) unless check?(type)
|
||||||
|
|
||||||
|
advance!
|
||||||
|
end
|
||||||
|
|
||||||
|
def check?(*types)
|
||||||
|
return false if eot?
|
||||||
|
|
||||||
|
types.include?(peek.type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def advance!
|
||||||
|
self.current += 1 unless eot?
|
||||||
|
prev
|
||||||
|
end
|
||||||
|
|
||||||
|
def eot? = peek.type === :EOF
|
||||||
|
def peek = tokens.fetch(current)
|
||||||
|
def prev = tokens.fetch(current - 1)
|
||||||
|
|
||||||
|
def synchronize!
|
||||||
|
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
|
||||||
|
|
||||||
|
# In the book, this returns nil when there's an error, but
|
||||||
|
# that feels weird so let's move that error handling up the
|
||||||
|
# stack for now.
|
||||||
|
def parse(tokens)
|
||||||
|
state = State.new(tokens, 0)
|
||||||
|
state.expression
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,67 @@
|
|||||||
|
require_relative "../test_helper"
|
||||||
|
|
||||||
|
require "lox/ast_printer"
|
||||||
|
require "lox/parser"
|
||||||
|
require "lox/scanner"
|
||||||
|
require "lox/token"
|
||||||
|
|
||||||
|
class TestParser < Lox::Test
|
||||||
|
def setup
|
||||||
|
@ast_printer = Lox::AstPrinter.new
|
||||||
|
@scanner = Lox::Scanner.new
|
||||||
|
@parser = Lox::Parser.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_expression
|
||||||
|
assert_parsed "(== 4.0 (>= 2.0 1.0))", :expression, "4 == 2 >= 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_term
|
||||||
|
assert_parsed "(+ 4.0 (/ 2.0 1.0))", :term, "4 + 2 / 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_factor
|
||||||
|
assert_parsed "(/ 4.0 (- 2.0))", :factor, "4 / -2"
|
||||||
|
assert_parsed "(* 4.0 2.0)", :factor, "4 * 2"
|
||||||
|
assert_parsed "(- 2.0)", :factor, "-2"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unary
|
||||||
|
assert_parsed "(! 42.0)", :unary, "!42"
|
||||||
|
assert_parsed "(- 42.0)", :unary, "-42"
|
||||||
|
assert_parsed "42.0", :unary, "42"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_primary
|
||||||
|
assert_parsed "false", :primary, "false"
|
||||||
|
assert_parsed "true", :primary, "true"
|
||||||
|
assert_parsed "nil", :primary, "nil"
|
||||||
|
assert_parsed "42.0", :primary, "42"
|
||||||
|
assert_parsed "foo", :primary, "\"foo\""
|
||||||
|
assert_parsed "(group foo)", :primary, "(\"foo\")"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_errors
|
||||||
|
e = assert_raises Lox::ParseError do
|
||||||
|
parse("(42", :primary)
|
||||||
|
end
|
||||||
|
assert_equal "[line 1] Error at end: Expect ')' after expression.", e.message
|
||||||
|
|
||||||
|
e = assert_raises Lox::ParseError do
|
||||||
|
parse("foo foo", :primary)
|
||||||
|
end
|
||||||
|
assert_equal "[line 1] Error at 'foo': Expect expression.", e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse(src, name)
|
||||||
|
tokens = @scanner.scan(src)
|
||||||
|
Lox::Parser::State.new(tokens).send(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_parsed(expected, name, src)
|
||||||
|
expr = parse(src, name)
|
||||||
|
assert_equal expected, @ast_printer.print(expr)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in new issue