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.

144 lines
2.9 KiB

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