"""Parse tree listeners. Basically, you should use ASTListener(source_type: SourceTypeLiteral) in most cases. Todo: * Fill `source` field in SourceLocation and pass it to each `_get_source_location()` call. * Compare `SourceLocation` creation behavior with the one in Acorn/ESPrima """ import logging from typing import Optional, List, Union import antlr4.ParserRuleContext from antlr4 import ErrorNode, ParserRuleContext from ..lex.JavaScriptParser import JavaScriptParser from ..lex.JavaScriptParserListener import JavaScriptParserListener as JSBaseListener from . import nodes def _get_source_location( ctx: antlr4.ParserRuleContext, source: Optional[str] ) -> nodes.SourceLocation: """Internal function to obtain `SourceObject` from parser context.""" start_pos = nodes.Position(ctx.start.line, ctx.start.column) end_pos = nodes.Position(ctx.stop.line, ctx.stop.column) # If an end is not on a newline, shift end position column by 1 # to match exact token end, not the last character if end_pos.column != 0: end_pos.column += 1 return nodes.SourceLocation(source=source, start=start_pos, end=end_pos) class NodeListener(JSBaseListener): def visitErrorNode(self, node: ErrorNode): pass def enterEveryRule(self, ctx: ParserRuleContext): pass class LiteralListener(JSBaseListener): _literal: nodes.Literal @property def literal(self): return self._literal def enterLiteral(self, ctx: JavaScriptParser.LiteralContext): loc = _get_source_location(ctx, None) if ctx.NullLiteral() is not None: self._literal = nodes.NullLiteral(loc) elif ctx.BooleanLiteral() is not None: value = ctx.BooleanLiteral().getText() == "true" self._literal = nodes.BooleanLiteral(loc, value) elif ctx.StringLiteral() is not None: self._literal = nodes.StringLiteral(loc, ctx.StringLiteral().getText()) else: ctx.getChild(0).enterRule(self) def enterNumericLiteral(self, ctx: JavaScriptParser.NumericLiteralContext): # Thank you, PEP-515, very cool! loc = _get_source_location(ctx, None) value = float(ctx.DecimalLiteral().getText()) self._literal = nodes.NumericLiteral(loc, value) def enterBigintLiteral(self, ctx: JavaScriptParser.BigintLiteralContext): raise NotImplementedError("Bigint literals") class ExpressionListener(JSBaseListener): _expr: nodes.Expression @property def expression(self): return self._expr def enterExpressionStatement( self, ctx: JavaScriptParser.ExpressionStatementContext ): ctx.expressionSequence().enterRule(self) def enterExpressionSequence(self, ctx: JavaScriptParser.ExpressionSequenceContext): expressions: List[nodes.Expression] = [] loc = _get_source_location(ctx, None) for expr in ctx.singleExpression(): expr_listener = ExpressionListener() expr.enterRule(expr_listener) expressions.append(expr_listener.expression) self._expr = nodes.SequenceExpression(loc, expressions) def enterParenthesizedExpression( self, ctx: JavaScriptParser.ParenthesizedExpressionContext ): ctx.expressionSequence().enterRule(self) def enterLiteralExpression(self, ctx: JavaScriptParser.LiteralExpressionContext): literal_listener = LiteralListener() ctx.literal().enterRule(literal_listener) self._expr = literal_listener.literal # TODO single expression class AssignableListener(NodeListener): _result: Union[nodes.Identifier, nodes.ObjectPattern, nodes.ArrayPattern] @property def result(self): return self._result def enterAssignable(self, ctx: JavaScriptParser.AssignableContext): logging.debug("Entered section Assignable") ctx.getChild(0).enterRule(self) def enterIdentifier(self, ctx: JavaScriptParser.IdentifierContext): logging.debug("Entered section Identifier") loc = _get_source_location(ctx, None) self._result = nodes.Identifier(loc, ctx.getText()) def enterArrayLiteral(self, ctx: JavaScriptParser.ArrayLiteralContext): logging.debug("Entered section ArrayLiteral") raise NotImplementedError("ArrayLiteral assignment") # TODO def enterObjectLiteral(self, ctx: JavaScriptParser.ObjectLiteralContext): logging.debug("Entered section ObjectLiteral") raise NotImplementedError("ObjectLiteral assignment") # TODO class VariableDeclarationListener(JSBaseListener): _var_decl: nodes.VariableDeclarator @property def var_declarator(self): return self._var_decl def enterVariableDeclaration( self, ctx: JavaScriptParser.VariableDeclarationContext ): loc = _get_source_location(ctx, None) assign_listener = AssignableListener() ctx.assignable().enterRule(assign_listener) init = None # or value from ExpressionListener if ctx.singleExpression() is not None: expression_listener = ExpressionListener() ctx.singleExpression().enterRule(expression_listener) init = expression_listener.expression self._var_decl = nodes.VariableDeclarator(loc, assign_listener.result, init) class StatementListener(JSBaseListener): _stmt: nodes.Statement @property def statement(self) -> nodes.Statement: """Statement AST node generated after parse tree walking.""" return self._stmt def __init__(self, in_loop: bool = False, in_func: bool = False): """ Statement listener. Generates a Statement. Args: in_loop (bool): allow `continue` and `break` statements in_func (bool): allow `return` statement """ self._in_loop = in_loop self._in_func = in_func def enterStatement(self, ctx: JavaScriptParser.StatementContext): """Obtain an actual statement.""" logging.debug("Entered section Statement") ctx.getChild(0).enterRule(self) def enterBlock(self, ctx: JavaScriptParser.BlockContext): """Listener for BlockStatement.""" logging.debug("Entered section Block") stmt_list: List[nodes.Statement] = [] for stmt in ctx.statementList().children: stmt_listener = StatementListener() stmt.enterRule(stmt_listener) stmt_list.append(stmt_listener.statement) loc = _get_source_location(ctx, None) self._stmt = nodes.BlockStatement(loc, stmt_list) def enterVariableStatement(self, ctx: JavaScriptParser.VariableStatementContext): logging.debug("Entered section VariableStatement") ctx.variableDeclarationList().enterRule(self) def enterVariableDeclarationList( self, ctx: JavaScriptParser.VariableDeclarationListContext ): """Listener for VariableDeclaration.""" logging.debug("Entered section VariableDeclaration") var_modifier: nodes.VarDeclKind = ctx.varModifier().getText() var_decls: List[nodes.VariableDeclarator] = [] for var_decl in ctx.variableDeclaration(): var_decl_listener = VariableDeclarationListener() var_decl.enterRule(var_decl_listener) var_decls.append(var_decl_listener.var_declarator) loc = _get_source_location(ctx, None) self._stmt = nodes.VariableDeclaration(loc, var_modifier, var_decls) def enterEmptyStatement(self, ctx: JavaScriptParser.EmptyStatementContext): """Listener for EmptyStatement.""" logging.debug("Entered section EmptyStatement") loc = _get_source_location(ctx, None) self._stmt = nodes.EmptyStatement(loc) pass def enterExpressionStatement( self, ctx: JavaScriptParser.ExpressionStatementContext ): """Listener for ExpressionStatement.""" logging.debug("Entered section ExpressionStatement") expr_listener = ExpressionListener() ctx.expressionSequence().enterRule(expr_listener) loc = _get_source_location(ctx, None) self._stmt = nodes.ExpressionStatement(loc, expr_listener.expression) def enterIfStatement(self, ctx: JavaScriptParser.IfStatementContext): """Listener for IfStatement.""" logging.debug("Entered section IfStatement") raise NotImplementedError("IfStatement") def enterFunctionDeclaration( self, ctx: JavaScriptParser.FunctionDeclarationContext ): """Listener for FunctionDeclaration.""" logging.debug("Entered section FunctionDeclaration") raise NotImplementedError("FunctionDeclaration") def enterDoStatement(self, ctx: JavaScriptParser.DoStatementContext): """Listener for DoStatement (do-while).""" raise NotImplementedError("DoWhileStatement") def enterWhileStatement(self, ctx: JavaScriptParser.WhileStatementContext): """Listener for WhileStatement.""" logging.debug("Entered section WhileStatement") raise NotImplementedError("WhileStatement") def enterForStatement(self, ctx: JavaScriptParser.ForStatementContext): """Listener for ForStatement.""" logging.debug("Entered section ForStatement") raise NotImplementedError("ForStatement") def enterForInStatement(self, ctx: JavaScriptParser.ForInStatementContext): """Listener for ForInStatement.""" logging.debug("Entered section ForInStatement") raise NotImplementedError("ForInStatement") def enterContinueStatement(self, ctx: JavaScriptParser.ContinueStatementContext): logging.debug("Entered section ContinueStatement") raise NotImplementedError("ContinueStatement") def enterBreakStatement(self, ctx: JavaScriptParser.BreakStatementContext): logging.debug("Entered BreakStatement") raise NotImplementedError("BreakStatement") def enterReturnStatement(self, ctx: JavaScriptParser.ReturnStatementContext): logging.debug("Entered ReturnStatement") raise NotImplementedError("ReturnStatement") def enterImportStatement(self, ctx: JavaScriptParser.ImportStatementContext): logging.debug("Entered ImportStatement") raise NotImplementedError("ImportStatement") def enterExportDeclaration(self, ctx: JavaScriptParser.ExportDeclarationContext): logging.debug("Entered ExportDeclaration") raise NotImplementedError("ExportDeclaration") def enterExportDefaultDeclaration( self, ctx: JavaScriptParser.ExportDefaultDeclarationContext ): logging.debug("Entered ExportDefaultDeclaration") raise NotImplementedError("ExportDefaultDeclaration") def enterClassDeclaration(self, ctx: JavaScriptParser.ClassDeclarationContext): logging.debug("Entered ClassDeclaration") raise NotImplementedError("ClassDeclaration") class SourceElementListener(JSBaseListener): """The proxy between Program and Statement.""" _elems: List[nodes.Statement] = [] @property def source_elements(self) -> List[nodes.Statement]: """Source elements AST nodes generated after parse tree walking.""" return self._elems def enterSourceElement(self, ctx: JavaScriptParser.SourceElementContext): logging.debug("Entered section Source Element") stmt_listener = StatementListener() stmt = ctx.statement() stmt.enterRule(stmt_listener) self._elems.append(stmt_listener.statement) class ASTListener(JSBaseListener): """AST listener.""" _program_node: Optional[nodes.Program] = None _source_type: nodes.SourceTypeLiteral @property def program_node(self) -> nodes.Program: """The `Program` AST node generated after parse tree walking.""" if self._program_node is None: raise ValueError("Program AST node is None, did you run the listener?") return self._program_node def __init__(self, source_type: nodes.SourceTypeLiteral = "script"): """AST listener constructor. Args: source_type (nodes.SourceTypeLiteral): source type. Could be `script` or `module`. Set to `script` by default. """ self._source_type = source_type def enterProgram(self, ctx: JavaScriptParser.ProgramContext): logging.debug("Entered section Program") logging.debug("JS source type: %s", self._source_type) hashbang = ctx.HashBangLine() if hashbang is not None: hashbang_exec = hashbang.getText()[2:] logging.debug('Found a hashbang "%s"', hashbang_exec) # TODO treat it somehow source_elem_listener = SourceElementListener() for elem in ctx.sourceElements().children: elem.enterRule(source_elem_listener) loc = _get_source_location(ctx, None) # FIXME add source name self._program_node = nodes.Program( loc, self._source_type, source_elem_listener.source_elements )