Compare commits

...

12 Commits

Author SHA1 Message Date
Yury Kurlykov c504830928
Add ES2020 nodes 2020-04-28 18:41:51 +10:00
Yury Kurlykov 85aa1b2a34
Add ES2018 nodes 2020-04-28 16:55:26 +10:00
Yury Kurlykov 245bae4175
Make Property ES6+ compliant 2020-04-28 16:51:14 +10:00
Yury Kurlykov 74d53ddf34
Add ES2016 nodes
A whole standard just for power operator? ECMA must be kidding.
2020-04-28 16:05:37 +10:00
Yury Kurlykov 7a1eca4aca
Add ES2015 nodes 2020-04-28 15:54:16 +10:00
Yury Kurlykov b4bbc8460e
Raise NotImplementedError on WIP nodes 2020-04-28 15:53:28 +10:00
Yury Kurlykov ace635c999
Implement EmptyStatement node generation
I don't even know what does the "procrastination" word mean. `EmptyStatement` is the most useful node ever.
2020-04-28 15:52:35 +10:00
Yury Kurlykov 5266ebed21
Add .gitignore to repo
It was there since day one, but I forgot to exclude it from the gitignore itself. Well, it happens.
2020-04-28 12:04:52 +10:00
Yury Kurlykov afe6c8d244
Add __init__.py in lex module
First of all, it has a docstring.
2020-04-28 12:01:35 +10:00
Yury Kurlykov ea5b03d93e
Fix import statements
I just found out my project couldn't run anywhere but IDE.
2020-04-28 12:00:06 +10:00
Yury Kurlykov 42b4e40d9e
Add `name` field in Identifier 2020-04-28 11:20:15 +10:00
Yury Kurlykov 6372a043a2
Fix ImportError
It is not the best idea to name a module just like the native one.
2020-04-28 11:19:40 +10:00
6 changed files with 432 additions and 66 deletions

View File

@ -10,7 +10,7 @@ import coloredlogs
from jasminesnake import __version__, __snake__, LOG_LEVELS from jasminesnake import __version__, __snake__, LOG_LEVELS
from .js_stream import JSBaseStream, JSStringStream, JSFileStream from .js_stream import JSBaseStream, JSStringStream, JSFileStream
from .lex.ErrorListeners import LogErrorListener from .lex.ErrorListeners import LogErrorListener
from ast import nodes, to_ascii_tree, from_parse_tree from .ast import to_ascii_tree, from_parse_tree
def create_argument_parser(): def create_argument_parser():

View File

@ -4,14 +4,14 @@ from enum import Enum
from typing import Union from typing import Union
from antlr4 import ParseTreeWalker from antlr4 import ParseTreeWalker
import lex.JavaScriptParser as Parser import jasminesnake.lex.JavaScriptParser as Parser
import ast.nodes from . import nodes
from .parse_tree_listeners import ASTListener from .parse_tree_listeners import ASTListener
JSP = Parser.JavaScriptParser JSP = Parser.JavaScriptParser
def from_parse_tree(tree: JSP.ProgramContext) -> ast.nodes.Program: def from_parse_tree(tree: JSP.ProgramContext) -> nodes.Program:
"""Generate AST from ANTLR parse tree. """Generate AST from ANTLR parse tree.
Args: Args:
@ -26,7 +26,7 @@ def from_parse_tree(tree: JSP.ProgramContext) -> ast.nodes.Program:
def to_ascii_tree( def to_ascii_tree(
node: Union[ast.nodes.Position, ast.nodes.SourceLocation, ast.nodes.Node], node: Union[nodes.Position, nodes.SourceLocation, nodes.Node],
name_prefix: str = "", name_prefix: str = "",
nesting_lvl: int = 0, nesting_lvl: int = 0,
): ):

View File

