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: To run tests:
- pylint - pytest
- Tox - Tox
You can get ANTLR [here](https://www.antlr.org/), other dependencies could be installed with pip: 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 HashBangLine: { self.isStartOfFile()}? '#!' ~[\r\n\u2028\u2029]*; // only allowed at start
MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN); MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN);
SingleLineComment: '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN); SingleLineComment: '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
RegularExpressionLiteral: '/' RegularExpressionFirstChar RegularExpressionChar* {self.isRegexPossible()}? '/' IdentifierPart*; //RegularExpressionLiteral: '/' RegularExpressionFirstChar RegularExpressionChar* {self.isRegexPossible()}? '/' IdentifierPart*;
OpenBracket: '['; OpenBracket: '[';
CloseBracket: ']'; CloseBracket: ']';

View File

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

View File

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

View File

@ -1059,6 +1059,35 @@ class BigIntLiteral(Literal):
self._fields.update({"bigint": self.bigint}) 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 # "Property" block

View File

@ -6,9 +6,11 @@ Todo:
* Fill `source` field in SourceLocation and pass it to each `_get_source_location()` call. * Fill `source` field in SourceLocation and pass it to each `_get_source_location()` call.
* Compare `SourceLocation` creation behavior with the one in Acorn/ESPrima * 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 antlr4 import ErrorNode, 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
@ -31,7 +33,78 @@ def _get_source_location(
return 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 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] _result: Union[nodes.Identifier, nodes.ObjectPattern, nodes.ArrayPattern]
@property @property
@ -70,11 +143,11 @@ class VariableDeclarationListener(JSBaseListener):
assign_listener = AssignableListener() assign_listener = AssignableListener()
ctx.assignable().enterRule(assign_listener) ctx.assignable().enterRule(assign_listener)
init = None # or value from ExpressionListener
if ctx.singleExpression() is not None: if ctx.singleExpression() is not None:
raise NotImplementedError("VariableDeclarator initialization") expression_listener = ExpressionListener()
ctx.singleExpression().enterRule(expression_listener)
# ctx.singleExpression().enterRule(expression_listener) # FIXME No ExpressionListener yet init = expression_listener.expression
init = None # value from ExpressionListener
self._var_decl = nodes.VariableDeclarator(loc, assign_listener.result, init) self._var_decl = nodes.VariableDeclarator(loc, assign_listener.result, init)
@ -88,6 +161,17 @@ class StatementListener(JSBaseListener):
return self._stmt 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): def enterStatement(self, ctx: JavaScriptParser.StatementContext):
"""Obtain an actual statement.""" """Obtain an actual statement."""
logging.debug("Entered section Statement") logging.debug("Entered section Statement")
@ -137,17 +221,17 @@ class StatementListener(JSBaseListener):
def enterExpressionStatement( def enterExpressionStatement(
self, ctx: JavaScriptParser.ExpressionStatementContext self, ctx: JavaScriptParser.ExpressionStatementContext
): ):
"""Listener for ExpressionStatement. """Listener for ExpressionStatement."""
TODO: check up expression containers.
"""
logging.debug("Entered section 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): 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 raise NotImplementedError("IfStatement")
pass
def enterFunctionDeclaration( def enterFunctionDeclaration(
self, ctx: JavaScriptParser.FunctionDeclarationContext self, ctx: JavaScriptParser.FunctionDeclarationContext
@ -156,7 +240,54 @@ class StatementListener(JSBaseListener):
logging.debug("Entered section FunctionDeclaration") logging.debug("Entered section FunctionDeclaration")
raise NotImplementedError("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): class SourceElementListener(JSBaseListener):

View File

@ -1,15 +1,19 @@
from antlr4.error.ErrorListener import ErrorListener from antlr4.error.ErrorListener import ErrorListener
import logging import logging
from antlr4.error.Errors import ParseCancellationException
class LogErrorListener(ErrorListener): class LogErrorListener(ErrorListener):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
logging.debug( logging.critical("SyntaxError: %s", msg)
"{}\n{}\n{}\n{}\n{}".format(offendingSymbol, line, column, msg, e) raise ParseCancellationException("Syntax Error lol")
) # logging.debug(
# "{}\n{}\n{}\n{}\n{}".format(offendingSymbol, line, column, msg, e)
# )
def reportAmbiguity( def reportAmbiguity(
self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs 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] [pytest]
envlist = py38 envlist = py37,py38
[testenv] [testenv]
changedir = tests changedir = tests