def parse_identifier(self) -> ast.Node: # Parse the identifier's name. identifier_token = self.consume(TokenKind.identifier) if identifier_token is None: raise self.expected_identifier() # Parse the optional specializers. backtrack = self.stream_position self.consume_newlines() if self.consume(TokenKind.lbracket): # We first try to parse a single annotation, with no label, so as to handle the # syntactic sugar consisting of omitting labels for for generic types with only a # single placeholder. self.consume_newlines() sugar_backtrack = self.stream_position try: specializers = {'_0': self.parse_type()} self.consume_newlines() end_token = self.consume(TokenKind.rbracket) if end_token is None: raise self.unexpected_token(expected=']') except: self.rewind_to(sugar_backtrack) pairs = self.parse_sequence(TokenKind.rbracket, self.parse_specializer) specializers = {} for (name_token, value) in pairs: if name_token.value in specializers: raise exc.DuplicateKey(key=name_token) specializers[name_token.value] = value end_token = self.consume(TokenKind.rbracket) if end_token is None: raise self.unexpected_token(expected=']') end = end_token.source_range.end else: self.rewind_to(backtrack) specializers = None end = identifier_token.source_range.end return ast.Identifier(name=identifier_token.value, specializers=specializers, source_range=SourceRange( start=identifier_token.source_range.start, end=end))
def parse_prefix_expression(self) -> ast.PrefixExpression: # Parse the operator of the expression. start_token = self.consume() if (start_token is None) or (start_token.value not in self.prefix_operators): raise self.unexpected_token(expected='prefix operator') operator_identifier = ast.Identifier( name=start_token.value, specializers=None, source_range=start_token.source_range) # Parse the operand of the expression. operand = self.parse_expression() return ast.PrefixExpression( operator=operator_identifier, operand=operand, source_range=SourceRange( start=operator_identifier.source_range.start, end=operand.source_range.end))
def p_identifier(p): """ identifier : IDENTIFIER """ p[0] = ast.Identifier(p[1])
def parse_atom(self) -> ast.Node: start_token = self.peek() if start_token.kind == TokenKind.lparen: atom = self.parse_parenthesized(self.parse_expression) elif start_token.kind in scalar_literal_kinds: token = self.consume() atom = ast.ScalarLiteral(value=token.value, source_range=token.source_range) elif start_token.kind == TokenKind.argref: token = self.consume() atom = ast.ArgRef(source_range=token.source_range) elif start_token.kind == TokenKind.identifier: atom = self.parse_identifier() elif start_token.kind == TokenKind.lbracket: atom = self.parse_list_literal() elif start_token.kind == TokenKind.lbrace: atom = self.parse_object_literal() elif start_token.kind == TokenKind.if_: atom = self.parse_if_expression() elif start_token.kind == TokenKind.match: atom = self.parse_match_expression() elif (start_token.kind == TokenKind.operator) and (start_token.value in self.prefix_operators): atom = self.parse_prefix_expression() else: raise self.unexpected_token(expected='expression') # Parse the optional "suffix" of the expression. while True: backtrack = self.stream_position self.consume_newlines() # If we can parse an object, we interpret it as a call expression. try: argument = self.attempt(self.parse_object_literal) if argument is None: value = self.parse_expression() # Operators that can act as both an infix and a prefix or postfix operator # introduce some ambuiguity, as to how an expression like `a + b` should be # parsed. The most intuitive way to interpret this expression is arguably to # see `+` as an infix operator, but one may also see this as the application of # `a` to the expression `+b` (i.e. `a { _0 = +b }`), or the application of `a+` # the expression `b` (i.e. `a+ { _0 = b }`). # We choose to desambiguise this situation by prioritizing infix expressions. if isinstance(value, ast.PrefixExpression): if value.operator.name in self.infix_operators: self.rewind_to(backtrack) break # If the value is the argument reference (i.e. `$`), we use it as an object # literal so that calls of the form `f $` aren't reduced to `f { _0 = $ }`. if isinstance(value, ast.ArgRef): argument = value else: key = ast.ScalarLiteral( value='_0', source_range=SourceRange( start=self.peek().source_range.start)) prop = ast.ObjectLiteralProperty( key=key, value=value, source_range=SourceRange( start=key.source_range.start, end=value.source_range.end)) argument = ast.ObjectLiteral( properties=[prop], source_range=prop.source_range) atom = ast.CallExpression(callee=atom, argument=argument, source_range=SourceRange( start=atom.source_range.start, end=argument.source_range.end)) continue except: self.rewind_to(backtrack) self.consume_newlines() suffix_token = self.peek() # An underscore corresponds to a call to a function without any argument. if suffix_token == TokenKind.underscore: end_token = self.consume() atom = ast.CallExpression(callee=atom, argument=None, source_range=SourceRange( start=atom.source_range.start, end=end_token.source_range.end)) continue # If we can parse a postfix operator, we interpret it as a postfix expression. if suffix_token.kind == TokenKind.operator and ( suffix_token.value in self.postfix_operators): operator = self.consume() # Backtrack if the operator is also infix and the remainder of the stream can be # parsed as an expression. if operator.value in self.infix_operators: if self.attempt(self.parse_expression) is not None: self.rewind_to(backtrack) break operator_identifier = ast.Identifier( name=operator.value, specializers=None, source_range=operator.source_range) atom = ast.PostfixExpression( operator=operator_identifier, operand=atom, source_range=SourceRange( start=operator_identifier.source_range.start, end=atom.source_range.end)) continue self.rewind_to(backtrack) break return atom
def parse_expression(self) -> ast.Node: # Attempt to parse a binding. if self.peek().kind == TokenKind.let: return self.parse_binding() # Attempt to parse a term. left = self.attempt(self.parse_closure_expression) or self.parse_atom() # Attempt to parse the remainder of an infix expression. while True: backtrack = self.stream_position self.consume_newlines() operator = self.consume(TokenKind.operator) if operator is None: self.rewind_to(backtrack) break if operator.value not in self.infix_operators: raise exc.UnknownOperator(operator=operator) # The infix operators `.`, `?` or `!` represent attribute retrieval expressions. Just # like object keys, an identifier with no specializers on the right operand of an # attribute retrieval expression is interpreted as a character string by default. # Hence, we need to treat this syntactic sugar case here. if operator.value in {'.', '?', '!'}: right = self.parse_property_key() else: right = self.parse_atom() operator_identifier = ast.Identifier( name=operator.value, specializers=None, source_range=operator.source_range) # If the left operand is an infix expression, we should check the precedence and # associativity of its operator against the current one. if isinstance(left, ast.InfixExpression): lprec = self.infix_operators[left.operator.name]['precedence'] rprec = self.infix_operators[operator.value]['precedence'] associativity = self.infix_operators[ left.operator.name]['associativity'] if ((lprec < rprec) or ((left.operator.name == operator.value) and (associativity == 'right'))): new_right = ast.InfixExpression( operator=operator_identifier, left=left.right, right=right, source_range=SourceRange( start=left.right.source_range.start, end=right.source_range.end)) left = ast.InfixExpression( operator=left.operator, left=left.left, right=new_right, source_range=SourceRange( start=left.left.source_range.start, end=right.source_range.end)) continue left = ast.InfixExpression(operator=operator_identifier, left=left, right=right, source_range=SourceRange( start=left.source_range.start, end=right.source_range.end)) continue return left
def p_identifier(p): ''' identifier : IDENTIFIER ''' p[0] = ast.Identifier(p[1])