Module refinery.lib.scripts.js.parser
Expand source code Browse git
from __future__ import annotations
from refinery.lib.scripts.js.lexer import _ESCAPE_MAP, JsLexer
from refinery.lib.scripts.js.model import (
Expression,
JsArrayExpression,
JsArrayPattern,
JsArrowFunctionExpression,
JsAssignmentExpression,
JsAssignmentPattern,
JsAwaitExpression,
JsBigIntLiteral,
JsBinaryExpression,
JsBlockStatement,
JsBooleanLiteral,
JsBreakStatement,
JsCallExpression,
JsCatchClause,
JsClassBody,
JsClassDeclaration,
JsClassExpression,
JsConditionalExpression,
JsContinueStatement,
JsDebuggerStatement,
JsDoWhileStatement,
JsEmptyStatement,
JsErrorNode,
JsExportAllDeclaration,
JsExportDefaultDeclaration,
JsExportNamedDeclaration,
JsExportSpecifier,
JsExpressionStatement,
JsForInStatement,
JsForOfStatement,
JsForStatement,
JsFunctionDeclaration,
JsFunctionExpression,
JsIdentifier,
JsIfStatement,
JsImportDeclaration,
JsImportDefaultSpecifier,
JsImportNamespaceSpecifier,
JsImportSpecifier,
JsLabeledStatement,
JsLogicalExpression,
JsMemberExpression,
JsMethodDefinition,
JsNewExpression,
JsNullLiteral,
JsNumericLiteral,
JsObjectExpression,
JsObjectPattern,
JsParenthesizedExpression,
JsProperty,
JsPropertyDefinition,
JsRegExpLiteral,
JsRestElement,
JsReturnStatement,
JsScript,
JsSequenceExpression,
JsSpreadElement,
JsStringLiteral,
JsSwitchCase,
JsSwitchStatement,
JsTaggedTemplateExpression,
JsTemplateElement,
JsTemplateLiteral,
JsThisExpression,
JsThrowStatement,
JsTryStatement,
JsUnaryExpression,
JsUpdateExpression,
JsVariableDeclaration,
JsVariableDeclarator,
JsWhileStatement,
JsWithStatement,
JsYieldExpression,
Statement,
)
from refinery.lib.scripts.js.token import JsToken, JsTokenKind
class JsParser:
def __init__(self, source: str):
self._lexer = JsLexer(source)
self._source = source
self._tokens = self._lexer.tokenize()
self._current: JsToken = JsToken(JsTokenKind.EOF, '', 0)
self._preceded_by_newline: bool = False
self._no_in: bool = False
self._pending_comments: list[str] = []
self._advance()
def _advance(self) -> JsToken:
prev = self._current
had_newline = False
while True:
tok = next(self._tokens, JsToken(JsTokenKind.EOF, '', len(self._source)))
if tok.kind == JsTokenKind.NEWLINE:
had_newline = True
continue
if tok.kind == JsTokenKind.COMMENT:
self._pending_comments.append(tok.value)
continue
break
self._current = tok
self._preceded_by_newline = had_newline
return prev
def _drain_comments(self, node):
if self._pending_comments:
node.leading_comments.extend(self._pending_comments)
self._pending_comments.clear()
def _peek(self) -> JsToken:
return self._current
def _at(self, *kinds: JsTokenKind) -> bool:
return self._current.kind in kinds
def _eat(self, kind: JsTokenKind) -> JsToken | None:
if self._current.kind == kind:
return self._advance()
return None
def _expect(self, kind: JsTokenKind) -> JsToken:
if self._current.kind == kind:
return self._advance()
tok = self._current
self._advance()
return JsToken(kind, tok.value, tok.offset)
def _eat_semicolon(self) -> bool:
if self._eat(JsTokenKind.SEMICOLON):
return True
if self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
return True
if self._preceded_by_newline:
return True
return False
def parse(self) -> JsScript:
return self._parse_program()
def _parse_program(self) -> JsScript:
offset = self._current.offset
body: list[Statement] = []
while not self._at(JsTokenKind.EOF):
mark = self._current.offset
comments = list(self._pending_comments)
self._pending_comments.clear()
try:
stmt = self._parse_statement()
except Exception:
stmt = None
if stmt is not None:
stmt.leading_comments.extend(comments)
body.append(stmt)
elif self._current.offset == mark:
tok = self._advance()
error = JsExpressionStatement(
offset=tok.offset,
expression=JsErrorNode(offset=tok.offset, text=tok.value),
)
error.leading_comments.extend(comments)
body.append(error)
return JsScript(body=body, offset=offset)
def _parse_statement(self) -> Statement | None:
offset = self._current.offset
kind = self._current.kind
if kind == JsTokenKind.LBRACE:
return self._parse_block_statement()
if kind == JsTokenKind.SEMICOLON:
self._advance()
return JsEmptyStatement(offset=offset)
if kind in (JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST):
return self._parse_variable_declaration()
if kind == JsTokenKind.IF:
return self._parse_if_statement()
if kind == JsTokenKind.WHILE:
return self._parse_while_statement()
if kind == JsTokenKind.DO:
return self._parse_do_while_statement()
if kind == JsTokenKind.FOR:
return self._parse_for_statement()
if kind == JsTokenKind.SWITCH:
return self._parse_switch_statement()
if kind == JsTokenKind.TRY:
return self._parse_try_statement()
if kind == JsTokenKind.WITH:
return self._parse_with_statement()
if kind == JsTokenKind.RETURN:
return self._parse_return_statement()
if kind == JsTokenKind.THROW:
return self._parse_throw_statement()
if kind == JsTokenKind.BREAK:
return self._parse_break_statement()
if kind == JsTokenKind.CONTINUE:
return self._parse_continue_statement()
if kind == JsTokenKind.FUNCTION:
return self._parse_function_declaration()
if kind == JsTokenKind.CLASS:
return self._parse_class_declaration()
if kind == JsTokenKind.DEBUGGER:
self._advance()
self._eat_semicolon()
return JsDebuggerStatement(offset=offset)
if kind == JsTokenKind.IMPORT:
return self._parse_import_declaration()
if kind == JsTokenKind.EXPORT:
return self._parse_export_declaration()
if kind == JsTokenKind.ASYNC:
return self._parse_async_statement()
expr = self._parse_expression()
if (
isinstance(expr, JsIdentifier)
and self._eat(JsTokenKind.COLON)
):
body = self._parse_statement()
return JsLabeledStatement(label=expr, body=body, offset=offset)
self._eat_semicolon()
return JsExpressionStatement(expression=expr, offset=offset)
def _parse_block_statement(self) -> JsBlockStatement:
offset = self._current.offset
self._expect(JsTokenKind.LBRACE)
body: list[Statement] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
mark = self._current.offset
comments = list(self._pending_comments)
self._pending_comments.clear()
try:
stmt = self._parse_statement()
except Exception:
stmt = None
if stmt is not None:
stmt.leading_comments.extend(comments)
body.append(stmt)
elif self._current.offset == mark:
tok = self._advance()
error = JsExpressionStatement(
offset=tok.offset,
expression=JsErrorNode(offset=tok.offset, text=tok.value),
)
error.leading_comments.extend(comments)
body.append(error)
self._expect(JsTokenKind.RBRACE)
return JsBlockStatement(body=body, offset=offset)
def _parse_variable_declaration(self) -> JsVariableDeclaration:
offset = self._current.offset
kind_tok = self._advance()
kind = kind_tok.value
declarations: list[JsVariableDeclarator] = []
declarations.append(self._parse_variable_declarator())
while self._eat(JsTokenKind.COMMA):
declarations.append(self._parse_variable_declarator())
self._eat_semicolon()
return JsVariableDeclaration(declarations=declarations, kind=kind, offset=offset)
def _parse_variable_declarator(self) -> JsVariableDeclarator:
offset = self._current.offset
id_node = self._parse_binding_pattern()
init = None
if self._eat(JsTokenKind.EQUALS):
init = self._parse_assignment_expression()
return JsVariableDeclarator(id=id_node, init=init, offset=offset)
def _parse_binding_pattern(self) -> Expression:
if self._at(JsTokenKind.LBRACKET):
return self._parse_array_pattern()
if self._at(JsTokenKind.LBRACE):
return self._parse_object_pattern()
return self._parse_binding_identifier()
def _parse_binding_identifier(self) -> Expression:
offset = self._current.offset
tok = self._expect(JsTokenKind.IDENTIFIER)
return JsIdentifier(name=tok.value, offset=offset)
def _parse_array_pattern(self) -> JsArrayPattern:
offset = self._current.offset
self._expect(JsTokenKind.LBRACKET)
elements: list[Expression | None] = []
while not self._at(JsTokenKind.RBRACKET, JsTokenKind.EOF):
if self._at(JsTokenKind.COMMA):
elements.append(None)
self._advance()
continue
if self._at(JsTokenKind.ELLIPSIS):
elements.append(self._parse_rest_element())
break
elem = self._parse_binding_pattern()
if self._eat(JsTokenKind.EQUALS):
right = self._parse_assignment_expression()
elem = JsAssignmentPattern(left=elem, right=right, offset=elem.offset)
elements.append(elem)
if not self._at(JsTokenKind.RBRACKET):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACKET)
return JsArrayPattern(elements=elements, offset=offset)
def _parse_object_pattern(self) -> JsObjectPattern:
offset = self._current.offset
self._expect(JsTokenKind.LBRACE)
properties: list[JsProperty | JsRestElement] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
if self._at(JsTokenKind.ELLIPSIS):
properties.append(self._parse_rest_element())
break
prop = self._parse_object_pattern_property()
properties.append(prop)
if not self._at(JsTokenKind.RBRACE):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACE)
return JsObjectPattern(properties=properties, offset=offset)
def _parse_object_pattern_property(self) -> JsProperty:
offset = self._current.offset
if self._at(JsTokenKind.LBRACKET):
self._advance()
key = self._parse_assignment_expression()
self._expect(JsTokenKind.RBRACKET)
self._expect(JsTokenKind.COLON)
value = self._parse_binding_pattern()
if self._eat(JsTokenKind.EQUALS):
right = self._parse_assignment_expression()
value = JsAssignmentPattern(left=value, right=right, offset=value.offset)
return JsProperty(
key=key, value=value, computed=True, shorthand=False, offset=offset)
key = self._parse_property_name()
if self._eat(JsTokenKind.COLON):
value = self._parse_binding_pattern()
if self._eat(JsTokenKind.EQUALS):
right = self._parse_assignment_expression()
value = JsAssignmentPattern(left=value, right=right, offset=value.offset)
return JsProperty(
key=key, value=value, computed=False, shorthand=False, offset=offset)
value = key
if self._eat(JsTokenKind.EQUALS):
right = self._parse_assignment_expression()
value = JsAssignmentPattern(left=key, right=right, offset=key.offset)
return JsProperty(key=key, value=value, computed=False, shorthand=True, offset=offset)
def _parse_rest_element(self) -> JsRestElement:
offset = self._current.offset
self._expect(JsTokenKind.ELLIPSIS)
argument = self._parse_binding_pattern()
return JsRestElement(argument=argument, offset=offset)
def _parse_if_statement(self) -> JsIfStatement:
offset = self._current.offset
self._expect(JsTokenKind.IF)
self._expect(JsTokenKind.LPAREN)
test = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
consequent = self._parse_statement()
alternate = None
if self._eat(JsTokenKind.ELSE):
alternate = self._parse_statement()
return JsIfStatement(
test=test, consequent=consequent, alternate=alternate, offset=offset)
def _parse_while_statement(self) -> JsWhileStatement:
offset = self._current.offset
self._expect(JsTokenKind.WHILE)
self._expect(JsTokenKind.LPAREN)
test = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsWhileStatement(test=test, body=body, offset=offset)
def _parse_do_while_statement(self) -> JsDoWhileStatement:
offset = self._current.offset
self._expect(JsTokenKind.DO)
body = self._parse_statement()
self._expect(JsTokenKind.WHILE)
self._expect(JsTokenKind.LPAREN)
test = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
self._eat_semicolon()
return JsDoWhileStatement(test=test, body=body, offset=offset)
def _parse_for_statement(self) -> Statement:
offset = self._current.offset
self._expect(JsTokenKind.FOR)
is_await = False
if self._eat(JsTokenKind.AWAIT):
is_await = True
self._expect(JsTokenKind.LPAREN)
if self._at(JsTokenKind.SEMICOLON):
self._advance()
return self._parse_for_rest(None, offset)
if self._at(JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST):
decl_offset = self._current.offset
kind_tok = self._advance()
kind = kind_tok.value
declarator = self._parse_variable_declarator()
decl = JsVariableDeclaration(
declarations=[declarator], kind=kind, offset=decl_offset)
if self._eat(JsTokenKind.IN):
right = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsForInStatement(
left=decl, right=right, body=body, offset=offset)
if self._at(JsTokenKind.OF) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'of'
):
self._advance()
right = self._parse_assignment_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsForOfStatement(
left=decl, right=right, body=body, is_await=is_await, offset=offset)
while self._eat(JsTokenKind.COMMA):
decl.declarations.append(self._parse_variable_declarator())
self._expect(JsTokenKind.SEMICOLON)
return self._parse_for_rest(decl, offset)
saved_no_in = self._no_in
self._no_in = True
init_expr = self._parse_expression()
self._no_in = saved_no_in
if self._eat(JsTokenKind.IN):
right = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsForInStatement(
left=init_expr, right=right, body=body, offset=offset)
if self._at(JsTokenKind.OF) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'of'
):
self._advance()
right = self._parse_assignment_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsForOfStatement(
left=init_expr, right=right, body=body, is_await=is_await, offset=offset)
self._expect(JsTokenKind.SEMICOLON)
return self._parse_for_rest(init_expr, offset)
def _parse_for_rest(
self,
init: Expression | Statement | None,
offset: int,
) -> JsForStatement:
test = None
if not self._at(JsTokenKind.SEMICOLON):
test = self._parse_expression()
self._expect(JsTokenKind.SEMICOLON)
update = None
if not self._at(JsTokenKind.RPAREN):
update = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsForStatement(
init=init, test=test, update=update, body=body, offset=offset)
def _parse_switch_statement(self) -> JsSwitchStatement:
offset = self._current.offset
self._expect(JsTokenKind.SWITCH)
self._expect(JsTokenKind.LPAREN)
discriminant = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
self._expect(JsTokenKind.LBRACE)
cases: list[JsSwitchCase] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
cases.append(self._parse_switch_case())
self._expect(JsTokenKind.RBRACE)
return JsSwitchStatement(
discriminant=discriminant, cases=cases, offset=offset)
def _parse_switch_case(self) -> JsSwitchCase:
offset = self._current.offset
test = None
if self._eat(JsTokenKind.CASE):
test = self._parse_expression()
self._expect(JsTokenKind.COLON)
elif self._eat(JsTokenKind.DEFAULT):
self._expect(JsTokenKind.COLON)
else:
self._advance()
consequent: list[Statement] = []
while not self._at(
JsTokenKind.CASE, JsTokenKind.DEFAULT,
JsTokenKind.RBRACE, JsTokenKind.EOF,
):
stmt = self._parse_statement()
if stmt is not None:
consequent.append(stmt)
return JsSwitchCase(test=test, consequent=consequent, offset=offset)
def _parse_try_statement(self) -> JsTryStatement:
offset = self._current.offset
self._expect(JsTokenKind.TRY)
block = self._parse_block_statement()
handler = None
finalizer = None
if self._eat(JsTokenKind.CATCH):
handler = self._parse_catch_clause()
if self._eat(JsTokenKind.FINALLY):
finalizer = self._parse_block_statement()
return JsTryStatement(
block=block, handler=handler, finalizer=finalizer, offset=offset)
def _parse_catch_clause(self) -> JsCatchClause:
offset = self._current.offset
param = None
if self._eat(JsTokenKind.LPAREN):
param = self._parse_binding_pattern()
self._expect(JsTokenKind.RPAREN)
body = self._parse_block_statement()
return JsCatchClause(param=param, body=body, offset=offset)
def _parse_with_statement(self) -> JsWithStatement:
offset = self._current.offset
self._expect(JsTokenKind.WITH)
self._expect(JsTokenKind.LPAREN)
obj = self._parse_expression()
self._expect(JsTokenKind.RPAREN)
body = self._parse_statement()
return JsWithStatement(object=obj, body=body, offset=offset)
def _parse_return_statement(self) -> JsReturnStatement:
offset = self._current.offset
self._expect(JsTokenKind.RETURN)
argument = None
if not self._preceded_by_newline and not self._at(
JsTokenKind.SEMICOLON, JsTokenKind.RBRACE, JsTokenKind.EOF,
):
argument = self._parse_expression()
self._eat_semicolon()
return JsReturnStatement(argument=argument, offset=offset)
def _parse_throw_statement(self) -> JsThrowStatement:
offset = self._current.offset
self._expect(JsTokenKind.THROW)
argument = None
if not self._preceded_by_newline:
argument = self._parse_expression()
self._eat_semicolon()
return JsThrowStatement(argument=argument, offset=offset)
def _parse_break_statement(self) -> JsBreakStatement:
offset = self._current.offset
self._expect(JsTokenKind.BREAK)
label = None
if not self._preceded_by_newline and self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
label = JsIdentifier(name=tok.value, offset=tok.offset)
self._eat_semicolon()
return JsBreakStatement(label=label, offset=offset)
def _parse_continue_statement(self) -> JsContinueStatement:
offset = self._current.offset
self._expect(JsTokenKind.CONTINUE)
label = None
if not self._preceded_by_newline and self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
label = JsIdentifier(name=tok.value, offset=tok.offset)
self._eat_semicolon()
return JsContinueStatement(label=label, offset=offset)
def _parse_function_declaration(
self,
is_async: bool = False,
) -> JsFunctionDeclaration:
offset = self._current.offset
self._expect(JsTokenKind.FUNCTION)
generator = bool(self._eat(JsTokenKind.STAR))
id_node = None
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
id_node = JsIdentifier(name=tok.value, offset=tok.offset)
params = self._parse_formal_parameters()
body = self._parse_block_statement()
return JsFunctionDeclaration(
id=id_node,
params=params,
body=body,
generator=generator,
is_async=is_async,
offset=offset,
)
def _parse_formal_parameters(self) -> list[Expression]:
self._expect(JsTokenKind.LPAREN)
params: list[Expression] = []
while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF):
if self._at(JsTokenKind.ELLIPSIS):
params.append(self._parse_rest_element())
break
param = self._parse_binding_pattern()
if self._eat(JsTokenKind.EQUALS):
default = self._parse_assignment_expression()
param = JsAssignmentPattern(
left=param, right=default, offset=param.offset)
params.append(param)
if not self._at(JsTokenKind.RPAREN):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RPAREN)
return params
def _parse_class_declaration(self) -> JsClassDeclaration:
offset = self._current.offset
self._expect(JsTokenKind.CLASS)
id_node = None
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
id_node = JsIdentifier(name=tok.value, offset=tok.offset)
super_class = None
if self._eat(JsTokenKind.EXTENDS):
super_class = self._parse_assignment_expression()
body = self._parse_class_body()
return JsClassDeclaration(
id=id_node, super_class=super_class, body=body, offset=offset)
def _parse_class_body(self) -> JsClassBody:
offset = self._current.offset
self._expect(JsTokenKind.LBRACE)
members: list[JsMethodDefinition | JsPropertyDefinition] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
if self._eat(JsTokenKind.SEMICOLON):
continue
members.append(self._parse_class_member())
self._expect(JsTokenKind.RBRACE)
return JsClassBody(body=members, offset=offset)
def _parse_class_member(self) -> JsMethodDefinition | JsPropertyDefinition:
offset = self._current.offset
is_static = False
if (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'static'
):
saved_pos = self._current
self._advance()
if self._at(
JsTokenKind.LBRACE, JsTokenKind.RBRACE, JsTokenKind.EOF,
JsTokenKind.SEMICOLON,
):
key = JsIdentifier(name='static', offset=saved_pos.offset)
return self._finish_class_member(key, False, False, offset)
is_static = True
kind = 'method'
is_generator = False
if self._eat(JsTokenKind.STAR):
is_generator = True
if self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'get':
saved = self._current
self._advance()
if self._at(JsTokenKind.LPAREN):
key = JsIdentifier(name='get', offset=saved.offset)
return self._finish_class_member(key, is_static, is_generator, offset)
kind = 'get'
elif self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'set':
saved = self._current
self._advance()
if self._at(JsTokenKind.LPAREN):
key = JsIdentifier(name='set', offset=saved.offset)
return self._finish_class_member(key, is_static, is_generator, offset)
kind = 'set'
elif self._at(JsTokenKind.ASYNC):
saved = self._current
self._advance()
if self._at(JsTokenKind.LPAREN):
key = JsIdentifier(name='async', offset=saved.offset)
return self._finish_class_member(key, is_static, is_generator, offset)
computed = False
if self._at(JsTokenKind.LBRACKET):
computed = True
self._advance()
key = self._parse_assignment_expression()
self._expect(JsTokenKind.RBRACKET)
else:
key = self._parse_property_name()
if kind == 'method' and not is_generator and not self._at(JsTokenKind.LPAREN):
value = None
if self._eat(JsTokenKind.EQUALS):
value = self._parse_assignment_expression()
self._eat_semicolon()
return JsPropertyDefinition(
key=key, value=value, computed=computed,
is_static=is_static, offset=offset)
return self._finish_class_member(key, is_static, is_generator, offset, kind, computed)
def _finish_class_member(
self,
key: Expression,
is_static: bool,
is_generator: bool,
offset: int,
kind: str = 'method',
computed: bool = False,
) -> JsMethodDefinition:
func_offset = self._current.offset
params = self._parse_formal_parameters()
body = self._parse_block_statement()
value = JsFunctionExpression(
params=params,
body=body,
generator=is_generator,
offset=func_offset,
)
if isinstance(key, JsIdentifier) and key.name == 'constructor' and kind == 'method':
kind = 'constructor'
return JsMethodDefinition(
key=key, value=value, kind=kind, computed=computed,
is_static=is_static, offset=offset)
def _parse_import_declaration(self) -> JsImportDeclaration:
offset = self._current.offset
self._expect(JsTokenKind.IMPORT)
if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE):
source = self._parse_string_literal()
self._eat_semicolon()
return JsImportDeclaration(source=source, offset=offset)
specifiers: list[
JsImportSpecifier | JsImportDefaultSpecifier | JsImportNamespaceSpecifier
] = []
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
specifiers.append(JsImportDefaultSpecifier(
local=JsIdentifier(name=tok.value, offset=tok.offset),
offset=tok.offset,
))
if self._eat(JsTokenKind.COMMA):
if self._at(JsTokenKind.STAR):
specifiers.append(self._parse_namespace_import())
elif self._at(JsTokenKind.LBRACE):
specifiers.extend(self._parse_named_imports())
elif self._at(JsTokenKind.STAR):
specifiers.append(self._parse_namespace_import())
elif self._at(JsTokenKind.LBRACE):
specifiers.extend(self._parse_named_imports())
self._expect_contextual('from')
source = self._parse_string_literal()
self._eat_semicolon()
return JsImportDeclaration(
specifiers=specifiers, source=source, offset=offset)
def _parse_namespace_import(self) -> JsImportNamespaceSpecifier:
offset = self._current.offset
self._expect(JsTokenKind.STAR)
self._expect_contextual('as')
tok = self._expect(JsTokenKind.IDENTIFIER)
return JsImportNamespaceSpecifier(
local=JsIdentifier(name=tok.value, offset=tok.offset),
offset=offset,
)
def _parse_named_imports(self) -> list[JsImportSpecifier]:
self._expect(JsTokenKind.LBRACE)
specs: list[JsImportSpecifier] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
spec_offset = self._current.offset
tok = self._advance()
imported = JsIdentifier(name=tok.value, offset=tok.offset)
local = imported
if self._at(JsTokenKind.AS) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as'
):
self._advance()
ltok = self._expect(JsTokenKind.IDENTIFIER)
local = JsIdentifier(name=ltok.value, offset=ltok.offset)
specs.append(JsImportSpecifier(
imported=imported, local=local, offset=spec_offset))
if not self._at(JsTokenKind.RBRACE):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACE)
return specs
def _parse_export_declaration(self) -> Statement:
offset = self._current.offset
self._expect(JsTokenKind.EXPORT)
if self._eat(JsTokenKind.DEFAULT):
if self._at(JsTokenKind.FUNCTION):
decl = self._parse_function_declaration()
return JsExportDefaultDeclaration(declaration=decl, offset=offset)
if self._at(JsTokenKind.CLASS):
decl = self._parse_class_declaration()
return JsExportDefaultDeclaration(declaration=decl, offset=offset)
if self._at(JsTokenKind.ASYNC):
decl = self._parse_async_statement()
return JsExportDefaultDeclaration(declaration=decl, offset=offset)
expr = self._parse_assignment_expression()
self._eat_semicolon()
return JsExportDefaultDeclaration(declaration=expr, offset=offset)
if self._at(JsTokenKind.STAR):
self._advance()
exported = None
if self._at(JsTokenKind.AS) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as'
):
self._advance()
tok = self._expect(JsTokenKind.IDENTIFIER)
exported = JsIdentifier(name=tok.value, offset=tok.offset)
self._expect_contextual('from')
source = self._parse_string_literal()
self._eat_semicolon()
return JsExportAllDeclaration(
source=source, exported=exported, offset=offset)
if self._at(JsTokenKind.LBRACE):
return self._parse_export_named(offset)
if self._at(JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST):
decl = self._parse_variable_declaration()
return JsExportNamedDeclaration(declaration=decl, offset=offset)
if self._at(JsTokenKind.FUNCTION):
decl = self._parse_function_declaration()
return JsExportNamedDeclaration(declaration=decl, offset=offset)
if self._at(JsTokenKind.CLASS):
decl = self._parse_class_declaration()
return JsExportNamedDeclaration(declaration=decl, offset=offset)
if self._at(JsTokenKind.ASYNC):
decl = self._parse_async_statement()
return JsExportNamedDeclaration(declaration=decl, offset=offset)
self._advance()
return JsExportNamedDeclaration(offset=offset)
def _parse_export_named(self, offset: int) -> JsExportNamedDeclaration:
self._expect(JsTokenKind.LBRACE)
specifiers: list[JsExportSpecifier] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
spec_offset = self._current.offset
tok = self._advance()
local = JsIdentifier(name=tok.value, offset=tok.offset)
exported = local
if self._at(JsTokenKind.AS) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as'
):
self._advance()
etok = self._advance()
exported = JsIdentifier(name=etok.value, offset=etok.offset)
specifiers.append(JsExportSpecifier(
local=local, exported=exported, offset=spec_offset))
if not self._at(JsTokenKind.RBRACE):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACE)
source = None
if self._at(JsTokenKind.FROM) or (
self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'from'
):
self._advance()
source = self._parse_string_literal()
self._eat_semicolon()
return JsExportNamedDeclaration(
specifiers=specifiers, source=source, offset=offset)
def _parse_async_statement(self) -> Statement:
offset = self._current.offset
self._expect(JsTokenKind.ASYNC)
if self._at(JsTokenKind.FUNCTION) and not self._preceded_by_newline:
return self._parse_function_declaration(is_async=True)
expr = self._parse_expression_starting_with_async(offset)
self._eat_semicolon()
return JsExpressionStatement(expression=expr, offset=offset)
def _expect_contextual(self, keyword: str):
if self._at(JsTokenKind.FROM) and keyword == 'from':
self._advance()
return
if self._at(JsTokenKind.AS) and keyword == 'as':
self._advance()
return
if self._at(JsTokenKind.IDENTIFIER) and self._current.value == keyword:
self._advance()
return
self._advance()
def _parse_expression(self) -> Expression:
expr = self._parse_assignment_expression()
if self._at(JsTokenKind.COMMA):
exprs = [expr]
while self._eat(JsTokenKind.COMMA):
exprs.append(self._parse_assignment_expression())
return JsSequenceExpression(expressions=exprs, offset=expr.offset)
return expr
def _parse_assignment_expression(self) -> Expression:
left = self._parse_conditional_expression()
if self._current.kind.is_assignment:
op = self._advance().value
right = self._parse_assignment_expression()
left = self._to_pattern(left) if op == '=' else left
return JsAssignmentExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_conditional_expression(self) -> Expression:
expr = self._parse_nullish_coalescing_expression()
if self._eat(JsTokenKind.QUESTION):
consequent = self._parse_assignment_expression()
self._expect(JsTokenKind.COLON)
alternate = self._parse_assignment_expression()
return JsConditionalExpression(
test=expr, consequent=consequent, alternate=alternate,
offset=expr.offset)
return expr
def _parse_nullish_coalescing_expression(self) -> Expression:
left = self._parse_logical_or_expression()
while self._eat(JsTokenKind.QQ):
right = self._parse_logical_or_expression()
left = JsLogicalExpression(
left=left, operator='??', right=right, offset=left.offset)
return left
def _parse_logical_or_expression(self) -> Expression:
left = self._parse_logical_and_expression()
while self._eat(JsTokenKind.OR):
right = self._parse_logical_and_expression()
left = JsLogicalExpression(
left=left, operator='||', right=right, offset=left.offset)
return left
def _parse_logical_and_expression(self) -> Expression:
left = self._parse_bitwise_or_expression()
while self._eat(JsTokenKind.AND):
right = self._parse_bitwise_or_expression()
left = JsLogicalExpression(
left=left, operator='&&', right=right, offset=left.offset)
return left
def _parse_bitwise_or_expression(self) -> Expression:
left = self._parse_bitwise_xor_expression()
while self._eat(JsTokenKind.PIPE):
right = self._parse_bitwise_xor_expression()
left = JsBinaryExpression(
left=left, operator='|', right=right, offset=left.offset)
return left
def _parse_bitwise_xor_expression(self) -> Expression:
left = self._parse_bitwise_and_expression()
while self._eat(JsTokenKind.CARET):
right = self._parse_bitwise_and_expression()
left = JsBinaryExpression(
left=left, operator='^', right=right, offset=left.offset)
return left
def _parse_bitwise_and_expression(self) -> Expression:
left = self._parse_equality_expression()
while self._eat(JsTokenKind.AMP):
right = self._parse_equality_expression()
left = JsBinaryExpression(
left=left, operator='&', right=right, offset=left.offset)
return left
def _parse_equality_expression(self) -> Expression:
left = self._parse_relational_expression()
while self._at(
JsTokenKind.EQ2, JsTokenKind.BANG_EQ,
JsTokenKind.EQ3, JsTokenKind.BANG_EQ2,
):
op = self._advance().value
right = self._parse_relational_expression()
left = JsBinaryExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_relational_expression(self) -> Expression:
left = self._parse_shift_expression()
while self._at(
JsTokenKind.LT, JsTokenKind.GT,
JsTokenKind.LT_EQ, JsTokenKind.GT_EQ,
JsTokenKind.INSTANCEOF, JsTokenKind.IN,
):
if self._no_in and self._at(JsTokenKind.IN):
break
op = self._advance().value
right = self._parse_shift_expression()
left = JsBinaryExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_shift_expression(self) -> Expression:
left = self._parse_additive_expression()
while self._at(
JsTokenKind.LT2, JsTokenKind.GT2, JsTokenKind.GT3,
):
op = self._advance().value
right = self._parse_additive_expression()
left = JsBinaryExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_additive_expression(self) -> Expression:
left = self._parse_multiplicative_expression()
while self._at(JsTokenKind.PLUS, JsTokenKind.MINUS):
op = self._advance().value
right = self._parse_multiplicative_expression()
left = JsBinaryExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_multiplicative_expression(self) -> Expression:
left = self._parse_exponentiation_expression()
while self._at(JsTokenKind.STAR, JsTokenKind.SLASH, JsTokenKind.PERCENT):
op = self._advance().value
right = self._parse_exponentiation_expression()
left = JsBinaryExpression(
left=left, operator=op, right=right, offset=left.offset)
return left
def _parse_exponentiation_expression(self) -> Expression:
expr = self._parse_unary_expression()
if self._eat(JsTokenKind.STAR2):
right = self._parse_exponentiation_expression()
return JsBinaryExpression(
left=expr, operator='**', right=right, offset=expr.offset)
return expr
def _parse_unary_expression(self) -> Expression:
if self._at(
JsTokenKind.BANG, JsTokenKind.TILDE,
JsTokenKind.TYPEOF, JsTokenKind.VOID, JsTokenKind.DELETE,
):
tok = self._advance()
operand = self._parse_unary_expression()
return JsUnaryExpression(
operator=tok.value, operand=operand, prefix=True, offset=tok.offset)
if self._at(JsTokenKind.PLUS) and not self._preceded_by_newline:
tok = self._advance()
operand = self._parse_unary_expression()
return JsUnaryExpression(
operator='+', operand=operand, prefix=True, offset=tok.offset)
if self._at(JsTokenKind.MINUS) and not self._preceded_by_newline:
tok = self._advance()
operand = self._parse_unary_expression()
return JsUnaryExpression(
operator='-', operand=operand, prefix=True, offset=tok.offset)
if self._at(JsTokenKind.AWAIT):
tok = self._advance()
operand = self._parse_unary_expression()
return JsAwaitExpression(argument=operand, offset=tok.offset)
return self._parse_update_expression()
def _parse_update_expression(self) -> Expression:
if self._at(JsTokenKind.INC, JsTokenKind.DEC):
tok = self._advance()
argument = self._parse_call_expression()
return JsUpdateExpression(
operator=tok.value, argument=argument, prefix=True, offset=tok.offset)
expr = self._parse_call_expression()
if not self._preceded_by_newline and self._at(
JsTokenKind.INC, JsTokenKind.DEC,
):
tok = self._advance()
return JsUpdateExpression(
operator=tok.value, argument=expr, prefix=False, offset=expr.offset)
return expr
def _parse_call_expression(self) -> Expression:
expr = self._parse_new_expression()
while True:
if self._at(JsTokenKind.LPAREN):
expr = self._parse_call_arguments(expr, optional=False)
elif self._eat(JsTokenKind.DOT):
prop_tok = self._advance()
prop = JsIdentifier(name=prop_tok.value, offset=prop_tok.offset)
expr = JsMemberExpression(
object=expr, property=prop,
computed=False, optional=False, offset=expr.offset)
elif self._at(JsTokenKind.LBRACKET):
self._advance()
prop = self._parse_expression()
self._expect(JsTokenKind.RBRACKET)
expr = JsMemberExpression(
object=expr, property=prop,
computed=True, optional=False, offset=expr.offset)
elif self._eat(JsTokenKind.QUESTION_DOT):
if self._at(JsTokenKind.LPAREN):
expr = self._parse_call_arguments(expr, optional=True)
elif self._at(JsTokenKind.LBRACKET):
self._advance()
prop = self._parse_expression()
self._expect(JsTokenKind.RBRACKET)
expr = JsMemberExpression(
object=expr, property=prop,
computed=True, optional=True, offset=expr.offset)
else:
prop_tok = self._advance()
prop = JsIdentifier(name=prop_tok.value, offset=prop_tok.offset)
expr = JsMemberExpression(
object=expr, property=prop,
computed=False, optional=True, offset=expr.offset)
elif self._at(
JsTokenKind.TEMPLATE_FULL, JsTokenKind.TEMPLATE_HEAD,
):
quasi = self._parse_template_literal()
expr = JsTaggedTemplateExpression(
tag=expr, quasi=quasi, offset=expr.offset)
else:
break
return expr
def _parse_call_arguments(
self,
callee: Expression,
optional: bool,
) -> JsCallExpression:
saved_no_in = self._no_in
self._no_in = False
self._expect(JsTokenKind.LPAREN)
args: list[Expression] = []
while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF):
if self._at(JsTokenKind.ELLIPSIS):
offset = self._current.offset
self._advance()
arg = self._parse_assignment_expression()
args.append(JsSpreadElement(argument=arg, offset=offset))
else:
args.append(self._parse_assignment_expression())
if not self._at(JsTokenKind.RPAREN):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RPAREN)
self._no_in = saved_no_in
return JsCallExpression(
callee=callee, arguments=args, optional=optional, offset=callee.offset)
def _parse_new_expression(self) -> Expression:
if self._at(JsTokenKind.NEW):
offset = self._current.offset
self._advance()
if self._at(JsTokenKind.DOT):
self._advance()
tok = self._advance()
return JsMemberExpression(
object=JsIdentifier(name='new', offset=offset),
property=JsIdentifier(name=tok.value, offset=tok.offset),
computed=False,
offset=offset,
)
callee = self._parse_new_expression()
args: list[Expression] = []
if self._at(JsTokenKind.LPAREN):
self._advance()
while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF):
if self._at(JsTokenKind.ELLIPSIS):
so = self._current.offset
self._advance()
arg = self._parse_assignment_expression()
args.append(JsSpreadElement(argument=arg, offset=so))
else:
args.append(self._parse_assignment_expression())
if not self._at(JsTokenKind.RPAREN):
self._expect(JsTokenKind.COMMA)
self._expect(JsTokenKind.RPAREN)
return JsNewExpression(callee=callee, arguments=args, offset=offset)
return self._parse_primary_expression()
def _parse_primary_expression(self) -> Expression:
tok = self._current
offset = tok.offset
if self._at(JsTokenKind.IDENTIFIER):
self._advance()
if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline:
self._advance()
param = JsIdentifier(name=tok.value, offset=offset)
body = self._parse_arrow_body()
return JsArrowFunctionExpression(
params=[param], body=body, offset=offset)
return JsIdentifier(name=tok.value, offset=offset)
if self._at(JsTokenKind.INTEGER):
self._advance()
raw = tok.value
text = raw.replace('_', '')
if text.startswith(('0x', '0X')):
value = int(text, 16)
elif text.startswith(('0o', '0O')):
value = int(text, 8)
elif text.startswith(('0b', '0B')):
value = int(text, 2)
else:
value = int(text)
return JsNumericLiteral(value=value, raw=raw, offset=offset)
if self._at(JsTokenKind.FLOAT):
self._advance()
raw = tok.value
value = float(raw.replace('_', ''))
return JsNumericLiteral(value=value, raw=raw, offset=offset)
if self._at(JsTokenKind.BIGINT):
self._advance()
raw = tok.value
text = raw.replace('_', '').rstrip('n')
if text.startswith(('0x', '0X')):
value = int(text, 16)
elif text.startswith(('0o', '0O')):
value = int(text, 8)
elif text.startswith(('0b', '0B')):
value = int(text, 2)
else:
value = int(text)
return JsBigIntLiteral(value=value, raw=raw, offset=offset)
if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE):
return self._parse_string_literal()
if self._at(JsTokenKind.REGEXP):
self._advance()
raw = tok.value
last_slash = raw.rfind('/')
pattern = raw[1:last_slash]
flags = raw[last_slash + 1:]
return JsRegExpLiteral(
pattern=pattern, flags=flags, raw=raw, offset=offset)
if self._at(JsTokenKind.TEMPLATE_FULL, JsTokenKind.TEMPLATE_HEAD):
return self._parse_template_literal()
if self._at(JsTokenKind.TRUE):
self._advance()
return JsBooleanLiteral(value=True, offset=offset)
if self._at(JsTokenKind.FALSE):
self._advance()
return JsBooleanLiteral(value=False, offset=offset)
if self._at(JsTokenKind.NULL):
self._advance()
return JsNullLiteral(offset=offset)
if self._at(JsTokenKind.THIS):
self._advance()
return JsThisExpression(offset=offset)
if self._at(JsTokenKind.SUPER):
self._advance()
return JsIdentifier(name='super', offset=offset)
if self._at(JsTokenKind.LBRACKET):
return self._parse_array_literal()
if self._at(JsTokenKind.LBRACE):
return self._parse_object_literal()
if self._at(JsTokenKind.LPAREN):
return self._parse_paren_or_arrow()
if self._at(JsTokenKind.FUNCTION):
return self._parse_function_expression()
if self._at(JsTokenKind.CLASS):
return self._parse_class_expression()
if self._at(JsTokenKind.ASYNC):
return self._parse_async_expression()
if self._at(JsTokenKind.YIELD):
return self._parse_yield_expression()
self._advance()
return JsErrorNode(text=tok.value, message='unexpected token', offset=offset)
def _parse_string_literal(self) -> JsStringLiteral:
tok = self._advance()
raw = tok.value
if len(raw) >= 2:
value = self._decode_string_value(raw[1:-1])
else:
value = raw
return JsStringLiteral(value=value, raw=raw, offset=tok.offset)
@staticmethod
def _decode_string_value(text: str) -> str:
parts: list[str] = []
i = 0
length = len(text)
while i < length:
c = text[i]
if c != '\\' or i + 1 >= length:
parts.append(c)
i += 1
continue
i += 1
c = text[i]
i += 1
mapped = _ESCAPE_MAP.get(c)
if mapped is not None:
parts.append(mapped)
continue
if c == 'x' and i + 1 < length:
hexstr = text[i:i + 2]
if len(hexstr) == 2 and all(
h in '0123456789abcdefABCDEF' for h in hexstr
):
parts.append(chr(int(hexstr, 16)))
i += 2
continue
parts.append('x')
continue
if c == 'u':
if i < length and text[i] == '{':
end = text.find('}', i + 1)
if end != -1:
hexstr = text[i + 1:end]
if hexstr and all(
h in '0123456789abcdefABCDEF' for h in hexstr
):
parts.append(chr(int(hexstr, 16)))
i = end + 1
continue
i = end + 1
parts.append('u')
continue
elif i + 3 < length:
hexstr = text[i:i + 4]
if len(hexstr) == 4 and all(
h in '0123456789abcdefABCDEF' for h in hexstr
):
parts.append(chr(int(hexstr, 16)))
i += 4
continue
parts.append('u')
continue
if c in '\r\n':
if c == '\r' and i < length and text[i] == '\n':
i += 1
continue
parts.append(c)
return ''.join(parts)
def _parse_template_literal(self) -> JsTemplateLiteral:
offset = self._current.offset
quasis: list[JsTemplateElement] = []
expressions: list[Expression] = []
if self._at(JsTokenKind.TEMPLATE_FULL):
tok = self._advance()
raw = tok.value
value = raw[1:-1]
quasis.append(JsTemplateElement(
value=value, raw=raw, tail=True, offset=tok.offset))
return JsTemplateLiteral(
quasis=quasis, expressions=expressions, offset=offset)
tok = self._advance()
raw = tok.value
value = raw[1:-2]
quasis.append(JsTemplateElement(
value=value, raw=raw, tail=False, offset=tok.offset))
while True:
expr = self._parse_expression()
expressions.append(expr)
if self._at(JsTokenKind.TEMPLATE_TAIL):
tok = self._advance()
raw = tok.value
value = raw[1:-1]
quasis.append(JsTemplateElement(
value=value, raw=raw, tail=True, offset=tok.offset))
break
elif self._at(JsTokenKind.TEMPLATE_MIDDLE):
tok = self._advance()
raw = tok.value
value = raw[1:-2]
quasis.append(JsTemplateElement(
value=value, raw=raw, tail=False, offset=tok.offset))
else:
quasis.append(JsTemplateElement(
value='', raw='', tail=True, offset=self._current.offset))
break
return JsTemplateLiteral(
quasis=quasis, expressions=expressions, offset=offset)
def _parse_array_literal(self) -> JsArrayExpression:
saved_no_in = self._no_in
self._no_in = False
offset = self._current.offset
self._expect(JsTokenKind.LBRACKET)
elements: list[Expression | None] = []
while not self._at(JsTokenKind.RBRACKET, JsTokenKind.EOF):
if self._at(JsTokenKind.COMMA):
elements.append(None)
self._advance()
continue
if self._at(JsTokenKind.ELLIPSIS):
so = self._current.offset
self._advance()
arg = self._parse_assignment_expression()
elements.append(JsSpreadElement(argument=arg, offset=so))
else:
elements.append(self._parse_assignment_expression())
if not self._at(JsTokenKind.RBRACKET):
self._eat(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACKET)
self._no_in = saved_no_in
return JsArrayExpression(elements=elements, offset=offset)
def _parse_object_literal(self) -> JsObjectExpression:
saved_no_in = self._no_in
self._no_in = False
offset = self._current.offset
self._expect(JsTokenKind.LBRACE)
properties: list[JsProperty | JsSpreadElement] = []
while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF):
if self._at(JsTokenKind.ELLIPSIS):
so = self._current.offset
self._advance()
arg = self._parse_assignment_expression()
properties.append(JsSpreadElement(argument=arg, offset=so))
else:
properties.append(self._parse_object_property())
if not self._at(JsTokenKind.RBRACE):
self._eat(JsTokenKind.COMMA)
self._expect(JsTokenKind.RBRACE)
self._no_in = saved_no_in
return JsObjectExpression(properties=properties, offset=offset)
def _parse_object_property(self) -> JsProperty:
offset = self._current.offset
is_generator = bool(self._eat(JsTokenKind.STAR))
if self._at(JsTokenKind.IDENTIFIER) and self._current.value in ('get', 'set'):
kind_val = self._current.value
saved = self._current
self._advance()
if self._at(
JsTokenKind.LPAREN,
JsTokenKind.COLON,
JsTokenKind.COMMA,
JsTokenKind.RBRACE,
JsTokenKind.EQUALS
):
key = JsIdentifier(name=kind_val, offset=saved.offset)
return self._finish_property_value(key, False, False, offset)
key = self._parse_property_name_from_current()
return self._make_method_property(key, kind_val, False, offset)
if self._at(JsTokenKind.ASYNC) and not is_generator:
saved = self._current
self._advance()
if self._preceded_by_newline or self._at(
JsTokenKind.COLON, JsTokenKind.COMMA,
JsTokenKind.RBRACE, JsTokenKind.EQUALS,
JsTokenKind.LPAREN,
):
if self._at(JsTokenKind.LPAREN):
key = JsIdentifier(name='async', offset=saved.offset)
return self._make_method_property(key, 'init', False, offset)
key = JsIdentifier(name='async', offset=saved.offset)
return self._finish_property_value(key, False, False, offset)
gen = bool(self._eat(JsTokenKind.STAR))
key = self._parse_property_name_from_current()
return self._make_method_property(key, 'init', gen, offset, is_async=True)
computed = False
if self._at(JsTokenKind.LBRACKET):
computed = True
self._advance()
key = self._parse_assignment_expression()
self._expect(JsTokenKind.RBRACKET)
else:
key = self._parse_property_name()
if is_generator or self._at(JsTokenKind.LPAREN):
return self._make_method_property(key, 'init', is_generator, offset, computed=computed)
return self._finish_property_value(key, computed, False, offset)
def _finish_property_value(
self,
key: Expression,
computed: bool,
is_generator: bool,
offset: int,
) -> JsProperty:
if self._eat(JsTokenKind.COLON):
value = self._parse_assignment_expression()
return JsProperty(
key=key, value=value, computed=computed,
shorthand=False, offset=offset)
return JsProperty(
key=key, value=key, computed=computed,
shorthand=True, offset=offset)
def _make_method_property(
self,
key: Expression,
kind: str,
is_generator: bool,
offset: int,
computed: bool = False,
is_async: bool = False,
) -> JsProperty:
func_offset = self._current.offset
params = self._parse_formal_parameters()
body = self._parse_block_statement()
value = JsFunctionExpression(
params=params, body=body, generator=is_generator,
is_async=is_async, offset=func_offset)
return JsProperty(
key=key, value=value, computed=computed,
shorthand=False, method=True, kind=kind, offset=offset)
def _parse_property_name(self) -> Expression:
tok = self._current
if self._at(JsTokenKind.INTEGER, JsTokenKind.FLOAT):
self._advance()
raw = tok.value
text = raw.replace('_', '')
return JsNumericLiteral(
value=float(text) if tok.kind == JsTokenKind.FLOAT else int(text),
raw=raw,
offset=tok.offset,
)
if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE):
return self._parse_string_literal()
self._advance()
return JsIdentifier(name=tok.value, offset=tok.offset)
def _parse_property_name_from_current(self) -> Expression:
return self._parse_property_name()
def _parse_paren_or_arrow(self) -> Expression:
saved_no_in = self._no_in
self._no_in = False
try:
offset = self._current.offset
self._expect(JsTokenKind.LPAREN)
if self._at(JsTokenKind.RPAREN):
self._advance()
self._expect(JsTokenKind.ARROW)
body = self._parse_arrow_body()
return JsArrowFunctionExpression(params=[], body=body, offset=offset)
if self._at(JsTokenKind.ELLIPSIS):
params = self._parse_arrow_params_rest()
self._expect(JsTokenKind.RPAREN)
self._expect(JsTokenKind.ARROW)
body = self._parse_arrow_body()
return JsArrowFunctionExpression(params=params, body=body, offset=offset)
expr = self._parse_expression()
if self._at(JsTokenKind.RPAREN):
self._advance()
if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline:
self._advance()
params = self._expr_to_params(expr)
body = self._parse_arrow_body()
return JsArrowFunctionExpression(
params=params, body=body, offset=offset)
return JsParenthesizedExpression(expression=expr, offset=offset)
self._expect(JsTokenKind.RPAREN)
return JsParenthesizedExpression(expression=expr, offset=offset)
finally:
self._no_in = saved_no_in
def _parse_arrow_params_rest(self) -> list[Expression]:
params: list[Expression] = []
while self._at(JsTokenKind.ELLIPSIS):
params.append(self._parse_rest_element())
if self._at(JsTokenKind.COMMA):
self._advance()
return params
def _parse_arrow_body(self) -> Expression | JsBlockStatement:
if self._at(JsTokenKind.LBRACE):
return self._parse_block_statement()
return self._parse_assignment_expression()
def _expr_to_params(self, expr: Expression) -> list[Expression]:
if isinstance(expr, JsSequenceExpression):
return [self._to_param(e) for e in expr.expressions]
return [self._to_param(expr)]
def _to_param(self, expr: Expression) -> Expression:
if isinstance(expr, JsIdentifier):
return expr
if isinstance(expr, JsAssignmentExpression) and expr.operator == '=':
return JsAssignmentPattern(
left=self._to_param(expr.left),
right=expr.right,
offset=expr.offset,
)
if isinstance(expr, JsSpreadElement):
return JsRestElement(argument=self._to_param(expr.argument), offset=expr.offset)
if isinstance(expr, JsArrayExpression):
elements = [
self._to_param(e) if e is not None else None
for e in expr.elements
]
return JsArrayPattern(elements=elements, offset=expr.offset)
if isinstance(expr, JsObjectExpression):
props: list[JsProperty | JsRestElement] = []
for p in expr.properties:
if isinstance(p, JsSpreadElement):
props.append(JsRestElement(
argument=self._to_param(p.argument), offset=p.offset))
else:
props.append(p)
return JsObjectPattern(properties=props, offset=expr.offset)
return expr
def _to_pattern(self, expr: Expression) -> Expression:
if isinstance(expr, JsArrayExpression):
elements = [
self._to_pattern(e) if e is not None else None
for e in expr.elements
]
return JsArrayPattern(elements=elements, offset=expr.offset)
if isinstance(expr, JsObjectExpression):
props: list[JsProperty | JsRestElement] = []
for p in expr.properties:
if isinstance(p, JsSpreadElement):
props.append(JsRestElement(
argument=self._to_pattern(p.argument), offset=p.offset))
else:
props.append(p)
return JsObjectPattern(properties=props, offset=expr.offset)
return expr
def _parse_function_expression(self) -> JsFunctionExpression:
offset = self._current.offset
self._expect(JsTokenKind.FUNCTION)
generator = bool(self._eat(JsTokenKind.STAR))
id_node = None
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
id_node = JsIdentifier(name=tok.value, offset=tok.offset)
params = self._parse_formal_parameters()
body = self._parse_block_statement()
return JsFunctionExpression(
id=id_node, params=params, body=body,
generator=generator, offset=offset)
def _parse_class_expression(self) -> JsClassExpression:
offset = self._current.offset
self._expect(JsTokenKind.CLASS)
id_node = None
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
id_node = JsIdentifier(name=tok.value, offset=tok.offset)
super_class = None
if self._eat(JsTokenKind.EXTENDS):
super_class = self._parse_assignment_expression()
body = self._parse_class_body()
return JsClassExpression(
id=id_node, super_class=super_class, body=body, offset=offset)
def _parse_async_expression(self) -> Expression:
offset = self._current.offset
self._advance()
return self._parse_expression_starting_with_async(offset)
def _parse_expression_starting_with_async(self, offset: int) -> Expression:
if self._at(JsTokenKind.FUNCTION) and not self._preceded_by_newline:
self._advance()
generator = bool(self._eat(JsTokenKind.STAR))
id_node = None
if self._at(JsTokenKind.IDENTIFIER):
tok = self._advance()
id_node = JsIdentifier(name=tok.value, offset=tok.offset)
params = self._parse_formal_parameters()
body = self._parse_block_statement()
return JsFunctionExpression(
id=id_node, params=params, body=body,
generator=generator, is_async=True, offset=offset)
if self._at(JsTokenKind.IDENTIFIER) and not self._preceded_by_newline:
tok = self._advance()
if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline:
self._advance()
param = JsIdentifier(name=tok.value, offset=tok.offset)
body = self._parse_arrow_body()
return JsArrowFunctionExpression(
params=[param], body=body, is_async=True, offset=offset)
return JsIdentifier(name=tok.value, offset=tok.offset)
if self._at(JsTokenKind.LPAREN) and not self._preceded_by_newline:
paren_result = self._parse_paren_or_arrow()
if isinstance(paren_result, JsArrowFunctionExpression):
paren_result.is_async = True
paren_result.offset = offset
return paren_result
return JsIdentifier(name='async', offset=offset)
def _parse_yield_expression(self) -> JsYieldExpression:
offset = self._current.offset
self._advance()
delegate = False
if self._eat(JsTokenKind.STAR):
delegate = True
argument = None
if not self._preceded_by_newline and not self._at(
JsTokenKind.SEMICOLON, JsTokenKind.RBRACE,
JsTokenKind.RPAREN, JsTokenKind.RBRACKET,
JsTokenKind.COMMA, JsTokenKind.COLON, JsTokenKind.EOF,
):
argument = self._parse_assignment_expression()
return JsYieldExpression(
argument=argument, delegate=delegate, offset=offset)
Classes
class JsParser (source)-
Expand source code Browse git
class JsParser: def __init__(self, source: str): self._lexer = JsLexer(source) self._source = source self._tokens = self._lexer.tokenize() self._current: JsToken = JsToken(JsTokenKind.EOF, '', 0) self._preceded_by_newline: bool = False self._no_in: bool = False self._pending_comments: list[str] = [] self._advance() def _advance(self) -> JsToken: prev = self._current had_newline = False while True: tok = next(self._tokens, JsToken(JsTokenKind.EOF, '', len(self._source))) if tok.kind == JsTokenKind.NEWLINE: had_newline = True continue if tok.kind == JsTokenKind.COMMENT: self._pending_comments.append(tok.value) continue break self._current = tok self._preceded_by_newline = had_newline return prev def _drain_comments(self, node): if self._pending_comments: node.leading_comments.extend(self._pending_comments) self._pending_comments.clear() def _peek(self) -> JsToken: return self._current def _at(self, *kinds: JsTokenKind) -> bool: return self._current.kind in kinds def _eat(self, kind: JsTokenKind) -> JsToken | None: if self._current.kind == kind: return self._advance() return None def _expect(self, kind: JsTokenKind) -> JsToken: if self._current.kind == kind: return self._advance() tok = self._current self._advance() return JsToken(kind, tok.value, tok.offset) def _eat_semicolon(self) -> bool: if self._eat(JsTokenKind.SEMICOLON): return True if self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): return True if self._preceded_by_newline: return True return False def parse(self) -> JsScript: return self._parse_program() def _parse_program(self) -> JsScript: offset = self._current.offset body: list[Statement] = [] while not self._at(JsTokenKind.EOF): mark = self._current.offset comments = list(self._pending_comments) self._pending_comments.clear() try: stmt = self._parse_statement() except Exception: stmt = None if stmt is not None: stmt.leading_comments.extend(comments) body.append(stmt) elif self._current.offset == mark: tok = self._advance() error = JsExpressionStatement( offset=tok.offset, expression=JsErrorNode(offset=tok.offset, text=tok.value), ) error.leading_comments.extend(comments) body.append(error) return JsScript(body=body, offset=offset) def _parse_statement(self) -> Statement | None: offset = self._current.offset kind = self._current.kind if kind == JsTokenKind.LBRACE: return self._parse_block_statement() if kind == JsTokenKind.SEMICOLON: self._advance() return JsEmptyStatement(offset=offset) if kind in (JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST): return self._parse_variable_declaration() if kind == JsTokenKind.IF: return self._parse_if_statement() if kind == JsTokenKind.WHILE: return self._parse_while_statement() if kind == JsTokenKind.DO: return self._parse_do_while_statement() if kind == JsTokenKind.FOR: return self._parse_for_statement() if kind == JsTokenKind.SWITCH: return self._parse_switch_statement() if kind == JsTokenKind.TRY: return self._parse_try_statement() if kind == JsTokenKind.WITH: return self._parse_with_statement() if kind == JsTokenKind.RETURN: return self._parse_return_statement() if kind == JsTokenKind.THROW: return self._parse_throw_statement() if kind == JsTokenKind.BREAK: return self._parse_break_statement() if kind == JsTokenKind.CONTINUE: return self._parse_continue_statement() if kind == JsTokenKind.FUNCTION: return self._parse_function_declaration() if kind == JsTokenKind.CLASS: return self._parse_class_declaration() if kind == JsTokenKind.DEBUGGER: self._advance() self._eat_semicolon() return JsDebuggerStatement(offset=offset) if kind == JsTokenKind.IMPORT: return self._parse_import_declaration() if kind == JsTokenKind.EXPORT: return self._parse_export_declaration() if kind == JsTokenKind.ASYNC: return self._parse_async_statement() expr = self._parse_expression() if ( isinstance(expr, JsIdentifier) and self._eat(JsTokenKind.COLON) ): body = self._parse_statement() return JsLabeledStatement(label=expr, body=body, offset=offset) self._eat_semicolon() return JsExpressionStatement(expression=expr, offset=offset) def _parse_block_statement(self) -> JsBlockStatement: offset = self._current.offset self._expect(JsTokenKind.LBRACE) body: list[Statement] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): mark = self._current.offset comments = list(self._pending_comments) self._pending_comments.clear() try: stmt = self._parse_statement() except Exception: stmt = None if stmt is not None: stmt.leading_comments.extend(comments) body.append(stmt) elif self._current.offset == mark: tok = self._advance() error = JsExpressionStatement( offset=tok.offset, expression=JsErrorNode(offset=tok.offset, text=tok.value), ) error.leading_comments.extend(comments) body.append(error) self._expect(JsTokenKind.RBRACE) return JsBlockStatement(body=body, offset=offset) def _parse_variable_declaration(self) -> JsVariableDeclaration: offset = self._current.offset kind_tok = self._advance() kind = kind_tok.value declarations: list[JsVariableDeclarator] = [] declarations.append(self._parse_variable_declarator()) while self._eat(JsTokenKind.COMMA): declarations.append(self._parse_variable_declarator()) self._eat_semicolon() return JsVariableDeclaration(declarations=declarations, kind=kind, offset=offset) def _parse_variable_declarator(self) -> JsVariableDeclarator: offset = self._current.offset id_node = self._parse_binding_pattern() init = None if self._eat(JsTokenKind.EQUALS): init = self._parse_assignment_expression() return JsVariableDeclarator(id=id_node, init=init, offset=offset) def _parse_binding_pattern(self) -> Expression: if self._at(JsTokenKind.LBRACKET): return self._parse_array_pattern() if self._at(JsTokenKind.LBRACE): return self._parse_object_pattern() return self._parse_binding_identifier() def _parse_binding_identifier(self) -> Expression: offset = self._current.offset tok = self._expect(JsTokenKind.IDENTIFIER) return JsIdentifier(name=tok.value, offset=offset) def _parse_array_pattern(self) -> JsArrayPattern: offset = self._current.offset self._expect(JsTokenKind.LBRACKET) elements: list[Expression | None] = [] while not self._at(JsTokenKind.RBRACKET, JsTokenKind.EOF): if self._at(JsTokenKind.COMMA): elements.append(None) self._advance() continue if self._at(JsTokenKind.ELLIPSIS): elements.append(self._parse_rest_element()) break elem = self._parse_binding_pattern() if self._eat(JsTokenKind.EQUALS): right = self._parse_assignment_expression() elem = JsAssignmentPattern(left=elem, right=right, offset=elem.offset) elements.append(elem) if not self._at(JsTokenKind.RBRACKET): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACKET) return JsArrayPattern(elements=elements, offset=offset) def _parse_object_pattern(self) -> JsObjectPattern: offset = self._current.offset self._expect(JsTokenKind.LBRACE) properties: list[JsProperty | JsRestElement] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): if self._at(JsTokenKind.ELLIPSIS): properties.append(self._parse_rest_element()) break prop = self._parse_object_pattern_property() properties.append(prop) if not self._at(JsTokenKind.RBRACE): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACE) return JsObjectPattern(properties=properties, offset=offset) def _parse_object_pattern_property(self) -> JsProperty: offset = self._current.offset if self._at(JsTokenKind.LBRACKET): self._advance() key = self._parse_assignment_expression() self._expect(JsTokenKind.RBRACKET) self._expect(JsTokenKind.COLON) value = self._parse_binding_pattern() if self._eat(JsTokenKind.EQUALS): right = self._parse_assignment_expression() value = JsAssignmentPattern(left=value, right=right, offset=value.offset) return JsProperty( key=key, value=value, computed=True, shorthand=False, offset=offset) key = self._parse_property_name() if self._eat(JsTokenKind.COLON): value = self._parse_binding_pattern() if self._eat(JsTokenKind.EQUALS): right = self._parse_assignment_expression() value = JsAssignmentPattern(left=value, right=right, offset=value.offset) return JsProperty( key=key, value=value, computed=False, shorthand=False, offset=offset) value = key if self._eat(JsTokenKind.EQUALS): right = self._parse_assignment_expression() value = JsAssignmentPattern(left=key, right=right, offset=key.offset) return JsProperty(key=key, value=value, computed=False, shorthand=True, offset=offset) def _parse_rest_element(self) -> JsRestElement: offset = self._current.offset self._expect(JsTokenKind.ELLIPSIS) argument = self._parse_binding_pattern() return JsRestElement(argument=argument, offset=offset) def _parse_if_statement(self) -> JsIfStatement: offset = self._current.offset self._expect(JsTokenKind.IF) self._expect(JsTokenKind.LPAREN) test = self._parse_expression() self._expect(JsTokenKind.RPAREN) consequent = self._parse_statement() alternate = None if self._eat(JsTokenKind.ELSE): alternate = self._parse_statement() return JsIfStatement( test=test, consequent=consequent, alternate=alternate, offset=offset) def _parse_while_statement(self) -> JsWhileStatement: offset = self._current.offset self._expect(JsTokenKind.WHILE) self._expect(JsTokenKind.LPAREN) test = self._parse_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsWhileStatement(test=test, body=body, offset=offset) def _parse_do_while_statement(self) -> JsDoWhileStatement: offset = self._current.offset self._expect(JsTokenKind.DO) body = self._parse_statement() self._expect(JsTokenKind.WHILE) self._expect(JsTokenKind.LPAREN) test = self._parse_expression() self._expect(JsTokenKind.RPAREN) self._eat_semicolon() return JsDoWhileStatement(test=test, body=body, offset=offset) def _parse_for_statement(self) -> Statement: offset = self._current.offset self._expect(JsTokenKind.FOR) is_await = False if self._eat(JsTokenKind.AWAIT): is_await = True self._expect(JsTokenKind.LPAREN) if self._at(JsTokenKind.SEMICOLON): self._advance() return self._parse_for_rest(None, offset) if self._at(JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST): decl_offset = self._current.offset kind_tok = self._advance() kind = kind_tok.value declarator = self._parse_variable_declarator() decl = JsVariableDeclaration( declarations=[declarator], kind=kind, offset=decl_offset) if self._eat(JsTokenKind.IN): right = self._parse_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsForInStatement( left=decl, right=right, body=body, offset=offset) if self._at(JsTokenKind.OF) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'of' ): self._advance() right = self._parse_assignment_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsForOfStatement( left=decl, right=right, body=body, is_await=is_await, offset=offset) while self._eat(JsTokenKind.COMMA): decl.declarations.append(self._parse_variable_declarator()) self._expect(JsTokenKind.SEMICOLON) return self._parse_for_rest(decl, offset) saved_no_in = self._no_in self._no_in = True init_expr = self._parse_expression() self._no_in = saved_no_in if self._eat(JsTokenKind.IN): right = self._parse_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsForInStatement( left=init_expr, right=right, body=body, offset=offset) if self._at(JsTokenKind.OF) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'of' ): self._advance() right = self._parse_assignment_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsForOfStatement( left=init_expr, right=right, body=body, is_await=is_await, offset=offset) self._expect(JsTokenKind.SEMICOLON) return self._parse_for_rest(init_expr, offset) def _parse_for_rest( self, init: Expression | Statement | None, offset: int, ) -> JsForStatement: test = None if not self._at(JsTokenKind.SEMICOLON): test = self._parse_expression() self._expect(JsTokenKind.SEMICOLON) update = None if not self._at(JsTokenKind.RPAREN): update = self._parse_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsForStatement( init=init, test=test, update=update, body=body, offset=offset) def _parse_switch_statement(self) -> JsSwitchStatement: offset = self._current.offset self._expect(JsTokenKind.SWITCH) self._expect(JsTokenKind.LPAREN) discriminant = self._parse_expression() self._expect(JsTokenKind.RPAREN) self._expect(JsTokenKind.LBRACE) cases: list[JsSwitchCase] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): cases.append(self._parse_switch_case()) self._expect(JsTokenKind.RBRACE) return JsSwitchStatement( discriminant=discriminant, cases=cases, offset=offset) def _parse_switch_case(self) -> JsSwitchCase: offset = self._current.offset test = None if self._eat(JsTokenKind.CASE): test = self._parse_expression() self._expect(JsTokenKind.COLON) elif self._eat(JsTokenKind.DEFAULT): self._expect(JsTokenKind.COLON) else: self._advance() consequent: list[Statement] = [] while not self._at( JsTokenKind.CASE, JsTokenKind.DEFAULT, JsTokenKind.RBRACE, JsTokenKind.EOF, ): stmt = self._parse_statement() if stmt is not None: consequent.append(stmt) return JsSwitchCase(test=test, consequent=consequent, offset=offset) def _parse_try_statement(self) -> JsTryStatement: offset = self._current.offset self._expect(JsTokenKind.TRY) block = self._parse_block_statement() handler = None finalizer = None if self._eat(JsTokenKind.CATCH): handler = self._parse_catch_clause() if self._eat(JsTokenKind.FINALLY): finalizer = self._parse_block_statement() return JsTryStatement( block=block, handler=handler, finalizer=finalizer, offset=offset) def _parse_catch_clause(self) -> JsCatchClause: offset = self._current.offset param = None if self._eat(JsTokenKind.LPAREN): param = self._parse_binding_pattern() self._expect(JsTokenKind.RPAREN) body = self._parse_block_statement() return JsCatchClause(param=param, body=body, offset=offset) def _parse_with_statement(self) -> JsWithStatement: offset = self._current.offset self._expect(JsTokenKind.WITH) self._expect(JsTokenKind.LPAREN) obj = self._parse_expression() self._expect(JsTokenKind.RPAREN) body = self._parse_statement() return JsWithStatement(object=obj, body=body, offset=offset) def _parse_return_statement(self) -> JsReturnStatement: offset = self._current.offset self._expect(JsTokenKind.RETURN) argument = None if not self._preceded_by_newline and not self._at( JsTokenKind.SEMICOLON, JsTokenKind.RBRACE, JsTokenKind.EOF, ): argument = self._parse_expression() self._eat_semicolon() return JsReturnStatement(argument=argument, offset=offset) def _parse_throw_statement(self) -> JsThrowStatement: offset = self._current.offset self._expect(JsTokenKind.THROW) argument = None if not self._preceded_by_newline: argument = self._parse_expression() self._eat_semicolon() return JsThrowStatement(argument=argument, offset=offset) def _parse_break_statement(self) -> JsBreakStatement: offset = self._current.offset self._expect(JsTokenKind.BREAK) label = None if not self._preceded_by_newline and self._at(JsTokenKind.IDENTIFIER): tok = self._advance() label = JsIdentifier(name=tok.value, offset=tok.offset) self._eat_semicolon() return JsBreakStatement(label=label, offset=offset) def _parse_continue_statement(self) -> JsContinueStatement: offset = self._current.offset self._expect(JsTokenKind.CONTINUE) label = None if not self._preceded_by_newline and self._at(JsTokenKind.IDENTIFIER): tok = self._advance() label = JsIdentifier(name=tok.value, offset=tok.offset) self._eat_semicolon() return JsContinueStatement(label=label, offset=offset) def _parse_function_declaration( self, is_async: bool = False, ) -> JsFunctionDeclaration: offset = self._current.offset self._expect(JsTokenKind.FUNCTION) generator = bool(self._eat(JsTokenKind.STAR)) id_node = None if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() id_node = JsIdentifier(name=tok.value, offset=tok.offset) params = self._parse_formal_parameters() body = self._parse_block_statement() return JsFunctionDeclaration( id=id_node, params=params, body=body, generator=generator, is_async=is_async, offset=offset, ) def _parse_formal_parameters(self) -> list[Expression]: self._expect(JsTokenKind.LPAREN) params: list[Expression] = [] while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF): if self._at(JsTokenKind.ELLIPSIS): params.append(self._parse_rest_element()) break param = self._parse_binding_pattern() if self._eat(JsTokenKind.EQUALS): default = self._parse_assignment_expression() param = JsAssignmentPattern( left=param, right=default, offset=param.offset) params.append(param) if not self._at(JsTokenKind.RPAREN): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RPAREN) return params def _parse_class_declaration(self) -> JsClassDeclaration: offset = self._current.offset self._expect(JsTokenKind.CLASS) id_node = None if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() id_node = JsIdentifier(name=tok.value, offset=tok.offset) super_class = None if self._eat(JsTokenKind.EXTENDS): super_class = self._parse_assignment_expression() body = self._parse_class_body() return JsClassDeclaration( id=id_node, super_class=super_class, body=body, offset=offset) def _parse_class_body(self) -> JsClassBody: offset = self._current.offset self._expect(JsTokenKind.LBRACE) members: list[JsMethodDefinition | JsPropertyDefinition] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): if self._eat(JsTokenKind.SEMICOLON): continue members.append(self._parse_class_member()) self._expect(JsTokenKind.RBRACE) return JsClassBody(body=members, offset=offset) def _parse_class_member(self) -> JsMethodDefinition | JsPropertyDefinition: offset = self._current.offset is_static = False if ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'static' ): saved_pos = self._current self._advance() if self._at( JsTokenKind.LBRACE, JsTokenKind.RBRACE, JsTokenKind.EOF, JsTokenKind.SEMICOLON, ): key = JsIdentifier(name='static', offset=saved_pos.offset) return self._finish_class_member(key, False, False, offset) is_static = True kind = 'method' is_generator = False if self._eat(JsTokenKind.STAR): is_generator = True if self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'get': saved = self._current self._advance() if self._at(JsTokenKind.LPAREN): key = JsIdentifier(name='get', offset=saved.offset) return self._finish_class_member(key, is_static, is_generator, offset) kind = 'get' elif self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'set': saved = self._current self._advance() if self._at(JsTokenKind.LPAREN): key = JsIdentifier(name='set', offset=saved.offset) return self._finish_class_member(key, is_static, is_generator, offset) kind = 'set' elif self._at(JsTokenKind.ASYNC): saved = self._current self._advance() if self._at(JsTokenKind.LPAREN): key = JsIdentifier(name='async', offset=saved.offset) return self._finish_class_member(key, is_static, is_generator, offset) computed = False if self._at(JsTokenKind.LBRACKET): computed = True self._advance() key = self._parse_assignment_expression() self._expect(JsTokenKind.RBRACKET) else: key = self._parse_property_name() if kind == 'method' and not is_generator and not self._at(JsTokenKind.LPAREN): value = None if self._eat(JsTokenKind.EQUALS): value = self._parse_assignment_expression() self._eat_semicolon() return JsPropertyDefinition( key=key, value=value, computed=computed, is_static=is_static, offset=offset) return self._finish_class_member(key, is_static, is_generator, offset, kind, computed) def _finish_class_member( self, key: Expression, is_static: bool, is_generator: bool, offset: int, kind: str = 'method', computed: bool = False, ) -> JsMethodDefinition: func_offset = self._current.offset params = self._parse_formal_parameters() body = self._parse_block_statement() value = JsFunctionExpression( params=params, body=body, generator=is_generator, offset=func_offset, ) if isinstance(key, JsIdentifier) and key.name == 'constructor' and kind == 'method': kind = 'constructor' return JsMethodDefinition( key=key, value=value, kind=kind, computed=computed, is_static=is_static, offset=offset) def _parse_import_declaration(self) -> JsImportDeclaration: offset = self._current.offset self._expect(JsTokenKind.IMPORT) if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE): source = self._parse_string_literal() self._eat_semicolon() return JsImportDeclaration(source=source, offset=offset) specifiers: list[ JsImportSpecifier | JsImportDefaultSpecifier | JsImportNamespaceSpecifier ] = [] if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() specifiers.append(JsImportDefaultSpecifier( local=JsIdentifier(name=tok.value, offset=tok.offset), offset=tok.offset, )) if self._eat(JsTokenKind.COMMA): if self._at(JsTokenKind.STAR): specifiers.append(self._parse_namespace_import()) elif self._at(JsTokenKind.LBRACE): specifiers.extend(self._parse_named_imports()) elif self._at(JsTokenKind.STAR): specifiers.append(self._parse_namespace_import()) elif self._at(JsTokenKind.LBRACE): specifiers.extend(self._parse_named_imports()) self._expect_contextual('from') source = self._parse_string_literal() self._eat_semicolon() return JsImportDeclaration( specifiers=specifiers, source=source, offset=offset) def _parse_namespace_import(self) -> JsImportNamespaceSpecifier: offset = self._current.offset self._expect(JsTokenKind.STAR) self._expect_contextual('as') tok = self._expect(JsTokenKind.IDENTIFIER) return JsImportNamespaceSpecifier( local=JsIdentifier(name=tok.value, offset=tok.offset), offset=offset, ) def _parse_named_imports(self) -> list[JsImportSpecifier]: self._expect(JsTokenKind.LBRACE) specs: list[JsImportSpecifier] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): spec_offset = self._current.offset tok = self._advance() imported = JsIdentifier(name=tok.value, offset=tok.offset) local = imported if self._at(JsTokenKind.AS) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as' ): self._advance() ltok = self._expect(JsTokenKind.IDENTIFIER) local = JsIdentifier(name=ltok.value, offset=ltok.offset) specs.append(JsImportSpecifier( imported=imported, local=local, offset=spec_offset)) if not self._at(JsTokenKind.RBRACE): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACE) return specs def _parse_export_declaration(self) -> Statement: offset = self._current.offset self._expect(JsTokenKind.EXPORT) if self._eat(JsTokenKind.DEFAULT): if self._at(JsTokenKind.FUNCTION): decl = self._parse_function_declaration() return JsExportDefaultDeclaration(declaration=decl, offset=offset) if self._at(JsTokenKind.CLASS): decl = self._parse_class_declaration() return JsExportDefaultDeclaration(declaration=decl, offset=offset) if self._at(JsTokenKind.ASYNC): decl = self._parse_async_statement() return JsExportDefaultDeclaration(declaration=decl, offset=offset) expr = self._parse_assignment_expression() self._eat_semicolon() return JsExportDefaultDeclaration(declaration=expr, offset=offset) if self._at(JsTokenKind.STAR): self._advance() exported = None if self._at(JsTokenKind.AS) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as' ): self._advance() tok = self._expect(JsTokenKind.IDENTIFIER) exported = JsIdentifier(name=tok.value, offset=tok.offset) self._expect_contextual('from') source = self._parse_string_literal() self._eat_semicolon() return JsExportAllDeclaration( source=source, exported=exported, offset=offset) if self._at(JsTokenKind.LBRACE): return self._parse_export_named(offset) if self._at(JsTokenKind.VAR, JsTokenKind.LET, JsTokenKind.CONST): decl = self._parse_variable_declaration() return JsExportNamedDeclaration(declaration=decl, offset=offset) if self._at(JsTokenKind.FUNCTION): decl = self._parse_function_declaration() return JsExportNamedDeclaration(declaration=decl, offset=offset) if self._at(JsTokenKind.CLASS): decl = self._parse_class_declaration() return JsExportNamedDeclaration(declaration=decl, offset=offset) if self._at(JsTokenKind.ASYNC): decl = self._parse_async_statement() return JsExportNamedDeclaration(declaration=decl, offset=offset) self._advance() return JsExportNamedDeclaration(offset=offset) def _parse_export_named(self, offset: int) -> JsExportNamedDeclaration: self._expect(JsTokenKind.LBRACE) specifiers: list[JsExportSpecifier] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): spec_offset = self._current.offset tok = self._advance() local = JsIdentifier(name=tok.value, offset=tok.offset) exported = local if self._at(JsTokenKind.AS) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'as' ): self._advance() etok = self._advance() exported = JsIdentifier(name=etok.value, offset=etok.offset) specifiers.append(JsExportSpecifier( local=local, exported=exported, offset=spec_offset)) if not self._at(JsTokenKind.RBRACE): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACE) source = None if self._at(JsTokenKind.FROM) or ( self._at(JsTokenKind.IDENTIFIER) and self._current.value == 'from' ): self._advance() source = self._parse_string_literal() self._eat_semicolon() return JsExportNamedDeclaration( specifiers=specifiers, source=source, offset=offset) def _parse_async_statement(self) -> Statement: offset = self._current.offset self._expect(JsTokenKind.ASYNC) if self._at(JsTokenKind.FUNCTION) and not self._preceded_by_newline: return self._parse_function_declaration(is_async=True) expr = self._parse_expression_starting_with_async(offset) self._eat_semicolon() return JsExpressionStatement(expression=expr, offset=offset) def _expect_contextual(self, keyword: str): if self._at(JsTokenKind.FROM) and keyword == 'from': self._advance() return if self._at(JsTokenKind.AS) and keyword == 'as': self._advance() return if self._at(JsTokenKind.IDENTIFIER) and self._current.value == keyword: self._advance() return self._advance() def _parse_expression(self) -> Expression: expr = self._parse_assignment_expression() if self._at(JsTokenKind.COMMA): exprs = [expr] while self._eat(JsTokenKind.COMMA): exprs.append(self._parse_assignment_expression()) return JsSequenceExpression(expressions=exprs, offset=expr.offset) return expr def _parse_assignment_expression(self) -> Expression: left = self._parse_conditional_expression() if self._current.kind.is_assignment: op = self._advance().value right = self._parse_assignment_expression() left = self._to_pattern(left) if op == '=' else left return JsAssignmentExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_conditional_expression(self) -> Expression: expr = self._parse_nullish_coalescing_expression() if self._eat(JsTokenKind.QUESTION): consequent = self._parse_assignment_expression() self._expect(JsTokenKind.COLON) alternate = self._parse_assignment_expression() return JsConditionalExpression( test=expr, consequent=consequent, alternate=alternate, offset=expr.offset) return expr def _parse_nullish_coalescing_expression(self) -> Expression: left = self._parse_logical_or_expression() while self._eat(JsTokenKind.QQ): right = self._parse_logical_or_expression() left = JsLogicalExpression( left=left, operator='??', right=right, offset=left.offset) return left def _parse_logical_or_expression(self) -> Expression: left = self._parse_logical_and_expression() while self._eat(JsTokenKind.OR): right = self._parse_logical_and_expression() left = JsLogicalExpression( left=left, operator='||', right=right, offset=left.offset) return left def _parse_logical_and_expression(self) -> Expression: left = self._parse_bitwise_or_expression() while self._eat(JsTokenKind.AND): right = self._parse_bitwise_or_expression() left = JsLogicalExpression( left=left, operator='&&', right=right, offset=left.offset) return left def _parse_bitwise_or_expression(self) -> Expression: left = self._parse_bitwise_xor_expression() while self._eat(JsTokenKind.PIPE): right = self._parse_bitwise_xor_expression() left = JsBinaryExpression( left=left, operator='|', right=right, offset=left.offset) return left def _parse_bitwise_xor_expression(self) -> Expression: left = self._parse_bitwise_and_expression() while self._eat(JsTokenKind.CARET): right = self._parse_bitwise_and_expression() left = JsBinaryExpression( left=left, operator='^', right=right, offset=left.offset) return left def _parse_bitwise_and_expression(self) -> Expression: left = self._parse_equality_expression() while self._eat(JsTokenKind.AMP): right = self._parse_equality_expression() left = JsBinaryExpression( left=left, operator='&', right=right, offset=left.offset) return left def _parse_equality_expression(self) -> Expression: left = self._parse_relational_expression() while self._at( JsTokenKind.EQ2, JsTokenKind.BANG_EQ, JsTokenKind.EQ3, JsTokenKind.BANG_EQ2, ): op = self._advance().value right = self._parse_relational_expression() left = JsBinaryExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_relational_expression(self) -> Expression: left = self._parse_shift_expression() while self._at( JsTokenKind.LT, JsTokenKind.GT, JsTokenKind.LT_EQ, JsTokenKind.GT_EQ, JsTokenKind.INSTANCEOF, JsTokenKind.IN, ): if self._no_in and self._at(JsTokenKind.IN): break op = self._advance().value right = self._parse_shift_expression() left = JsBinaryExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_shift_expression(self) -> Expression: left = self._parse_additive_expression() while self._at( JsTokenKind.LT2, JsTokenKind.GT2, JsTokenKind.GT3, ): op = self._advance().value right = self._parse_additive_expression() left = JsBinaryExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_additive_expression(self) -> Expression: left = self._parse_multiplicative_expression() while self._at(JsTokenKind.PLUS, JsTokenKind.MINUS): op = self._advance().value right = self._parse_multiplicative_expression() left = JsBinaryExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_multiplicative_expression(self) -> Expression: left = self._parse_exponentiation_expression() while self._at(JsTokenKind.STAR, JsTokenKind.SLASH, JsTokenKind.PERCENT): op = self._advance().value right = self._parse_exponentiation_expression() left = JsBinaryExpression( left=left, operator=op, right=right, offset=left.offset) return left def _parse_exponentiation_expression(self) -> Expression: expr = self._parse_unary_expression() if self._eat(JsTokenKind.STAR2): right = self._parse_exponentiation_expression() return JsBinaryExpression( left=expr, operator='**', right=right, offset=expr.offset) return expr def _parse_unary_expression(self) -> Expression: if self._at( JsTokenKind.BANG, JsTokenKind.TILDE, JsTokenKind.TYPEOF, JsTokenKind.VOID, JsTokenKind.DELETE, ): tok = self._advance() operand = self._parse_unary_expression() return JsUnaryExpression( operator=tok.value, operand=operand, prefix=True, offset=tok.offset) if self._at(JsTokenKind.PLUS) and not self._preceded_by_newline: tok = self._advance() operand = self._parse_unary_expression() return JsUnaryExpression( operator='+', operand=operand, prefix=True, offset=tok.offset) if self._at(JsTokenKind.MINUS) and not self._preceded_by_newline: tok = self._advance() operand = self._parse_unary_expression() return JsUnaryExpression( operator='-', operand=operand, prefix=True, offset=tok.offset) if self._at(JsTokenKind.AWAIT): tok = self._advance() operand = self._parse_unary_expression() return JsAwaitExpression(argument=operand, offset=tok.offset) return self._parse_update_expression() def _parse_update_expression(self) -> Expression: if self._at(JsTokenKind.INC, JsTokenKind.DEC): tok = self._advance() argument = self._parse_call_expression() return JsUpdateExpression( operator=tok.value, argument=argument, prefix=True, offset=tok.offset) expr = self._parse_call_expression() if not self._preceded_by_newline and self._at( JsTokenKind.INC, JsTokenKind.DEC, ): tok = self._advance() return JsUpdateExpression( operator=tok.value, argument=expr, prefix=False, offset=expr.offset) return expr def _parse_call_expression(self) -> Expression: expr = self._parse_new_expression() while True: if self._at(JsTokenKind.LPAREN): expr = self._parse_call_arguments(expr, optional=False) elif self._eat(JsTokenKind.DOT): prop_tok = self._advance() prop = JsIdentifier(name=prop_tok.value, offset=prop_tok.offset) expr = JsMemberExpression( object=expr, property=prop, computed=False, optional=False, offset=expr.offset) elif self._at(JsTokenKind.LBRACKET): self._advance() prop = self._parse_expression() self._expect(JsTokenKind.RBRACKET) expr = JsMemberExpression( object=expr, property=prop, computed=True, optional=False, offset=expr.offset) elif self._eat(JsTokenKind.QUESTION_DOT): if self._at(JsTokenKind.LPAREN): expr = self._parse_call_arguments(expr, optional=True) elif self._at(JsTokenKind.LBRACKET): self._advance() prop = self._parse_expression() self._expect(JsTokenKind.RBRACKET) expr = JsMemberExpression( object=expr, property=prop, computed=True, optional=True, offset=expr.offset) else: prop_tok = self._advance() prop = JsIdentifier(name=prop_tok.value, offset=prop_tok.offset) expr = JsMemberExpression( object=expr, property=prop, computed=False, optional=True, offset=expr.offset) elif self._at( JsTokenKind.TEMPLATE_FULL, JsTokenKind.TEMPLATE_HEAD, ): quasi = self._parse_template_literal() expr = JsTaggedTemplateExpression( tag=expr, quasi=quasi, offset=expr.offset) else: break return expr def _parse_call_arguments( self, callee: Expression, optional: bool, ) -> JsCallExpression: saved_no_in = self._no_in self._no_in = False self._expect(JsTokenKind.LPAREN) args: list[Expression] = [] while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF): if self._at(JsTokenKind.ELLIPSIS): offset = self._current.offset self._advance() arg = self._parse_assignment_expression() args.append(JsSpreadElement(argument=arg, offset=offset)) else: args.append(self._parse_assignment_expression()) if not self._at(JsTokenKind.RPAREN): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RPAREN) self._no_in = saved_no_in return JsCallExpression( callee=callee, arguments=args, optional=optional, offset=callee.offset) def _parse_new_expression(self) -> Expression: if self._at(JsTokenKind.NEW): offset = self._current.offset self._advance() if self._at(JsTokenKind.DOT): self._advance() tok = self._advance() return JsMemberExpression( object=JsIdentifier(name='new', offset=offset), property=JsIdentifier(name=tok.value, offset=tok.offset), computed=False, offset=offset, ) callee = self._parse_new_expression() args: list[Expression] = [] if self._at(JsTokenKind.LPAREN): self._advance() while not self._at(JsTokenKind.RPAREN, JsTokenKind.EOF): if self._at(JsTokenKind.ELLIPSIS): so = self._current.offset self._advance() arg = self._parse_assignment_expression() args.append(JsSpreadElement(argument=arg, offset=so)) else: args.append(self._parse_assignment_expression()) if not self._at(JsTokenKind.RPAREN): self._expect(JsTokenKind.COMMA) self._expect(JsTokenKind.RPAREN) return JsNewExpression(callee=callee, arguments=args, offset=offset) return self._parse_primary_expression() def _parse_primary_expression(self) -> Expression: tok = self._current offset = tok.offset if self._at(JsTokenKind.IDENTIFIER): self._advance() if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline: self._advance() param = JsIdentifier(name=tok.value, offset=offset) body = self._parse_arrow_body() return JsArrowFunctionExpression( params=[param], body=body, offset=offset) return JsIdentifier(name=tok.value, offset=offset) if self._at(JsTokenKind.INTEGER): self._advance() raw = tok.value text = raw.replace('_', '') if text.startswith(('0x', '0X')): value = int(text, 16) elif text.startswith(('0o', '0O')): value = int(text, 8) elif text.startswith(('0b', '0B')): value = int(text, 2) else: value = int(text) return JsNumericLiteral(value=value, raw=raw, offset=offset) if self._at(JsTokenKind.FLOAT): self._advance() raw = tok.value value = float(raw.replace('_', '')) return JsNumericLiteral(value=value, raw=raw, offset=offset) if self._at(JsTokenKind.BIGINT): self._advance() raw = tok.value text = raw.replace('_', '').rstrip('n') if text.startswith(('0x', '0X')): value = int(text, 16) elif text.startswith(('0o', '0O')): value = int(text, 8) elif text.startswith(('0b', '0B')): value = int(text, 2) else: value = int(text) return JsBigIntLiteral(value=value, raw=raw, offset=offset) if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE): return self._parse_string_literal() if self._at(JsTokenKind.REGEXP): self._advance() raw = tok.value last_slash = raw.rfind('/') pattern = raw[1:last_slash] flags = raw[last_slash + 1:] return JsRegExpLiteral( pattern=pattern, flags=flags, raw=raw, offset=offset) if self._at(JsTokenKind.TEMPLATE_FULL, JsTokenKind.TEMPLATE_HEAD): return self._parse_template_literal() if self._at(JsTokenKind.TRUE): self._advance() return JsBooleanLiteral(value=True, offset=offset) if self._at(JsTokenKind.FALSE): self._advance() return JsBooleanLiteral(value=False, offset=offset) if self._at(JsTokenKind.NULL): self._advance() return JsNullLiteral(offset=offset) if self._at(JsTokenKind.THIS): self._advance() return JsThisExpression(offset=offset) if self._at(JsTokenKind.SUPER): self._advance() return JsIdentifier(name='super', offset=offset) if self._at(JsTokenKind.LBRACKET): return self._parse_array_literal() if self._at(JsTokenKind.LBRACE): return self._parse_object_literal() if self._at(JsTokenKind.LPAREN): return self._parse_paren_or_arrow() if self._at(JsTokenKind.FUNCTION): return self._parse_function_expression() if self._at(JsTokenKind.CLASS): return self._parse_class_expression() if self._at(JsTokenKind.ASYNC): return self._parse_async_expression() if self._at(JsTokenKind.YIELD): return self._parse_yield_expression() self._advance() return JsErrorNode(text=tok.value, message='unexpected token', offset=offset) def _parse_string_literal(self) -> JsStringLiteral: tok = self._advance() raw = tok.value if len(raw) >= 2: value = self._decode_string_value(raw[1:-1]) else: value = raw return JsStringLiteral(value=value, raw=raw, offset=tok.offset) @staticmethod def _decode_string_value(text: str) -> str: parts: list[str] = [] i = 0 length = len(text) while i < length: c = text[i] if c != '\\' or i + 1 >= length: parts.append(c) i += 1 continue i += 1 c = text[i] i += 1 mapped = _ESCAPE_MAP.get(c) if mapped is not None: parts.append(mapped) continue if c == 'x' and i + 1 < length: hexstr = text[i:i + 2] if len(hexstr) == 2 and all( h in '0123456789abcdefABCDEF' for h in hexstr ): parts.append(chr(int(hexstr, 16))) i += 2 continue parts.append('x') continue if c == 'u': if i < length and text[i] == '{': end = text.find('}', i + 1) if end != -1: hexstr = text[i + 1:end] if hexstr and all( h in '0123456789abcdefABCDEF' for h in hexstr ): parts.append(chr(int(hexstr, 16))) i = end + 1 continue i = end + 1 parts.append('u') continue elif i + 3 < length: hexstr = text[i:i + 4] if len(hexstr) == 4 and all( h in '0123456789abcdefABCDEF' for h in hexstr ): parts.append(chr(int(hexstr, 16))) i += 4 continue parts.append('u') continue if c in '\r\n': if c == '\r' and i < length and text[i] == '\n': i += 1 continue parts.append(c) return ''.join(parts) def _parse_template_literal(self) -> JsTemplateLiteral: offset = self._current.offset quasis: list[JsTemplateElement] = [] expressions: list[Expression] = [] if self._at(JsTokenKind.TEMPLATE_FULL): tok = self._advance() raw = tok.value value = raw[1:-1] quasis.append(JsTemplateElement( value=value, raw=raw, tail=True, offset=tok.offset)) return JsTemplateLiteral( quasis=quasis, expressions=expressions, offset=offset) tok = self._advance() raw = tok.value value = raw[1:-2] quasis.append(JsTemplateElement( value=value, raw=raw, tail=False, offset=tok.offset)) while True: expr = self._parse_expression() expressions.append(expr) if self._at(JsTokenKind.TEMPLATE_TAIL): tok = self._advance() raw = tok.value value = raw[1:-1] quasis.append(JsTemplateElement( value=value, raw=raw, tail=True, offset=tok.offset)) break elif self._at(JsTokenKind.TEMPLATE_MIDDLE): tok = self._advance() raw = tok.value value = raw[1:-2] quasis.append(JsTemplateElement( value=value, raw=raw, tail=False, offset=tok.offset)) else: quasis.append(JsTemplateElement( value='', raw='', tail=True, offset=self._current.offset)) break return JsTemplateLiteral( quasis=quasis, expressions=expressions, offset=offset) def _parse_array_literal(self) -> JsArrayExpression: saved_no_in = self._no_in self._no_in = False offset = self._current.offset self._expect(JsTokenKind.LBRACKET) elements: list[Expression | None] = [] while not self._at(JsTokenKind.RBRACKET, JsTokenKind.EOF): if self._at(JsTokenKind.COMMA): elements.append(None) self._advance() continue if self._at(JsTokenKind.ELLIPSIS): so = self._current.offset self._advance() arg = self._parse_assignment_expression() elements.append(JsSpreadElement(argument=arg, offset=so)) else: elements.append(self._parse_assignment_expression()) if not self._at(JsTokenKind.RBRACKET): self._eat(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACKET) self._no_in = saved_no_in return JsArrayExpression(elements=elements, offset=offset) def _parse_object_literal(self) -> JsObjectExpression: saved_no_in = self._no_in self._no_in = False offset = self._current.offset self._expect(JsTokenKind.LBRACE) properties: list[JsProperty | JsSpreadElement] = [] while not self._at(JsTokenKind.RBRACE, JsTokenKind.EOF): if self._at(JsTokenKind.ELLIPSIS): so = self._current.offset self._advance() arg = self._parse_assignment_expression() properties.append(JsSpreadElement(argument=arg, offset=so)) else: properties.append(self._parse_object_property()) if not self._at(JsTokenKind.RBRACE): self._eat(JsTokenKind.COMMA) self._expect(JsTokenKind.RBRACE) self._no_in = saved_no_in return JsObjectExpression(properties=properties, offset=offset) def _parse_object_property(self) -> JsProperty: offset = self._current.offset is_generator = bool(self._eat(JsTokenKind.STAR)) if self._at(JsTokenKind.IDENTIFIER) and self._current.value in ('get', 'set'): kind_val = self._current.value saved = self._current self._advance() if self._at( JsTokenKind.LPAREN, JsTokenKind.COLON, JsTokenKind.COMMA, JsTokenKind.RBRACE, JsTokenKind.EQUALS ): key = JsIdentifier(name=kind_val, offset=saved.offset) return self._finish_property_value(key, False, False, offset) key = self._parse_property_name_from_current() return self._make_method_property(key, kind_val, False, offset) if self._at(JsTokenKind.ASYNC) and not is_generator: saved = self._current self._advance() if self._preceded_by_newline or self._at( JsTokenKind.COLON, JsTokenKind.COMMA, JsTokenKind.RBRACE, JsTokenKind.EQUALS, JsTokenKind.LPAREN, ): if self._at(JsTokenKind.LPAREN): key = JsIdentifier(name='async', offset=saved.offset) return self._make_method_property(key, 'init', False, offset) key = JsIdentifier(name='async', offset=saved.offset) return self._finish_property_value(key, False, False, offset) gen = bool(self._eat(JsTokenKind.STAR)) key = self._parse_property_name_from_current() return self._make_method_property(key, 'init', gen, offset, is_async=True) computed = False if self._at(JsTokenKind.LBRACKET): computed = True self._advance() key = self._parse_assignment_expression() self._expect(JsTokenKind.RBRACKET) else: key = self._parse_property_name() if is_generator or self._at(JsTokenKind.LPAREN): return self._make_method_property(key, 'init', is_generator, offset, computed=computed) return self._finish_property_value(key, computed, False, offset) def _finish_property_value( self, key: Expression, computed: bool, is_generator: bool, offset: int, ) -> JsProperty: if self._eat(JsTokenKind.COLON): value = self._parse_assignment_expression() return JsProperty( key=key, value=value, computed=computed, shorthand=False, offset=offset) return JsProperty( key=key, value=key, computed=computed, shorthand=True, offset=offset) def _make_method_property( self, key: Expression, kind: str, is_generator: bool, offset: int, computed: bool = False, is_async: bool = False, ) -> JsProperty: func_offset = self._current.offset params = self._parse_formal_parameters() body = self._parse_block_statement() value = JsFunctionExpression( params=params, body=body, generator=is_generator, is_async=is_async, offset=func_offset) return JsProperty( key=key, value=value, computed=computed, shorthand=False, method=True, kind=kind, offset=offset) def _parse_property_name(self) -> Expression: tok = self._current if self._at(JsTokenKind.INTEGER, JsTokenKind.FLOAT): self._advance() raw = tok.value text = raw.replace('_', '') return JsNumericLiteral( value=float(text) if tok.kind == JsTokenKind.FLOAT else int(text), raw=raw, offset=tok.offset, ) if self._at(JsTokenKind.STRING_SINGLE, JsTokenKind.STRING_DOUBLE): return self._parse_string_literal() self._advance() return JsIdentifier(name=tok.value, offset=tok.offset) def _parse_property_name_from_current(self) -> Expression: return self._parse_property_name() def _parse_paren_or_arrow(self) -> Expression: saved_no_in = self._no_in self._no_in = False try: offset = self._current.offset self._expect(JsTokenKind.LPAREN) if self._at(JsTokenKind.RPAREN): self._advance() self._expect(JsTokenKind.ARROW) body = self._parse_arrow_body() return JsArrowFunctionExpression(params=[], body=body, offset=offset) if self._at(JsTokenKind.ELLIPSIS): params = self._parse_arrow_params_rest() self._expect(JsTokenKind.RPAREN) self._expect(JsTokenKind.ARROW) body = self._parse_arrow_body() return JsArrowFunctionExpression(params=params, body=body, offset=offset) expr = self._parse_expression() if self._at(JsTokenKind.RPAREN): self._advance() if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline: self._advance() params = self._expr_to_params(expr) body = self._parse_arrow_body() return JsArrowFunctionExpression( params=params, body=body, offset=offset) return JsParenthesizedExpression(expression=expr, offset=offset) self._expect(JsTokenKind.RPAREN) return JsParenthesizedExpression(expression=expr, offset=offset) finally: self._no_in = saved_no_in def _parse_arrow_params_rest(self) -> list[Expression]: params: list[Expression] = [] while self._at(JsTokenKind.ELLIPSIS): params.append(self._parse_rest_element()) if self._at(JsTokenKind.COMMA): self._advance() return params def _parse_arrow_body(self) -> Expression | JsBlockStatement: if self._at(JsTokenKind.LBRACE): return self._parse_block_statement() return self._parse_assignment_expression() def _expr_to_params(self, expr: Expression) -> list[Expression]: if isinstance(expr, JsSequenceExpression): return [self._to_param(e) for e in expr.expressions] return [self._to_param(expr)] def _to_param(self, expr: Expression) -> Expression: if isinstance(expr, JsIdentifier): return expr if isinstance(expr, JsAssignmentExpression) and expr.operator == '=': return JsAssignmentPattern( left=self._to_param(expr.left), right=expr.right, offset=expr.offset, ) if isinstance(expr, JsSpreadElement): return JsRestElement(argument=self._to_param(expr.argument), offset=expr.offset) if isinstance(expr, JsArrayExpression): elements = [ self._to_param(e) if e is not None else None for e in expr.elements ] return JsArrayPattern(elements=elements, offset=expr.offset) if isinstance(expr, JsObjectExpression): props: list[JsProperty | JsRestElement] = [] for p in expr.properties: if isinstance(p, JsSpreadElement): props.append(JsRestElement( argument=self._to_param(p.argument), offset=p.offset)) else: props.append(p) return JsObjectPattern(properties=props, offset=expr.offset) return expr def _to_pattern(self, expr: Expression) -> Expression: if isinstance(expr, JsArrayExpression): elements = [ self._to_pattern(e) if e is not None else None for e in expr.elements ] return JsArrayPattern(elements=elements, offset=expr.offset) if isinstance(expr, JsObjectExpression): props: list[JsProperty | JsRestElement] = [] for p in expr.properties: if isinstance(p, JsSpreadElement): props.append(JsRestElement( argument=self._to_pattern(p.argument), offset=p.offset)) else: props.append(p) return JsObjectPattern(properties=props, offset=expr.offset) return expr def _parse_function_expression(self) -> JsFunctionExpression: offset = self._current.offset self._expect(JsTokenKind.FUNCTION) generator = bool(self._eat(JsTokenKind.STAR)) id_node = None if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() id_node = JsIdentifier(name=tok.value, offset=tok.offset) params = self._parse_formal_parameters() body = self._parse_block_statement() return JsFunctionExpression( id=id_node, params=params, body=body, generator=generator, offset=offset) def _parse_class_expression(self) -> JsClassExpression: offset = self._current.offset self._expect(JsTokenKind.CLASS) id_node = None if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() id_node = JsIdentifier(name=tok.value, offset=tok.offset) super_class = None if self._eat(JsTokenKind.EXTENDS): super_class = self._parse_assignment_expression() body = self._parse_class_body() return JsClassExpression( id=id_node, super_class=super_class, body=body, offset=offset) def _parse_async_expression(self) -> Expression: offset = self._current.offset self._advance() return self._parse_expression_starting_with_async(offset) def _parse_expression_starting_with_async(self, offset: int) -> Expression: if self._at(JsTokenKind.FUNCTION) and not self._preceded_by_newline: self._advance() generator = bool(self._eat(JsTokenKind.STAR)) id_node = None if self._at(JsTokenKind.IDENTIFIER): tok = self._advance() id_node = JsIdentifier(name=tok.value, offset=tok.offset) params = self._parse_formal_parameters() body = self._parse_block_statement() return JsFunctionExpression( id=id_node, params=params, body=body, generator=generator, is_async=True, offset=offset) if self._at(JsTokenKind.IDENTIFIER) and not self._preceded_by_newline: tok = self._advance() if self._at(JsTokenKind.ARROW) and not self._preceded_by_newline: self._advance() param = JsIdentifier(name=tok.value, offset=tok.offset) body = self._parse_arrow_body() return JsArrowFunctionExpression( params=[param], body=body, is_async=True, offset=offset) return JsIdentifier(name=tok.value, offset=tok.offset) if self._at(JsTokenKind.LPAREN) and not self._preceded_by_newline: paren_result = self._parse_paren_or_arrow() if isinstance(paren_result, JsArrowFunctionExpression): paren_result.is_async = True paren_result.offset = offset return paren_result return JsIdentifier(name='async', offset=offset) def _parse_yield_expression(self) -> JsYieldExpression: offset = self._current.offset self._advance() delegate = False if self._eat(JsTokenKind.STAR): delegate = True argument = None if not self._preceded_by_newline and not self._at( JsTokenKind.SEMICOLON, JsTokenKind.RBRACE, JsTokenKind.RPAREN, JsTokenKind.RBRACKET, JsTokenKind.COMMA, JsTokenKind.COLON, JsTokenKind.EOF, ): argument = self._parse_assignment_expression() return JsYieldExpression( argument=argument, delegate=delegate, offset=offset)Methods
def parse(self)-
Expand source code Browse git
def parse(self) -> JsScript: return self._parse_program()