Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'release/0.2'

  • Loading branch information...
commit bdc006bb8e71ad906206dd78a7fe32b6fa4ea2db 2 parents 8025dc3 + b64cdf1
@rspivak authored
View
12 CHANGES
@@ -0,0 +1,12 @@
+Change History
+**************
+
+0.2 (2011-05-07)
+================
+- Added a JavaScript parser
+- Added pretty printer
+- Added node visitor
+
+0.1 (2011-05-02)
+================
+- Initial public version. It contains only a JavaScript lexer
View
2  MANIFEST.in
@@ -1 +1 @@
-include README.rst
+include README.rst CHANGES
View
29 README.rst
@@ -5,7 +5,34 @@ Welcome to SlimIt
It compiles JavaScript into more compact code so that it downloads
and runs faster.
-At version `0.1` it provides only a JavaScript lexer.
+At version `0.2` it provides a library that includes a JavaScript
+parser, lexer, pretty printer and a tree visitor.
+
+Iterate over, modify a JavaScript AST and pretty print it
+---------------------------------------------------------
+
+>>> from slimit.parser import Parser
+>>> from slimit.visitors import nodevisitor
+>>> from slimit import ast
+>>>
+>>> parser = Parser()
+>>> tree = parser.parse('for(var i=0; i<10; i++) {var x=5+i;}')
+>>> for node in nodevisitor.visit(tree):
+... if isinstance(node, ast.Identifier) and node.value == 'i':
+... node.value = 'hello'
+...
+>>> print tree.to_ecma() # print awesome javascript :)
+for (var hello = 0; hello < 10; hello++) {
+ var x = 5 + hello;
+}
+>>>
+
+`N.B.` First time you invoke ``parse`` method it will generate the
+``lextab.py`` and ``yacctab.py`` LALR tables in current directory and
+you may see some warnings - that's OK. Previously generated tables
+are cached and reused if possible. For more details visit `PLY's
+official site <http://www.dabeaz.com/ply/ply.html>`_.
+
Using lexer in your project
---------------------------
View
BIN  docs-source/_static/slimit-small.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2  docs-source/_templates/sidebarintro.html
@@ -1,6 +1,6 @@
<h3>About SlimIt</h3>
<p>
- SlimIt is a JavaScript minifier.
+ SlimIt is a JavaScript minifier and a library that parses JavaScript and returns AST
</p>
<h3>Useful Links</h3>
<ul>
View
31 docs-source/index.rst
@@ -10,7 +10,34 @@ Welcome to SlimIt
It compiles JavaScript into more compact code so that it downloads
and runs faster.
-At version `0.1` it provides only a JavaScript lexer.
+At version `0.2` it provides a library that includes a JavaScript
+parser, lexer, pretty printer and a tree visitor.
+
+Iterate over, modify a JavaScript AST and pretty print it
+---------------------------------------------------------
+
+>>> from slimit.parser import Parser
+>>> from slimit.visitors import nodevisitor
+>>> from slimit import ast
+>>>
+>>> parser = Parser()
+>>> tree = parser.parse('for(var i=0; i<10; i++) {var x=5+i;}')
+>>> for node in nodevisitor.visit(tree):
+... if isinstance(node, ast.Identifier) and node.value == 'i':
+... node.value = 'hello'
+...
+>>> print tree.to_ecma() # print awesome javascript :)
+for (var hello = 0; hello < 10; hello++) {
+ var x = 5 + hello;
+}
+>>>
+
+`N.B.` First time you invoke ``parse`` method it will generate the
+``lextab.py`` and ``yacctab.py`` LALR tables in current directory and
+you may see some warnings - that's OK. Previously generated tables
+are cached and reused if possible. For more details visit `PLY's
+official site <http://www.dabeaz.com/ply/ply.html>`_.
+
Using lexer in your project
---------------------------
@@ -47,6 +74,7 @@ LexToken(SEMI,';',1,5)
>>> token.type, token.value, token.lineno, token.lexpos
('ID', 'a', 1, 0)
+
Installation
------------
@@ -58,7 +86,6 @@ Using ``easy_install``::
$ sudo easy_install slimit
-
.. toctree::
:maxdepth: 2
View
14 setup.py
@@ -11,32 +11,26 @@
Operating System :: Unix
"""
-long_description = """\
-SlimIt is a JavaScript source-to-source compiler and optimizer / minifier.
-It compiles JavaScript into more compact code so that it downloads
-and runs faster.
-"""
-
def read(*rel_names):
return open(os.path.join(os.path.dirname(__file__), *rel_names)).read()
setup(
name='slimit',
- version='0.1',
- url='http://github.com/rspivak/slimit',
+ version='0.2',
+ url='http://slimit.org',
license='MIT',
description='SlimIt - JavaScript minifier',
author='Ruslan Spivak',
author_email='ruslan.spivak@gmail.com',
packages=find_packages('src'),
package_dir={'': 'src'},
- install_requires=['distribute', 'ply'],
+ install_requires=['distribute', 'ply>=3.4'],
zip_safe=False,
entry_points="""\
[console_scripts]
""",
classifiers=filter(None, classifiers.split('\n')),
- long_description=read('README.rst'),
+ long_description=read('README.rst') + '\n\n' + read('CHANGES'),
extras_require={'test': []}
)
View
384 src/slimit/ast.py
@@ -0,0 +1,384 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+
+class Node(object):
+ def __init__(self, children=None):
+ self._children_list = [] if children is None else children
+
+ def __iter__(self):
+ for child in self.children():
+ if child is not None:
+ yield child
+
+ def children(self):
+ return self._children_list
+
+ def to_ecma(self):
+ # Can't import at module level as ecmavisitor depends
+ # on ast module...
+ from slimit.visitors.ecmavisitor import ECMAVisitor
+ visitor = ECMAVisitor()
+ return visitor.visit(self)
+
+class Program(Node):
+ pass
+
+class Block(Node):
+ pass
+
+class Boolean(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class Null(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class Number(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class Identifier(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class String(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class Regex(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class Array(Node):
+ def __init__(self, items):
+ self.items = items
+
+ def children(self):
+ return self.items
+
+class Object(Node):
+ def __init__(self, properties=None):
+ self.properties = [] if properties is None else properties
+
+ def children(self):
+ return self.properties
+
+class NewExpr(Node):
+ def __init__(self, identifier, args=None):
+ self.identifier = identifier
+ self.args = [] if args is None else args
+
+ def children(self):
+ return [self.identifier, self.args]
+
+class FunctionCall(Node):
+ def __init__(self, identifier, args=None):
+ self.identifier = identifier
+ self.args = [] if args is None else args
+
+ def children(self):
+ return [self.identifier] + self.args
+
+class BracketAccessor(Node):
+ def __init__(self, node, expr):
+ self.node = node
+ self.expr = expr
+
+ def children(self):
+ return [self.node, self.expr]
+
+class DotAccessor(Node):
+ def __init__(self, node, identifier):
+ self.node = node
+ self.identifier = identifier
+
+ def children(self):
+ return [self.node, self.identifier]
+
+class Assign(Node):
+ def __init__(self, op, left, right):
+ self.op = op
+ self.left = left
+ self.right = right
+
+ def children(self):
+ return [self.left, self.right]
+
+class VarStatement(Node):
+ pass
+
+class VarDecl(Node):
+ def __init__(self, identifier, initializer=None):
+ self.identifier = identifier
+ self.initializer = initializer
+
+ def children(self):
+ return [self.identifier, self.initializer]
+
+class UnaryOp(Node):
+ def __init__(self, op, value, postfix=False):
+ self.op = op
+ self.value = value
+ self.postfix = postfix
+
+ def children(self):
+ return [self.value]
+
+class BinOp(Node):
+ def __init__(self, op, left, right):
+ self.op = op
+ self.left = left
+ self.right = right
+
+ def children(self):
+ return [self.left, self.right]
+
+class Conditional(Node):
+ """Conditional Operator ( ? : )"""
+ def __init__(self, predicate, consequent, alternative):
+ self.predicate = predicate
+ self.consequent = consequent
+ self.alternative = alternative
+
+ def children(self):
+ return [self.predicate, self.consequent, self.alternative]
+
+class If(Node):
+ def __init__(self, predicate, consequent, alternative=None):
+ self.predicate = predicate
+ self.consequent = consequent
+ self.alternative = alternative
+
+ def children(self):
+ return [self.predicate, self.consequent, self.alternative]
+
+class DoWhile(Node):
+ def __init__(self, predicate, statement):
+ self.predicate = predicate
+ self.statement = statement
+
+ def children(self):
+ return [self.predicate, self.statement]
+
+class While(Node):
+ def __init__(self, predicate, statement):
+ self.predicate = predicate
+ self.statement = statement
+
+ def children(self):
+ return [self.predicate, self.statement]
+
+class For(Node):
+ def __init__(self, init, cond, count, statement):
+ self.init = init
+ self.cond = cond
+ self.count = count
+ self.statement = statement
+
+ def children(self):
+ return [self.init, self.cond, self.count, self.statement]
+
+class ForIn(Node):
+ def __init__(self, item, iterable, statement):
+ self.item = item
+ self.iterable = iterable
+ self.statement = statement
+
+ def children(self):
+ return [self.item, self.iterable, self.statement]
+
+class Continue(Node):
+ def __init__(self, identifier=None):
+ self.identifier = identifier
+
+ def children(self):
+ return [self.identifier]
+
+class Break(Node):
+ def __init__(self, identifier=None):
+ self.identifier = identifier
+
+ def children(self):
+ return [self.identifier]
+
+class Return(Node):
+ def __init__(self, expr=None):
+ self.expr = expr
+
+ def children(self):
+ return [self.expr]
+
+class With(Node):
+ def __init__(self, expr, statement):
+ self.expr = expr
+ self.statement = statement
+
+ def children(self):
+ return [self.expr, self.statement]
+
+class Switch(Node):
+ def __init__(self, expr, cases, default=None):
+ self.expr = expr
+ self.cases = cases
+ self.default = default
+
+ def children(self):
+ return [self.expr] + self.cases + [self.default]
+
+class Case(Node):
+ def __init__(self, expr, elements):
+ self.expr = expr
+ self.elements = elements if elements is not None else []
+
+ def children(self):
+ return [self.expr] + self.elements
+
+class Default(Node):
+ def __init__(self, elements):
+ self.elements = elements if elements is not None else []
+
+ def children(self):
+ return self.elements
+
+class Label(Node):
+ def __init__(self, identifier, statement):
+ self.identifier = identifier
+ self.statement = statement
+
+ def children(self):
+ return [self.identifier, self.statement]
+
+class Throw(Node):
+ def __init__(self, expr):
+ self.expr = expr
+
+ def children(self):
+ return [self.expr]
+
+class Try(Node):
+ def __init__(self, statements, catch=None, fin=None):
+ self.statements = statements
+ self.catch = catch
+ self.fin = fin
+
+ def children(self):
+ return [self.statements] + [self.catch, self.fin]
+
+class Catch(Node):
+ def __init__(self, identifier, elements):
+ self.identifier = identifier
+ self.elements = elements
+
+ def children(self):
+ return [self.identifier, self.elements]
+
+class Finally(Node):
+ def __init__(self, elements):
+ self.elements = elements
+
+ def children(self):
+ return self.elements
+
+class Debugger(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class FuncDecl(Node):
+ def __init__(self, identifier, parameters, elements):
+ self.identifier = identifier
+ self.parameters = parameters if parameters is not None else []
+ self.elements = elements if elements is not None else []
+
+ def children(self):
+ return [self.identifier] + self.parameters + self.elements
+
+class FuncExpr(Node):
+ def __init__(self, identifier, parameters, elements):
+ self.identifier = identifier
+ self.parameters = parameters if parameters is not None else []
+ self.elements = elements if elements is not None else []
+
+ def children(self):
+ return [self.identifier] + self.parameters + self.elements
+
+class Comma(Node):
+ def __init__(self, left, right):
+ self.left = left
+ self.right = right
+
+ def children(self):
+ return [self.left, self.right]
+
+class EmptyStatement(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class ExprStatement(Node):
+ def __init__(self, expr):
+ self.expr = expr
+
+ def children(self):
+ return [self.expr]
+
+class Elision(Node):
+ def __init__(self, value):
+ self.value = value
+
+ def children(self):
+ return []
+
+class This(Node):
+ def __init__(self):
+ pass
+
+ def children(self):
+ return []
View
116 src/slimit/lexer.py
@@ -93,6 +93,7 @@ class Lexer(object):
def __init__(self):
self.prev_token = None
self.cur_token = None
+ self.next_tokens = []
self.build()
def build(self, **kwargs):
@@ -103,32 +104,52 @@ def input(self, text):
self.lexer.input(text)
def token(self):
+ if self.next_tokens:
+ return self.next_tokens.pop()
+
lexer = self.lexer
- pos = lexer.lexpos
- try:
- char = lexer.lexdata[pos]
- while char.isspace():
- pos += 1
+ while True:
+ pos = lexer.lexpos
+ try:
char = lexer.lexdata[pos]
- next_char = lexer.lexdata[pos + 1]
- except IndexError:
- return self._get_update_token()
-
- if char != '/' or (char == '/' and next_char in ('/', '*')):
- return self._get_update_token()
-
- # current character is '/' which is either division or regex
- cur_token = self.cur_token
- is_division_allowed = (
- cur_token is not None and
- cur_token.type in TOKENS_THAT_IMPLY_DIVISON
- )
- if is_division_allowed:
- return self._get_update_token()
- else:
- self.prev_token = self.cur_token
- self.cur_token = self._read_regex()
- return self.cur_token
+ while char in ' \t':
+ pos += 1
+ char = lexer.lexdata[pos]
+ next_char = lexer.lexdata[pos + 1]
+ except IndexError:
+ return self._get_update_token()
+
+ if char != '/' or (char == '/' and next_char in ('/', '*')):
+ token = self._get_update_token()
+ if token.type in ('LINE_TERMINATOR',
+ 'LINE_COMMENT', 'BLOCK_COMMENT'):
+ continue
+ else:
+ return token
+
+ # current character is '/' which is either division or regex
+ cur_token = self.cur_token
+ is_division_allowed = (
+ cur_token is not None and
+ cur_token.type in TOKENS_THAT_IMPLY_DIVISON
+ )
+ if is_division_allowed:
+ return self._get_update_token()
+ else:
+ self.prev_token = self.cur_token
+ self.cur_token = self._read_regex()
+ return self.cur_token
+
+ def auto_semi(self, token):
+ if (token is None or token.type == 'RBRACE'
+ or self._is_prev_token_lt()
+ ):
+ if token:
+ self.next_tokens.append(token)
+ return self._create_semi_token(token)
+
+ def _is_prev_token_lt(self):
+ return self.prev_token and self.prev_token.type == 'LINE_TERMINATOR'
def _read_regex(self):
self.lexer.begin('regex')
@@ -139,8 +160,29 @@ def _read_regex(self):
def _get_update_token(self):
self.prev_token = self.cur_token
self.cur_token = self.lexer.token()
+ # insert semicolon before restricted tokens
+ # See section 7.9.1 ECMA262
+ if (self.cur_token is not None
+ and self.cur_token.type == 'LINE_TERMINATOR'
+ and self.prev_token is not None
+ and self.prev_token.type in ['BREAK', 'CONTINUE',
+ 'RETURN', 'THROW']
+ ):
+ return self._create_semi_token(self.cur_token)
return self.cur_token
+ def _create_semi_token(self, orig_token):
+ token = ply.lex.LexToken()
+ token.type = 'SEMI'
+ token.value = ';'
+ if orig_token is not None:
+ token.lineno = orig_token.lineno
+ token.lexpos = orig_token.lexpos
+ else:
+ token.lineno = 0
+ token.lexpos = 0
+ return token
+
# iterator protocol
def __iter__(self):
return self
@@ -162,7 +204,7 @@ def next(self):
'INSTANCEOF', 'NEW', 'RETURN', 'SWITCH', 'THIS', 'THROW', 'TRY',
'TYPEOF', 'VAR', 'VOID', 'WHILE', 'WITH',
# future reserved words
- 'CLASS', 'CONST', 'ENUM', 'EXPORT', 'EXTENDS', 'IMPORT', 'SUPER',
+ # 'CLASS', 'CONST', 'ENUM', 'EXPORT', 'EXTENDS', 'IMPORT', 'SUPER',
)
keywords_dict = dict((key.lower(), key) for key in keywords)
@@ -173,8 +215,9 @@ def next(self):
# Punctuators
'PERIOD', 'COMMA', 'SEMI', 'COLON', # . , ; :
'PLUS', 'MINUS', 'MULT', 'DIV', 'MOD', # + - * / %
- 'BAND', 'BOR', 'BXOR', 'BNEG', # & | ^ ~
- 'QM', 'EM', # ? and !
+ 'BAND', 'BOR', 'BXOR', 'BNOT', # & | ^ ~
+ 'CONDOP', # conditional operator ?
+ 'NOT', # !
'LPAREN', 'RPAREN', # ( and )
'LBRACE', 'RBRACE', # { and }
'LBRACKET', 'RBRACKET', # [ and ]
@@ -199,8 +242,7 @@ def next(self):
# Comments
'LINE_COMMENT', 'BLOCK_COMMENT',
- # Automatically inserted semicolon
- # 'AUTOPLUSPLUS', 'AUTOMINUSMINUS', 'IF_WITHOUT_ELSE',
+ 'LINE_TERMINATOR',
) + keywords
# adapted from https://bitbucket.org/ned/jslex
@@ -250,9 +292,9 @@ def t_regex_error(self, token):
t_BAND = r'&'
t_BOR = r'\|'
t_BXOR = r'\^'
- t_BNEG = r'~'
- t_QM = r'\?'
- t_EM = r'!'
+ t_BNOT = r'~'
+ t_CONDOP = r'\?'
+ t_NOT = r'!'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'{'
@@ -287,10 +329,12 @@ def t_regex_error(self, token):
t_XOREQUAL = r'\^='
t_OREQUAL = r'\|='
- t_LINE_COMMENT = r'//.*?$'
- t_BLOCK_COMMENT = r'/\*(.|\n|\r)*?\*/'
+ t_LINE_COMMENT = r'//[^\r\n]*'
+ t_BLOCK_COMMENT = r'/\*[^*]*\*+([^/*][^*]*\*+)*/'
+
+ t_LINE_TERMINATOR = r'[\n\r]+'
- t_ignore = ' \t\n'
+ t_ignore = ' \t'
t_NUMBER = r"""
(?:
@@ -350,7 +394,7 @@ def t_FALSE(self, token):
# XXX: <ZWNJ> <ZWJ> ?
identifier_start = r'(?:' + r'[a-zA-Z_$]' + r'|' + LETTER + r')+'
identifier_part = (
- r'(?:' + COMBINING_MARK + r'|' + DIGIT +
+ r'(?:' + COMBINING_MARK + r'|' + r'[0-9a-zA-Z_$]' + r'|' + DIGIT +
r'|' + CONNECTOR_PUNCTUATION + r')*'
)
identifier = identifier_start + identifier_part
View
1,172 src/slimit/parser.py
@@ -0,0 +1,1172 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+import ply.yacc
+
+from slimit import ast
+from slimit.lexer import Lexer
+
+
+class Parser(object):
+ """JavaScript parser(ECMA-262 5th edition grammar).
+
+ The '*noin' variants are needed to avoid confusing the `in` operator in
+ a relational expression with the `in` operator in a `for` statement.
+
+ '*nobf' stands for 'no brace or function'
+ """
+
+ def __init__(self, lex_optimize=True, lextab='lextab',
+ yacc_optimize=True, yacctab='yacctab', yacc_debug=False):
+ self.lex_optimize = lex_optimize
+ self.lextab = lextab
+ self.yacc_optimize = yacc_optimize
+ self.yacctab = yacctab
+ self.yacc_debug = yacc_debug
+
+ self.lexer = Lexer()
+ self.lexer.build(optimize=lex_optimize, lextab=lextab)
+ self.tokens = self.lexer.tokens
+
+ self.parser = ply.yacc.yacc(
+ module=self, optimize=yacc_optimize,
+ debug=yacc_debug, tabmodule=yacctab, start='program')
+
+ def parse(self, text, debug=False):
+ return self.parser.parse(text, lexer=self.lexer, debug=debug)
+
+ def p_empty(self, p):
+ """empty :"""
+ pass
+
+ def p_auto_semi(self, p):
+ """auto_semi : error"""
+ pass
+
+ def p_error(self, token):
+ if token is None or token.type != 'SEMI':
+ next_token = self.lexer.auto_semi(token)
+ if next_token is not None:
+ self.parser.errok()
+ return next_token
+ elif token is not None and token.type == 'LINE_TERMINATOR':
+ # handle last LINE_TERMINATOR at the end of file
+ return self.lexer.token()
+
+ raise SyntaxError(
+ 'Unexpected token (%s, %r) at %s:%s between %s and %s' % (
+ token.type, token.value, token.lineno, token.lexpos,
+ self.lexer.prev_token, self.lexer.token())
+ )
+
+ # Comment rules
+ # def p_single_line_comment(self, p):
+ # """single_line_comment : LINE_COMMENT"""
+ # pass
+
+ # def p_multi_line_comment(self, p):
+ # """multi_line_comment : BLOCK_COMMENT"""
+ # pass
+
+ # Main rules
+
+ def p_program(self, p):
+ """program : source_elements"""
+ p[0] = ast.Program(p[1])
+
+ def p_source_elements(self, p):
+ """source_elements : empty
+ | source_element_list
+ """
+ p[0] = p[1]
+
+ def p_source_element_list(self, p):
+ """source_element_list : source_element
+ | source_element_list source_element
+ """
+ if len(p) == 2: # single source element
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[2])
+ p[0] = p[1]
+
+ def p_source_element(self, p):
+ """source_element : statement
+ | function_declaration
+ """
+ p[0] = p[1]
+
+ def p_statement(self, p):
+ """statement : block
+ | variable_statement
+ | empty_statement
+ | expr_statement
+ | if_statement
+ | iteration_statement
+ | continue_statement
+ | break_statement
+ | return_statement
+ | with_statement
+ | switch_statement
+ | labelled_statement
+ | throw_statement
+ | try_statement
+ | debugger_statement
+ """
+ p[0] = p[1]
+
+ # By having source_elements in the production we support
+ # also function_declaration inside blocks
+ def p_block(self, p):
+ """block : LBRACE source_elements RBRACE"""
+ p[0] = ast.Block(p[2])
+
+ def p_literal(self, p):
+ """literal : null_literal
+ | boolean_literal
+ | numeric_literal
+ | string_literal
+ | regex_literal
+ """
+ p[0] = p[1]
+
+ def p_boolean_literal(self, p):
+ """boolean_literal : TRUE
+ | FALSE
+ """
+ p[0] = ast.Boolean(p[1])
+
+ def p_null_literal(self, p):
+ """null_literal : NULL"""
+ p[0] = ast.Null(p[1])
+
+ def p_numeric_literal(self, p):
+ """numeric_literal : NUMBER"""
+ p[0] = ast.Number(p[1])
+
+ def p_string_literal(self, p):
+ """string_literal : STRING"""
+ p[0] = ast.String(p[1])
+
+ def p_regex_literal(self, p):
+ """regex_literal : REGEX"""
+ p[0] = ast.Regex(p[1])
+
+ def p_identifier(self, p):
+ """identifier : ID"""
+ p[0] = ast.Identifier(p[1])
+
+ ###########################################
+ # Expressions
+ ###########################################
+ def p_primary_expr(self, p):
+ """primary_expr : primary_expr_no_brace
+ | object_literal
+ """
+ p[0] = p[1]
+
+ def p_primary_expr_no_brace(self, p):
+ """primary_expr_no_brace : THIS
+ | identifier
+ | literal
+ | array_literal
+ | LPAREN expr RPAREN
+ """
+ if p[1] == 'this':
+ p[0] = ast.This()
+ elif len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[2]._parens = True
+ p[0] = p[2]
+
+ def p_array_literal_1(self, p):
+ """array_literal : LBRACKET elision_opt RBRACKET"""
+ p[0] = ast.Array(items=p[2])
+
+ def p_array_literal_2(self, p):
+ """array_literal : LBRACKET element_list RBRACKET
+ | LBRACKET element_list COMMA elision_opt RBRACKET
+ """
+ items = p[2]
+ if len(p) == 6:
+ items.extend(p[4])
+ p[0] = ast.Array(items=items)
+
+
+ def p_element_list(self, p):
+ """element_list : elision_opt assignment_expr
+ | element_list COMMA elision_opt assignment_expr
+ """
+ if len(p) == 3:
+ p[0] = p[1] + [p[2]]
+ else:
+ p[1].extend(p[3])
+ p[1].append(p[4])
+ p[0] = p[1]
+
+ def p_elision_opt_1(self, p):
+ """elision_opt : empty"""
+ p[0] = []
+
+ def p_elision_opt_2(self, p):
+ """elision_opt : elision"""
+ p[0] = p[1]
+
+ def p_elision(self, p):
+ """elision : COMMA
+ | elision COMMA
+ """
+ if len(p) == 2:
+ p[0] = [ast.Elision(p[1])]
+ else:
+ p[1].append(ast.Elision(p[2]))
+ p[0] = p[1]
+
+ def p_object_literal(self, p):
+ """object_literal : LBRACE RBRACE
+ | LBRACE property_list RBRACE
+ | LBRACE property_list COMMA RBRACE
+ """
+ if len(p) == 3:
+ p[0] = ast.Object()
+ else:
+ p[0] = ast.Object(properties=p[2])
+
+ def p_property_list(self, p):
+ """property_list : property_assignment
+ | property_list COMMA property_assignment
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[3])
+ p[0] = p[1]
+
+ # XXX: GET / SET
+ def p_property_assignment(self, p):
+ """property_assignment : property_name COLON assignment_expr"""
+ if len(p) == 4:
+ p[0] = ast.Assign(left=p[1], op=p[2], right=p[3])
+
+ def p_property_name(self, p):
+ """property_name : identifier
+ | string_literal
+ | numeric_literal
+ """
+ p[0] = p[1]
+
+ # 11.2 Left-Hand-Side Expressions
+ def p_member_expr(self, p):
+ """member_expr : primary_expr
+ | function_expr
+ | member_expr LBRACKET expr RBRACKET
+ | member_expr PERIOD identifier
+ | NEW member_expr arguments
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ elif p[1] == 'new':
+ p[0] = ast.NewExpr(p[2], p[3])
+ elif p[2] == '.':
+ p[0] = ast.DotAccessor(p[1], p[3])
+ else:
+ p[0] = ast.BracketAccessor(p[1], p[3])
+
+ def p_member_expr_nobf(self, p):
+ """member_expr_nobf : primary_expr_no_brace
+ | member_expr_nobf LBRACKET expr RBRACKET
+ | member_expr_nobf PERIOD identifier
+ | NEW member_expr arguments
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ elif p[1] == 'new':
+ p[0] = ast.NewExpr(p[2], p[3])
+ elif p[2] == '.':
+ p[0] = ast.DotAccessor(p[1], p[3])
+ else:
+ p[0] = ast.BracketAccessor(p[1], p[3])
+
+ def p_new_expr(self, p):
+ """new_expr : member_expr
+ | NEW new_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.NewExpr(p[2])
+
+ def p_new_expr_nobf(self, p):
+ """new_expr_nobf : member_expr_nobf
+ | NEW new_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.NewExpr(p[2])
+
+ def p_call_expr(self, p):
+ """call_expr : member_expr arguments
+ | call_expr arguments
+ | call_expr LBRACKET expr RBRACKET
+ | call_expr PERIOD identifier
+ """
+ if len(p) == 3:
+ p[0] = ast.FunctionCall(p[1], p[2])
+ elif len(p) == 4:
+ p[0] = ast.DotAccessor(p[1], p[3])
+ else:
+ p[0] = ast.BracketAccessor(p[1], p[3])
+
+ def p_call_expr_nobf(self, p):
+ """call_expr_nobf : member_expr_nobf arguments
+ | call_expr_nobf arguments
+ | call_expr_nobf LBRACKET expr RBRACKET
+ | call_expr_nobf PERIOD identifier
+ """
+ if len(p) == 3:
+ p[0] = ast.FunctionCall(p[1], p[2])
+ elif len(p) == 4:
+ p[0] = ast.DotAccessor(p[1], p[3])
+ else:
+ p[0] = ast.BracketAccessor(p[1], p[3])
+
+ def p_arguments(self, p):
+ """arguments : LPAREN RPAREN
+ | LPAREN argument_list RPAREN
+ """
+ if len(p) == 4:
+ p[0] = p[2]
+
+ def p_argument_list(self, p):
+ """argument_list : assignment_expr
+ | argument_list COMMA assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[3])
+ p[0] = p[1]
+
+ def p_lef_hand_side_expr(self, p):
+ """left_hand_side_expr : new_expr
+ | call_expr
+ """
+ p[0] = p[1]
+
+ def p_lef_hand_side_expr_nobf(self, p):
+ """left_hand_side_expr_nobf : new_expr_nobf
+ | call_expr_nobf
+ """
+ p[0] = p[1]
+
+ # 11.3 Postfix Expressions
+ def p_postfix_expr(self, p):
+ """postfix_expr : left_hand_side_expr
+ | left_hand_side_expr PLUSPLUS
+ | left_hand_side_expr MINUSMINUS
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.UnaryOp(op=p[2], value=p[1], postfix=True)
+
+ def p_postfix_expr_nobf(self, p):
+ """postfix_expr_nobf : left_hand_side_expr_nobf
+ | left_hand_side_expr_nobf PLUSPLUS
+ | left_hand_side_expr_nobf MINUSMINUS
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.UnaryOp(op=p[2], value=p[1], postfix=True)
+
+ # 11.4 Unary Operators
+ def p_unary_expr(self, p):
+ """unary_expr : postfix_expr
+ | unary_expr_common
+ """
+ p[0] = p[1]
+
+ def p_unary_expr_nobf(self, p):
+ """unary_expr_nobf : postfix_expr_nobf
+ | unary_expr_common
+ """
+ p[0] = p[1]
+
+ def p_unary_expr_common(self, p):
+ """unary_expr_common : DELETE unary_expr
+ | VOID unary_expr
+ | TYPEOF unary_expr
+ | PLUSPLUS unary_expr
+ | MINUSMINUS unary_expr
+ | PLUS unary_expr
+ | MINUS unary_expr
+ | BNOT unary_expr
+ | NOT unary_expr
+ """
+ p[0] = ast.UnaryOp(p[1], p[2])
+
+ # 11.5 Multiplicative Operators
+ def p_multiplicative_expr(self, p):
+ """multiplicative_expr : unary_expr
+ | multiplicative_expr MULT unary_expr
+ | multiplicative_expr DIV unary_expr
+ | multiplicative_expr MOD unary_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_multiplicative_expr_nobf(self, p):
+ """multiplicative_expr_nobf : unary_expr_nobf
+ | multiplicative_expr_nobf MULT unary_expr
+ | multiplicative_expr_nobf DIV unary_expr
+ | multiplicative_expr_nobf MOD unary_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.6 Additive Operators
+ def p_additive_expr(self, p):
+ """additive_expr : multiplicative_expr
+ | additive_expr PLUS multiplicative_expr
+ | additive_expr MINUS multiplicative_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_additive_expr_nobf(self, p):
+ """additive_expr_nobf : multiplicative_expr_nobf
+ | additive_expr_nobf PLUS multiplicative_expr
+ | additive_expr_nobf MINUS multiplicative_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.7 Bitwise Shift Operators
+ def p_shift_expr(self, p):
+ """shift_expr : additive_expr
+ | shift_expr LSHIFT additive_expr
+ | shift_expr RSHIFT additive_expr
+ | shift_expr URSHIFT additive_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_shift_expr_nobf(self, p):
+ """shift_expr_nobf : additive_expr_nobf
+ | shift_expr_nobf LSHIFT additive_expr
+ | shift_expr_nobf RSHIFT additive_expr
+ | shift_expr_nobf URSHIFT additive_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+
+ # 11.8 Relational Operators
+ def p_relational_expr(self, p):
+ """relational_expr : shift_expr
+ | relational_expr LT shift_expr
+ | relational_expr GT shift_expr
+ | relational_expr LE shift_expr
+ | relational_expr GE shift_expr
+ | relational_expr INSTANCEOF shift_expr
+ | relational_expr IN shift_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_relational_expr_noin(self, p):
+ """relational_expr_noin : shift_expr
+ | relational_expr_noin LT shift_expr
+ | relational_expr_noin GT shift_expr
+ | relational_expr_noin LE shift_expr
+ | relational_expr_noin GE shift_expr
+ | relational_expr_noin INSTANCEOF shift_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_relational_expr_nobf(self, p):
+ """relational_expr_nobf : shift_expr_nobf
+ | relational_expr_nobf LT shift_expr
+ | relational_expr_nobf GT shift_expr
+ | relational_expr_nobf LE shift_expr
+ | relational_expr_nobf GE shift_expr
+ | relational_expr_nobf INSTANCEOF shift_expr
+ | relational_expr_nobf IN shift_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.9 Equality Operators
+ def p_equality_expr(self, p):
+ """equality_expr : relational_expr
+ | equality_expr EQEQ relational_expr
+ | equality_expr NE relational_expr
+ | equality_expr STREQ relational_expr
+ | equality_expr STRNEQ relational_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_equality_expr_noin(self, p):
+ """equality_expr_noin : relational_expr_noin
+ | equality_expr_noin EQEQ relational_expr
+ | equality_expr_noin NE relational_expr
+ | equality_expr_noin STREQ relational_expr
+ | equality_expr_noin STRNEQ relational_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_equality_expr_nobf(self, p):
+ """equality_expr_nobf : relational_expr_nobf
+ | equality_expr_nobf EQEQ relational_expr
+ | equality_expr_nobf NE relational_expr
+ | equality_expr_nobf STREQ relational_expr
+ | equality_expr_nobf STRNEQ relational_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.10 Binary Bitwise Operators
+ def p_bitwise_and_expr(self, p):
+ """bitwise_and_expr : equality_expr
+ | bitwise_and_expr BAND equality_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_and_expr_noin(self, p):
+ """bitwise_and_expr_noin \
+ : equality_expr_noin
+ | bitwise_and_expr_noin BAND equality_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_and_expr_nobf(self, p):
+ """bitwise_and_expr_nobf \
+ : equality_expr_nobf
+ | bitwise_and_expr_nobf BAND equality_expr_nobf
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_xor_expr(self, p):
+ """bitwise_xor_expr : bitwise_and_expr
+ | bitwise_xor_expr BXOR bitwise_and_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_xor_expr_noin(self, p):
+ """
+ bitwise_xor_expr_noin \
+ : bitwise_and_expr_noin
+ | bitwise_xor_expr_noin BXOR bitwise_and_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_xor_expr_nobf(self, p):
+ """
+ bitwise_xor_expr_nobf \
+ : bitwise_and_expr_nobf
+ | bitwise_xor_expr_nobf BXOR bitwise_and_expr_nobf
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_or_expr(self, p):
+ """bitwise_or_expr : bitwise_xor_expr
+ | bitwise_or_expr BOR bitwise_xor_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_or_expr_noin(self, p):
+ """
+ bitwise_or_expr_noin \
+ : bitwise_xor_expr_noin
+ | bitwise_or_expr_noin BOR bitwise_xor_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_bitwise_or_expr_nobf(self, p):
+ """
+ bitwise_or_expr_nobf \
+ : bitwise_xor_expr_nobf
+ | bitwise_or_expr_nobf BOR bitwise_xor_expr_nobf
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.11 Binary Logical Operators
+ def p_logical_and_expr(self, p):
+ """logical_and_expr : bitwise_or_expr
+ | logical_and_expr AND bitwise_or_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_logical_and_expr_noin(self, p):
+ """
+ logical_and_expr_noin : bitwise_or_expr_noin
+ | logical_and_expr_noin AND bitwise_or_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_logical_and_expr_nobf(self, p):
+ """
+ logical_and_expr_nobf : bitwise_or_expr_nobf
+ | logical_and_expr_nobf AND bitwise_or_expr_nobf
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_logical_or_expr(self, p):
+ """logical_or_expr : logical_and_expr
+ | logical_or_expr OR logical_and_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_logical_or_expr_noin(self, p):
+ """logical_or_expr_noin : logical_and_expr_noin
+ | logical_or_expr_noin OR logical_and_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ def p_logical_or_expr_nobf(self, p):
+ """logical_or_expr_nobf : logical_and_expr_nobf
+ | logical_or_expr_nobf OR logical_and_expr_nobf
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.BinOp(op=p[2], left=p[1], right=p[3])
+
+ # 11.12 Conditional Operator ( ? : )
+ def p_conditional_expr(self, p):
+ """
+ conditional_expr \
+ : logical_or_expr
+ | logical_or_expr CONDOP assignment_expr COLON assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Conditional(
+ predicate=p[1], consequent=p[3], alternative=p[5])
+
+ def p_conditional_expr_noin(self, p):
+ """
+ conditional_expr_noin \
+ : logical_or_expr_noin
+ | logical_or_expr_noin CONDOP assignment_expr_noin COLON \
+ assignment_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Conditional(
+ predicate=p[1], consequent=p[3], alternative=p[5])
+
+ def p_conditional_expr_nobf(self, p):
+ """
+ conditional_expr_nobf \
+ : logical_or_expr_nobf
+ | logical_or_expr_nobf CONDOP assignment_expr COLON assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Conditional(
+ predicate=p[1], consequent=p[3], alternative=p[5])
+
+ # 11.13 Assignment Operators
+ def p_assignment_expr(self, p):
+ """
+ assignment_expr \
+ : conditional_expr
+ | left_hand_side_expr assignment_operator assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Assign(left=p[1], op=p[2], right=p[3])
+
+ def p_assignment_expr_noin(self, p):
+ """
+ assignment_expr_noin \
+ : conditional_expr_noin
+ | left_hand_side_expr assignment_operator assignment_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Assign(left=p[1], op=p[2], right=p[3])
+
+ def p_assignment_expr_nobf(self, p):
+ """
+ assignment_expr_nobf \
+ : conditional_expr_nobf
+ | left_hand_side_expr_nobf assignment_operator assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Assign(left=p[1], op=p[2], right=p[3])
+
+ def p_assignment_operator(self, p):
+ """assignment_operator : EQ
+ | MULTEQUAL
+ | DIVEQUAL
+ | MODEQUAL
+ | PLUSEQUAL
+ | MINUSEQUAL
+ | LSHIFTEQUAL
+ | RSHIFTEQUAL
+ | URSHIFTEQUAL
+ | ANDEQUAL
+ | XOREQUAL
+ | OREQUAL
+ """
+ p[0] = p[1]
+
+ # 11.4 Comma Operator
+ def p_expr(self, p):
+ """expr : assignment_expr
+ | expr COMMA assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Comma(left=p[1], right=p[3])
+
+ def p_expr_noin(self, p):
+ """expr_noin : assignment_expr_noin
+ | expr_noin COMMA assignment_expr_noin
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Comma(left=p[1], right=p[3])
+
+ def p_expr_nobf(self, p):
+ """expr_nobf : assignment_expr_nobf
+ | expr_nobf COMMA assignment_expr
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ast.Comma(left=p[1], right=p[3])
+
+ # 12.2 Variable Statement
+ def p_variable_statement(self, p):
+ """variable_statement : VAR variable_declaration_list SEMI
+ | VAR variable_declaration_list auto_semi
+ """
+ p[0] = ast.VarStatement(p[2])
+
+ def p_variable_declaration_list(self, p):
+ """
+ variable_declaration_list \
+ : variable_declaration
+ | variable_declaration_list COMMA variable_declaration
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[3])
+ p[0] = p[1]
+
+ def p_variable_declaration_list_noin(self, p):
+ """
+ variable_declaration_list_noin \
+ : variable_declaration_noin
+ | variable_declaration_list_noin COMMA variable_declaration_noin
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[3])
+ p[0] = p[1]
+
+ def p_variable_declaration(self, p):
+ """variable_declaration : identifier
+ | identifier initializer
+ """
+ if len(p) == 2:
+ p[0] = ast.VarDecl(p[1])
+ else:
+ p[0] = ast.VarDecl(p[1], p[2])
+
+ def p_variable_declaration_noin(self, p):
+ """variable_declaration_noin : identifier
+ | identifier initializer_noin
+ """
+ if len(p) == 2:
+ p[0] = ast.VarDecl(p[1])
+ else:
+ p[0] = ast.VarDecl(p[1], p[2])
+
+ def p_initializer(self, p):
+ """initializer : EQ assignment_expr"""
+ p[0] = p[2]
+
+ def p_initializer_noin(self, p):
+ """initializer_noin : EQ assignment_expr_noin"""
+ p[0] = p[2]
+
+ # 12.3 Empty Statement
+ def p_empty_statement(self, p):
+ """empty_statement : SEMI"""
+ p[0] = ast.EmptyStatement(p[1])
+
+ # 12.4 Expression Statement
+ def p_expr_statement(self, p):
+ """expr_statement : expr_nobf SEMI
+ | expr_nobf auto_semi
+ """
+ p[0] = ast.ExprStatement(p[1])
+
+ # 12.5 The if Statement
+ def p_if_statement_1(self, p):
+ """if_statement : IF LPAREN expr RPAREN statement"""
+ p[0] = ast.If(predicate=p[3], consequent=p[5])
+
+ def p_if_statement_2(self, p):
+ """if_statement : IF LPAREN expr RPAREN statement ELSE statement"""
+ p[0] = ast.If(predicate=p[3], consequent=p[5], alternative=p[7])
+
+ # 12.6 Iteration Statements
+ def p_iteration_statement_1(self, p):
+ """
+ iteration_statement \
+ : DO statement WHILE LPAREN expr RPAREN SEMI
+ | DO statement WHILE LPAREN expr RPAREN auto_semi
+ """
+ p[0] = ast.DoWhile(predicate=p[5], statement=p[2])
+
+ def p_iteration_statement_2(self, p):
+ """iteration_statement : WHILE LPAREN expr RPAREN statement"""
+ p[0] = ast.While(predicate=p[3], statement=p[5])
+
+ def p_iteration_statement_3(self, p):
+ """
+ iteration_statement \
+ : FOR LPAREN expr_noin_opt SEMI expr_opt SEMI expr_opt RPAREN \
+ statement
+ | FOR LPAREN VAR variable_declaration_list_noin SEMI expr_opt SEMI\
+ expr_opt RPAREN statement
+ """
+ if len(p) == 10:
+ p[0] = ast.For(init=p[3], cond=p[5], count=p[7], statement=p[9])
+ else:
+ init = ast.VarStatement(p[4])
+ p[0] = ast.For(init=init, cond=p[6], count=p[8], statement=p[10])
+
+ def p_iteration_statement_4(self, p):
+ """
+ iteration_statement \
+ : FOR LPAREN left_hand_side_expr IN expr RPAREN statement
+ """
+ p[0] = ast.ForIn(item=p[3], iterable=p[5], statement=p[7])
+
+ def p_iteration_statement_5(self, p):
+ """
+ iteration_statement : \
+ FOR LPAREN VAR identifier IN expr RPAREN statement
+ """
+ p[0] = ast.ForIn(item=ast.VarDecl(p[4]), iterable=p[6], statement=p[8])
+
+ def p_iteration_statement_6(self, p):
+ """
+ iteration_statement \
+ : FOR LPAREN VAR identifier initializer_noin IN expr RPAREN statement
+ """
+ p[0] = ast.ForIn(item=ast.VarDecl(identifier=p[4], initializer=p[5]),
+ iterable=p[7], statement=p[9])
+
+ def p_expr_opt(self, p):
+ """expr_opt : empty
+ | expr
+ """
+ p[0] = p[1]
+
+ def p_expr_noin_opt(self, p):
+ """expr_noin_opt : empty
+ | expr_noin
+ """
+ p[0] = p[1]
+
+ # 12.7 The continue Statement
+ def p_continue_statement_1(self, p):
+ """continue_statement : CONTINUE SEMI
+ | CONTINUE auto_semi
+ """
+ p[0] = ast.Continue()
+
+ def p_continue_statement_2(self, p):
+ """continue_statement : CONTINUE identifier SEMI
+ | CONTINUE identifier auto_semi
+ """
+ p[0] = ast.Continue(p[2])
+
+ # 12.8 The break Statement
+ def p_break_statement_1(self, p):
+ """break_statement : BREAK SEMI
+ | BREAK auto_semi
+ """
+ p[0] = ast.Break()
+
+ def p_break_statement_2(self, p):
+ """break_statement : BREAK identifier SEMI
+ | BREAK identifier auto_semi
+ """
+ p[0] = ast.Break(p[2])
+
+
+ # 12.9 The return Statement
+ def p_return_statement_1(self, p):
+ """return_statement : RETURN SEMI
+ | RETURN auto_semi
+ """
+ p[0] = ast.Return()
+
+ def p_return_statement_2(self, p):
+ """return_statement : RETURN expr SEMI
+ | RETURN expr auto_semi
+ """
+ p[0] = ast.Return(expr=p[2])
+
+ # 12.10 The with Statement
+ def p_with_statement(self, p):
+ """with_statement : WITH LPAREN expr RPAREN statement"""
+ p[0] = ast.With(expr=p[3], statement=p[5])
+
+ # 12.11 The switch Statement
+ def p_switch_statement(self, p):
+ """switch_statement : SWITCH LPAREN expr RPAREN case_block"""
+ cases = []
+ default = None
+ # iterate over return values from case_block
+ for item in p[5]:
+ if isinstance(item, ast.Default):
+ default = item
+ elif isinstance(item, list):
+ cases.extend(item)
+
+ p[0] = ast.Switch(expr=p[3], cases=cases, default=default)
+
+ def p_case_block(self, p):
+ """
+ case_block \
+ : LBRACE case_clauses_opt RBRACE
+ | LBRACE case_clauses_opt default_clause case_clauses_opt RBRACE
+ """
+ p[0] = p[2:-1]
+
+ def p_case_clauses_opt(self, p):
+ """case_clauses_opt : empty
+ | case_clauses
+ """
+ p[0] = p[1]
+
+ def p_case_clauses(self, p):
+ """case_clauses : case_clause
+ | case_clauses case_clause
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[2])
+ p[0] = p[1]
+
+ def p_case_clause(self, p):
+ """case_clause : CASE expr COLON source_elements"""
+ p[0] = ast.Case(expr=p[2], elements=p[4])
+
+ def p_default_clause(self, p):
+ """default_clause : DEFAULT COLON source_elements"""
+ p[0] = ast.Default(elements=p[3])
+
+ # 12.12 Labelled Statements
+ def p_labelled_statement(self, p):
+ """labelled_statement : identifier COLON statement"""
+ p[0] = ast.Label(identifier=p[1], statement=p[3])
+
+ # 12.13 The throw Statement
+ def p_throw_statement(self, p):
+ """throw_statement : THROW expr SEMI
+ | THROW expr auto_semi
+ """
+ p[0] = ast.Throw(expr=p[2])
+
+ # 12.14 The try Statement
+ def p_try_statement_1(self, p):
+ """try_statement : TRY block catch"""
+ p[0] = ast.Try(statements=p[2], catch=p[3])
+
+ def p_try_statement_2(self, p):
+ """try_statement : TRY block finally"""
+ p[0] = ast.Try(statements=p[2], fin=p[3])
+
+ def p_try_statement_3(self, p):
+ """try_statement : TRY block catch finally"""
+ p[0] = ast.Try(statements=p[2], catch=p[3], fin=p[4])
+
+ def p_catch(self, p):
+ """catch : CATCH LPAREN identifier RPAREN block"""
+ p[0] = ast.Catch(identifier=p[3], elements=p[5])
+
+ def p_finally(self, p):
+ """finally : FINALLY block"""
+ p[0] = ast.Finally(elements=p[2])
+
+ # 12.15 The debugger statement
+ def p_debugger_statement(self, p):
+ """debugger_statement : DEBUGGER SEMI
+ | DEBUGGER auto_semi
+ """
+ p[0] = ast.Debugger(p[1])
+
+ # 13 Function Definition
+ def p_function_declaration(self, p):
+ """
+ function_declaration \
+ : FUNCTION identifier LPAREN RPAREN LBRACE function_body RBRACE
+ | FUNCTION identifier LPAREN formal_parameter_list RPAREN LBRACE \
+ function_body RBRACE
+ """
+ if len(p) == 8:
+ p[0] = ast.FuncDecl(
+ identifier=p[2], parameters=None, elements=p[6])
+ else:
+ p[0] = ast.FuncDecl(
+ identifier=p[2], parameters=p[4], elements=p[7])
+
+ def p_function_expr_1(self, p):
+ """
+ function_expr \
+ : FUNCTION LPAREN RPAREN LBRACE function_body RBRACE
+ | FUNCTION LPAREN formal_parameter_list RPAREN \
+ LBRACE function_body RBRACE
+ """
+ if len(p) == 7:
+ p[0] = ast.FuncExpr(
+ identifier=None, parameters=None, elements=p[5])
+ else:
+ p[0] = ast.FuncExpr(
+ identifier=None, parameters=p[3], elements=p[6])
+
+ def p_function_expr_2(self, p):
+ """
+ function_expr \
+ : FUNCTION identifier LPAREN RPAREN LBRACE function_body RBRACE
+ | FUNCTION identifier LPAREN formal_parameter_list RPAREN \
+ LBRACE function_body RBRACE
+ """
+ if len(p) == 8:
+ p[0] = ast.FuncExpr(
+ identifier=p[2], parameters=None, elements=p[6])
+ else:
+ p[0] = ast.FuncExpr(
+ identifier=p[2], parameters=p[4], elements=p[7])
+
+
+ def p_formal_parameter_list(self, p):
+ """formal_parameter_list : identifier
+ | formal_parameter_list COMMA identifier
+ """
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[1].append(p[3])
+ p[0] = p[1]
+
+ def p_function_body(self, p):
+ """function_body : source_elements"""
+ p[0] = p[1]
View
444 src/slimit/tests/test_ecmavisitor.py
@@ -0,0 +1,444 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+import textwrap
+import unittest
+
+from slimit.parser import Parser
+
+
+class ECMAVisitorTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.maxDiff = 2000
+
+ TEST_CASES = [
+ ################################
+ # block
+ ################################
+ """
+ {
+ var a = 5;
+ }
+ """,
+
+ ################################
+ # variable statement
+ ################################
+ """
+ var a;
+ var b;
+ var a, b = 3;
+ var a = 1, b;
+ var a = 5, b = 7;
+ """,
+
+ # empty statement
+ """
+ ;
+ ;
+ ;
+ """,
+
+ # test 3
+ ################################
+ # if
+ ################################
+ 'if (true) var x = 100;',
+
+ """
+ if (true) {
+ var x = 100;
+ var y = 200;
+ }
+ """,
+
+ 'if (true) if (true) var x = 100; else var y = 200;',
+
+ # test 6
+ """
+ if (true) {
+ var x = 100;
+ } else {
+ var y = 200;
+ }
+ """,
+ ################################
+ # iteration
+ ################################
+ """
+ for (i = 0; i < 10; i++) {
+ x = 10 * i;
+ }
+ """,
+
+ """
+ for (var i = 0; i < 10; i++) {
+ x = 10 * i;
+ }
+ """,
+
+ # test 9
+ """
+ for (i = 0, j = 10; i < j && j < 15; i++, j++) {
+ x = i * j;
+ }
+ """,
+
+ """
+ for (var i = 0, j = 10; i < j && j < 15; i++, j++) {
+ x = i * j;
+ }
+ """,
+
+ """
+ for (p in obj) {
+
+ }
+ """,
+
+ # test 12
+ """
+ for (var p in obj) {
+ p = 1;
+ }
+ """,
+
+ """
+ do {
+ x += 1;
+ } while (true);
+ """,
+
+ """
+ while (false) {
+ x = null;
+ }
+ """,
+
+ # test 15
+ ################################
+ # continue statement
+ ################################
+ """
+ while (true) {
+ continue;
+ s = 'I am not reachable';
+ }
+ """,
+
+ """
+ while (true) {
+ continue label1;
+ s = 'I am not reachable';
+ }
+ """,
+
+ ################################
+ # break statement
+ ################################
+ """
+ while (true) {
+ break;
+ s = 'I am not reachable';
+ }
+ """,
+ # test 18
+ """
+ while (true) {
+ break label1;
+ s = 'I am not reachable';
+ }
+ """,
+
+ ################################
+ # return statement
+ ################################
+ """
+ {
+ return;
+ }
+ """,
+
+ """
+ {
+ return 1;
+ }
+ """,
+
+ # test21
+ ################################
+ # with statement
+ ################################
+ """
+ with (x) {
+ var y = x * 2;
+ }
+ """,
+
+ ################################
+ # labelled statement
+ ################################
+ """
+ label: while (true) {
+ x *= 3;
+ }
+ """,
+
+ ################################
+ # switch statement
+ ################################
+ """
+ switch (day_of_week) {
+ case 6:
+ case 7:
+ x = 'Weekend';
+ break;
+ case 1:
+ x = 'Monday';
+ break;
+ default:
+ break;
+ }
+ """,
+
+ # test 24
+ ################################
+ # throw statement
+ ################################
+ """
+ throw 'exc';
+ """,
+
+ ################################
+ # debugger statement
+ ################################
+ 'debugger;',
+
+ ################################
+ # expression statement
+ ################################
+ """
+ 5 + 7 - 20 * 10;
+ ++x;
+ --x;
+ x++;
+ x--;
+ x = 17 /= 3;
+ s = mot ? z : /x:3;x<5;y</g / i;
+ """,
+
+ # test 27
+ ################################
+ # try statement
+ ################################
+ """
+ try {
+ x = 3;
+ } catch (exc) {
+ x = exc;
+ }
+ """,
+
+ """
+ try {
+ x = 3;
+ } finally {
+ x = null;
+ }
+ """,
+
+ """
+ try {
+ x = 5;
+ } catch (exc) {
+ x = exc;
+ } finally {
+ y = null;
+ }
+ """,
+
+ # test 30
+ ################################
+ # function
+ ################################
+ """
+ function foo(x, y) {
+ z = 10;
+ return x + y + z;
+ }
+ """,
+
+ """
+ function foo() {
+ return 10;
+ }
+ """,
+
+ """
+ var a = function() {
+ return 10;
+ };
+ """,
+ # test 33
+ """
+ var a = function foo(x, y) {
+ return x + y;
+ };
+ """,
+ # nested function declaration
+ """
+ function foo() {
+ function bar() {
+
+ }
+ }
+ """,
+
+ """
+ var mult = function(x) {
+ return x * 10;
+ }();
+ """,
+
+ # function call
+ # test 36
+ 'foo();',
+ 'foo(x, 7);',
+ 'foo()[10];',
+ # test 39
+ 'foo().foo;',
+
+ ################################
+ # misc
+ ################################
+
+ # new
+ 'var foo = new Foo();',
+ # dot accessor
+ 'var bar = new Foo.Bar();',
+
+ # test 42
+ # bracket accessor
+ 'var bar = new Foo.Bar()[7];',
+
+ # object literal
+ """
+ var obj = {
+ foo: 10,
+ bar: 20
+ };
+ """,
+ """
+ var obj = {
+ 1: 'a',
+ 2: 'b'
+ };
+ """,
+ # test 45
+ """
+ var obj = {
+ 'a': 100,
+ 'b': 200
+ };
+ """,
+ """
+ var obj = {
+ };
+ """,
+
+ # array
+ """
+ var a = [1,2,3,4,5];
+ var res = a[3];
+ """,
+ # test 48
+ # elision
+ 'var a = [,,,];',
+ 'var a = [1,,,4];',
+ 'var a = [1,,3,,5];',
+
+ # test 51
+ """
+ String.prototype.foo = function(data) {
+ var tmpl = this.toString();
+ return tmpl.replace(/{{\s*(.*?)\s*}}/g, function(a, b) {
+ var node = data;
+ if (true) {
+ var value = true;
+ } else {
+ var value = false;
+ }
+ $.each(n.split('.'), function(i, sym) {
+ node = node[sym];
+ });
+ return node;
+ });
+ };
+ """,
+
+ #######################################
+ # Make sure parentheses are not removed
+ #######################################
+
+ # ... Expected an identifier and instead saw '/'
+ 'Expr.match[type].source + (/(?![^\[]*\])(?![^\(]*\))/.source);',
+
+ '(options = arguments[i]) != null;',
+
+ # test 54
+ 'return (/h\d/i).test(elem.nodeName);',
+
+ """
+ (function() {
+ x = 5;
+ })();
+ """,
+
+ 'return !(match === true || elem.getAttribute("classid") !== match);',
+
+ # test 57
+ 'var el = (elem ? elem.ownerDocument || elem : 0).documentElement;',
+
+ # typeof
+ 'typeof second.length === "number";',
+ ]
+
+def make_test_function(input, expected):
+
+ def test_func(self):
+ parser = Parser()
+ result = parser.parse(input).to_ecma()
+ self.assertMultiLineEqual(result, expected)
+
+ return test_func
+
+for index, input in enumerate(ECMAVisitorTestCase.TEST_CASES):
+ input = textwrap.dedent(input).strip()
+ func = make_test_function(input, input)
+ setattr(ECMAVisitorTestCase, 'test_case_%d' % index, func)
+
+