Compare commits

..

6 Commits

Author SHA1 Message Date
Yury Kurlykov 041f4c31fb
Expose `start` and `end` fields in SourceLocation 2020-04-28 04:39:50 +10:00
Yury Kurlykov 6b4865af8d
Print AST as info
Debug channel barely suits for this case.
2020-04-28 04:38:13 +10:00
Yury Kurlykov eea056550c
Implement AST printout
File output coming soon...
2020-04-28 04:28:53 +10:00
Yury Kurlykov 494ed06f28
Implement variable statement AST generation (WIP) 2020-04-28 04:28:14 +10:00
Yury Kurlykov 47d6d31239
Fix a typo in VariableDeclarator type hints 2020-04-28 04:26:33 +10:00
Yury Kurlykov 6414fecbfe
Add AST to ASCII converter 2020-04-28 03:30:37 +10:00
6 changed files with 239 additions and 19 deletions

View File

@ -3,7 +3,7 @@ So here it is.
""" """
import logging import logging
__version__ = "0.0.2" __version__ = "0.0.7"
__snake__ = r""" __snake__ = r"""
_________ _________ _________ _________
/ \ / \ / \ / \

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
import ast from ast import nodes, to_ascii_tree, from_parse_tree
def create_argument_parser(): def create_argument_parser():
@ -70,8 +70,11 @@ def main():
stream = JSFileStream(args.infile, LogErrorListener()) stream = JSFileStream(args.infile, LogErrorListener())
tree = stream.parse() tree = stream.parse()
ast_tree = ast.from_parse_tree(tree)
ast_tree = from_parse_tree(tree)
ascii_ast = to_ascii_tree(ast_tree)
logging.info("Got an AST!\n%s", ascii_ast)
# TODO: run logic # TODO: run logic
sys.exit(0) sys.exit(0)
@ -92,7 +95,11 @@ def main():
tree = stream.parse() tree = stream.parse()
logging.debug("Got tree %s", tree.toStringTree(stream.parser.ruleNames)) logging.debug("Got tree %s", tree.toStringTree(stream.parser.ruleNames))
ast_tree = ast.from_parse_tree(tree) ast_tree = from_parse_tree(tree)
ascii_ast = to_ascii_tree(ast_tree)
logging.info("Got an AST!")
logging.info(ascii_ast)
# TODO: run logic # TODO: run logic
except EOFError: except EOFError:
print("Ctrl-D received, shutting down...") print("Ctrl-D received, shutting down...")

View File

@ -1,7 +1,8 @@
"""AST module.""" """AST module."""
from enum import Enum
from typing import Union
from antlr4 import ParseTreeWalker from antlr4 import ParseTreeWalker
from tree_format import format_tree
import lex.JavaScriptParser as Parser import lex.JavaScriptParser as Parser
import ast.nodes import ast.nodes
@ -24,6 +25,46 @@ def from_parse_tree(tree: JSP.ProgramContext) -> ast.nodes.Program:
return ast_listener.program_node return ast_listener.program_node
def to_ascii_tree(
node: Union[ast.nodes.Position, ast.nodes.SourceLocation, ast.nodes.Node],
name_prefix: str = "",
nesting_lvl: int = 0,
):
if nesting_lvl < 0:
raise ValueError("Nesting level can't be below 0")
FORK = "+"
VERTICAL = "|"
HORIZONTAL = "-"
SUBENTRY_PREFIX = f"{FORK}{HORIZONTAL}{HORIZONTAL} "
NESTED_PREFIX = f"{VERTICAL} "
value = str(node)
children = None
if isinstance(node, Enum):
value = str(node.value)
if isinstance(node, list):
value = ""
children = [(index, val) for index, val in enumerate(node)]
if hasattr(node, "fields"):
children = [(k, node.fields[k]) for k in node.fields.keys()]
result = f"{NESTED_PREFIX * (nesting_lvl - 1)}{SUBENTRY_PREFIX * (nesting_lvl > 0)}"
result += f"{name_prefix}{value}\n"
if children is not None:
for (child_name, child_value) in children:
result += to_ascii_tree(child_value, f"{child_name}: ", nesting_lvl + 1)
# result += "\n"
return result
# Delete temporary imports # Delete temporary imports
del JSP del JSP
del Parser del Parser