@ -8,20 +8,29 @@ The module lacks support of:
* debugger statement * debugger statement
* with statement * with statement
* RegExp * RegExp
* ES6 features: * ES2015 features:
* generators/yield statement * generators/yield statement
* for-of statement * for-of statement
* template literals * template literals
* and other ES6 features :) * ES2017 features:
* async/await.
Basically, the whole standard consists of it, so no ES2017 support.
* ES2018 features:
* for-await-of statement
* template literals
* ES2019 features
* catch binding omission.
The only ES2019 feature.
More about ESTree standard: More about ESTree standard:
https://github.com/estree/estree/ https://github.com/estree/estree/
Todo: Todo:
* Add support for lacking features * Add support for lacking features
* Make another attempt to split up this module
""" """
from typing import List, Union, Optional, Literal as TypeLiteral, TypedDict, Any from typing import List, Union, Optional, Literal as TypeLiteral, Any
from enum import Enum from enum import Enum
from collections import OrderedDict from collections import OrderedDict
@ -30,8 +39,11 @@ from collections import OrderedDict
# Custom types used in the nodes # Custom types used in the nodes
number = Union[int, float] number = float
"""A type union consisting of int and float Python types. Consider it as Number type from JavaScript.""" """A type representing Number type in JavaScript."""
bigint = int
"""A type representing BigInt type in JavaScript."""
SourceTypeLiteral = TypeLiteral["script", "module"] SourceTypeLiteral = TypeLiteral["script", "module"]
"""The type for the `sourceType` field.""" """The type for the `sourceType` field."""
@ -42,6 +54,9 @@ VarDeclKind = TypeLiteral["var", "let", "const"]
PropKind = TypeLiteral["init", "get", "set"] PropKind = TypeLiteral["init", "get", "set"]
"""A type for a `kind` field of `Property`.""" """A type for a `kind` field of `Property`."""
MethodDefinitionKind = TypeLiteral["constructor", "method", "get", "set"]
"""A type for a `kind` field of `MethodDefinition`."""
class UnaryOperator(Enum): class UnaryOperator(Enum):
"""A unary operator token.""" """A unary operator token."""
@ -86,6 +101,7 @@ class BinaryOperator(Enum):
AND = "&" AND = "&"
IN = "in" IN = "in"
INSTANCEOF = "instanceof" INSTANCEOF = "instanceof"
POW = "**"
class AssignmentOperator(Enum): class AssignmentOperator(Enum):
@ -103,6 +119,7 @@ class AssignmentOperator(Enum):
OR = "|=" OR = "|="
XOR = "^=" XOR = "^="
AND = "&=" AND = "&="
POW = "**="
class LogicalOperator(Enum): class LogicalOperator(Enum):
@ -110,6 +127,7 @@ class LogicalOperator(Enum):
OR = "||" OR = "||"
AND = "&&" AND = "&&"
NULLISH_COALESCING = "??"
# Nodes forward declarations # Nodes forward declarations
@ -145,6 +163,10 @@ class Identifier:
... ...
class Literal:
...
# "Node objects" block # "Node objects" block
@ -218,20 +240,6 @@ class Node:
return self._fields return self._fields
# "Literal" block
class Literal(Expression):
"""A literal token. Note that a literal can be an expression."""
def __init__(
self, loc: Optional[SourceLocation], value: Union[str, bool, number, None]
):
super().__init__("Literal", loc)
self.value = value
self._fields.update({"value": self.value})
# "Programs" block # "Programs" block
@ -559,7 +567,11 @@ class ArrayExpression(Expression):
class ObjectExpression(Expression): class ObjectExpression(Expression):
"""An object expression.""" """An object expression."""
def __init__(self, loc: Optional[SourceLocation], properties: List[Property]): def __init__(
self,
loc: Optional[SourceLocation],
properties: List[Union[Property, SpreadElement]],
):
super().__init__("ObjectExpression", loc) super().__init__("ObjectExpression", loc)
self.properties = properties self.properties = properties
self._fields.update({"properties": self.properties}) self._fields.update({"properties": self.properties})
@ -954,6 +966,9 @@ InExpression = _generate_binary_expression(BinaryOperator.IN, """An "in" express
InstanceofExpression = _generate_binary_expression( InstanceofExpression = _generate_binary_expression(
BinaryOperator.INSTANCEOF, """An "instanceof" expression.""" BinaryOperator.INSTANCEOF, """An "instanceof" expression."""
) )
PowBinaryExpression = _generate_binary_expression(
BinaryOperator.POW, """A power expression, e.g. ``2**3``."""
)
SimpleAssignExpression = _generate_assignment_expression( SimpleAssignExpression = _generate_assignment_expression(
AssignmentOperator.ASSIGN, """An assignment done with operator ``=`` expression.""" AssignmentOperator.ASSIGN, """An assignment done with operator ``=`` expression."""
) )
@ -997,12 +1012,51 @@ AndAssignExpression = _generate_assignment_expression(
AssignmentOperator.AND, AssignmentOperator.AND,
"""A "bit and" assignment done with operator ``&=`` expression.""", """A "bit and" assignment done with operator ``&=`` expression.""",
) )
PowAssignExpression = _generate_assignment_expression(
AssignmentOperator.POW, """A power assignment expression, e.g. ``x**=2``."""
)
OrLogicExpression = _generate_logical_expression( OrLogicExpression = _generate_logical_expression(
LogicalOperator.OR, """An "or" logical expression.""" LogicalOperator.OR, """An "or" logical expression."""
) )
AndLogicExpression = _generate_logical_expression( AndLogicExpression = _generate_logical_expression(
LogicalOperator.AND, """An "and" logical expression.""" LogicalOperator.AND, """An "and" logical expression."""
) )
NullishCoalescingLogicExpression = _generate_logical_expression(
LogicalOperator.NULLISH_COALESCING, """A nullish coalescing logical expression."""
)
# "Literal" block
class Literal(Expression):
"""A literal token. Note that a literal can be an expression."""
def __init__(
self,
loc: Optional[SourceLocation],
value: Union[str, bool, number, bigint, None],
):
super().__init__("Literal", loc)
self.value = value
self._fields.update({"value": self.value})
class BigIntLiteral(Literal):
"""`bigint` property is the string representation of the ``BigInt`` value. It doesn't include the suffix ``n``.
In environments that don't support ``BigInt`` values, value property will be `None` as the ``BigInt`` value can't
be represented natively.
"""
def __init__(
self,
loc: Optional[SourceLocation],
value: Union[str, bool, number, bigint, None],
bigint: str,
):
super().__init__(loc, value)
self.bigint = bigint
self._fields.update({"bigint": self.bigint})
# "Property" block # "Property" block
@ -1016,7 +1070,7 @@ class Property(Node):
def __init__( def __init__(
self, self,
loc: Optional[SourceLocation], loc: Optional[SourceLocation],
key: Union[Literal, Identifier], key: Expression,
value: Expression, value: Expression,
kind: PropKind, kind: PropKind,
method: bool, method: bool,
@ -1046,7 +1100,7 @@ class AssignmentProperty(Property):
def __init__( def __init__(
self, self,
loc: Optional[SourceLocation], loc: Optional[SourceLocation],
key: Union[Literal, Identifier], key: Expression,
value: Pattern, value: Pattern,
shorthand: bool, shorthand: bool,
computed: bool, computed: bool,
@ -1067,14 +1121,18 @@ class Pattern(Node):
super().__init__(node_type, loc) super().__init__(node_type, loc)
class ObjectPatternKeyValue(TypedDict): class RestElement(Pattern):
key: Union[Literal, Identifier] def __init__(self, loc: Optional[SourceLocation], argument: Pattern):
value: Pattern super().__init__("RestElement", loc)
self.argument = argument
self._fields.update({"argument": self.argument})
class ObjectPattern(Pattern): class ObjectPattern(Pattern):
def __init__( def __init__(
self, loc: Optional[SourceLocation], properties: List[ObjectPatternKeyValue] self,
loc: Optional[SourceLocation],
properties: List[Union[AssignmentProperty, RestElement]],
): ):
super().__init__("ObjectPattern", loc) super().__init__("ObjectPattern", loc)
self.properties = properties self.properties = properties
@ -1090,6 +1148,14 @@ class ArrayPattern(Pattern):
self._fields.update({"elements": self.elements}) self._fields.update({"elements": self.elements})
class AssignmentPattern(Pattern):
def __init__(self, loc: Optional[SourceLocation], left: Pattern, right: Expression):
super().__init__("AssignmentPattern", loc)
self.left = left
self.right = right
self._fields.update({"left": self.left, "right": self.right})
# "Identifier" block # "Identifier" block
@ -1099,3 +1165,281 @@ class Identifier(Expression, Pattern):
def __init__(self, loc: Optional[SourceLocation], name: str): def __init__(self, loc: Optional[SourceLocation], name: str):
super().__init__("Identifier", loc) super().__init__("Identifier", loc)
self.name = name self.name = name
self._fields.update({"name": self.name})
# "Classes" block
class MethodDefinition(Node):
def __init__(
self,
loc: Optional[SourceLocation],
key: Expression,
value: FunctionExpression,
kind: MethodDefinitionKind,
computed: bool,
static: bool,
):
super().__init__("MethodDefinition", loc)
self.key = key
self.value = value
self.kind = kind
self.computed = computed
self.static = static
self._fields.update(
{
"key": self.key,
"value": self.value,
"kind": self.kind,
"computed": self.computed,
"static": self.static,
}
)
class ClassBody(Node):
def __init__(self, loc: Optional[SourceLocation], body: List[MethodDefinition]):
super().__init__("ClassBody", loc)
self.body = body
self._fields.update({"body": self.body})
class Class(Node):
def __init__(
self,
node_type: str,
loc: Optional[SourceLocation],
class_id: Optional[Identifier],
super_class: Optional[Expression],
body: ClassBody,
):
super().__init__(node_type, loc)
self.id = class_id
self.super_class = super_class
self.body = body
self._fields.update(
{"id": self.id, "superClass": self.super_class, "body": self.body}
)
class ClassDeclaration(Class, Declaration):
def __init__(
self,
loc: Optional[SourceLocation],
class_id: Identifier,
super_class: Optional[Expression],
body: ClassBody,
):
super().__init__("ClassDeclaration", loc, class_id, super_class, body)
class ClassExpression(Class, Expression):
def __init__(
self,
loc: Optional[SourceLocation],
class_id: Optional[Identifier],
super_class: Optional[Expression],
body: ClassBody,
):
super().__init__("ClassExpression", loc, class_id, super_class, body)
class MetaProperty(Expression):
"""`MetaProperty` node represents ``new.target`` meta property in ES2015.
In the future, it will represent other meta properties as well.
"""
def __init__(
self, loc: Optional[SourceLocation], meta: Identifier, meta_property: Identifier
):
super().__init__("MetaProperty", loc)
self.meta = (meta,)
self.property = meta_property
self._fields.update({"meta": self.meta, "property": self.property})
# "Modules" block
class ModuleDeclaration(Node):
"""A module ``import`` or ``export`` declaration."""
def __init__(self, node_type: str, loc: Optional[SourceLocation]):
super().__init__(node_type, loc)
class ModuleSpecifier(Node):
"""A specifier in an import or export declaration."""
def __init__(
self, node_type: str, loc: Optional[SourceLocation], local: Identifier
):
super().__init__(node_type, loc)
self.local = local
self._fields.update({"local": self.local})
class ImportSpecifier(ModuleSpecifier):
"""An imported variable binding, e.g., ``{foo}`` in ``import {foo} from "mod"``
or ``{foo as bar}`` in ``import {foo as bar} from "mod"``. The `imported` field
refers to the name of the export imported from the module. The `local` field
refers to the binding imported into the local module scope. If it is a basic named
import, such as in ``import {foo} from "mod"``, both `imported` and `local` are
equivalent `Identifier` nodes; in this case an `Identifier` node representing ``foo``.
If it is an aliased import, such as in ``import {foo as bar} from "mod"``, the
`imported` field is an `Identifier` node representing ``foo``, and the `local` field
is an `Identifier` node representing ``bar``.
"""
def __init__(
self, loc: Optional[SourceLocation], local: Identifier, imported: Identifier
):
super().__init__("ImportSpecifier", loc, local)
self.imported = imported
self._fields.update({"imported": self.imported})
class ImportDefaultSpecifier(ModuleSpecifier):
"""A default import specifier, e.g., ``foo`` in ``import foo from "mod.js"``."""
def __init__(self, loc: Optional[SourceLocation], local: Identifier):
super().__init__("ImportDefaultSpecifier", loc, local)
class ImportNamespaceSpecifier(ModuleSpecifier):
"""A namespace import specifier, e.g., ``* as foo`` in ``import * as foo from "mod.js"``."""
def __init__(self, loc: Optional[SourceLocation], local: Identifier):
super().__init__("ImportNamespaceSpecifier", loc, local)
class ImportDeclaration(ModuleDeclaration):
"""An import declaration, e.g., ``import foo from "mod";``."""
def __init__(
self,
loc: Optional[SourceLocation],
specifiers: List[
Union[ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier]
],
source: Literal,
):
super().__init__("ImportDeclaration", loc)
self.specifiers = specifiers
self.source = source
self._fields.update({"specifiers": self.specifiers, "source": self.source})
class ImportExpression(Expression):
"""`ImportExpression` node represents Dynamic Imports such as ``import(source)``.
The `source` property is the importing source as similar to ImportDeclaration node,
but it can be an arbitrary expression node.
"""
def __init__(self, loc: Optional[SourceLocation], source: Expression):
super().__init__("ImportExpression", loc)
self.source = source
self._fields.update({"source": self.source})
class ExportSpecifier(ModuleSpecifier):
"""An exported variable binding, e.g., ``{foo}`` in ``export {foo}`` or ``{bar as foo}``
in ``export {bar as foo}``. The `exported` field refers to the name exported in the module.
The `local` field refers to the binding into the local module scope. If it is a basic named
export, such as in ``export {foo}``, both `exported` and `local` are equivalent `Identifier`
nodes; in this case an `Identifier` node representing ``foo``. If it is an aliased export,
such as in ``export {bar as foo}``, the `exported` field is an `Identifier` node representing
``foo``, and the `local` field is an `Identifier` node representing ``bar``.
"""
def __init__(
self, loc: Optional[SourceLocation], local: Identifier, exported: Identifier
):
super().__init__("ExportSpecifier", loc, local)
self.exported = exported
self._fields.update({"exported": self.exported})
class ExportNamedDeclaration(ModuleDeclaration):
"""An export named declaration, e.g., ``export {foo, bar};``, ``export {foo} from "mod";``
or ``export var foo = 1;``.
Notes:
Having `declaration` populated with non-empty `specifiers` or non-null `source` results
in an invalid state.
"""
def __init__(
self,
loc: Optional[SourceLocation],
declaration: Optional[Declaration],
specifiers: List[ExportSpecifier],
source: Optional[Literal],
):
super().__init__("ExportNamedDeclaration", loc)
self.declaration = declaration
self.specifiers = specifiers
self.source = source
self._fields.update(
{
"declaration": self.declaration,
"specifiers": self.specifiers,
"source": self.source,
}
)
class AnonymousDefaultExportedFunctionDeclaration(Function):
def __init__(
self, loc: Optional[SourceLocation], params: List[Pattern], body: FunctionBody
):
super().__init__("FunctionDeclaration", loc, None, params, body)
class AnonymousDefaultExportedClassDeclaration(Class):
def __init__(
self,
loc: Optional[SourceLocation],
super_class: Optional[Expression],
body: ClassBody,
):
super().__init__("ClassDeclaration", loc, None, super_class, body)
class ExportDefaultDeclaration(ModuleDeclaration):
"""An export default declaration, e.g., ``export default function () {};`` or ``export default 1;``."""
def __init__(
self,
loc: Optional[SourceLocation],
declaration: Union[
AnonymousDefaultExportedFunctionDeclaration,
FunctionDeclaration,
AnonymousDefaultExportedClassDeclaration,
ClassDeclaration,
Expression,
],
):
super().__init__("ExportDefaultDeclaration", loc)
self.declaration = declaration
self._fields.update({"declaration": self.declaration})
class ExportAllDeclaration(ModuleDeclaration):
"""An export batch declaration, e.g., ``export * from "mod";``.
The `exported` property contains an `Identifier` when a different exported
name is specified using ``as``, e.g., ``export * as foo from "mod";``.
"""
def __init__(
self,
loc: Optional[SourceLocation],
source: Literal,
exported: Optional[Identifier],
):
super().__init__("ExportAllDeclaration", loc)
self.source = source
self.exported = exported
self._fields.update({"source": self.source, "exported": self.exported})

View File

@ -1,32 +1,38 @@
"""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 import logging
from typing import Optional, List, Union from typing import Optional, List, Union
import antlr4.ParserRuleContext import antlr4.ParserRuleContext
from lex.JavaScriptParser import JavaScriptParser from ..lex.JavaScriptParser import JavaScriptParser
from lex.JavaScriptParserListener import JavaScriptParserListener as JSBaseListener from ..lex.JavaScriptParserListener import JavaScriptParserListener as JSBaseListener
import ast.nodes from . import nodes
def _get_source_location( def _get_source_location(
ctx: antlr4.ParserRuleContext, source: Optional[str] ctx: antlr4.ParserRuleContext, source: Optional[str]
) -> ast.nodes.SourceLocation: ) -> nodes.SourceLocation:
"""Internal function to obtain `SourceObject` from parser context.""" """Internal function to obtain `SourceObject` from parser context."""
start_pos = ast.nodes.Position(ctx.start.line, ctx.start.column) start_pos = nodes.Position(ctx.start.line, ctx.start.column)
end_pos = ast.nodes.Position(ctx.stop.line, ctx.stop.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 # If an end is not on a newline, shift end position column by 1
# to match exact token end, not the last character # to match exact token end, not the last character
if end_pos.column != 0: if end_pos.column != 0:
end_pos.column += 1 end_pos.column += 1
return ast.nodes.SourceLocation(source=source, start=start_pos, end=end_pos) return nodes.SourceLocation(source=source, start=start_pos, end=end_pos)
class AssignableListener(JSBaseListener): class AssignableListener(JSBaseListener):
_result: Union[ _result: Union[nodes.Identifier, nodes.ObjectPattern, nodes.ArrayPattern]
ast.nodes.Identifier, ast.nodes.ObjectPattern, ast.nodes.ArrayPattern
]
@property @property
def result(self): def result(self):
@ -39,19 +45,19 @@ class AssignableListener(JSBaseListener):
def enterIdentifier(self, ctx: JavaScriptParser.IdentifierContext): def enterIdentifier(self, ctx: JavaScriptParser.IdentifierContext):
logging.debug("Entered section Identifier") logging.debug("Entered section Identifier")
loc = _get_source_location(ctx, None) loc = _get_source_location(ctx, None)
self._result = ast.nodes.Identifier(loc, ctx.getText()) self._result = nodes.Identifier(loc, ctx.getText())
def enterArrayLiteral(self, ctx: JavaScriptParser.ArrayLiteralContext): def enterArrayLiteral(self, ctx: JavaScriptParser.ArrayLiteralContext):
logging.debug("Entered section ArrayLiteral") logging.debug("Entered section ArrayLiteral")
pass # TODO raise NotImplementedError("ArrayLiteral assignment") # TODO
def enterObjectLiteral(self, ctx: JavaScriptParser.ObjectLiteralContext): def enterObjectLiteral(self, ctx: JavaScriptParser.ObjectLiteralContext):
logging.debug("Entered section ObjectLiteral") logging.debug("Entered section ObjectLiteral")
pass # TODO raise NotImplementedError("ObjectLiteral assignment") # TODO
class VariableDeclarationListener(JSBaseListener): class VariableDeclarationListener(JSBaseListener):
_var_decl: ast.nodes.VariableDeclarator _var_decl: nodes.VariableDeclarator
@property @property
def var_declarator(self): def var_declarator(self):
@ -63,17 +69,21 @@ class VariableDeclarationListener(JSBaseListener):
loc = _get_source_location(ctx, None) loc = _get_source_location(ctx, None)
assign_listener = AssignableListener() assign_listener = AssignableListener()
ctx.assignable().enterRule(assign_listener) ctx.assignable().enterRule(assign_listener)
if ctx.singleExpression() is not None:
raise NotImplementedError("VariableDeclarator initialization")
# ctx.singleExpression().enterRule(expression_listener) # FIXME No ExpressionListener yet # ctx.singleExpression().enterRule(expression_listener) # FIXME No ExpressionListener yet
self._var_decl = ast.nodes.VariableDeclarator( init = None # value from ExpressionListener
loc, assign_listener.result, None
) # FIXME self._var_decl = nodes.VariableDeclarator(loc, assign_listener.result, init)
class StatementListener(JSBaseListener): class StatementListener(JSBaseListener):
_stmt: ast.nodes.Statement _stmt: nodes.Statement
@property @property
def statement(self) -> ast.nodes.Statement: def statement(self) -> nodes.Statement:
"""Statement AST node generated after parse tree walking.""" """Statement AST node generated after parse tree walking."""
return self._stmt return self._stmt
@ -87,14 +97,14 @@ class StatementListener(JSBaseListener):
"""Listener for BlockStatement.""" """Listener for BlockStatement."""
logging.debug("Entered section Block") logging.debug("Entered section Block")
stmt_list: List[ast.nodes.Statement] = [] stmt_list: List[nodes.Statement] = []
for stmt in ctx.statementList().children: for stmt in ctx.statementList().children:
stmt_listener = StatementListener() stmt_listener = StatementListener()
stmt.enterRule(stmt_listener) stmt.enterRule(stmt_listener)
stmt_list.append(stmt_listener.statement) stmt_list.append(stmt_listener.statement)
loc = _get_source_location(ctx, None) # FIXME source param is None loc = _get_source_location(ctx, None)
self._stmt = ast.nodes.BlockStatement(loc, stmt_list) self._stmt = nodes.BlockStatement(loc, stmt_list)
def enterVariableStatement(self, ctx: JavaScriptParser.VariableStatementContext): def enterVariableStatement(self, ctx: JavaScriptParser.VariableStatementContext):
logging.debug("Entered section VariableStatement") logging.debug("Entered section VariableStatement")
@ -106,8 +116,8 @@ class StatementListener(JSBaseListener):
"""Listener for VariableDeclaration.""" """Listener for VariableDeclaration."""
logging.debug("Entered section VariableDeclaration") logging.debug("Entered section VariableDeclaration")
var_modifier: ast.nodes.VarDeclKind = ctx.varModifier().getText() var_modifier: nodes.VarDeclKind = ctx.varModifier().getText()
var_decls: List[ast.nodes.VariableDeclarator] = [] var_decls: List[nodes.VariableDeclarator] = []
for var_decl in ctx.variableDeclaration(): for var_decl in ctx.variableDeclaration():
var_decl_listener = VariableDeclarationListener() var_decl_listener = VariableDeclarationListener()
@ -115,11 +125,13 @@ class StatementListener(JSBaseListener):
var_decls.append(var_decl_listener.var_declarator) var_decls.append(var_decl_listener.var_declarator)
loc = _get_source_location(ctx, None) loc = _get_source_location(ctx, None)
self._stmt = ast.nodes.VariableDeclaration(loc, var_modifier, var_decls) self._stmt = nodes.VariableDeclaration(loc, var_modifier, var_decls)
def enterEmptyStatement(self, ctx: JavaScriptParser.EmptyStatementContext): def enterEmptyStatement(self, ctx: JavaScriptParser.EmptyStatementContext):
"""Listener for EmptyStatement.""" """Listener for EmptyStatement."""
logging.debug("Entered section EmptyStatement") logging.debug("Entered section EmptyStatement")
loc = _get_source_location(ctx, None)
self._stmt = nodes.EmptyStatement(loc)
pass pass
def enterExpressionStatement( def enterExpressionStatement(
@ -129,11 +141,12 @@ class StatementListener(JSBaseListener):
TODO: check up expression containers. TODO: check up expression containers.
""" """
logging.debug("Entered section ExpressionStatement") logging.debug("Entered section ExpressionStatement")
pass raise NotImplementedError("ExpressionStatement")
def enterIfStatement(self, ctx: JavaScriptParser.IfStatementContext): def enterIfStatement(self, ctx: JavaScriptParser.IfStatementContext):
"""Listener for IfStatement.""" """Listener for IfStatement."""
logging.debug("Entered section IfStatement") logging.debug("Entered section IfStatement")
raise NotImplementedError("ExpressionStatement") # FIXME
pass pass
def enterFunctionDeclaration( def enterFunctionDeclaration(
@ -141,7 +154,7 @@ class StatementListener(JSBaseListener):
): ):
"""Listener for FunctionDeclaration.""" """Listener for FunctionDeclaration."""
logging.debug("Entered section FunctionDeclaration") logging.debug("Entered section FunctionDeclaration")
pass raise NotImplementedError("FunctionDeclaration")
# TODO: import/export, ClassDeclaration, iter statements, continue. break, return # TODO: import/export, ClassDeclaration, iter statements, continue. break, return
@ -149,10 +162,10 @@ class StatementListener(JSBaseListener):
class SourceElementListener(JSBaseListener): class SourceElementListener(JSBaseListener):
"""The proxy between Program and Statement.""" """The proxy between Program and Statement."""
_elems: List[ast.nodes.Statement] = [] _elems: List[nodes.Statement] = []
@property @property
def source_elements(self) -> List[ast.nodes.Statement]: def source_elements(self) -> List[nodes.Statement]:
"""Source elements AST nodes generated after parse tree walking.""" """Source elements AST nodes generated after parse tree walking."""
return self._elems return self._elems
@ -168,22 +181,22 @@ class SourceElementListener(JSBaseListener):
class ASTListener(JSBaseListener): class ASTListener(JSBaseListener):
"""AST listener.""" """AST listener."""
_program_node: Optional[ast.nodes.Program] = None _program_node: Optional[nodes.Program] = None
_source_type: ast.nodes.SourceTypeLiteral _source_type: nodes.SourceTypeLiteral
@property @property
def program_node(self) -> ast.nodes.Program: def program_node(self) -> nodes.Program:
"""The `Program` AST node generated after parse tree walking.""" """The `Program` AST node generated after parse tree walking."""
if self._program_node is None: if self._program_node is None:
raise ValueError("Program AST node is None, did you run the listener?") raise ValueError("Program AST node is None, did you run the listener?")
return self._program_node return self._program_node
def __init__(self, source_type: ast.nodes.SourceTypeLiteral = "script"): def __init__(self, source_type: nodes.SourceTypeLiteral = "script"):
"""AST listener constructor. """AST listener constructor.
Args: Args:
source_type (ast.nodes.SourceTypeLiteral): source type. Could be `script` or `module`. Set to source_type (nodes.SourceTypeLiteral): source type. Could be `script` or `module`. Set to
`script` by default. `script` by default.
""" """
self._source_type = source_type self._source_type = source_type
@ -204,6 +217,6 @@ class ASTListener(JSBaseListener):
elem.enterRule(source_elem_listener) elem.enterRule(source_elem_listener)
loc = _get_source_location(ctx, None) # FIXME add source name loc = _get_source_location(ctx, None) # FIXME add source name
self._program_node = ast.nodes.Program( self._program_node = nodes.Program(
loc, self._source_type, source_elem_listener.source_elements loc, self._source_type, source_elem_listener.source_elements
) )

6
jasminesnake/lex/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*
!.gitignore
!JavaScriptBaseLexer.py
!JavaScriptBaseParser.py
!ErrorListeners.py
!__init__.py

View File

@ -0,0 +1,3 @@
"""Lexer/parser module.
Consists mostly of auto-generated files spewed by ANTLR.
"""