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
__version__ = "0.0.2"
__version__ = "0.0.7"
__snake__ = r"""
_________ _________
/ \ / \

View File

@ -10,7 +10,7 @@ import coloredlogs
from jasminesnake import __version__, __snake__, LOG_LEVELS
from .js_stream import JSBaseStream, JSStringStream, JSFileStream
from .lex.ErrorListeners import LogErrorListener
import ast
from ast import nodes, to_ascii_tree, from_parse_tree
def create_argument_parser():
@ -70,8 +70,11 @@ def main():
stream = JSFileStream(args.infile, LogErrorListener())
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
sys.exit(0)
@ -92,7 +95,11 @@ def main():
tree = stream.parse()
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
except EOFError:
print("Ctrl-D received, shutting down...")

View File

@ -1,7 +1,8 @@
"""AST module."""
from enum import Enum
from typing import Union
from antlr4 import ParseTreeWalker
from tree_format import format_tree
import lex.JavaScriptParser as Parser
import ast.nodes
@ -24,6 +25,46 @@ def from_parse_tree(tree: JSP.ProgramContext) -> ast.nodes.Program:
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
del JSP
del Parser

View File

@ -21,8 +21,9 @@ Todo:
* 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 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*
# 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
@ -155,6 +160,9 @@ class Position:
self.line = line
self.column = column
def __str__(self):
return f"{self.line}:{self.column}"
class SourceLocation:
"""
@ -172,6 +180,14 @@ class SourceLocation:
self.start = start
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:
"""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.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)}"
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(Identifier, self).__init__("Identifier", loc)
self.name = name
@property
def fields(self):
return self._fields
# "Literal" block
@ -214,6 +229,7 @@ class Literal(Expression):
):
super().__init__("Literal", loc)
self.value = value
self._fields.update({"value": self.value})
# "Programs" block
@ -231,6 +247,7 @@ class Program(Node):
super().__init__("Program", loc)
self.body = body
self.source_type = source_type
self._fields.update({"sourceType": self.source_type, "body": self.body})
# "Functions" block
@ -257,6 +274,7 @@ class Function(Node):
self.id = function_id
self.params = params
self.body = body
self._fields.update({"id": self.id, "params": self.params, "body": self.body})
# "Statements" block
@ -282,6 +300,7 @@ class BlockStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], body: List[Statement]):
super().__init__("BlockStatement", loc)
self.body = body
self._fields.update({"body": self.body})
class ExpressionStatement(Statement):
@ -290,6 +309,7 @@ class ExpressionStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], expression: Expression):
super().__init__("ExpressionStatement", loc)
self.expression = expression
self._fields.update({"expression": self.expression})
class Directive(Node):
@ -303,6 +323,9 @@ class Directive(Node):
super().__init__("Directive", loc)
self.expression = expression
self.directive = directive
self._fields.update(
{"expression": self.expression, "directive": self.directive}
)
class FunctionBody(BlockStatement):
@ -320,6 +343,7 @@ class ReturnStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], argument: Optional[Expression]):
super().__init__("ReturnStatement", loc)
self.argument = argument
self._fields.update({"argument": self.argument})
class BreakStatement(Statement):
@ -328,6 +352,7 @@ class BreakStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]):
super().__init__("BreakStatement", loc)
self.label = label
self._fields.update({"label": self.label})
class ContinueStatement(Statement):
@ -336,6 +361,7 @@ class ContinueStatement(Statement):
def __init__(self, loc: Optional[SourceLocation], label: Optional[Identifier]):
super().__init__("ContinueStatement", loc)
self.label = label
self._fields.update({"label": self.label})
class IfStatement(Statement):
@ -352,6 +378,13 @@ class IfStatement(Statement):
self.test = test
self.consequent = consequent
self.alternate = alternate
self._fields.update(
{
"test": self.test,
"consequent": self.consequent,
"alternate": self.alternate,
}
)
class WhileStatement(Statement):
@ -363,6 +396,7 @@ class WhileStatement(Statement):
super().__init__("WhileStatement", loc)
self.test = test
self.body = body
self._fields.update({"test": self.test, "body": self.body})
class DoWhileStatement(Statement):
@ -374,6 +408,7 @@ class DoWhileStatement(Statement):
super().__init__("DoWhileStatement", loc)
self.body = body
self.test = test
self._fields.update({"body": self.body, "test": self.test})
class ForStatement(Statement):
@ -392,6 +427,14 @@ class ForStatement(Statement):
self.test = test
self.update = update
self.body = body
self._fields.update(
{
"init": self.init,
"test": self.test,
"update": self.update,
"body": self.body,
}
)
class ForInStatement(Statement):
@ -408,6 +451,7 @@ class ForInStatement(Statement):
self.left = left
self.right = right
self.body = body
self._fields.update({"left": self.left, "right": self.right, "body": self.body})
# "Declarations" block
@ -438,11 +482,12 @@ class VariableDeclarator(Node):
"""A variable declarator."""
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)
self.id = var_id
self.init = init
self._fields.update({"id": self.id, "init": self.init})
class VariableDeclaration(Declaration):
@ -457,6 +502,7 @@ class VariableDeclaration(Declaration):
super().__init__("VariableDeclaration", loc)
self.declarations = declarations
self.kind = kind
self._fields.update({"kind": self.kind, "declarations": self.declarations})
# "Expressions" block
@ -487,6 +533,7 @@ class SpreadElement(Node):
def __init__(self, loc: Optional[SourceLocation], argument: Expression):
super().__init__("SpreadElement", loc)
self.argument = argument
self._fields.update({"argument": self.argument})
class ThisExpression(Expression):
@ -506,6 +553,7 @@ class ArrayExpression(Expression):
):
super().__init__("ArrayExpression", loc)
self.elements = elements
self._fields.update({"elements": self.elements})
class ObjectExpression(Expression):
@ -514,6 +562,7 @@ class ObjectExpression(Expression):
def __init__(self, loc: Optional[SourceLocation], properties: List[Property]):
super().__init__("ObjectExpression", loc)
self.properties = properties
self._fields.update({"properties": self.properties})
class FunctionExpression(Function, Expression):
@ -541,6 +590,7 @@ class ArrowFunctionExpression(Function, Expression):
):
super().__init__("ArrowFunctionExpression", loc, None, params, body)
self.expression = expression
self._fields.update({"expression": self.expression})
class UnaryExpression(Expression):
@ -557,6 +607,13 @@ class UnaryExpression(Expression):
self.operator = operator
self.prefix = prefix
self.argument = argument
self._fields.update(
{
"operator": self.operator,
"prefix": self.prefix,
"argument": self.argument,
}
)
class UpdateExpression(Expression):
@ -573,6 +630,13 @@ class UpdateExpression(Expression):
self.operator = operator
self.argument = argument
self.prefix = prefix
self._fields.update(
{
"operator": self.operator,
"argument": self.argument,
"prefix": self.prefix,
}
)
class BinaryExpression(Expression):
@ -589,6 +653,9 @@ class BinaryExpression(Expression):
self.operator = operator
self.left = left
self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class AssignmentExpression(Expression):
@ -607,6 +674,9 @@ class AssignmentExpression(Expression):
self.operator = operator
self.left = left
self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class LogicalExpression(Expression):
@ -623,6 +693,9 @@ class LogicalExpression(Expression):
self.operator = operator
self.left = left
self.right = right
self._fields.update(
{"operator": self.operator, "left": self.left, "right": self.right}
)
class MemberExpression(Expression, Pattern):
@ -641,6 +714,13 @@ class MemberExpression(Expression, Pattern):
self.object = member_object
self.property = member_property
self.computed = computed
self._fields.update(
{
"object": self.object,
"property": self.property,
"computed": self.computed,
}
)
class ConditionalExpression(Expression):
@ -657,6 +737,13 @@ class ConditionalExpression(Expression):
self.test = test
self.alternate = alternate
self.consequent = consequent
self._fields.update(
{
"test": self.test,
"alternate": self.alternate,
"consequent": self.consequent,
}
)
class CallExpression(Expression):
@ -671,6 +758,7 @@ class CallExpression(Expression):
super().__init__("CallExpression", loc)
self.callee = callee
self.arguments = arguments
self._fields.update({"callee": self.callee, "arguments": self.arguments})
class NewExpression(Expression):
@ -685,6 +773,7 @@ class NewExpression(Expression):
super().__init__("NewExpression", loc)
self.callee = callee
self.arguments = arguments
self._fields.update({"callee": self.callee, "arguments": self.arguments})
class SequenceExpression(Expression):
@ -693,6 +782,7 @@ class SequenceExpression(Expression):
def __init__(self, loc: Optional[SourceLocation], expressions: List[Expression]):
super().__init__("SequenceExpression", loc)
self.expressions = expressions
self._fields.update({"expressions": self.expressions})
def _generate_unary_expression(operator: UnaryOperator, docstring: str):
@ -940,6 +1030,16 @@ class Property(Node):
self.method = method
self.shorthand = shorthand
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):
@ -978,6 +1078,7 @@ class ObjectPattern(Pattern):
):
super().__init__("ObjectPattern", loc)
self.properties = properties
self._fields.update({"properties": self.properties})
class ArrayPattern(Pattern):
@ -986,3 +1087,15 @@ class ArrayPattern(Pattern):
):
super().__init__("ArrayPattern", loc)
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
from typing import Optional, List
from typing import Optional, List, Union
import antlr4.ParserRuleContext
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)
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):
_stmt: ast.nodes.Statement
@ -50,12 +96,26 @@ class StatementListener(JSBaseListener):
loc = _get_source_location(ctx, None) # FIXME source param is None
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(
self, ctx: JavaScriptParser.VariableDeclarationListContext
):
"""Listener for 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):
"""Listener for EmptyStatement."""

View File

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