View File

@ -21,8 +21,9 @@ Todo:
* Add support for lacking features * Add support for lacking features
""" """
from typing import List, Union, Optional, Literal as TypeLiteral, TypedDict from typing import List, Union, Optional, Literal as TypeLiteral, TypedDict, Any
from enum import Enum from enum import Enum
from collections import OrderedDict
# The Lord sees I actually wanted to split it up, but ESTree hierarchy is so messed up... No. It's actually *fucked up* # The Lord sees I actually wanted to split it up, but ESTree hierarchy is so messed up... No. It's actually *fucked up*
# that much that I couldn't even resolve circular dependencies in the submodules. I have to reap what I've sown. # that much that I couldn't even resolve circular dependencies in the submodules. I have to reap what I've sown.
@ -140,6 +141,10 @@ class Property:
... ...
class Identifier:
...
# "Node objects" block # "Node objects" block
@ -155,6 +160,9 @@ class Position:
self.line = line self.line = line
self.column = column self.column = column
def __str__(self):
return f"{self.line}:{self.column}"
class SourceLocation: class SourceLocation:
""" """
@ -172,6 +180,14 @@ class SourceLocation:
self.start = start self.start = start
self.end = end self.end = end
@property
def fields(self):
return OrderedDict({"start": self.start, "end": self.end})
def __str__(self):
src = "" if self.source is None else f"{self.source}:"
return f"{src}{str(self.start)}"
class Node: class Node:
"""ESTree AST nodes are represented as Node objects, which may have any prototype inheritance but which implement """ESTree AST nodes are represented as Node objects, which may have any prototype inheritance but which implement
@ -191,16 +207,15 @@ class Node:
self.type = node_type self.type = node_type
self.loc = loc self.loc = loc
self._fields: OrderedDict[str, Any] = OrderedDict()
self._fields.update({"type": self.type, "loc": self.loc})
# "Identifier" block def __str__(self):
return f"{self.type} at {str(self.loc)}"
@property
class Identifier(Expression, Pattern): def fields(self):
"""An identifier. Note that an identifier may be an expression or a destructuring pattern.""" return self._fields
def __init__(self, loc: Optional[SourceLocation], name: str):
super(Identifier, self).__init__("Identifier", loc)
self.name = name
# "Literal" block # "Literal" block
@ -214,6 +229,7 @@ class Literal(Expression):
): ):
super().__init__("Literal", loc) super().__init__("Literal", loc)
self.value = value self.value = value
self._fields.update({"value": self.value})
# "Programs" block # "Programs" block
@ -231,6 +247,7 @@ class Program(Node):
super().__init__("Program", loc) super().__init__("Program", loc)
self.body = body self.body = body
self.source_type = source_type self.source_type = source_type
self._fields.update({"sourceType": self.source_type, "body": self.body})
# "Functions" block # "Functions" block
@ -257,6 +274,7 @@ class Function(Node):
self.id = function_id self.id = function_id
self.params = params self.params = params
self.body = body self.body = body
self._fields.update({"id": self.id, "params": self.params, "body": self.body})
# "Statements" block # "Statements" block
@ -282,6 +300,7 @@ class BlockStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], body: List[Statement]): def __init__(self, loc: Optional[SourceLocation], body: List[Statement]):
super().__init__("BlockStatement", loc) super().__init__("BlockStatement", loc)
self.body = body self.body = body
self._fields.update({"body": self.body})
class ExpressionStatement(Statement): class ExpressionStatement(Statement):
@ -290,6 +309,7 @@ class ExpressionStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], expression: Expression): def __init__(self, loc: Optional[SourceLocation], expression: Expression):
super().__init__("ExpressionStatement", loc) super().__init__("ExpressionStatement", loc)
self.expression = expression self.expression = expression
self._fields.update({"expression": self.expression})
class Directive(Node): class Directive(Node):
@ -303,6 +323,9 @@ class Directive(Node):
super().__init__("Directive", loc) super().__init__("Directive", loc)
self.expression = expression self.expression = expression
self.directive = directive self.directive = directive
self._fields.update(
{"expression": self.expression, "directive": self.directive}
)
class FunctionBody(BlockStatement): class FunctionBody(BlockStatement):
@ -320,6 +343,7 @@ class ReturnStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], argument: Optional[Expression]): def __init__(self, loc: Optional[SourceLocation], argument: Optional[Expression]):
super().__init__("ReturnStatement", loc) super().__init__("ReturnStatement", loc)
self.argument = argument self.argument = argument
self._fields.update({"argument": self.argument})
class BreakStatement(Statement): class BreakStatement(Statement):
@ -328,6 +352,7 @@ class BreakStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]): def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]):
super().__init__("BreakStatement", loc) super().__init__("BreakStatement", loc)
self.label = label self.label = label
self._fields.update({"label": self.label})
class ContinueStatement(Statement): class ContinueStatement(Statement):
@ -336,6 +361,7 @@ class ContinueStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]): def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]):
super().__init__("ContinueStatement", loc) super().__init__("ContinueStatement", loc)
self.label = label self.label = label
self._fields.update({"label": self.label})
class IfStatement(Statement): class IfStatement(Statement):
@ -352,6 +378,13 @@ class IfStatement(Statement):
self.test = test self.test = test
self.consequent = consequent self.consequent = consequent
self.alternate = alternate self.alternate = alternate
self._fields.update(
{
"test": self.test,
"consequent": self.consequent,
"alternate": self.alternate,
}
)
class WhileStatement(Statement): class WhileStatement(Statement):
@ -363,6 +396,7 @@ class WhileStatement(Statement):
super().__init__("WhileStatement", loc) super().__init__("WhileStatement", loc)
self.test = test self.test = test
self.body = body self.body = body
self._fields.update({"test": self.test, "body": self.body})
class DoWhileStatement(Statement): class DoWhileStatement(Statement):
@ -374,6 +408,7 @@ class DoWhileStatement(Statement):
super().__init__("DoWhileStatement", loc) super().__init__("DoWhileStatement", loc)
self.body = body self.body = body
self.test = test self.test = test
self._fields.update({"body": self.body, "test": self.test})
class ForStatement(Statement): class ForStatement(Statement):
@ -392,6 +427,14 @@ class ForStatement(Statement):
self.test = test self.test = test
self.update = update self.update = update
self.body = body self.body = body
self._fields.update(
{
"init": self.init,
"test": self.test,
"update": self.update,
"body": self.body,
}
)
class ForInStatement(Statement): class ForInStatement(Statement):
@ -408,6 +451,7 @@ class ForInStatement(Statement):
self.left = left self.left = left
self.right = right self.right = right
self.body = body self.body = body
self._fields.update({"left": self.left, "right": self.right, "body": self.body})
# "Declarations" block # "Declarations" block
@ -438,11 +482,12 @@ class VariableDeclarator(Node):
"""A variable declarator.""" """A variable declarator."""
def __init__( def __init__(
self, loc: Optional[SourceLocation], var_id: Pattern, init: Optional[Exception] self, loc: Optional[SourceLocation], var_id: Pattern, init: Optional[Expression]
): ):
super().__init__("VariableDeclarator", loc) super().__init__("VariableDeclarator", loc)
self.id = var_id self.id = var_id
self.init = init self.init = init
self._fields.update({"id": self.id, "init": self.init})
class VariableDeclaration(Declaration): class VariableDeclaration(Declaration):
@ -457,6 +502,7 @@ class VariableDeclaration(Declaration):
super().__init__("VariableDeclaration", loc) super().__init__("VariableDeclaration", loc)
self.declarations = declarations self.declarations = declarations
self.kind = kind self.kind = kind
self._fields.update({"kind": self.kind, "declarations": self.declarations})
# "Expressions" block # "Expressions" block
@ -487,6 +533,7 @@ class SpreadElement(Node):
def __init__(self, loc: Optional[SourceLocation], argument: Expression): def __init__(self, loc: Optional[SourceLocation], argument: Expression):
super().__init__("SpreadElement", loc) super().__init__("SpreadElement", loc)
self.argument = argument self.argument = argument
self._fields.update({"argument": self.argument})
class ThisExpression(Expression): class ThisExpression(Expression):
@ -506,6 +553,7 @@ class ArrayExpression(Expression):
): ):
super().__init__("ArrayExpression", loc) super().__init__("ArrayExpression", loc)
self.elements = elements self.elements = elements
self._fields.update({"elements": self.elements})
class ObjectExpression(Expression): class ObjectExpression(Expression):
@ -514,6 +562,7 @@ class ObjectExpression(Expression):
def __init__(self, loc: Optional[SourceLocation], properties: List[Property]): def __init__(self, loc: Optional[SourceLocation], properties: List[Property]):
super().__init__("ObjectExpression", loc) super().__init__("ObjectExpression", loc)
self.properties = properties self.properties = properties
self._fields.update({"properties": self.properties})
class FunctionExpression(Function, Expression): class FunctionExpression(Function, Expression):
@ -541,6 +590,7 @@ class ArrowFunctionExpression(Function, Expression):
): ):
super().__init__("ArrowFunctionExpression", loc, None, params, body) super().__init__("ArrowFunctionExpression", loc, None, params, body)
self.expression = expression self.expression = expression
self._fields.update({"expression": self.expression})
class UnaryExpression(Expression): class UnaryExpression(Expression):
@ -557,6 +607,13 @@ class UnaryExpression(Expression):
self.operator = operator self.operator = operator
self.prefix = prefix self.prefix = prefix
self.argument = argument self.argument = argument
self._fields.update(
{
"operator": self.operator,
"prefix": self.prefix,
"argument": self.argument,
}
)
class UpdateExpression(Expression): class UpdateExpression(Expression):
@ -573,6 +630,13 @@ class UpdateExpression(Expression):
self.operator = operator self.operator = operator
self.argument = argument self.argument = argument
self.prefix = prefix self.prefix = prefix
self._fields.update(
{
"operator": self.operator,
"argument": self.argument,
"prefix": self.prefix,
}
)
class BinaryExpression(Expression): class BinaryExpression(Expression):
@ -589,6 +653,9 @@ class BinaryExpression(Expression):
self.operator = operator self.operator = operator
self.left = left self.left = left
self.right = right self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class AssignmentExpression(Expression): class AssignmentExpression(Expression):
@ -607,6 +674,9 @@ class AssignmentExpression(Expression):
self.operator = operator self.operator = operator
self.left = left self.left = left
self.right = right self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class LogicalExpression(Expression): class LogicalExpression(Expression):
@ -623,6 +693,9 @@ class LogicalExpression(Expression):
self.operator = operator self.operator = operator
self.left = left self.left = left
self.right = right self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class MemberExpression(Expression, Pattern): class MemberExpression(Expression, Pattern):
@ -641,6 +714,13 @@ class MemberExpression(Expression, Pattern):
self.object = member_object self.object = member_object
self.property = member_property self.property = member_property
self.computed = computed self.computed = computed
self._fields.update(
{
"object": self.object,
"property": self.property,
"computed": self.computed,
}
)
class ConditionalExpression(Expression): class ConditionalExpression(Expression):
@ -657,6 +737,13 @@ class ConditionalExpression(Expression):
self.test = test self.test = test
self.alternate = alternate self.alternate = alternate
self.consequent = consequent self.consequent = consequent
self._fields.update(
{
"test": self.test,
"alternate": self.alternate,
"consequent": self.consequent,
}
)
class CallExpression(Expression): class CallExpression(Expression):
@ -671,6 +758,7 @@ class CallExpression(Expression):
super().__init__("CallExpression", loc) super().__init__("CallExpression", loc)
self.callee = callee self.callee = callee
self.arguments = arguments self.arguments = arguments
self._fields.update({"callee": self.callee, "arguments": self.arguments})
class NewExpression(Expression): class NewExpression(Expression):
@ -685,6 +773,7 @@ class NewExpression(Expression):
super().__init__("NewExpression", loc) super().__init__("NewExpression", loc)
self.callee = callee self.callee = callee
self.arguments = arguments self.arguments = arguments
self._fields.update({"callee": self.callee, "arguments": self.arguments})
class SequenceExpression(Expression): class SequenceExpression(Expression):
@ -693,6 +782,7 @@ class SequenceExpression(Expression):
def __init__(self, loc: Optional[SourceLocation], expressions: List[Expression]): def __init__(self, loc: Optional[SourceLocation], expressions: List[Expression]):
super().__init__("SequenceExpression", loc) super().__init__("SequenceExpression", loc)
self.expressions = expressions self.expressions = expressions
self._fields.update({"expressions": self.expressions})
def _generate_unary_expression(operator: UnaryOperator, docstring: str): def _generate_unary_expression(operator: UnaryOperator, docstring: str):
@ -940,6 +1030,16 @@ class Property(Node):
self.method = method self.method = method
self.shorthand = shorthand self.shorthand = shorthand
self.computed = computed self.computed = computed
self._fields.update(
{
"key": self.key,
"value": self.value,
"kind": self.kind,
"method": self.method,
"shorthand": self.shorthand,
"computed": self.computed,
}
)
class AssignmentProperty(Property): class AssignmentProperty(Property):
@ -978,6 +1078,7 @@ class ObjectPattern(Pattern):
): ):
super().__init__("ObjectPattern", loc) super().__init__("ObjectPattern", loc)
self.properties = properties self.properties = properties
self._fields.update({"properties": self.properties})
class ArrayPattern(Pattern): class ArrayPattern(Pattern):
@ -986,3 +1087,15 @@ class ArrayPattern(Pattern):
): ):
super().__init__("ArrayPattern", loc) super().__init__("ArrayPattern", loc)
self.elements = elements self.elements = elements
self._fields.update({"elements": self.elements})
# "Identifier" block
class Identifier(Expression, Pattern):
"""An identifier. Note that an identifier may be an expression or a destructuring pattern."""
def __init__(self, loc: Optional[SourceLocation], name: str):
super().__init__("Identifier", loc)
self.name = name

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Optional, List from typing import Optional, List, Union
import antlr4.ParserRuleContext import antlr4.ParserRuleContext
from lex.JavaScriptParser import JavaScriptParser from lex.JavaScriptParser import JavaScriptParser
@ -23,6 +23,52 @@ def _get_source_location(
return ast.nodes.SourceLocation(source=source, start=start_pos, end=end_pos) return ast.nodes.SourceLocation(source=source, start=start_pos, end=end_pos)
class AssignableListener(JSBaseListener):
_result: Union[
ast.nodes.Identifier, ast.nodes.ObjectPattern, ast.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 = ast.nodes.Identifier(loc, ctx.getText())
def enterArrayLiteral(self, ctx: JavaScriptParser.ArrayLiteralContext):
logging.debug("Entered section ArrayLiteral")
pass # TODO
def enterObjectLiteral(self, ctx: JavaScriptParser.ObjectLiteralContext):
logging.debug("Entered section ObjectLiteral")
pass # TODO
class VariableDeclarationListener(JSBaseListener):
_var_decl: ast.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)
# ctx.singleExpression().enterRule(expression_listener) # FIXME No ExpressionListener yet
self._var_decl = ast.nodes.VariableDeclarator(
loc, assign_listener.result, None
) # FIXME
class StatementListener(JSBaseListener): class StatementListener(JSBaseListener):
_stmt: ast.nodes.Statement _stmt: ast.nodes.Statement
@ -50,12 +96,26 @@ class StatementListener(JSBaseListener):
loc = _get_source_location(ctx, None) # FIXME source param is None loc = _get_source_location(ctx, None) # FIXME source param is None
self._stmt = ast.nodes.BlockStatement(loc, stmt_list) self._stmt = ast.nodes.BlockStatement(loc, stmt_list)
def enterVariableStatement(self, ctx: JavaScriptParser.VariableStatementContext):
logging.debug("Entered section VariableStatement")
ctx.variableDeclarationList().enterRule(self)
def enterVariableDeclarationList( def enterVariableDeclarationList(
self, ctx: JavaScriptParser.VariableDeclarationListContext self, ctx: JavaScriptParser.VariableDeclarationListContext
): ):
"""Listener for VariableDeclaration.""" """Listener for VariableDeclaration."""
logging.debug("Entered section VariableDeclaration") logging.debug("Entered section VariableDeclaration")
pass
var_modifier: ast.nodes.VarDeclKind = ctx.varModifier().getText()
var_decls: List[ast.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 = ast.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."""

View File

@ -1,4 +1,3 @@
antlr4-python3-runtime==4.8 antlr4-python3-runtime==4.8
colorama==0.4.3 colorama==0.4.3
coloredlogs==14.0 coloredlogs==14.0
tree_format==0.1.2