You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
2.7 KiB
136 lines
2.7 KiB
require_relative "error"
|
|
require_relative "expr"
|
|
|
|
module Lox
|
|
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)
|
|
state.expression
|
|
end
|
|
end
|
|
end
|