Compare commits

...

5 Commits

Author SHA1 Message Date
Yury Kurlykov 5cd616ceb3
Add basic test 2020-06-26 16:43:51 +10:00
Yury Kurlykov ab066f9cd6
Add test with a bug 2020-06-26 16:41:29 +10:00
Yury Kurlykov ea923d2273
Commit last changes 2020-06-26 16:24:06 +10:00
Yury Kurlykov 44d7e2410f
Comment out RegularExpressionLiteral
I'm not going to implement RegExp in the nearest future, so I don't think it is needed there uncommented.
2020-04-30 00:58:22 +10:00
Yury Kurlykov 2708b1e61f
Add specific Literal classes
They are needed for the further development convenience.
2020-04-30 00:55:25 +10:00
10 changed files with 202 additions and 20 deletions

View File

@ -11,7 +11,7 @@ Another JavaScript interpreter written on Python 3.
To run tests:
- pylint
- pytest
- Tox
You can get ANTLR [here](https://www.antlr.org/), other dependencies could be installed with pip:

View File

@ -37,7 +37,7 @@ options { superClass=JavaScriptBaseLexer; }
HashBangLine: { self.isStartOfFile()}? '#!' ~[\r\n\u2028\u2029]*; // only allowed at start
MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN);
SingleLineComment: '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
RegularExpressionLiteral: '/' RegularExpressionFirstChar RegularExpressionChar* {self.isRegexPossible()}? '/' IdentifierPart*;
//RegularExpressionLiteral: '/' RegularExpressionFirstChar RegularExpressionChar* {self.isRegexPossible()}? '/' IdentifierPart*;
OpenBracket: '[';
CloseBracket: ']';

View File

@ -403,7 +403,7 @@ literal
| BooleanLiteral
| StringLiteral
// | TemplateStringLiteral
| RegularExpressionLiteral
// | RegularExpressionLiteral
| numericLiteral
| bigintLiteral
;

View File

@ -3,7 +3,7 @@ So here it is.
"""
import logging
__version__ = "0.0.7"
__version__ = "0.0.9"
__snake__ = r"""
_________ _________
/ \ / \
@ -32,4 +32,5 @@ LOG_LEVELS = {
},
}
# TODO: make it usable as a module too

View File

@ -1059,6 +1059,35 @@ class BigIntLiteral(Literal):
self._fields.update({"bigint": self.bigint})
class NullLiteral(Literal):
def __init__(self, loc: Optional[SourceLocation]):
super().__init__(loc, None)
def __str__(self):
return "null"
class BooleanLiteral(Literal):
def __init__(self, loc: Optional[SourceLocation], value: bool):
super().__init__(loc, value)
def __str__(self):
return str(self.value).lower()
class StringLiteral(Literal):
def __init__(self, loc: Optional[SourceLocation], value: str):
super().__init__(loc, value)
def __str__(self):
return f'"{self.value}"'
class NumericLiteral(Literal):
def __init__(self, loc: Optional[SourceLocation], value: number):
super().__init__(loc, value)
# "Property" block

View File

@ -6,9 +6,11 @@ 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
@ -31,7 +33,78 @@ def _get_source_location(
return nodes.SourceLocation(source=source, start=start_pos, end=end_pos)
class AssignableListener(JSBaseListener):
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
@ -70,11 +143,11 @@ class VariableDeclarationListener(JSBaseListener):
assign_listener = AssignableListener()
ctx.assignable().enterRule(assign_listener)
init = None # or value from ExpressionListener
if ctx.singleExpression() is not None:
raise NotImplementedError("VariableDeclarator initialization")
# ctx.singleExpression().enterRule(expression_listener) # FIXME No ExpressionListener yet
init = None # value from ExpressionListener
expression_listener = ExpressionListener()
ctx.singleExpression().enterRule(expression_listener)
init = expression_listener.expression
self._var_decl = nodes.VariableDeclarator(loc, assign_listener.result, init)
@ -88,6 +161,17 @@ class StatementListener(JSBaseListener):
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")
@ -137,17 +221,17 @@ class StatementListener(JSBaseListener):
def enterExpressionStatement(
self, ctx: JavaScriptParser.ExpressionStatementContext
):
"""Listener for ExpressionStatement.
TODO: check up expression containers.
"""
"""Listener for ExpressionStatement."""
logging.debug("Entered section ExpressionStatement")
raise NotImplementedError("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("ExpressionStatement") # FIXME
pass
raise NotImplementedError("IfStatement")
def enterFunctionDeclaration(
self, ctx: JavaScriptParser.FunctionDeclarationContext
@ -156,7 +240,54 @@ class StatementListener(JSBaseListener):
logging.debug("Entered section FunctionDeclaration")
raise NotImplementedError("FunctionDeclaration")
# TODO: import/export, ClassDeclaration, iter statements, continue. break, return
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):

View File

@ -1,15 +1,19 @@
from antlr4.error.ErrorListener import ErrorListener
import logging
from antlr4.error.Errors import ParseCancellationException
class LogErrorListener(ErrorListener):
def __init__(self):
super().__init__()
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
logging.debug(
"{}\n{}\n{}\n{}\n{}".format(offendingSymbol, line, column, msg, e)
)
logging.critical("SyntaxError: %s", msg)
raise ParseCancellationException("Syntax Error lol")
# logging.debug(
# "{}\n{}\n{}\n{}\n{}".format(offendingSymbol, line, column, msg, e)
# )
def reportAmbiguity(
self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs

2
tests/apr30_bug.js Normal file
View File

@ -0,0 +1,2 @@
// Guess what? It should be parsable, but it's not (git rev 44d7e241)
{let a = 15, b; {1_337.2_28, false, "String"}}

15
tests/test.js Normal file
View File

@ -0,0 +1,15 @@
// The code below won't work since the grammar treat Unicode
//"\x20\x77hil\x65\x20exe\x63uting c\x6cient e\x76\145\x6et ";
//{let a = 15, b}
{{{let t = false, n = true}}}
//++a15;
var a, b=14;
let c;
;
4444444444444444444444;
//228,1444444444444;t,fnm
//if (1) 0;
//function f4444444(){}

View File

@ -1,5 +1,5 @@
[pytest]
envlist = py38
envlist = py37,py38
[testenv]
changedir = tests