class Parser def initialize(lexer) @lexer = lexer @current_token = @lexer.get_token end def parse tree = Array.new tree << statement until at_end tree end def statement if @current_token.type == TokenKinds::IF conditional elsif @current_token.type == TokenKinds::LET variable_declaration elsif @current_token.type == TokenKinds::FUNCTION next_token = @lexer.peek if next_token && next_token.type == TokenKinds::IDENTIFIER function_definition else expression_statement end elsif @current_token.type == TokenKinds::CLASS class_definition elsif @current_token.type == TokenKinds::FOR for_loop else expression_statement end end def expression_statement expr = expression eat(TokenKinds::SEMICOLON) return expr end def variable_declaration eat(TokenKinds::LET) name = identifier eat(TokenKinds::EQUALS) value = expression eat(TokenKinds::SEMICOLON) AST::VariableDeclaration.new(name, value) end def function_definition eat(TokenKinds::FUNCTION) _function_definition end def method_definition eat(TokenKinds::METHOD) _function_definition end def classmethod_definition eat(TokenKinds::CLASSMETHOD) _function_definition end def _function_definition name = identifier params = parameters body = block AST::FunctionDefinition.new(name, params, body) end def class_definition eat(TokenKinds::CLASS) class_name = AST::Identifier.new(eat(TokenKinds::CLASS_NAME).value) eat(TokenKinds::LBRACE) property_declarations = Array.new while [TokenKinds::PUBLIC, TokenKinds::PRIVATE].include?( @current_token.type ) is_public = if @current_token.type == TokenKinds::PUBLIC eat(TokenKinds::PUBLIC) true elsif @current_token.type == TokenKinds::PRIVATE eat(TokenKinds::PRIVATE) false end while !at_end && true name = identifier property_declarations << AST::PropertyDeclaration.new(name, is_public) if @current_token.type == TokenKinds::COMMA eat(TokenKinds::COMMA) else break end end eat(TokenKinds::SEMICOLON) end class_methods = Array.new instance_methods = Array.new while [TokenKinds::METHOD, TokenKinds::CLASSMETHOD].include?(@current_token.type) case @current_token.type when TokenKinds::METHOD instance_methods << method_definition when TokenKinds::CLASSMETHOD class_methods << classmethod_definition end end eat(TokenKinds::RBRACE) AST::ClassDefinition.new(class_name, property_declarations, instance_methods, class_methods) end def conditional branches = Array.new eat(TokenKinds::IF) condition = expression block = self.block branches << AST::Branch.new(condition, block) while @current_token.type == TokenKinds::ELSEIF eat(TokenKinds::ELSEIF) condition = expression block = self.block branches << AST::Branch.new(condition, block) end if @current_token.type == TokenKinds::ELSE eat(TokenKinds::ELSE) block = self.block branches << AST::Branch.new(AST::Boolean.new(true), block) end AST::Conditional.new(branches) end def for_loop eat(TokenKinds::FOR) iterator = identifier eat(TokenKinds::IN) iterable = expression body = block AST::ForLoop.new(iterator, iterable, body) end def parameters params = Array.new eat(TokenKinds::LPAREN) while @current_token.type == TokenKinds::IDENTIFIER params << identifier if @current_token.type == TokenKinds::COMMA eat(TokenKinds::COMMA) else break end end eat(TokenKinds::RPAREN) params end def block statements = Array.new eat(TokenKinds::LBRACE) statements << statement until @current_token.type == TokenKinds::RBRACE eat(TokenKinds::RBRACE) AST::Block.new(statements) end def expression assignment end private def assignment expr = binary if @current_token.type == TokenKinds::EQUALS unless expr.is_a?(AST::Identifier) raise 'Invalid left hand side of assignment' end eat(TokenKinds::EQUALS) value = expression AST::Assignment.new(expr, value) else expr end end def binary left = comparison if @current_token.type == TokenKinds::OPERATOR operator = @current_token.value advance right = comparison AST::Binary.new(operator, left, right) else left end end def comparison left = multiplication if @current_token.type == TokenKinds::OPERATOR && %i[< > <= >=].include?(@current_token.type) operator = @current_token.value advance right = multiplication AST::Binary.new(operator, left, right) else left end end def addition left = multiplication if @current_token.type == TokenKinds::OPERATOR && %i[+ -].include?(@current_token.value) operator = @current_token.value advance right = multiplication AST::Binary.new(operator, left, right) else left end end def multiplication left = unary if @current_token.type == TokenKinds::OPERATOR && %i[* /].include?(@current_token.value) operator = @current_token.value advance right = unary AST::Binary.new(operator, left, right) else left end end def unary if @current_token.type == TokenKinds::OPERATOR && %i[- !].include?(@current_token.value) operator = eat(TokenKinds::OPERATOR).value expr = primary AST::Unary.new(operator, expr) else primary end end def primary token = @current_token expr = case token.type when TokenKinds::NULL advance AST::Null.new when TokenKinds::BOOLEAN advance AST::Boolean.new(token.value) when TokenKinds::NUMBER advance AST::Number.new(token.value) when TokenKinds::STRING advance AST::String.new(token.value) when TokenKinds::ATOM advance AST::Atom.new(token.value) when TokenKinds::IDENTIFIER identifier when TokenKinds::LBRACKET array when TokenKinds::LBRACE hash when TokenKinds::LPAREN eat(TokenKinds::LPAREN) e = expression eat(TokenKinds::RPAREN) e when TokenKinds::FUNCTION function_expression else raise "Unexpected token #{token.type}" end while [TokenKinds::LPAREN, TokenKinds::LBRACKET, TokenKinds::HASH].include?( @current_token.type ) case @current_token.type when TokenKinds::LPAREN args = arguments expr = AST::FunctionCall.new(expr, args) when TokenKinds::LBRACKET eat(TokenKinds::LBRACKET) key = expression eat(TokenKinds::RBRACKET) expr = AST::Index.new(expr, key) when TokenKinds::HASH eat(TokenKinds::HASH) property = identifier expr = AST::Property.new(expr, property) end end expr end def function_expression eat(TokenKinds::FUNCTION) params = parameters body = block AST::FunctionDefinition.new(nil, params, body) end def identifier token = eat(TokenKinds::IDENTIFIER) AST::Identifier.new(token.value) end def array elements = Array.new eat(TokenKinds::LBRACKET) until @current_token.type == TokenKinds::RBRACKET elements << expression if @current_token.type == TokenKinds::COMMA eat(TokenKinds::COMMA) else break end end eat(TokenKinds::RBRACKET) AST::Array.new(elements) end def hash h = Hash.new eat(TokenKinds::LBRACE) until @current_token.type == TokenKinds::RBRACE key = eat(TokenKinds::ATOM).value eat(TokenKinds::ROCKET) value = expression h[key] = value if @current_token.type == TokenKinds::COMMA eat(TokenKinds::COMMA) else break end end eat(TokenKinds::RBRACE) AST::Hash.new(h) end def arguments args = Array.new eat(TokenKinds::LPAREN) until @current_token.type == TokenKinds::RPAREN args << expression if @current_token.type == TokenKinds::COMMA eat(TokenKinds::COMMA) else break end end eat(TokenKinds::RPAREN) args end def eat(type) token = @current_token if token.type == type advance token else if token.nil? raise "Unexpected #{token.type} - expected #{type}" else raise "Unexpected #{token.type} - expected #{type}" end end end def advance @current_token = @lexer.get_token end def at_end @current_token.type == TokenKinds::EOF end end