module Chervil class Parser def initialize(lexer) @lexer = lexer @tree = Array.new @current_token = @lexer.get_next_token @error = nil end def parse until @current_token.type == :eof @tree << expr return @error unless @error.nil? end @tree end def expr if [:string, :number, :boolean].include?(@current_token.type) constant elsif @current_token.type == :identifier identifier elsif @current_token.type == :lparen list elsif @current_token.type == :quote quotation else @error = Error.new("Unexpected token #{@current_token.type}: #{@current_token.value}") end end def quotation eat(:quote) value = expr AST::Quotation.new(value) end def list eat(:lparen) if @current_token.type == :identifier if @current_token.value == 'define' return definition elsif @current_token.value == 'if' return conditional elsif @current_token.value == 'lambda' return lambda_ end end elements = Array.new until @current_token.type == :rparen || @current_token.type == :eof elements << expr end eat(:rparen) AST::List.new(elements) end def definition eat(:identifier) # the `define` if @current_token.type == :identifier name = identifier value = expr eat(:rparen) AST::Definition.new(name, value) elsif @current_token.type == :lparen eat(:lparen) name = identifier params = Array.new until [:rparen, :eof].include?(@current_token.type) params << expr end eat(:rparen) body = Array.new until [:rparen, :eof].include?(@current_token.type) body << expr end eat(:rparen) AST::Definition.new(name, AST::Function.new(name, params, body)) end end def conditional eat(:identifier) # the `if` predicate = expr true_branch = expr false_branch = expr eat(:rparen) AST::Conditional.new(predicate, true_branch, false_branch) end def lambda_ eat(:identifier) # the `lambda` name = AST::Identifier.new("lambda") params = Array.new eat(:lparen) while @current_token.type == :identifier params.push(identifier) end eat(:rparen) body = Array.new until [:rparen, :eof].include?(@current_token.type) body << expr end eat(:rparen) AST::Function.new(name, params, body) end def identifier identifier = eat(:identifier) AST::Identifier.new(identifier.value) end def constant case @current_token.type when :number token = eat(:number) AST::Number.new(token.value.to_f) when :string token = eat(:string) AST::String.new(token.value) when :boolean token = eat(:boolean) AST::Boolean.new(token.value) end end def eat(type) if @current_token.type == type token = @current_token @current_token = @lexer.get_next_token token else @error = Error.new("Expected #{type} but got #{@current_token.type}") end end end end