├── .coveragerc ├── .gitignore ├── CHANGES ├── CREDIT ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile └── source │ ├── _static │ ├── feed-24x24.png │ ├── github-24x24.png │ ├── linkedin-24x24.png │ ├── slimit-small.png │ └── twitter-24x24.png │ ├── _templates │ ├── sidebarintro.html │ └── sidebarlogo.html │ ├── _theme │ └── nature │ │ ├── layout.html │ │ ├── static │ │ └── nature.css_t │ │ └── theme.conf │ ├── conf.py │ └── index.rst ├── setup.py └── src └── slimit ├── __init__.py ├── ast.py ├── lexer.py ├── lextab.py ├── mangler.py ├── minifier.py ├── parser.py ├── scope.py ├── tests ├── __init__.py ├── test_cmd.py ├── test_ecmavisitor.py ├── test_lexer.py ├── test_mangler.py ├── test_minifier.py ├── test_nodevisitor.py └── test_parser.py ├── unicode_chars.py ├── visitors ├── __init__.py ├── ecmavisitor.py ├── minvisitor.py ├── nodevisitor.py └── scopevisitor.py └── yacctab.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | /Users/*/.buildout/eggs/* 4 | /home/*/.buildout/eggs/* 5 | parts/* 6 | eggs/* 7 | */test* 8 | *lextab.py 9 | *yacctab.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .installed.cfg 4 | .coverage 5 | htmlcov 6 | bin 7 | build 8 | develop-eggs 9 | dist 10 | downloads 11 | eggs 12 | htmlcov 13 | parts 14 | src/*.egg-info 15 | var -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Change History 2 | ============== 3 | 0.8.1 (2013-03-26) 4 | ------------------ 5 | - Bug fix: https://github.com/rspivak/slimit/pull/45 6 | Fix syntax error in the output of for statement with some form of expressions 7 | 8 | 0.8.0 (2013-03-23) 9 | ------------------ 10 | - Python 3.x support 11 | - Bug fix: https://github.com/rspivak/slimit/issues/42 12 | slimit removes parentheses from ternary expression, causes syntax error in jQuery 13 | - Bug fix: https://github.com/rspivak/slimit/issues/37 14 | simple identifier in FOR init 15 | - Bug fix: https://github.com/rspivak/slimit/issues/36 16 | using $ for mangled function names conflicts with jQuery 17 | 18 | 0.7.4 (2012-06-5) 19 | ------------------ 20 | - Bug fix: https://github.com/rspivak/slimit/issues/34 21 | 'class' is reserved keyword now 22 | 23 | 0.7.3 (2012-05-21) 24 | ------------------ 25 | - Bug fix (unary op in FOR init): https://github.com/rspivak/slimit/pull/33 26 | 27 | 0.7.2 (2012-05-17) 28 | ------------------ 29 | - Added support for get/set properties: 30 | https://github.com/rspivak/slimit/issues/32 31 | 32 | 0.7.1 (2012-05-10) 33 | ------------------ 34 | - Function call support in FOR init section: 35 | https://github.com/rspivak/slimit/pull/31 36 | 37 | 0.7 (2012-04-16) 38 | ---------------- 39 | - Multiline string support: https://github.com/rspivak/slimit/issues/24 40 | 41 | 0.6.2 (2012-04-07) 42 | ------------------ 43 | - Bug fix: https://github.com/rspivak/slimit/issues/29 44 | - Bug fix: https://github.com/rspivak/slimit/issues/28 45 | 46 | 0.6.1 (2012-03-15) 47 | ------------------ 48 | - Added command-line option *-t/--mangle-toplevel* to turn on 49 | global scope name mangling. As of this version it's off by 50 | default: https://github.com/rspivak/slimit/issues/27 51 | - Removed dependency on a 'distribute' package 52 | - Bug fix: https://github.com/rspivak/slimit/issues/26 53 | - Bug fix: https://github.com/rspivak/slimit/issues/25 54 | 55 | 0.6 (2012-02-04) 56 | ---------------- 57 | - Added optimization: foo["bar"] ==> foo.bar 58 | - Added base class for custom AST node visitors 59 | - Documentation updates 60 | - Bug fix: https://github.com/rspivak/slimit/issues/22 61 | - Bug fix: https://github.com/rspivak/slimit/issues/21 62 | 63 | 0.5.5 (2011-10-05) 64 | ------------------ 65 | - Bugfix: https://github.com/rspivak/slimit/issues/7 66 | 67 | 0.5.4 (2011-10-01) 68 | ------------------ 69 | - Bugfix: https://github.com/rspivak/slimit/issues/6 70 | Division with "this" fails 71 | 72 | 0.5.3 (2011-06-29) 73 | ------------------ 74 | - Bugfix: https://github.com/rspivak/slimit/issues/5 75 | 76 | 0.5.2 (2011-06-14) 77 | ------------------ 78 | - Bugfix: https://github.com/rspivak/slimit/issues/4 79 | - Bugfix: https://github.com/rspivak/slimit/issues/3 80 | 81 | 0.5.1 (2011-06-06) 82 | ------------------ 83 | - Bugfix: https://github.com/rspivak/slimit/issues/2 84 | 85 | 0.5 (2011-06-06) 86 | ---------------- 87 | - Added name mangling 88 | 89 | 0.4 (2011-05-12) 90 | ---------------- 91 | - Minify more by removing block braces { } 92 | - More tests 93 | 94 | 0.3.2 (2011-05-09) 95 | ------------------ 96 | - More hacks to use pre-generated lex and yacc tables when called from 97 | the command line 98 | 99 | 0.3.1 (2011-05-09) 100 | ------------------ 101 | - Use pre-generated lex and yacc tables when called from the command line 102 | 103 | 0.3 (2011-05-09) 104 | ---------------- 105 | - Added minifier 106 | 107 | 0.2 (2011-05-07) 108 | ---------------- 109 | - Added a JavaScript parser 110 | - Added pretty printer 111 | - Added node visitor 112 | 113 | 0.1 (2011-05-02) 114 | ---------------- 115 | - Initial public version. It contains only a JavaScript lexer 116 | -------------------------------------------------------------------------------- /CREDIT: -------------------------------------------------------------------------------- 1 | Patches 2 | ------- 3 | 4 | - Waldemar Kornewald 5 | - Maurizio Sambati https://github.com/duilio 6 | - Aron Griffis https://github.com/agriffis 7 | - lelit https://github.com/lelit 8 | - Dan McDougall https://github.com/liftoff 9 | - harig https://github.com/harig 10 | - Mike Taylor https://github.com/miketaylr 11 | 12 | 13 | Bug reports 14 | ----------- 15 | 16 | - Rui Pereira 17 | - Dima Kozlov 18 | - BadKnees https://github.com/BadKnees 19 | - Waldemar Kornewald 20 | - Michał Bartoszkiewicz https://github.com/embe 21 | - Hasan Yasin Öztürk https://github.com/hasanyasin 22 | - David K. Hess https://github.com/davidkhess 23 | - Robert Cadena https://github.com/rcadena 24 | - rivol https://github.com/rivol 25 | - Maurizio Sambati https://github.com/duilio 26 | - fdev31 https://github.com/fdev31 27 | - edmellum https://github.com/edmellum -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Ruslan Spivak 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst CHANGES -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | :: 2 | 3 | _____ _ _____ __ __ _____ _______ 4 | / ____| | |_ _| \/ |_ _|__ __| 5 | | (___ | | | | | \ / | | | | | 6 | \___ \| | | | | |\/| | | | | | 7 | ____) | |____ _| |_| | | |_| |_ | | 8 | |_____/|______|_____|_| |_|_____| |_| 9 | 10 | 11 | Welcome to SlimIt 12 | ================================== 13 | 14 | `SlimIt` is a JavaScript minifier written in Python. 15 | It compiles JavaScript into more compact code so that it downloads 16 | and runs faster. 17 | 18 | `SlimIt` also provides a library that includes a JavaScript parser, 19 | lexer, pretty printer and a tree visitor. 20 | 21 | `https://slimit.readthedocs.io/ `_ 22 | 23 | Installation 24 | ------------ 25 | 26 | :: 27 | 28 | $ [sudo] pip install slimit 29 | 30 | Or the bleeding edge version from the git master branch: 31 | 32 | :: 33 | 34 | $ [sudo] pip install git+https://github.com/rspivak/slimit.git#egg=slimit 35 | 36 | 37 | There is also an official DEB package available at 38 | `http://packages.debian.org/sid/slimit `_ 39 | 40 | 41 | Let's minify some code 42 | ---------------------- 43 | 44 | From the command line: 45 | 46 | :: 47 | 48 | $ slimit -h 49 | Usage: slimit [options] [input file] 50 | 51 | If no input file is provided STDIN is used by default. 52 | Minified JavaScript code is printed to STDOUT. 53 | 54 | Options: 55 | -h, --help show this help message and exit 56 | -m, --mangle mangle names 57 | -t, --mangle-toplevel 58 | mangle top level scope (defaults to False) 59 | 60 | $ cat test.js 61 | var foo = function( obj ) { 62 | for ( var name in obj ) { 63 | return false; 64 | } 65 | return true; 66 | }; 67 | $ 68 | $ slimit --mangle < test.js 69 | var foo=function(a){for(var b in a)return false;return true;}; 70 | 71 | Or using library API: 72 | 73 | >>> from slimit import minify 74 | >>> text = """ 75 | ... var foo = function( obj ) { 76 | ... for ( var name in obj ) { 77 | ... return false; 78 | ... } 79 | ... return true; 80 | ... }; 81 | ... """ 82 | >>> print minify(text, mangle=True, mangle_toplevel=True) 83 | var a=function(a){for(var b in a)return false;return true;}; 84 | 85 | 86 | Iterate over, modify a JavaScript AST and pretty print it 87 | --------------------------------------------------------- 88 | 89 | >>> from slimit.parser import Parser 90 | >>> from slimit.visitors import nodevisitor 91 | >>> from slimit import ast 92 | >>> 93 | >>> parser = Parser() 94 | >>> tree = parser.parse('for(var i=0; i<10; i++) {var x=5+i;}') 95 | >>> for node in nodevisitor.visit(tree): 96 | ... if isinstance(node, ast.Identifier) and node.value == 'i': 97 | ... node.value = 'hello' 98 | ... 99 | >>> print tree.to_ecma() # print awesome javascript :) 100 | for (var hello = 0; hello < 10; hello++) { 101 | var x = 5 + hello; 102 | } 103 | >>> 104 | 105 | Writing custom node visitor 106 | --------------------------- 107 | 108 | >>> from slimit.parser import Parser 109 | >>> from slimit.visitors.nodevisitor import ASTVisitor 110 | >>> 111 | >>> text = """ 112 | ... var x = { 113 | ... "key1": "value1", 114 | ... "key2": "value2" 115 | ... }; 116 | ... """ 117 | >>> 118 | >>> class MyVisitor(ASTVisitor): 119 | ... def visit_Object(self, node): 120 | ... """Visit object literal.""" 121 | ... for prop in node: 122 | ... left, right = prop.left, prop.right 123 | ... print 'Property key=%s, value=%s' % (left.value, right.value) 124 | ... # visit all children in turn 125 | ... self.visit(prop) 126 | ... 127 | >>> 128 | >>> parser = Parser() 129 | >>> tree = parser.parse(text) 130 | >>> visitor = MyVisitor() 131 | >>> visitor.visit(tree) 132 | Property key="key1", value="value1" 133 | Property key="key2", value="value2" 134 | 135 | Using lexer in your project 136 | --------------------------- 137 | 138 | >>> from slimit.lexer import Lexer 139 | >>> lexer = Lexer() 140 | >>> lexer.input('a = 1;') 141 | >>> for token in lexer: 142 | ... print token 143 | ... 144 | LexToken(ID,'a',1,0) 145 | LexToken(EQ,'=',1,2) 146 | LexToken(NUMBER,'1',1,4) 147 | LexToken(SEMI,';',1,5) 148 | 149 | You can get one token at a time using ``token`` method: 150 | 151 | >>> lexer.input('a = 1;') 152 | >>> while True: 153 | ... token = lexer.token() 154 | ... if not token: 155 | ... break 156 | ... print token 157 | ... 158 | LexToken(ID,'a',1,0) 159 | LexToken(EQ,'=',1,2) 160 | LexToken(NUMBER,'1',1,4) 161 | LexToken(SEMI,';',1,5) 162 | 163 | `LexToken` instance has different attributes: 164 | 165 | >>> lexer.input('a = 1;') 166 | >>> token = lexer.token() 167 | >>> token.type, token.value, token.lineno, token.lexpos 168 | ('ID', 'a', 1, 0) 169 | 170 | Benchmarks 171 | ---------- 172 | 173 | **SAM** - JQuery size after minification in bytes (the smaller number the better) 174 | 175 | +-------------------------------+------------+------------+------------+ 176 | | Original jQuery 1.6.1 (bytes) | SlimIt SAM | rJSmin SAM | jsmin SAM | 177 | +===============================+============+============+============+ 178 | | 234,995 | 94,290 | 134,215 | 134,819 | 179 | +-------------------------------+------------+------------+------------+ 180 | 181 | Roadmap 182 | ------- 183 | - when doing name mangling handle cases with 'eval' and 'with' 184 | - foo["bar"] ==> foo.bar 185 | - consecutive declarations: var a = 10; var b = 20; ==> var a=10,b=20; 186 | - reduce simple constant expressions if the result takes less space: 187 | 1 +2 * 3 ==> 7 188 | - IF statement optimizations 189 | 190 | 1. if (foo) bar(); else baz(); ==> foo?bar():baz(); 191 | 2. if (!foo) bar(); else baz(); ==> foo?baz():bar(); 192 | 3. if (foo) bar(); ==> foo&&bar(); 193 | 4. if (!foo) bar(); ==> foo||bar(); 194 | 5. if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); 195 | 6. if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} 196 | 197 | - remove unreachable code that follows a return, throw, break or 198 | continue statement, except function/variable declarations 199 | - parsing speed improvements 200 | 201 | Acknowledgments 202 | --------------- 203 | - The lexer and parser are built with `PLY `_ 204 | - Several test cases and regexes from `jslex `_ 205 | - Some visitor ideas - `pycparser `_ 206 | - Many grammar rules are taken from `rkelly `_ 207 | - Name mangling and different optimization ideas - `UglifyJS `_ 208 | - ASI implementation was inspired by `pyjsparser `_ 209 | 210 | License 211 | ------- 212 | The MIT License (MIT) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/slimit.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/slimit.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/slimit" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/slimit" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/source/_static/feed-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/docs/source/_static/feed-24x24.png -------------------------------------------------------------------------------- /docs/source/_static/github-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/docs/source/_static/github-24x24.png -------------------------------------------------------------------------------- /docs/source/_static/linkedin-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/docs/source/_static/linkedin-24x24.png -------------------------------------------------------------------------------- /docs/source/_static/slimit-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/docs/source/_static/slimit-small.png -------------------------------------------------------------------------------- /docs/source/_static/twitter-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/docs/source/_static/twitter-24x24.png -------------------------------------------------------------------------------- /docs/source/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 |

About SlimIt

2 |

3 | SlimIt is a JavaScript minifier 4 |

5 |

Useful Links

6 | 10 |

Author

11 |

12 | 13 | blog 14 | 15 | 16 | linkedin 17 | 18 | 19 | twitter 20 | 21 | 22 | github 23 | 24 |

25 | -------------------------------------------------------------------------------- /docs/source/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/source/_theme/nature/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | 3 | {%- block extrahead %} 4 | Fork me on GitHub 5 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /docs/source/_theme/nature/static/nature.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * nature.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- nature theme. 6 | * 7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: Arial, sans-serif; 18 | font-size: 100%; 19 | background-color: #111; 20 | color: #555; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 0 0 0 230px; 32 | } 33 | 34 | hr { 35 | border: 1px solid #B1B4B6; 36 | } 37 | 38 | div.document { 39 | background-color: #fafafa; 40 | } 41 | 42 | div.body { 43 | background-color: #ffffff; 44 | color: #3E4349; 45 | padding: 0 30px 30px 30px; 46 | font-size: 17px; 47 | } 48 | 49 | div.footer { 50 | color: #555; 51 | width: 100%; 52 | padding: 13px 0; 53 | text-align: center; 54 | font-size: 75%; 55 | } 56 | 57 | div.footer a { 58 | color: #444; 59 | text-decoration: underline; 60 | } 61 | 62 | div.related { 63 | background-color: #6BA81E; 64 | line-height: 32px; 65 | color: #fff; 66 | font-size: 0.9em; 67 | } 68 | 69 | div.related a { 70 | color: #E2F3CC; 71 | } 72 | 73 | div.sphinxsidebar { 74 | font-size: 15px; 75 | line-height: 1.5em; 76 | } 77 | 78 | div.sphinxsidebarwrapper{ 79 | padding: 20px 0; 80 | } 81 | 82 | div.sphinxsidebar h3, 83 | div.sphinxsidebar h4 { 84 | font-family: Arial, sans-serif; 85 | color: #222; 86 | font-size: 1.4em; 87 | font-weight: normal; 88 | margin: 0; 89 | padding: 5px 10px; 90 | background-color: #ddd; 91 | } 92 | 93 | div.sphinxsidebar h4{ 94 | font-size: 1.1em; 95 | } 96 | 97 | div.sphinxsidebar h3 a { 98 | color: #444; 99 | } 100 | 101 | 102 | div.sphinxsidebar p { 103 | color: #888; 104 | padding: 5px 20px; 105 | } 106 | 107 | div.sphinxsidebar p.topless { 108 | } 109 | 110 | div.sphinxsidebar ul { 111 | margin: 10px 20px; 112 | padding: 0; 113 | color: #000; 114 | } 115 | 116 | div.sphinxsidebar a { 117 | color: #444; 118 | border-bottom: 1px dotted #999999; 119 | text-decoration: none; 120 | } 121 | 122 | div.sphinxsidebar a:hover { 123 | color: #444; 124 | border-bottom: 1px solid #999999; 125 | } 126 | 127 | 128 | div.sphinxsidebar input { 129 | border: 1px solid #ccc; 130 | font-family: sans-serif; 131 | font-size: 1em; 132 | } 133 | 134 | div.sphinxsidebar input[type=text]{ 135 | margin-left: 20px; 136 | } 137 | 138 | /* -- body styles ----------------------------------------------------------- */ 139 | 140 | a { 141 | color: #005B81; 142 | text-decoration: none; 143 | } 144 | 145 | a:hover { 146 | color: #E32E00; 147 | text-decoration: underline; 148 | } 149 | 150 | div.body h1, 151 | div.body h2, 152 | div.body h3, 153 | div.body h4, 154 | div.body h5, 155 | div.body h6 { 156 | font-family: Arial, sans-serif; 157 | border-bottom: 1px solid #C8D5E3; 158 | font-weight: normal; 159 | color: #212224; 160 | margin: 30px 0px 10px 0px; 161 | padding: 5px 0 5px 10px; 162 | } 163 | 164 | div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 240%; } 165 | div.body h2 { font-size: 180%; background-color: #C8D5E3; } 166 | div.body h3 { font-size: 150%; background-color: #D8DEE3; } 167 | div.body h4 { font-size: 130%; background-color: #D8DEE3; } 168 | div.body h5 { font-size: 100%; background-color: #D8DEE3; } 169 | div.body h6 { font-size: 100%; background-color: #D8DEE3; } 170 | 171 | a.headerlink { 172 | color: #c60f0f; 173 | font-size: 0.8em; 174 | padding: 0 4px 0 4px; 175 | text-decoration: none; 176 | } 177 | 178 | a.headerlink:hover { 179 | background-color: #c60f0f; 180 | color: white; 181 | } 182 | 183 | div.body p, div.body dd, div.body li { 184 | line-height: 1.5em; 185 | } 186 | 187 | div.admonition p.admonition-title + p { 188 | display: inline; 189 | } 190 | 191 | div.highlight{ 192 | background-color: white; 193 | } 194 | 195 | div.note { 196 | background-color: #eee; 197 | border: 1px solid #ccc; 198 | } 199 | 200 | div.seealso { 201 | background-color: #ffc; 202 | border: 1px solid #ff6; 203 | } 204 | 205 | div.topic { 206 | background-color: #eee; 207 | } 208 | 209 | div.warning { 210 | background-color: #ffe4e4; 211 | border: 1px solid #f66; 212 | } 213 | 214 | p.admonition-title { 215 | display: inline; 216 | } 217 | 218 | p.admonition-title:after { 219 | content: ":"; 220 | } 221 | 222 | pre { 223 | padding: 10px; 224 | background-color: White; 225 | color: #222; 226 | line-height: 1.2em; 227 | border: 1px solid #C6C9CB; 228 | font-size: 0.85em; 229 | margin: 1.5em 0 1.5em 0; 230 | -webkit-box-shadow: 1px 1px 1px #d8d8d8; 231 | -moz-box-shadow: 1px 1px 1px #d8d8d8; 232 | } 233 | 234 | tt { 235 | background-color: #ecf0f3; 236 | color: #222; 237 | /* padding: 1px 2px; */ 238 | font-size: 0.85em; 239 | font-family: monospace; 240 | } 241 | 242 | .viewcode-back { 243 | font-family: Arial, sans-serif; 244 | } 245 | 246 | div.viewcode-block:target { 247 | background-color: #f4debf; 248 | border-top: 1px solid #ac9; 249 | border-bottom: 1px solid #ac9; 250 | } 251 | -------------------------------------------------------------------------------- /docs/source/_theme/nature/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = nature.css 4 | pygments_style = tango 5 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # SlimIt documentation build configuration file, created by 4 | # sphinx-quickstart on Mon May 2 11:51:24 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'SlimIt' 44 | copyright = u'2011, Ruslan Spivak' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.5' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.5.5' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'nature' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | html_theme_path = ['_theme'] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | html_title = 'SlimIt' 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | html_sidebars = { 135 | 'index': ['sidebarlogo.html', 'sidebarintro.html'], 136 | } 137 | 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | html_domain_indices = False 145 | 146 | # If false, no index is generated. 147 | html_use_index = False 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'SlimItdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | # The paper size ('letter' or 'a4'). 176 | #latex_paper_size = 'letter' 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #latex_font_size = '10pt' 180 | 181 | # Grouping the document tree into LaTeX files. List of tuples 182 | # (source start file, target name, title, author, documentclass [howto/manual]). 183 | latex_documents = [ 184 | ('index', 'SlimIt.tex', u'SlimIt Documentation', 185 | u'Ruslan Spivak', 'manual'), 186 | ] 187 | 188 | # The name of an image file (relative to this directory) to place at the top of 189 | # the title page. 190 | #latex_logo = None 191 | 192 | # For "manual" documents, if this is true, then toplevel headings are parts, 193 | # not chapters. 194 | #latex_use_parts = False 195 | 196 | # If true, show page references after internal links. 197 | #latex_show_pagerefs = False 198 | 199 | # If true, show URL addresses after external links. 200 | #latex_show_urls = False 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #latex_preamble = '' 204 | 205 | # Documents to append as an appendix to all manuals. 206 | #latex_appendices = [] 207 | 208 | # If false, no module index is generated. 209 | #latex_domain_indices = True 210 | 211 | 212 | # -- Options for manual page output -------------------------------------------- 213 | 214 | # One entry per manual page. List of tuples 215 | # (source start file, name, description, authors, manual section). 216 | man_pages = [ 217 | ('index', 'slimit', u'SlimIt Documentation', 218 | [u'Ruslan Spivak'], 1) 219 | ] 220 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. SlimIt documentation master file, created by 2 | sphinx-quickstart on Mon May 2 11:51:24 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to SlimIt 7 | ================================== 8 | 9 | `SlimIt` is a JavaScript minifier written in Python. 10 | It compiles JavaScript into more compact code so that it downloads 11 | and runs faster. 12 | 13 | `SlimIt` also provides a library that includes a JavaScript parser, 14 | lexer, pretty printer and a tree visitor. 15 | 16 | Installation 17 | ------------ 18 | 19 | .. code-block:: bash 20 | 21 | $ [sudo] pip install slimit 22 | 23 | Or the bleeding edge version from the git master branch: 24 | 25 | .. code-block:: bash 26 | 27 | $ [sudo] pip install git+https://github.com/rspivak/slimit.git#egg=slimit 28 | 29 | Let's minify some code 30 | ---------------------- 31 | 32 | From the command line: 33 | 34 | .. code-block:: bash 35 | 36 | $ slimit -h 37 | Usage: slimit [options] [input file] 38 | 39 | If no input file is provided STDIN is used by default. 40 | Minified JavaScript code is printed to STDOUT. 41 | 42 | 43 | Options: 44 | -h, --help show this help message and exit 45 | -m, --mangle mangle names 46 | -t, --mangle-toplevel 47 | mangle top level scope (defaults to False) 48 | 49 | $ cat test.js 50 | var foo = function( obj ) { 51 | for ( var name in obj ) { 52 | return false; 53 | } 54 | return true; 55 | }; 56 | $ 57 | $ slimit --mangle < test.js 58 | var foo=function(a){for(var b in a)return false;return true;}; 59 | 60 | Or using library API: 61 | 62 | .. code-block:: python 63 | 64 | >>> from slimit import minify 65 | >>> text = """ 66 | ... var a = function( obj ) { 67 | ... for ( var name in obj ) { 68 | ... return false; 69 | ... } 70 | ... return true; 71 | ... }; 72 | ... """ 73 | >>> print minify(text, mangle=True, mangle_toplevel=True) 74 | var a=function(a){for(var b in a)return false;return true;}; 75 | 76 | Iterate over, modify a JavaScript AST and pretty print it 77 | --------------------------------------------------------- 78 | 79 | >>> from slimit.parser import Parser 80 | >>> from slimit.visitors import nodevisitor 81 | >>> from slimit import ast 82 | >>> 83 | >>> parser = Parser() 84 | >>> tree = parser.parse('for(var i=0; i<10; i++) {var x=5+i;}') 85 | >>> for node in nodevisitor.visit(tree): 86 | ... if isinstance(node, ast.Identifier) and node.value == 'i': 87 | ... node.value = 'hello' 88 | ... 89 | >>> print tree.to_ecma() # print awesome javascript :) 90 | for (var hello = 0; hello < 10; hello++) { 91 | var x = 5 + hello; 92 | } 93 | >>> 94 | 95 | Writing custom node visitor 96 | --------------------------- 97 | 98 | >>> from slimit.parser import Parser 99 | >>> from slimit.visitors.nodevisitor import ASTVisitor 100 | >>> 101 | >>> text = """ 102 | ... var x = { 103 | ... "key1": "value1", 104 | ... "key2": "value2" 105 | ... }; 106 | ... """ 107 | >>> 108 | >>> class MyVisitor(ASTVisitor): 109 | ... def visit_Object(self, node): 110 | ... """Visit object literal.""" 111 | ... for prop in node: 112 | ... left, right = prop.left, prop.right 113 | ... print 'Property key=%s, value=%s' % (left.value, right.value) 114 | ... # visit all children in turn 115 | ... self.visit(prop) 116 | ... 117 | >>> 118 | >>> parser = Parser() 119 | >>> tree = parser.parse(text) 120 | >>> visitor = MyVisitor() 121 | >>> visitor.visit(tree) 122 | Property key="key1", value="value1" 123 | Property key="key2", value="value2" 124 | 125 | 126 | Using lexer in your project 127 | --------------------------- 128 | 129 | >>> from slimit.lexer import Lexer 130 | >>> lexer = Lexer() 131 | >>> lexer.input('a = 1;') 132 | >>> for token in lexer: 133 | ... print token 134 | ... 135 | LexToken(ID,'a',1,0) 136 | LexToken(EQ,'=',1,2) 137 | LexToken(NUMBER,'1',1,4) 138 | LexToken(SEMI,';',1,5) 139 | 140 | You can get one token at a time using ``token`` method: 141 | 142 | >>> lexer.input('a = 1;') 143 | >>> while True: 144 | ... token = lexer.token() 145 | ... if not token: 146 | ... break 147 | ... print token 148 | ... 149 | LexToken(ID,'a',1,0) 150 | LexToken(EQ,'=',1,2) 151 | LexToken(NUMBER,'1',1,4) 152 | LexToken(SEMI,';',1,5) 153 | 154 | `LexToken` instance has different attributes: 155 | 156 | >>> lexer.input('a = 1;') 157 | >>> token = lexer.token() 158 | >>> token.type, token.value, token.lineno, token.lexpos 159 | ('ID', 'a', 1, 0) 160 | 161 | 162 | Benchmarks 163 | ---------- 164 | 165 | **SAM** - JQuery size after minification in bytes 166 | 167 | +-------------------------------+------------+------------+------------+ 168 | | Original jQuery 1.6.1 (bytes) | SlimIt SAM | rJSmin SAM | jsmin SAM | 169 | +===============================+============+============+============+ 170 | | 234,995 | 94,290 | 134,215 | 134,819 | 171 | +-------------------------------+------------+------------+------------+ 172 | 173 | 174 | Roadmap 175 | ------- 176 | - More minifications 177 | - Speed improvements 178 | 179 | .. toctree:: 180 | :maxdepth: 2 181 | 182 | License 183 | ------- 184 | The MIT License (MIT) 185 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | from setuptools import setup, find_packages 4 | try: 5 | from distutils.command.build_py import build_py_2to3 as build_py 6 | except ImportError: 7 | from distutils.command.build_py import build_py 8 | 9 | 10 | classifiers = """\ 11 | Intended Audience :: Developers 12 | License :: OSI Approved :: MIT License 13 | Programming Language :: Python 14 | Programming Language :: Python :: 3 15 | Topic :: Software Development :: Compilers 16 | Operating System :: Unix 17 | """ 18 | 19 | requirements = ['ply>=3.11'] 20 | major, minor = sys.version_info[:2] # Python version 21 | if major == 2 and minor <=6: 22 | # OrderedDict was added to the collections module in Python 2.7 and it is 23 | # there in all versions of Python 3. 24 | requirements.append('odict') 25 | if major == 3: 26 | PYTHON3 = True 27 | try: 28 | import lib2to3 # Just a check--the module is not actually used 29 | except ImportError: 30 | print("Python 3.X support requires the 2to3 tool.") 31 | sys.exit(1) 32 | 33 | def read(*rel_names): 34 | return open(os.path.join(os.path.dirname(__file__), *rel_names)).read() 35 | 36 | 37 | setup( 38 | name='slimit', 39 | version='0.8.1', 40 | url='https://slimit.readthedocs.io', 41 | cmdclass = {'build_py': build_py}, 42 | license='MIT', 43 | description='SlimIt - JavaScript minifier', 44 | author='Ruslan Spivak', 45 | author_email='ruslan.spivak@gmail.com', 46 | packages=find_packages('src'), 47 | package_dir={'': 'src'}, 48 | install_requires=requirements, 49 | zip_safe=False, 50 | entry_points="""\ 51 | [console_scripts] 52 | slimit = slimit.minifier:main 53 | """, 54 | classifiers=filter(None, classifiers.split('\n')), 55 | long_description=read('README.rst') + '\n\n' + read('CHANGES'), 56 | extras_require={'test': []} 57 | ) 58 | -------------------------------------------------------------------------------- /src/slimit/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | from slimit.minifier import minify # noqa: F401 28 | -------------------------------------------------------------------------------- /src/slimit/ast.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | 28 | class Node(object): 29 | def __init__(self, children=None): 30 | self._children_list = [] if children is None else children 31 | 32 | def __iter__(self): 33 | for child in self.children(): 34 | if child is not None: 35 | yield child 36 | 37 | def children(self): 38 | return self._children_list 39 | 40 | def to_ecma(self): 41 | # Can't import at module level as ecmavisitor depends 42 | # on ast module... 43 | from slimit.visitors.ecmavisitor import ECMAVisitor 44 | visitor = ECMAVisitor() 45 | return visitor.visit(self) 46 | 47 | class Program(Node): 48 | pass 49 | 50 | class Block(Node): 51 | pass 52 | 53 | class Boolean(Node): 54 | def __init__(self, value): 55 | self.value = value 56 | 57 | def children(self): 58 | return [] 59 | 60 | class Null(Node): 61 | def __init__(self, value): 62 | self.value = value 63 | 64 | def children(self): 65 | return [] 66 | 67 | class Number(Node): 68 | def __init__(self, value): 69 | self.value = value 70 | 71 | def children(self): 72 | return [] 73 | 74 | class Identifier(Node): 75 | def __init__(self, value): 76 | self.value = value 77 | 78 | def children(self): 79 | return [] 80 | 81 | class String(Node): 82 | def __init__(self, value): 83 | self.value = value 84 | 85 | def children(self): 86 | return [] 87 | 88 | class Regex(Node): 89 | def __init__(self, value): 90 | self.value = value 91 | 92 | def children(self): 93 | return [] 94 | 95 | class Array(Node): 96 | def __init__(self, items): 97 | self.items = items 98 | 99 | def children(self): 100 | return self.items 101 | 102 | class Object(Node): 103 | def __init__(self, properties=None): 104 | self.properties = [] if properties is None else properties 105 | 106 | def children(self): 107 | return self.properties 108 | 109 | class NewExpr(Node): 110 | def __init__(self, identifier, args=None): 111 | self.identifier = identifier 112 | self.args = [] if args is None else args 113 | 114 | def children(self): 115 | return [self.identifier, self.args] 116 | 117 | class FunctionCall(Node): 118 | def __init__(self, identifier, args=None): 119 | self.identifier = identifier 120 | self.args = [] if args is None else args 121 | 122 | def children(self): 123 | return [self.identifier] + self.args 124 | 125 | class BracketAccessor(Node): 126 | def __init__(self, node, expr): 127 | self.node = node 128 | self.expr = expr 129 | 130 | def children(self): 131 | return [self.node, self.expr] 132 | 133 | class DotAccessor(Node): 134 | def __init__(self, node, identifier): 135 | self.node = node 136 | self.identifier = identifier 137 | 138 | def children(self): 139 | return [self.node, self.identifier] 140 | 141 | class Assign(Node): 142 | def __init__(self, op, left, right): 143 | self.op = op 144 | self.left = left 145 | self.right = right 146 | 147 | def children(self): 148 | return [self.left, self.right] 149 | 150 | class GetPropAssign(Node): 151 | def __init__(self, prop_name, elements): 152 | """elements - function body""" 153 | self.prop_name = prop_name 154 | self.elements = elements 155 | 156 | def children(self): 157 | return [self.prop_name] + self.elements 158 | 159 | class SetPropAssign(Node): 160 | def __init__(self, prop_name, parameters, elements): 161 | """elements - function body""" 162 | self.prop_name = prop_name 163 | self.parameters = parameters 164 | self.elements = elements 165 | 166 | def children(self): 167 | return [self.prop_name] + self.parameters + self.elements 168 | 169 | class VarStatement(Node): 170 | pass 171 | 172 | class VarDecl(Node): 173 | def __init__(self, identifier, initializer=None): 174 | self.identifier = identifier 175 | self.identifier._mangle_candidate = True 176 | self.initializer = initializer 177 | 178 | def children(self): 179 | return [self.identifier, self.initializer] 180 | 181 | class UnaryOp(Node): 182 | def __init__(self, op, value, postfix=False): 183 | self.op = op 184 | self.value = value 185 | self.postfix = postfix 186 | 187 | def children(self): 188 | return [self.value] 189 | 190 | class BinOp(Node): 191 | def __init__(self, op, left, right): 192 | self.op = op 193 | self.left = left 194 | self.right = right 195 | 196 | def children(self): 197 | return [self.left, self.right] 198 | 199 | class Conditional(Node): 200 | """Conditional Operator ( ? : )""" 201 | def __init__(self, predicate, consequent, alternative): 202 | self.predicate = predicate 203 | self.consequent = consequent 204 | self.alternative = alternative 205 | 206 | def children(self): 207 | return [self.predicate, self.consequent, self.alternative] 208 | 209 | class If(Node): 210 | def __init__(self, predicate, consequent, alternative=None): 211 | self.predicate = predicate 212 | self.consequent = consequent 213 | self.alternative = alternative 214 | 215 | def children(self): 216 | return [self.predicate, self.consequent, self.alternative] 217 | 218 | class DoWhile(Node): 219 | def __init__(self, predicate, statement): 220 | self.predicate = predicate 221 | self.statement = statement 222 | 223 | def children(self): 224 | return [self.predicate, self.statement] 225 | 226 | class While(Node): 227 | def __init__(self, predicate, statement): 228 | self.predicate = predicate 229 | self.statement = statement 230 | 231 | def children(self): 232 | return [self.predicate, self.statement] 233 | 234 | class For(Node): 235 | def __init__(self, init, cond, count, statement): 236 | self.init = init 237 | self.cond = cond 238 | self.count = count 239 | self.statement = statement 240 | 241 | def children(self): 242 | return [self.init, self.cond, self.count, self.statement] 243 | 244 | class ForIn(Node): 245 | def __init__(self, item, iterable, statement): 246 | self.item = item 247 | self.iterable = iterable 248 | self.statement = statement 249 | 250 | def children(self): 251 | return [self.item, self.iterable, self.statement] 252 | 253 | class Continue(Node): 254 | def __init__(self, identifier=None): 255 | self.identifier = identifier 256 | 257 | def children(self): 258 | return [self.identifier] 259 | 260 | class Break(Node): 261 | def __init__(self, identifier=None): 262 | self.identifier = identifier 263 | 264 | def children(self): 265 | return [self.identifier] 266 | 267 | class Return(Node): 268 | def __init__(self, expr=None): 269 | self.expr = expr 270 | 271 | def children(self): 272 | return [self.expr] 273 | 274 | class With(Node): 275 | def __init__(self, expr, statement): 276 | self.expr = expr 277 | self.statement = statement 278 | 279 | def children(self): 280 | return [self.expr, self.statement] 281 | 282 | class Switch(Node): 283 | def __init__(self, expr, cases, default=None): 284 | self.expr = expr 285 | self.cases = cases 286 | self.default = default 287 | 288 | def children(self): 289 | return [self.expr] + self.cases + [self.default] 290 | 291 | class Case(Node): 292 | def __init__(self, expr, elements): 293 | self.expr = expr 294 | self.elements = elements if elements is not None else [] 295 | 296 | def children(self): 297 | return [self.expr] + self.elements 298 | 299 | class Default(Node): 300 | def __init__(self, elements): 301 | self.elements = elements if elements is not None else [] 302 | 303 | def children(self): 304 | return self.elements 305 | 306 | class Label(Node): 307 | def __init__(self, identifier, statement): 308 | self.identifier = identifier 309 | self.statement = statement 310 | 311 | def children(self): 312 | return [self.identifier, self.statement] 313 | 314 | class Throw(Node): 315 | def __init__(self, expr): 316 | self.expr = expr 317 | 318 | def children(self): 319 | return [self.expr] 320 | 321 | class Try(Node): 322 | def __init__(self, statements, catch=None, fin=None): 323 | self.statements = statements 324 | self.catch = catch 325 | self.fin = fin 326 | 327 | def children(self): 328 | return [self.statements] + [self.catch, self.fin] 329 | 330 | class Catch(Node): 331 | def __init__(self, identifier, elements): 332 | self.identifier = identifier 333 | # CATCH identifiers are subject to name mangling. we need to mark them. 334 | self.identifier._mangle_candidate = True 335 | self.elements = elements 336 | 337 | def children(self): 338 | return [self.identifier, self.elements] 339 | 340 | class Finally(Node): 341 | def __init__(self, elements): 342 | self.elements = elements 343 | 344 | def children(self): 345 | return self.elements 346 | 347 | class Debugger(Node): 348 | def __init__(self, value): 349 | self.value = value 350 | 351 | def children(self): 352 | return [] 353 | 354 | 355 | class FuncBase(Node): 356 | def __init__(self, identifier, parameters, elements): 357 | self.identifier = identifier 358 | self.parameters = parameters if parameters is not None else [] 359 | self.elements = elements if elements is not None else [] 360 | self._init_ids() 361 | 362 | def _init_ids(self): 363 | # function declaration/expression name and parameters are identifiers 364 | # and therefore are subject to name mangling. we need to mark them. 365 | if self.identifier is not None: 366 | self.identifier._mangle_candidate = True 367 | for param in self.parameters: 368 | param._mangle_candidate = True 369 | 370 | def children(self): 371 | return [self.identifier] + self.parameters + self.elements 372 | 373 | class FuncDecl(FuncBase): 374 | pass 375 | 376 | # The only difference is that function expression might not have an identifier 377 | class FuncExpr(FuncBase): 378 | pass 379 | 380 | 381 | class Comma(Node): 382 | def __init__(self, left, right): 383 | self.left = left 384 | self.right = right 385 | 386 | def children(self): 387 | return [self.left, self.right] 388 | 389 | class EmptyStatement(Node): 390 | def __init__(self, value): 391 | self.value = value 392 | 393 | def children(self): 394 | return [] 395 | 396 | class ExprStatement(Node): 397 | def __init__(self, expr): 398 | self.expr = expr 399 | 400 | def children(self): 401 | return [self.expr] 402 | 403 | class Elision(Node): 404 | def __init__(self, value): 405 | self.value = value 406 | 407 | def children(self): 408 | return [] 409 | 410 | class This(Node): 411 | def __init__(self): 412 | pass 413 | 414 | def children(self): 415 | return [] 416 | -------------------------------------------------------------------------------- /src/slimit/lexer.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | from __future__ import print_function 25 | 26 | __author__ = 'Ruslan Spivak ' 27 | 28 | import ply.lex 29 | 30 | from slimit.unicode_chars import ( 31 | LETTER, 32 | DIGIT, 33 | COMBINING_MARK, 34 | CONNECTOR_PUNCTUATION, 35 | ) 36 | 37 | # See "Regular Expression Literals" at 38 | # http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html 39 | TOKENS_THAT_IMPLY_DIVISON = frozenset([ 40 | 'ID', 41 | 'NUMBER', 42 | 'STRING', 43 | 'REGEX', 44 | 'TRUE', 45 | 'FALSE', 46 | 'NULL', 47 | 'THIS', 48 | 'PLUSPLUS', 49 | 'MINUSMINUS', 50 | 'RPAREN', 51 | 'RBRACE', 52 | 'RBRACKET', 53 | ]) 54 | 55 | 56 | class Lexer(object): 57 | """A JavaScript lexer. 58 | 59 | >>> from slimit.lexer import Lexer 60 | >>> lexer = Lexer() 61 | 62 | Lexer supports iteration: 63 | 64 | >>> lexer.input('a = 1;') 65 | >>> for token in lexer: 66 | ... print(token) 67 | ... 68 | LexToken(ID,'a',1,0) 69 | LexToken(EQ,'=',1,2) 70 | LexToken(NUMBER,'1',1,4) 71 | LexToken(SEMI,';',1,5) 72 | 73 | Or call one token at a time with 'token' method: 74 | 75 | >>> lexer.input('a = 1;') 76 | >>> while True: 77 | ... token = lexer.token() 78 | ... if not token: 79 | ... break 80 | ... print(token) 81 | ... 82 | LexToken(ID,'a',1,0) 83 | LexToken(EQ,'=',1,2) 84 | LexToken(NUMBER,'1',1,4) 85 | LexToken(SEMI,';',1,5) 86 | 87 | >>> lexer.input('a = 1;') 88 | >>> token = lexer.token() 89 | >>> token.type, token.value, token.lineno, token.lexpos 90 | ('ID', 'a', 1, 0) 91 | 92 | For more information see: 93 | http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf 94 | """ 95 | def __init__(self): 96 | self.prev_token = None 97 | self.cur_token = None 98 | self.next_tokens = [] 99 | self.build() 100 | 101 | def build(self, **kwargs): 102 | """Build the lexer.""" 103 | self.lexer = ply.lex.lex(object=self, **kwargs) 104 | 105 | def input(self, text): 106 | self.lexer.input(text) 107 | 108 | def token(self): 109 | if self.next_tokens: 110 | return self.next_tokens.pop() 111 | 112 | lexer = self.lexer 113 | while True: 114 | pos = lexer.lexpos 115 | try: 116 | char = lexer.lexdata[pos] 117 | while char in ' \t': 118 | pos += 1 119 | char = lexer.lexdata[pos] 120 | next_char = lexer.lexdata[pos + 1] 121 | except IndexError: 122 | tok = self._get_update_token() 123 | if tok is not None and tok.type == 'LINE_TERMINATOR': 124 | continue 125 | else: 126 | return tok 127 | 128 | if char != '/' or (char == '/' and next_char in ('/', '*')): 129 | tok = self._get_update_token() 130 | if tok.type in ('LINE_TERMINATOR', 131 | 'LINE_COMMENT', 'BLOCK_COMMENT'): 132 | continue 133 | else: 134 | return tok 135 | 136 | # current character is '/' which is either division or regex 137 | cur_token = self.cur_token 138 | is_division_allowed = ( 139 | cur_token is not None and 140 | cur_token.type in TOKENS_THAT_IMPLY_DIVISON 141 | ) 142 | if is_division_allowed: 143 | return self._get_update_token() 144 | else: 145 | self.prev_token = self.cur_token 146 | self.cur_token = self._read_regex() 147 | return self.cur_token 148 | 149 | def auto_semi(self, token): 150 | if (token is None or token.type == 'RBRACE' 151 | or self._is_prev_token_lt() 152 | ): 153 | if token: 154 | self.next_tokens.append(token) 155 | return self._create_semi_token(token) 156 | 157 | def _is_prev_token_lt(self): 158 | return self.prev_token and self.prev_token.type == 'LINE_TERMINATOR' 159 | 160 | def _read_regex(self): 161 | self.lexer.begin('regex') 162 | token = self.lexer.token() 163 | self.lexer.begin('INITIAL') 164 | return token 165 | 166 | def _get_update_token(self): 167 | self.prev_token = self.cur_token 168 | self.cur_token = self.lexer.token() 169 | # insert semicolon before restricted tokens 170 | # See section 7.9.1 ECMA262 171 | if (self.cur_token is not None 172 | and self.cur_token.type == 'LINE_TERMINATOR' 173 | and self.prev_token is not None 174 | and self.prev_token.type in ['BREAK', 'CONTINUE', 175 | 'RETURN', 'THROW'] 176 | ): 177 | return self._create_semi_token(self.cur_token) 178 | return self.cur_token 179 | 180 | def _create_semi_token(self, orig_token): 181 | token = ply.lex.LexToken() 182 | token.type = 'SEMI' 183 | token.value = ';' 184 | if orig_token is not None: 185 | token.lineno = orig_token.lineno 186 | token.lexpos = orig_token.lexpos 187 | else: 188 | token.lineno = 0 189 | token.lexpos = 0 190 | return token 191 | 192 | # iterator protocol 193 | def __iter__(self): 194 | return self 195 | 196 | def __next__(self): 197 | token = self.token() 198 | if not token: 199 | raise StopIteration 200 | 201 | return token 202 | 203 | def next(self): 204 | return self.__next__() 205 | 206 | states = ( 207 | ('regex', 'exclusive'), 208 | ) 209 | 210 | keywords = ( 211 | 'BREAK', 'CASE', 'CATCH', 'CONTINUE', 'DEBUGGER', 'DEFAULT', 'DELETE', 212 | 'DO', 'ELSE', 'FINALLY', 'FOR', 'FUNCTION', 'IF', 'IN', 213 | 'INSTANCEOF', 'NEW', 'RETURN', 'SWITCH', 'THIS', 'THROW', 'TRY', 214 | 'TYPEOF', 'VAR', 'VOID', 'WHILE', 'WITH', 'NULL', 'TRUE', 'FALSE', 215 | # future reserved words - well, it's uncommented now to make 216 | # IE8 happy because it chokes up on minification: 217 | # obj["class"] -> obj.class 218 | 'CLASS', 'CONST', 'ENUM', 'EXPORT', 'EXTENDS', 'IMPORT', 'SUPER', 219 | ) 220 | keywords_dict = dict((key.lower(), key) for key in keywords) 221 | 222 | tokens = ( 223 | # Punctuators 224 | 'PERIOD', 'COMMA', 'SEMI', 'COLON', # . , ; : 225 | 'PLUS', 'MINUS', 'MULT', 'DIV', 'MOD', # + - * / % 226 | 'BAND', 'BOR', 'BXOR', 'BNOT', # & | ^ ~ 227 | 'CONDOP', # conditional operator ? 228 | 'NOT', # ! 229 | 'LPAREN', 'RPAREN', # ( and ) 230 | 'LBRACE', 'RBRACE', # { and } 231 | 'LBRACKET', 'RBRACKET', # [ and ] 232 | 'EQ', 'EQEQ', 'NE', # = == != 233 | 'STREQ', 'STRNEQ', # === and !== 234 | 'LT', 'GT', # < and > 235 | 'LE', 'GE', # <= and >= 236 | 'OR', 'AND', # || and && 237 | 'PLUSPLUS', 'MINUSMINUS', # ++ and -- 238 | 'LSHIFT', # << 239 | 'RSHIFT', 'URSHIFT', # >> and >>> 240 | 'PLUSEQUAL', 'MINUSEQUAL', # += and -= 241 | 'MULTEQUAL', 'DIVEQUAL', # *= and /= 242 | 'LSHIFTEQUAL', # <<= 243 | 'RSHIFTEQUAL', 'URSHIFTEQUAL', # >>= and >>>= 244 | 'ANDEQUAL', 'MODEQUAL', # &= and %= 245 | 'XOREQUAL', 'OREQUAL', # ^= and |= 246 | 247 | # Terminal types 248 | 'NUMBER', 'STRING', 'ID', 'REGEX', 249 | 250 | # Properties 251 | 'GETPROP', 'SETPROP', 252 | 253 | # Comments 254 | 'LINE_COMMENT', 'BLOCK_COMMENT', 255 | 256 | 'LINE_TERMINATOR', 257 | ) + keywords 258 | 259 | # adapted from https://bitbucket.org/ned/jslex 260 | t_regex_REGEX = r"""(?: 261 | / # opening slash 262 | # First character is.. 263 | (?: [^*\\/[] # anything but * \ / or [ 264 | | \\. # or an escape sequence 265 | | \[ # or a class, which has 266 | (?: [^\]\\] # anything but \ or ] 267 | | \\. # or an escape sequence 268 | )* # many times 269 | \] 270 | ) 271 | # Following characters are same, except for excluding a star 272 | (?: [^\\/[] # anything but \ / or [ 273 | | \\. # or an escape sequence 274 | | \[ # or a class, which has 275 | (?: [^\]\\] # anything but \ or ] 276 | | \\. # or an escape sequence 277 | )* # many times 278 | \] 279 | )* # many times 280 | / # closing slash 281 | [a-zA-Z0-9]* # trailing flags 282 | ) 283 | """ 284 | 285 | t_regex_ignore = ' \t' 286 | 287 | def t_regex_error(self, token): 288 | raise TypeError( 289 | "Error parsing regular expression '%s' at %s" % ( 290 | token.value, token.lineno) 291 | ) 292 | 293 | # Punctuators 294 | t_PERIOD = r'\.' 295 | t_COMMA = r',' 296 | t_SEMI = r';' 297 | t_COLON = r':' 298 | t_PLUS = r'\+' 299 | t_MINUS = r'-' 300 | t_MULT = r'\*' 301 | t_DIV = r'/' 302 | t_MOD = r'%' 303 | t_BAND = r'&' 304 | t_BOR = r'\|' 305 | t_BXOR = r'\^' 306 | t_BNOT = r'~' 307 | t_CONDOP = r'\?' 308 | t_NOT = r'!' 309 | t_LPAREN = r'\(' 310 | t_RPAREN = r'\)' 311 | t_LBRACE = r'{' 312 | t_RBRACE = r'}' 313 | t_LBRACKET = r'\[' 314 | t_RBRACKET = r'\]' 315 | t_EQ = r'=' 316 | t_EQEQ = r'==' 317 | t_NE = r'!=' 318 | t_STREQ = r'===' 319 | t_STRNEQ = r'!==' 320 | t_LT = r'<' 321 | t_GT = r'>' 322 | t_LE = r'<=' 323 | t_GE = r'>=' 324 | t_OR = r'\|\|' 325 | t_AND = r'&&' 326 | t_PLUSPLUS = r'\+\+' 327 | t_MINUSMINUS = r'--' 328 | t_LSHIFT = r'<<' 329 | t_RSHIFT = r'>>' 330 | t_URSHIFT = r'>>>' 331 | t_PLUSEQUAL = r'\+=' 332 | t_MINUSEQUAL = r'-=' 333 | t_MULTEQUAL = r'\*=' 334 | t_DIVEQUAL = r'/=' 335 | t_LSHIFTEQUAL = r'<<=' 336 | t_RSHIFTEQUAL = r'>>=' 337 | t_URSHIFTEQUAL = r'>>>=' 338 | t_ANDEQUAL = r'&=' 339 | t_MODEQUAL = r'%=' 340 | t_XOREQUAL = r'\^=' 341 | t_OREQUAL = r'\|=' 342 | 343 | t_LINE_COMMENT = r'//[^\r\n]*' 344 | t_BLOCK_COMMENT = r'/\*[^*]*\*+([^/*][^*]*\*+)*/' 345 | 346 | t_LINE_TERMINATOR = r'[\n\r]+' 347 | 348 | t_ignore = ' \t' 349 | 350 | t_NUMBER = r""" 351 | (?: 352 | 0[xX][0-9a-fA-F]+ # hex_integer_literal 353 | | 0[0-7]+ # or octal_integer_literal (spec B.1.1) 354 | | (?: # or decimal_literal 355 | (?:0|[1-9][0-9]*) # decimal_integer_literal 356 | \. # dot 357 | [0-9]* # decimal_digits_opt 358 | (?:[eE][+-]?[0-9]+)? # exponent_part_opt 359 | | 360 | \. # dot 361 | [0-9]+ # decimal_digits 362 | (?:[eE][+-]?[0-9]+)? # exponent_part_opt 363 | | 364 | (?:0|[1-9][0-9]*) # decimal_integer_literal 365 | (?:[eE][+-]?[0-9]+)? # exponent_part_opt 366 | ) 367 | ) 368 | """ 369 | 370 | string = r""" 371 | (?: 372 | # double quoted string 373 | (?:" # opening double quote 374 | (?: [^"\\\n\r] # no \, line terminators or " 375 | | \\[a-zA-Z!-\/:-@\[-`{-~] # or escaped characters 376 | | \\x[0-9a-fA-F]{2} # or hex_escape_sequence 377 | | \\u[0-9a-fA-F]{4} # or unicode_escape_sequence 378 | )*? # zero or many times 379 | (?: \\\n # multiline ? 380 | (?: 381 | [^"\\\n\r] # no \, line terminators or " 382 | | \\[a-zA-Z!-\/:-@\[-`{-~] # or escaped characters 383 | | \\x[0-9a-fA-F]{2} # or hex_escape_sequence 384 | | \\u[0-9a-fA-F]{4} # or unicode_escape_sequence 385 | )*? # zero or many times 386 | )* 387 | ") # closing double quote 388 | | 389 | # single quoted string 390 | (?:' # opening single quote 391 | (?: [^'\\\n\r] # no \, line terminators or ' 392 | | \\[a-zA-Z!-\/:-@\[-`{-~] # or escaped characters 393 | | \\x[0-9a-fA-F]{2} # or hex_escape_sequence 394 | | \\u[0-9a-fA-F]{4} # or unicode_escape_sequence 395 | )*? # zero or many times 396 | (?: \\\n # multiline ? 397 | (?: 398 | [^'\\\n\r] # no \, line terminators or ' 399 | | \\[a-zA-Z!-\/:-@\[-`{-~] # or escaped characters 400 | | \\x[0-9a-fA-F]{2} # or hex_escape_sequence 401 | | \\u[0-9a-fA-F]{4} # or unicode_escape_sequence 402 | )*? # zero or many times 403 | )* 404 | ') # closing single quote 405 | ) 406 | """ # " 407 | 408 | @ply.lex.TOKEN(string) 409 | def t_STRING(self, token): 410 | # remove escape + new line sequence used for strings 411 | # written across multiple lines of code 412 | token.value = token.value.replace('\\\n', '') 413 | return token 414 | 415 | # XXX: ? 416 | identifier_start = r'(?:' + r'[a-zA-Z_$]' + r'|' + LETTER + r')+' 417 | identifier_part = ( 418 | r'(?:' + COMBINING_MARK + r'|' + r'[0-9a-zA-Z_$]' + r'|' + DIGIT + 419 | r'|' + CONNECTOR_PUNCTUATION + r')*' 420 | ) 421 | identifier = (identifier_start + identifier_part).replace(']|[', '') 422 | 423 | getprop = r'get' + r'(?=\s' + identifier + r')' 424 | @ply.lex.TOKEN(getprop) 425 | def t_GETPROP(self, token): 426 | return token 427 | 428 | setprop = r'set' + r'(?=\s' + identifier + r')' 429 | @ply.lex.TOKEN(setprop) 430 | def t_SETPROP(self, token): 431 | return token 432 | 433 | @ply.lex.TOKEN(identifier) 434 | def t_ID(self, token): 435 | token.type = self.keywords_dict.get(token.value, 'ID') 436 | return token 437 | 438 | def t_error(self, token): 439 | print( 440 | 'Illegal character %r at %s:%s after %s' % ( 441 | token.value[0], 442 | token.lineno, 443 | token.lexpos, 444 | self.prev_token 445 | ) 446 | ) 447 | token.lexer.skip(1) 448 | -------------------------------------------------------------------------------- /src/slimit/lextab.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # lextab.py. This file automatically created by PLY (version 3.11). Don't edit! 3 | _tabversion = '3.10' 4 | _lextokens = set(('AND', 'ANDEQUAL', 'BAND', 'BLOCK_COMMENT', 'BNOT', 'BOR', 'BREAK', 'BXOR', 'CASE', 'CATCH', 'CLASS', 'COLON', 'COMMA', 'CONDOP', 'CONST', 'CONTINUE', 'DEBUGGER', 'DEFAULT', 'DELETE', 'DIV', 'DIVEQUAL', 'DO', 'ELSE', 'ENUM', 'EQ', 'EQEQ', 'EXPORT', 'EXTENDS', 'FALSE', 'FINALLY', 'FOR', 'FUNCTION', 'GE', 'GETPROP', 'GT', 'ID', 'IF', 'IMPORT', 'IN', 'INSTANCEOF', 'LBRACE', 'LBRACKET', 'LE', 'LINE_COMMENT', 'LINE_TERMINATOR', 'LPAREN', 'LSHIFT', 'LSHIFTEQUAL', 'LT', 'MINUS', 'MINUSEQUAL', 'MINUSMINUS', 'MOD', 'MODEQUAL', 'MULT', 'MULTEQUAL', 'NE', 'NEW', 'NOT', 'NULL', 'NUMBER', 'OR', 'OREQUAL', 'PERIOD', 'PLUS', 'PLUSEQUAL', 'PLUSPLUS', 'RBRACE', 'RBRACKET', 'REGEX', 'RETURN', 'RPAREN', 'RSHIFT', 'RSHIFTEQUAL', 'SEMI', 'SETPROP', 'STREQ', 'STRING', 'STRNEQ', 'SUPER', 'SWITCH', 'THIS', 'THROW', 'TRUE', 'TRY', 'TYPEOF', 'URSHIFT', 'URSHIFTEQUAL', 'VAR', 'VOID', 'WHILE', 'WITH', 'XOREQUAL')) 5 | _lexreflags = 64 6 | _lexliterals = '' 7 | _lexstateinfo = {'INITIAL': 'inclusive', 'regex': 'exclusive'} 8 | _lexstatere = {'INITIAL': [('(?P\n (?:\n # double quoted string\n (?:" # opening double quote\n (?: [^"\\\\\\n\\r] # no \\, line terminators or "\n | \\\\[a-zA-Z!-\\/:-@\\[-`{-~] # or escaped characters\n | \\\\x[0-9a-fA-F]{2} # or hex_escape_sequence\n | \\\\u[0-9a-fA-F]{4} # or unicode_escape_sequence\n )*? # zero or many times\n (?: \\\\\\n # multiline ?\n (?:\n [^"\\\\\\n\\r] # no \\, line terminators or "\n | \\\\[a-zA-Z!-\\/:-@\\[-`{-~] # or escaped characters\n | \\\\x[0-9a-fA-F]{2} # or hex_escape_sequence\n | \\\\u[0-9a-fA-F]{4} # or unicode_escape_sequence\n )*? # zero or many times\n )*\n ") # closing double quote\n |\n # single quoted string\n (?:\' # opening single quote\n (?: [^\'\\\\\\n\\r] # no \\, line terminators or \'\n | \\\\[a-zA-Z!-\\/:-@\\[-`{-~] # or escaped characters\n | \\\\x[0-9a-fA-F]{2} # or hex_escape_sequence\n | \\\\u[0-9a-fA-F]{4} # or unicode_escape_sequence\n )*? # zero or many times\n (?: \\\\\\n # multiline ?\n (?:\n [^\'\\\\\\n\\r] # no \\, line terminators or \'\n | \\\\[a-zA-Z!-\\/:-@\\[-`{-~] # or escaped characters\n | \\\\x[0-9a-fA-F]{2} # or hex_escape_sequence\n | \\\\u[0-9a-fA-F]{4} # or unicode_escape_sequence\n )*? # zero or many times\n )*\n \') # closing single quote\n )\n )|(?Pget(?=\\s(?:[a-zA-Z_$A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԣԱ-Ֆՙա-ևא-תװ-ײء-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺऄ-हऽॐक़-ॡॱॲॻ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡഅ-ഌഎ-ഐഒ-നപ-ഹഽൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜໝༀཀ-ཇཉ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-Ⴥა-ჺჼᄀ-ᅙᅟ-ᆢᆨ-ᇹሀ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᰀ-ᰣᱍ-ᱏᱚ-ᱽᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₔℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-Ɐⱱ-ⱽⲀ-ⳤⴀ-ⴥⴰ-ⵥⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆷㇰ-ㇿ㐀䶵一鿃ꀀ-ꒌꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙟꙢ-ꙮꙿ-ꚗꜗ-ꜟꜢ-ꞈꞋꞌꟻ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가힣豈-鶴侮-頻並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])+(?:[̀-ͯ҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٞۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࠭ऀ-ं़ु-ै्॑-ॕॢॣঁ়ু-ৄ্ৢৣਁਂ਼ੁੂੇੈੋ-੍ੑੰੱੵઁં઼ુ-ૅેૈ્ૢૣଁ଼ିୁ-ୄ୍ୖୢୣஂீ்ా-ీె-ైొ-్ౕౖౢౣ಼ಿೆೌ್ೢೣു-ൄ്ൢൣ්ි-ුූัิ-ฺ็-๎ັິ-ູົຼ່-ໍཱ༹༘༙༵༷-ཾྀ-྄྆྇ྐ-ྗྙ-ྼ࿆ိ-ူဲ-့္်ွှၘၙၞ-ၠၱ-ၴႂႅႆႍႝ፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳិ-ួំ៉-៓៝᠋-᠍ᢩᤠ-ᤢᤧᤨᤲ᤹-᤻ᨘᨗᩖᩘ-ᩞ᩠ᩢᩥ-ᩬᩳ-᩿᩼ᬀ-ᬃ᬴ᬶ-ᬺᬼᭂ᭫-᭳ᮀᮁᮢ-ᮥᮨᮩᰬ-ᰳᰶ᰷᳐-᳔᳒-᳢᳠-᳨᳭᷀-᷽ᷦ-᷿⃐-⃥⃜⃡-⃰⳯-⳱ⷠ-〪ⷿ-゙゚〯꙯꙼꙽꛰꛱ꠂ꠆ꠋꠥꠦ꣄꣠-꣱ꤦ-꤭ꥇ-ꥑꦀ-ꦂ꦳ꦶ-ꦹꦼꨩ-ꨮꨱꨲꨵꨶꩃꩌꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꯥꯨ꯭ﬞ︀-️︠-︦ःा-ीॉ-ौॎংঃা-ীেৈোৌৗਃਾ-ੀઃા-ીૉોૌଂଃାୀେୈୋୌୗாிுூெ-ைொ-ௌௗఁ-ఃు-ౄಂಃಾೀ-ೄೇೈೊೋೕೖംഃാ-ീെ-ൈൊ-ൌൗංඃා-ෑෘ-ෟෲෳ༾༿ཿါာေးျြၖၗၢ-ၤၧ-ၭႃႄႇ-ႌႏႚ-ႜាើ-ៅះៈᤣ-ᤦᤩ-ᤫᤰᤱᤳ-ᤸᦰ-ᧀᧈᧉᨙ-ᨛᩕᩗᩡᩣᩤᩭ-ᩲᬄᬵᬻᬽ-ᭁᭃ᭄ᮂᮡᮦᮧ᮪ᰤ-ᰫᰴᰵ᳡ᳲꠣꠤꠧꢀꢁꢴ-ꣃꥒ꥓ꦃꦴꦵꦺꦻꦽ-꧀ꨯꨰꨳꨴꩍꩻꯣꯤꯦꯧꯩꯪ꯬0-9a-zA-Z_$0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧚᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꩐-꩙꯰-꯹0-9_‿⁀⁔︳︴﹍-﹏_])*))|(?Pset(?=\\s(?:[a-zA-Z_$A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԣԱ-Ֆՙա-ևא-תװ-ײء-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺऄ-हऽॐक़-ॡॱॲॻ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡഅ-ഌഎ-ഐഒ-നപ-ഹഽൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜໝༀཀ-ཇཉ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-Ⴥა-ჺჼᄀ-ᅙᅟ-ᆢᆨ-ᇹሀ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᰀ-ᰣᱍ-ᱏᱚ-ᱽᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₔℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-Ɐⱱ-ⱽⲀ-ⳤⴀ-ⴥⴰ-ⵥⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆷㇰ-ㇿ㐀䶵一鿃ꀀ-ꒌꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙟꙢ-ꙮꙿ-ꚗꜗ-ꜟꜢ-ꞈꞋꞌꟻ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가힣豈-鶴侮-頻並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])+(?:[̀-ͯ҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٞۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࠭ऀ-ं़ु-ै्॑-ॕॢॣঁ়ু-ৄ্ৢৣਁਂ਼ੁੂੇੈੋ-੍ੑੰੱੵઁં઼ુ-ૅેૈ્ૢૣଁ଼ିୁ-ୄ୍ୖୢୣஂீ்ా-ీె-ైొ-్ౕౖౢౣ಼ಿೆೌ್ೢೣു-ൄ്ൢൣ්ි-ුූัิ-ฺ็-๎ັິ-ູົຼ່-ໍཱ༹༘༙༵༷-ཾྀ-྄྆྇ྐ-ྗྙ-ྼ࿆ိ-ူဲ-့္်ွှၘၙၞ-ၠၱ-ၴႂႅႆႍႝ፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳិ-ួំ៉-៓៝᠋-᠍ᢩᤠ-ᤢᤧᤨᤲ᤹-᤻ᨘᨗᩖᩘ-ᩞ᩠ᩢᩥ-ᩬᩳ-᩿᩼ᬀ-ᬃ᬴ᬶ-ᬺᬼᭂ᭫-᭳ᮀᮁᮢ-ᮥᮨᮩᰬ-ᰳᰶ᰷᳐-᳔᳒-᳢᳠-᳨᳭᷀-᷽ᷦ-᷿⃐-⃥⃜⃡-⃰⳯-⳱ⷠ-〪ⷿ-゙゚〯꙯꙼꙽꛰꛱ꠂ꠆ꠋꠥꠦ꣄꣠-꣱ꤦ-꤭ꥇ-ꥑꦀ-ꦂ꦳ꦶ-ꦹꦼꨩ-ꨮꨱꨲꨵꨶꩃꩌꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꯥꯨ꯭ﬞ︀-️︠-︦ःा-ीॉ-ौॎংঃা-ীেৈোৌৗਃਾ-ੀઃા-ીૉોૌଂଃାୀେୈୋୌୗாிுூெ-ைொ-ௌௗఁ-ఃు-ౄಂಃಾೀ-ೄೇೈೊೋೕೖംഃാ-ീെ-ൈൊ-ൌൗංඃා-ෑෘ-ෟෲෳ༾༿ཿါာေးျြၖၗၢ-ၤၧ-ၭႃႄႇ-ႌႏႚ-ႜាើ-ៅះៈᤣ-ᤦᤩ-ᤫᤰᤱᤳ-ᤸᦰ-ᧀᧈᧉᨙ-ᨛᩕᩗᩡᩣᩤᩭ-ᩲᬄᬵᬻᬽ-ᭁᭃ᭄ᮂᮡᮦᮧ᮪ᰤ-ᰫᰴᰵ᳡ᳲꠣꠤꠧꢀꢁꢴ-ꣃꥒ꥓ꦃꦴꦵꦺꦻꦽ-꧀ꨯꨰꨳꨴꩍꩻꯣꯤꯦꯧꯩꯪ꯬0-9a-zA-Z_$0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧚᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꩐-꩙꯰-꯹0-9_‿⁀⁔︳︴﹍-﹏_])*))|(?P(?:[a-zA-Z_$A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԣԱ-Ֆՙա-ևא-תװ-ײء-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺऄ-हऽॐक़-ॡॱॲॻ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡഅ-ഌഎ-ഐഒ-നപ-ഹഽൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜໝༀཀ-ཇཉ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-Ⴥა-ჺჼᄀ-ᅙᅟ-ᆢᆨ-ᇹሀ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᰀ-ᰣᱍ-ᱏᱚ-ᱽᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₔℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-Ɐⱱ-ⱽⲀ-ⳤⴀ-ⴥⴰ-ⵥⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆷㇰ-ㇿ㐀䶵一鿃ꀀ-ꒌꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙟꙢ-ꙮꙿ-ꚗꜗ-ꜟꜢ-ꞈꞋꞌꟻ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가힣豈-鶴侮-頻並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])+(?:[̀-ͯ҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٞۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࠭ऀ-ं़ु-ै्॑-ॕॢॣঁ়ু-ৄ্ৢৣਁਂ਼ੁੂੇੈੋ-੍ੑੰੱੵઁં઼ુ-ૅેૈ્ૢૣଁ଼ିୁ-ୄ୍ୖୢୣஂீ்ా-ీె-ైొ-్ౕౖౢౣ಼ಿೆೌ್ೢೣു-ൄ്ൢൣ්ි-ුූัิ-ฺ็-๎ັິ-ູົຼ່-ໍཱ༹༘༙༵༷-ཾྀ-྄྆྇ྐ-ྗྙ-ྼ࿆ိ-ူဲ-့္်ွှၘၙၞ-ၠၱ-ၴႂႅႆႍႝ፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳិ-ួំ៉-៓៝᠋-᠍ᢩᤠ-ᤢᤧᤨᤲ᤹-᤻ᨘᨗᩖᩘ-ᩞ᩠ᩢᩥ-ᩬᩳ-᩿᩼ᬀ-ᬃ᬴ᬶ-ᬺᬼᭂ᭫-᭳ᮀᮁᮢ-ᮥᮨᮩᰬ-ᰳᰶ᰷᳐-᳔᳒-᳢᳠-᳨᳭᷀-᷽ᷦ-᷿⃐-⃥⃜⃡-⃰⳯-⳱ⷠ-〪ⷿ-゙゚〯꙯꙼꙽꛰꛱ꠂ꠆ꠋꠥꠦ꣄꣠-꣱ꤦ-꤭ꥇ-ꥑꦀ-ꦂ꦳ꦶ-ꦹꦼꨩ-ꨮꨱꨲꨵꨶꩃꩌꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꯥꯨ꯭ﬞ︀-️︠-︦ःा-ीॉ-ौॎংঃা-ীেৈোৌৗਃਾ-ੀઃા-ીૉોૌଂଃାୀେୈୋୌୗாிுூெ-ைொ-ௌௗఁ-ఃు-ౄಂಃಾೀ-ೄೇೈೊೋೕೖംഃാ-ീെ-ൈൊ-ൌൗංඃා-ෑෘ-ෟෲෳ༾༿ཿါာေးျြၖၗၢ-ၤၧ-ၭႃႄႇ-ႌႏႚ-ႜាើ-ៅះៈᤣ-ᤦᤩ-ᤫᤰᤱᤳ-ᤸᦰ-ᧀᧈᧉᨙ-ᨛᩕᩗᩡᩣᩤᩭ-ᩲᬄᬵᬻᬽ-ᭁᭃ᭄ᮂᮡᮦᮧ᮪ᰤ-ᰫᰴᰵ᳡ᳲꠣꠤꠧꢀꢁꢴ-ꣃꥒ꥓ꦃꦴꦵꦺꦻꦽ-꧀ꨯꨰꨳꨴꩍꩻꯣꯤꯦꯧꯩꯪ꯬0-9a-zA-Z_$0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧚᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꩐-꩙꯰-꯹0-9_‿⁀⁔︳︴﹍-﹏_])*)|(?P\n (?:\n 0[xX][0-9a-fA-F]+ # hex_integer_literal\n | 0[0-7]+ # or octal_integer_literal (spec B.1.1)\n | (?: # or decimal_literal\n (?:0|[1-9][0-9]*) # decimal_integer_literal\n \\. # dot\n [0-9]* # decimal_digits_opt\n (?:[eE][+-]?[0-9]+)? # exponent_part_opt\n |\n \\. # dot\n [0-9]+ # decimal_digits\n (?:[eE][+-]?[0-9]+)? # exponent_part_opt\n |\n (?:0|[1-9][0-9]*) # decimal_integer_literal\n (?:[eE][+-]?[0-9]+)? # exponent_part_opt\n )\n )\n )|(?P/\\*[^*]*\\*+([^/*][^*]*\\*+)*/)|(?P//[^\\r\\n]*)|(?P[\\n\\r]+)|(?P\\|\\|)|(?P\\+\\+)|(?P>>>=)|(?P<<=)|(?P\\*=)|(?P\\|=)|(?P\\+=)|(?P>>=)|(?P===)|(?P!==)|(?P>>>)|(?P\\^=)|(?P&&)|(?P&=)|(?P\\|)|(?P\\^)|(?P\\?)|(?P/=)|(?P==)|(?P>=)|(?P\\[)|(?P<=)|(?P\\()|(?P<<)|(?P-=)|(?P--)|(?P%=)|(?P\\*)|(?P!=)|(?P\\.)|(?P\\+)|(?P\\])|(?P\\))|(?P>>)|(?P&)|(?P~)|(?P:)|(?P,)|(?P/)|(?P=)|(?P>)|(?P{)|(?P<)|(?P-)|(?P%)|(?P!)|(?P})|(?P;)', [None, ('t_STRING', 'STRING'), ('t_GETPROP', 'GETPROP'), ('t_SETPROP', 'SETPROP'), ('t_ID', 'ID'), (None, 'NUMBER'), (None, 'BLOCK_COMMENT'), None, (None, 'LINE_COMMENT'), (None, 'LINE_TERMINATOR'), (None, 'OR'), (None, 'PLUSPLUS'), (None, 'URSHIFTEQUAL'), (None, 'LSHIFTEQUAL'), (None, 'MULTEQUAL'), (None, 'OREQUAL'), (None, 'PLUSEQUAL'), (None, 'RSHIFTEQUAL'), (None, 'STREQ'), (None, 'STRNEQ'), (None, 'URSHIFT'), (None, 'XOREQUAL'), (None, 'AND'), (None, 'ANDEQUAL'), (None, 'BOR'), (None, 'BXOR'), (None, 'CONDOP'), (None, 'DIVEQUAL'), (None, 'EQEQ'), (None, 'GE'), (None, 'LBRACKET'), (None, 'LE'), (None, 'LPAREN'), (None, 'LSHIFT'), (None, 'MINUSEQUAL'), (None, 'MINUSMINUS'), (None, 'MODEQUAL'), (None, 'MULT'), (None, 'NE'), (None, 'PERIOD'), (None, 'PLUS'), (None, 'RBRACKET'), (None, 'RPAREN'), (None, 'RSHIFT'), (None, 'BAND'), (None, 'BNOT'), (None, 'COLON'), (None, 'COMMA'), (None, 'DIV'), (None, 'EQ'), (None, 'GT'), (None, 'LBRACE'), (None, 'LT'), (None, 'MINUS'), (None, 'MOD'), (None, 'NOT'), (None, 'RBRACE'), (None, 'SEMI')])], 'regex': [('(?P(?:\n / # opening slash\n # First character is..\n (?: [^*\\\\/[] # anything but * \\ / or [\n | \\\\. # or an escape sequence\n | \\[ # or a class, which has\n (?: [^\\]\\\\] # anything but \\ or ]\n | \\\\. # or an escape sequence\n )* # many times\n \\]\n )\n # Following characters are same, except for excluding a star\n (?: [^\\\\/[] # anything but \\ / or [\n | \\\\. # or an escape sequence\n | \\[ # or a class, which has\n (?: [^\\]\\\\] # anything but \\ or ]\n | \\\\. # or an escape sequence\n )* # many times\n \\]\n )* # many times\n / # closing slash\n [a-zA-Z0-9]* # trailing flags\n )\n )', [None, (None, 'REGEX')])]} 9 | _lexstateignore = {'INITIAL': ' \t', 'regex': ' \t'} 10 | _lexstateerrorf = {'INITIAL': 't_error', 'regex': 't_regex_error'} 11 | _lexstateeoff = {} 12 | -------------------------------------------------------------------------------- /src/slimit/mangler.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | from slimit.scope import SymbolTable 28 | from slimit.visitors.scopevisitor import ( 29 | ScopeTreeVisitor, 30 | fill_scope_references, 31 | mangle_scope_tree, 32 | NameManglerVisitor, 33 | ) 34 | 35 | 36 | def mangle(tree, toplevel=False): 37 | """Mangle names. 38 | 39 | Args: 40 | toplevel: defaults to False. Defines if global 41 | scope should be mangled or not. 42 | """ 43 | sym_table = SymbolTable() 44 | visitor = ScopeTreeVisitor(sym_table) 45 | visitor.visit(tree) 46 | 47 | fill_scope_references(tree) 48 | mangle_scope_tree(sym_table.globals, toplevel) 49 | 50 | mangler = NameManglerVisitor() 51 | mangler.visit(tree) 52 | -------------------------------------------------------------------------------- /src/slimit/minifier.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import sys 28 | import optparse 29 | import textwrap 30 | 31 | from slimit import mangler 32 | from slimit.parser import Parser 33 | from slimit.visitors.minvisitor import ECMAMinifier 34 | 35 | 36 | def minify(text, mangle=False, mangle_toplevel=False): 37 | parser = Parser() 38 | tree = parser.parse(text) 39 | if mangle: 40 | mangler.mangle(tree, toplevel=mangle_toplevel) 41 | minified = ECMAMinifier().visit(tree) 42 | return minified 43 | 44 | 45 | def main(argv=None, inp=sys.stdin, out=sys.stdout): 46 | usage = textwrap.dedent("""\ 47 | %prog [options] [input file] 48 | 49 | If no input file is provided STDIN is used by default. 50 | Minified JavaScript code is printed to STDOUT. 51 | """) 52 | parser = optparse.OptionParser(usage=usage) 53 | parser.add_option('-m', '--mangle', action='store_true', 54 | dest='mangle', default=False, help='mangle names') 55 | parser.add_option('-t', '--mangle-toplevel', action='store_true', 56 | dest='mangle_toplevel', default=False, 57 | help='mangle top level scope (defaults to False)') 58 | 59 | if argv is None: 60 | argv = sys.argv[1:] 61 | options, args = parser.parse_args(argv) 62 | 63 | if len(args) == 1: 64 | text = open(args[0]).read() 65 | else: 66 | text = inp.read() 67 | 68 | minified = minify( 69 | text, mangle=options.mangle, mangle_toplevel=options.mangle_toplevel) 70 | out.write(minified) 71 | -------------------------------------------------------------------------------- /src/slimit/scope.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import itertools 28 | 29 | try: 30 | from collections import OrderedDict 31 | except ImportError: 32 | from odict import odict as OrderedDict 33 | 34 | from slimit.lexer import Lexer 35 | 36 | 37 | ID_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 38 | 39 | def powerset(iterable): 40 | """powerset('abc') -> a b c ab ac bc abc""" 41 | s = list(iterable) 42 | for chars in itertools.chain.from_iterable( 43 | itertools.combinations(s, r) for r in range(1, len(s)+1) 44 | ): 45 | yield ''.join(chars) 46 | 47 | 48 | class SymbolTable(object): 49 | def __init__(self): 50 | self.globals = GlobalScope() 51 | 52 | 53 | class Scope(object): 54 | 55 | def __init__(self, enclosing_scope=None): 56 | self.symbols = OrderedDict() 57 | # {symbol.name: mangled_name} 58 | self.mangled = {} 59 | # {mangled_name: symbol.name} 60 | self.rev_mangled = {} 61 | # names referenced from this scope and all sub-scopes 62 | # {name: scope} key is the name, value is the scope that 63 | # contains referenced name 64 | self.refs = {} 65 | # set to True if this scope or any subscope contains 'eval' 66 | self.has_eval = False 67 | # set to True if this scope or any subscope contains 'wit 68 | self.has_with = False 69 | self.enclosing_scope = enclosing_scope 70 | # sub-scopes 71 | self.children = [] 72 | # add ourselves as a child to the enclosing scope 73 | if enclosing_scope is not None: 74 | self.enclosing_scope.add_child(self) 75 | self.base54 = powerset(ID_CHARS) 76 | 77 | def __contains__(self, sym): 78 | return sym.name in self.symbols 79 | 80 | def add_child(self, scope): 81 | self.children.append(scope) 82 | 83 | def define(self, sym): 84 | self.symbols[sym.name] = sym 85 | # track scope for every symbol 86 | sym.scope = self 87 | 88 | def resolve(self, name): 89 | sym = self.symbols.get(name) 90 | if sym is not None: 91 | return sym 92 | elif self.enclosing_scope is not None: 93 | return self.enclosing_scope.resolve(name) 94 | 95 | def get_enclosing_scope(self): 96 | return self.enclosing_scope 97 | 98 | def _get_scope_with_mangled(self, name): 99 | """Return a scope containing passed mangled name.""" 100 | scope = self 101 | while True: 102 | parent = scope.get_enclosing_scope() 103 | if parent is None: 104 | return 105 | 106 | if name in parent.rev_mangled: 107 | return parent 108 | 109 | scope = parent 110 | 111 | def _get_scope_with_symbol(self, name): 112 | """Return a scope containing passed name as a symbol name.""" 113 | scope = self 114 | while True: 115 | parent = scope.get_enclosing_scope() 116 | if parent is None: 117 | return 118 | 119 | if name in parent.symbols: 120 | return parent 121 | 122 | scope = parent 123 | 124 | def get_next_mangled_name(self): 125 | """ 126 | 1. Do not shadow a mangled name from a parent scope 127 | if we reference the original name from that scope 128 | in this scope or any sub-scope. 129 | 130 | 2. Do not shadow an original name from a parent scope 131 | if it's not mangled and we reference it in this scope 132 | or any sub-scope. 133 | 134 | """ 135 | while True: 136 | mangled = next(self.base54) 137 | 138 | # case 1 139 | ancestor = self._get_scope_with_mangled(mangled) 140 | if (ancestor is not None 141 | and self.refs.get(ancestor.rev_mangled[mangled]) is ancestor 142 | ): 143 | continue 144 | 145 | # case 2 146 | ancestor = self._get_scope_with_symbol(mangled) 147 | if (ancestor is not None 148 | and self.refs.get(mangled) is ancestor 149 | and mangled not in ancestor.mangled 150 | ): 151 | continue 152 | 153 | # make sure a new mangled name is not a reserved word 154 | if mangled.upper() in Lexer.keywords: 155 | continue 156 | 157 | return mangled 158 | 159 | 160 | class GlobalScope(Scope): 161 | pass 162 | 163 | 164 | class LocalScope(Scope): 165 | pass 166 | 167 | 168 | class Symbol(object): 169 | def __init__(self, name): 170 | self.name = name 171 | self.scope = None 172 | 173 | 174 | class VarSymbol(Symbol): 175 | pass 176 | 177 | 178 | class FuncSymbol(Symbol, Scope): 179 | """Function symbol is both a symbol and a scope for arguments.""" 180 | 181 | def __init__(self, name, enclosing_scope): 182 | Symbol.__init__(self, name) 183 | Scope.__init__(self, enclosing_scope) 184 | -------------------------------------------------------------------------------- /src/slimit/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspivak/slimit/3533eba9ad5b39f3a015ae6269670022ab310847/src/slimit/tests/__init__.py -------------------------------------------------------------------------------- /src/slimit/tests/test_cmd.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import os 28 | import sys 29 | import tempfile 30 | import unittest 31 | 32 | from contextlib import contextmanager 33 | 34 | 35 | if sys.version_info[0] == 2: 36 | from StringIO import StringIO 37 | else: 38 | from io import StringIO 39 | 40 | 41 | @contextmanager 42 | def redirected_input_output(input=''): 43 | old_inp, old_out = sys.stdin, sys.stdout 44 | inp, out = StringIO(input), StringIO() 45 | sys.stdin, sys.stdout = inp, out 46 | try: 47 | yield out 48 | finally: 49 | sys.stdin, sys.stdout = old_inp, old_out 50 | 51 | 52 | @contextmanager 53 | def redirected_sys_argv(argv): 54 | old = sys.argv 55 | sys.argv = argv 56 | try: 57 | yield argv 58 | finally: 59 | sys.argv = old 60 | 61 | 62 | class CmdTestCase(unittest.TestCase): 63 | 64 | def setUp(self): 65 | fd, path = tempfile.mkstemp() 66 | self.path = path 67 | with os.fdopen(fd, 'w') as fout: 68 | fout.write('var global = 5;') 69 | 70 | def tearDown(self): 71 | os.remove(self.path) 72 | 73 | def test_main_dash_m_with_input_file(self): 74 | from slimit.minifier import main 75 | out = StringIO() 76 | main(['-m', '-t', self.path], out=out) 77 | self.assertEqual('var a=5;', out.getvalue()) 78 | 79 | def test_main_dash_dash_mangle_with_input_file(self): 80 | from slimit.minifier import main 81 | out = StringIO() 82 | main(['--mangle', '--mangle-toplevel', self.path], out=out) 83 | self.assertEqual('var a=5;', out.getvalue()) 84 | 85 | def test_main_dash_m_with_mock_stdin(self): 86 | from slimit.minifier import main 87 | out = StringIO() 88 | inp = StringIO('function foo() { var local = 5; }') 89 | main(['-m'], inp=inp, out=out) 90 | self.assertEqual('function foo(){var a=5;}', out.getvalue()) 91 | 92 | def test_main_stdin_stdout(self): 93 | # slimit.minifier should be deleted from sys.modules in order 94 | # to have a proper reference to sys.stdin and sys.stdou when 95 | # 'main' definition is evaluated during module import 96 | old_module = None 97 | try: 98 | old_module = sys.modules.pop('slimit.minifier') 99 | except KeyError: 100 | pass 101 | 102 | with redirected_input_output( 103 | input='function foo() { var local = 5; }') as out: 104 | from slimit.minifier import main 105 | main(['-m']) 106 | 107 | self.assertEqual('function foo(){var a=5;}', out.getvalue()) 108 | if old_module is not None: 109 | sys.modules['slimit.minifier'] = old_module 110 | 111 | def test_main_sys_argv(self): 112 | out = StringIO() 113 | inp = StringIO('var global = 5;') 114 | with redirected_sys_argv(['slimit', '-m', '-t']): 115 | from slimit.minifier import main 116 | main(inp=inp, out=out) 117 | 118 | self.assertEqual('var a=5;', out.getvalue()) 119 | -------------------------------------------------------------------------------- /src/slimit/tests/test_ecmavisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import textwrap 28 | import unittest 29 | 30 | from slimit.parser import Parser 31 | 32 | 33 | def decorator(cls): 34 | def make_test_function(input, expected): 35 | 36 | def test_func(self): 37 | parser = Parser() 38 | result = parser.parse(input).to_ecma() 39 | self.assertMultiLineEqual(result, expected) 40 | 41 | return test_func 42 | 43 | for index, input in enumerate(cls.TEST_CASES): 44 | input = textwrap.dedent(input).strip() 45 | func = make_test_function(input, input) 46 | setattr(cls, 'test_case_%d' % index, func) 47 | 48 | return cls 49 | 50 | 51 | @decorator 52 | class ECMAVisitorTestCase(unittest.TestCase): 53 | 54 | def setUp(self): 55 | self.maxDiff = 2000 56 | 57 | TEST_CASES = [ 58 | ################################ 59 | # block 60 | ################################ 61 | """ 62 | { 63 | var a = 5; 64 | } 65 | """, 66 | 67 | ################################ 68 | # variable statement 69 | ################################ 70 | """ 71 | var a; 72 | var b; 73 | var a, b = 3; 74 | var a = 1, b; 75 | var a = 5, b = 7; 76 | """, 77 | 78 | # empty statement 79 | """ 80 | ; 81 | ; 82 | ; 83 | """, 84 | 85 | # test 3 86 | ################################ 87 | # if 88 | ################################ 89 | 'if (true) var x = 100;', 90 | 91 | """ 92 | if (true) { 93 | var x = 100; 94 | var y = 200; 95 | } 96 | """, 97 | 98 | 'if (true) if (true) var x = 100; else var y = 200;', 99 | 100 | # test 6 101 | """ 102 | if (true) { 103 | var x = 100; 104 | } else { 105 | var y = 200; 106 | } 107 | """, 108 | ################################ 109 | # iteration 110 | ################################ 111 | """ 112 | for (i = 0; i < 10; i++) { 113 | x = 10 * i; 114 | } 115 | """, 116 | 117 | """ 118 | for (var i = 0; i < 10; i++) { 119 | x = 10 * i; 120 | } 121 | """, 122 | 123 | # test 9 124 | """ 125 | for (i = 0, j = 10; i < j && j < 15; i++, j++) { 126 | x = i * j; 127 | } 128 | """, 129 | 130 | """ 131 | for (var i = 0, j = 10; i < j && j < 15; i++, j++) { 132 | x = i * j; 133 | } 134 | """, 135 | 136 | """ 137 | for (p in obj) { 138 | 139 | } 140 | """, 141 | # retain the semicolon in the initialiser part of a 'for' statement 142 | """ 143 | for (Q || (Q = []); d < b; ) { 144 | d = 1; 145 | } 146 | """, 147 | 148 | """ 149 | for (new Foo(); d < b; ) { 150 | d = 1; 151 | } 152 | """, 153 | 154 | """ 155 | for (2 >> (foo ? 32 : 43) && 54; 21; ) { 156 | a = c; 157 | } 158 | """, 159 | 160 | """ 161 | for (/^.+/g; cond(); ++z) { 162 | ev(); 163 | } 164 | """, 165 | 166 | # test 12 167 | """ 168 | for (var p in obj) { 169 | p = 1; 170 | } 171 | """, 172 | 173 | """ 174 | do { 175 | x += 1; 176 | } while (true); 177 | """, 178 | 179 | """ 180 | while (false) { 181 | x = null; 182 | } 183 | """, 184 | 185 | # test 15 186 | ################################ 187 | # continue statement 188 | ################################ 189 | """ 190 | while (true) { 191 | continue; 192 | s = 'I am not reachable'; 193 | } 194 | """, 195 | 196 | """ 197 | while (true) { 198 | continue label1; 199 | s = 'I am not reachable'; 200 | } 201 | """, 202 | 203 | ################################ 204 | # break statement 205 | ################################ 206 | """ 207 | while (true) { 208 | break; 209 | s = 'I am not reachable'; 210 | } 211 | """, 212 | # test 18 213 | """ 214 | while (true) { 215 | break label1; 216 | s = 'I am not reachable'; 217 | } 218 | """, 219 | 220 | ################################ 221 | # return statement 222 | ################################ 223 | """ 224 | { 225 | return; 226 | } 227 | """, 228 | 229 | """ 230 | { 231 | return 1; 232 | } 233 | """, 234 | 235 | # test21 236 | ################################ 237 | # with statement 238 | ################################ 239 | """ 240 | with (x) { 241 | var y = x * 2; 242 | } 243 | """, 244 | 245 | ################################ 246 | # labelled statement 247 | ################################ 248 | """ 249 | label: while (true) { 250 | x *= 3; 251 | } 252 | """, 253 | 254 | ################################ 255 | # switch statement 256 | ################################ 257 | """ 258 | switch (day_of_week) { 259 | case 6: 260 | case 7: 261 | x = 'Weekend'; 262 | break; 263 | case 1: 264 | x = 'Monday'; 265 | break; 266 | default: 267 | break; 268 | } 269 | """, 270 | 271 | # test 24 272 | ################################ 273 | # throw statement 274 | ################################ 275 | """ 276 | throw 'exc'; 277 | """, 278 | 279 | ################################ 280 | # debugger statement 281 | ################################ 282 | 'debugger;', 283 | 284 | ################################ 285 | # expression statement 286 | ################################ 287 | """ 288 | 5 + 7 - 20 * 10; 289 | ++x; 290 | --x; 291 | x++; 292 | x--; 293 | x = 17 /= 3; 294 | s = mot ? z : /x:3;x<5;y <= >= || && ++ -- << >> ' 112 | '>>> += -= *= <<= >>= >>>= &= %= ^= |='), 113 | ['EQ =', 'EQEQ ==', 'NE !=', 'STREQ ===', 'STRNEQ !==', 'LT <', 114 | 'GT >', 'LE <=', 'GE >=', 'OR ||', 'AND &&', 'PLUSPLUS ++', 115 | 'MINUSMINUS --', 'LSHIFT <<', 'RSHIFT >>', 'URSHIFT >>>', 116 | 'PLUSEQUAL +=', 'MINUSEQUAL -=', 'MULTEQUAL *=', 'LSHIFTEQUAL <<=', 117 | 'RSHIFTEQUAL >>=', 'URSHIFTEQUAL >>>=', 'ANDEQUAL &=', 'MODEQUAL %=', 118 | 'XOREQUAL ^=', 'OREQUAL |=', 119 | ] 120 | ), 121 | ('. , ; : + - * % & | ^ ~ ? ! ( ) { } [ ]', 122 | ['PERIOD .', 'COMMA ,', 'SEMI ;', 'COLON :', 'PLUS +', 'MINUS -', 123 | 'MULT *', 'MOD %', 'BAND &', 'BOR |', 'BXOR ^', 'BNOT ~', 124 | 'CONDOP ?', 'NOT !', 'LPAREN (', 'RPAREN )', 'LBRACE {', 'RBRACE }', 125 | 'LBRACKET [', 'RBRACKET ]'] 126 | ), 127 | ('a / b', ['ID a', 'DIV /', 'ID b']), 128 | 129 | # Numbers 130 | (('3 3.3 0 0. 0.0 0.001 010 3.e2 3.e-2 3.e+2 3E2 3E+2 3E-2 ' 131 | '0.5e2 0.5e+2 0.5e-2 33 128.15 0x001 0X12ABCDEF 0xabcdef'), 132 | ['NUMBER 3', 'NUMBER 3.3', 'NUMBER 0', 'NUMBER 0.', 'NUMBER 0.0', 133 | 'NUMBER 0.001', 'NUMBER 010', 'NUMBER 3.e2', 'NUMBER 3.e-2', 134 | 'NUMBER 3.e+2', 'NUMBER 3E2', 'NUMBER 3E+2', 'NUMBER 3E-2', 135 | 'NUMBER 0.5e2', 'NUMBER 0.5e+2', 'NUMBER 0.5e-2', 'NUMBER 33', 136 | 'NUMBER 128.15', 'NUMBER 0x001', 'NUMBER 0X12ABCDEF', 137 | 'NUMBER 0xabcdef'] 138 | ), 139 | 140 | # Strings 141 | (""" '"' """, ["""STRING '"'"""]), 142 | (r'''"foo" 'foo' "x\";" 'x\';' "foo\tbar"''', 143 | ['STRING "foo"', """STRING 'foo'""", r'STRING "x\";"', 144 | r"STRING 'x\';'", r'STRING "foo\tbar"'] 145 | ), 146 | (r"""'\x55' "\x12ABCDEF" '!@#$%^&*()_+{}[]\";?'""", 147 | [r"STRING '\x55'", r'STRING "\x12ABCDEF"', 148 | r"STRING '!@#$%^&*()_+{}[]\";?'"] 149 | ), 150 | (r"""'\u0001' "\uFCEF" 'a\\\b\n'""", 151 | [r"STRING '\u0001'", r'STRING "\uFCEF"', r"STRING 'a\\\b\n'"] 152 | ), 153 | (u'"\u0442\u0435\u0441\u0442 \u0441\u0442\u0440\u043e\u043a\u0438\\""', [u'STRING "\u0442\u0435\u0441\u0442 \u0441\u0442\u0440\u043e\u043a\u0438\\""']), 154 | # Bug - https://github.com/rspivak/slimit/issues/5 155 | (r"var tagRegExp = new RegExp('<(\/*)(FooBar)', 'gi');", 156 | ['VAR var', 'ID tagRegExp', 'EQ =', 157 | 'NEW new', 'ID RegExp', 'LPAREN (', 158 | r"STRING '<(\/*)(FooBar)'", 'COMMA ,', "STRING 'gi'", 159 | 'RPAREN )', 'SEMI ;'] 160 | ), 161 | # same as above but inside double quotes 162 | (r'"<(\/*)(FooBar)"', [r'STRING "<(\/*)(FooBar)"']), 163 | # multiline string (string written across multiple lines 164 | # of code) https://github.com/rspivak/slimit/issues/24 165 | (r"""var a = 'hello \ 166 | world'""", 167 | ['VAR var', 'ID a', 'EQ =', "STRING 'hello world'"]), 168 | (r'''var a = "hello \ 169 | world"''', 170 | ['VAR var', 'ID a', 'EQ =', 'STRING "hello world"']), 171 | 172 | # # Comments 173 | # (""" 174 | # //comment 175 | # a = 5; 176 | # """, ['LINE_COMMENT //comment', 'ID a', 'EQ =', 'NUMBER 5', 'SEMI ;'] 177 | # ), 178 | # ('a//comment', ['ID a', 'LINE_COMMENT //comment']), 179 | # ('/***/b/=3//line', 180 | # ['BLOCK_COMMENT /***/', 'ID b', 'DIVEQUAL /=', 181 | # 'NUMBER 3', 'LINE_COMMENT //line'] 182 | # ), 183 | # ('/*\n * Copyright LGPL 2011 \n*/\na = 1;', 184 | # ['BLOCK_COMMENT /*\n * Copyright LGPL 2011 \n*/', 185 | # 'ID a', 'EQ =', 'NUMBER 1', 'SEMI ;'] 186 | # ), 187 | 188 | # regex 189 | (r'a=/a*/,1', ['ID a', 'EQ =', 'REGEX /a*/', 'COMMA ,', 'NUMBER 1']), 190 | (r'a=/a*[^/]+/,1', 191 | ['ID a', 'EQ =', 'REGEX /a*[^/]+/', 'COMMA ,', 'NUMBER 1'] 192 | ), 193 | (r'a=/a*\[^/,1', 194 | ['ID a', 'EQ =', r'REGEX /a*\[^/', 'COMMA ,', 'NUMBER 1'] 195 | ), 196 | (r'a=/\//,1', ['ID a', 'EQ =', r'REGEX /\//', 'COMMA ,', 'NUMBER 1']), 197 | # not a regex, just a division 198 | # https://github.com/rspivak/slimit/issues/6 199 | (r'x = this / y;', 200 | ['ID x', 'EQ =', 'THIS this', r'DIV /', r'ID y', r'SEMI ;']), 201 | 202 | # next two are from 203 | # http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions 204 | ("""for (var x = a in foo && "" || mot ? z:/x:3;x<5;y"', "OR ||", "ID mot", "CONDOP ?", 207 | "ID z", "COLON :", "REGEX /x:3;x<5;y" || mot ? z/x:3;x<5;y"', "OR ||", "ID mot", "CONDOP ?", 215 | "ID z", "DIV /", "ID x", "COLON :", "NUMBER 3", "SEMI ;", "ID x", 216 | "LT <", "NUMBER 5", "SEMI ;", "ID y", "LT <", "REGEX /g/i", 217 | "RPAREN )", "LBRACE {", "ID xyz", "LPAREN (", "ID x", "PLUSPLUS ++", 218 | "RPAREN )", "SEMI ;", "RBRACE }"] 219 | ), 220 | 221 | # Various "illegal" regexes that are valid according to the std. 222 | (r"""/????/, /++++/, /[----]/ """, 223 | ['REGEX /????/', 'COMMA ,', 224 | 'REGEX /++++/', 'COMMA ,', 'REGEX /[----]/'] 225 | ), 226 | 227 | # Stress cases from http://stackoverflow.com/questions/5533925/what-javascript-constructs-does-jslex-incorrectly-lex/5573409#5573409 228 | (r"""/\[/""", [r"""REGEX /\[/"""]), 229 | (r"""/[i]/""", [r"""REGEX /[i]/"""]), 230 | (r"""/[\]]/""", [r"""REGEX /[\]]/"""]), 231 | (r"""/a[\]]/""", [r"""REGEX /a[\]]/"""]), 232 | (r"""/a[\]]b/""", [r"""REGEX /a[\]]b/"""]), 233 | (r"""/[\]/]/gi""", [r"""REGEX /[\]/]/gi"""]), 234 | (r"""/\[[^\]]+\]/gi""", [r"""REGEX /\[[^\]]+\]/gi"""]), 235 | (""" 236 | rexl.re = { 237 | NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, 238 | UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, 239 | QUOTED_LITERAL: /^'(?:[^']|'')*'/, 240 | NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, 241 | SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ 242 | }; 243 | """, 244 | ["ID rexl", "PERIOD .", "ID re", "EQ =", "LBRACE {", 245 | "ID NAME", "COLON :", 246 | r"""REGEX /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", "COMMA ,", 247 | "ID UNQUOTED_LITERAL", "COLON :", 248 | r"""REGEX /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", 249 | "COMMA ,", "ID QUOTED_LITERAL", "COLON :", 250 | r"""REGEX /^'(?:[^']|'')*'/""", "COMMA ,", "ID NUMERIC_LITERAL", 251 | "COLON :", 252 | r"""REGEX /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "COMMA ,", 253 | "ID SYMBOL", "COLON :", 254 | r"""REGEX /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", 255 | "RBRACE }", "SEMI ;"] 256 | ), 257 | (""" 258 | rexl.re = { 259 | NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, 260 | UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, 261 | QUOTED_LITERAL: /^'(?:[^']|'')*'/, 262 | NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, 263 | SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ 264 | }; 265 | str = '"'; 266 | """, 267 | ["ID rexl", "PERIOD .", "ID re", "EQ =", "LBRACE {", 268 | "ID NAME", "COLON :", r"""REGEX /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", 269 | "COMMA ,", "ID UNQUOTED_LITERAL", "COLON :", 270 | r"""REGEX /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", 271 | "COMMA ,", "ID QUOTED_LITERAL", "COLON :", 272 | r"""REGEX /^'(?:[^']|'')*'/""", "COMMA ,", 273 | "ID NUMERIC_LITERAL", "COLON :", 274 | r"""REGEX /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "COMMA ,", 275 | "ID SYMBOL", "COLON :", 276 | r"""REGEX /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", 277 | "RBRACE }", "SEMI ;", 278 | "ID str", "EQ =", """STRING '"'""", "SEMI ;", 279 | ]), 280 | (r""" this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\")"; """, 281 | ["THIS this", "PERIOD .", "ID _js", "EQ =", 282 | r'''STRING "e.str(\""''', "PLUS +", "THIS this", "PERIOD .", 283 | "ID value", "PERIOD .", "ID replace", "LPAREN (", r"REGEX /\\/g", 284 | "COMMA ,", r'STRING "\\\\"', "RPAREN )", "PERIOD .", "ID replace", 285 | "LPAREN (", r'REGEX /"/g', "COMMA ,", r'STRING "\\\""', "RPAREN )", 286 | "PLUS +", r'STRING "\")"', "SEMI ;"]), 287 | ] 288 | 289 | 290 | def test_suite(): 291 | return unittest.TestSuite(( 292 | unittest.makeSuite(LexerTestCase), 293 | doctest.DocFileSuite( 294 | '../lexer.py', 295 | optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS 296 | ), 297 | )) 298 | -------------------------------------------------------------------------------- /src/slimit/tests/test_mangler.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import textwrap 28 | import unittest 29 | 30 | from slimit.parser import Parser 31 | from slimit.mangler import mangle 32 | 33 | 34 | def decorator(cls): 35 | def make_test_function(input, expected): 36 | def test_func(self): 37 | parser = Parser() 38 | tree = parser.parse(input) 39 | mangle(tree, toplevel=True) 40 | self.assertMultiLineEqual( 41 | textwrap.dedent(tree.to_ecma()).strip(), 42 | textwrap.dedent(expected).strip() 43 | ) 44 | 45 | return test_func 46 | 47 | for index, (input, expected) in enumerate(cls.TEST_CASES): 48 | func = make_test_function(input, expected) 49 | setattr(cls, 'test_case_%d' % index, func) 50 | 51 | return cls 52 | 53 | 54 | @decorator 55 | class ManglerTestCase(unittest.TestCase): 56 | 57 | TEST_CASES = [ 58 | # test nested function declaration 59 | # test that object properties ids are not changed 60 | (""" 61 | function test() { 62 | function is_false() { 63 | var xpos = 5; 64 | var point = { 65 | xpos: 17, 66 | ypos: 10 67 | }; 68 | return true; 69 | } 70 | } 71 | """, 72 | """ 73 | function a() { 74 | function a() { 75 | var a = 5; 76 | var b = { 77 | xpos: 17, 78 | ypos: 10 79 | }; 80 | return true; 81 | } 82 | } 83 | """), 84 | 85 | # test that mangled names are not shadowed when we reference 86 | # original names from any sub-scope 87 | (""" 88 | var result = function() { 89 | var long_name = 'long name'; 90 | var not_so_long = 'indeed', log = 5; 91 | global_x = 56; 92 | console.log(long_name + not_so_long); 93 | new_result = function(arg1, arg2) { 94 | var arg2 = 'qwerty'; 95 | console.log(long_name + not_so_long + arg1 + arg2 + global_x); 96 | }; 97 | }; 98 | """, 99 | """ 100 | var a = function() { 101 | var a = 'long name'; 102 | var b = 'indeed', c = 5; 103 | global_x = 56; 104 | console.log(a + b); 105 | new_result = function(c, d) { 106 | var d = 'qwerty'; 107 | console.log(a + b + c + d + global_x); 108 | }; 109 | }; 110 | """), 111 | 112 | # https://github.com/rspivak/slimit/issues/7 113 | (""" 114 | function a() { 115 | var $exc1 = null; 116 | try { 117 | lala(); 118 | } catch($exc) { 119 | if ($exc.__name__ == 'hi') { 120 | return 'bam'; 121 | } 122 | } 123 | return 'bum'; 124 | } 125 | """, 126 | """ 127 | function a() { 128 | var a = null; 129 | try { 130 | lala(); 131 | } catch (b) { 132 | if (b.__name__ == 'hi') { 133 | return 'bam'; 134 | } 135 | } 136 | return 'bum'; 137 | } 138 | """), 139 | 140 | # Handle the case when function arguments are redefined; 141 | # in the example below statement arg = 9; doesn't create 142 | # a global variable -it changes the value of arguments[0]. 143 | # The same is with statement var arg = 0; 144 | # http://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/ 145 | (""" 146 | function a(arg) { 147 | arg = 9; 148 | var arg = 0; 149 | return arg; 150 | } 151 | """, 152 | """ 153 | function a(a) { 154 | a = 9; 155 | var a = 0; 156 | return a; 157 | } 158 | """), 159 | ] 160 | -------------------------------------------------------------------------------- /src/slimit/tests/test_minifier.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import unittest 28 | 29 | from slimit import minify 30 | 31 | 32 | def decorator(cls): 33 | def make_test_function(input, expected): 34 | 35 | def test_func(self): 36 | self.assertMinified(input, expected) 37 | 38 | return test_func 39 | 40 | for index, (input, expected) in enumerate(cls.TEST_CASES): 41 | func = make_test_function(input, expected) 42 | setattr(cls, 'test_case_%d' % index, func) 43 | 44 | return cls 45 | 46 | 47 | @decorator 48 | class MinifierTestCase(unittest.TestCase): 49 | 50 | def assertMinified(self, source, expected): 51 | minified = minify(source) 52 | self.maxDiff = None 53 | self.assertSequenceEqual(minified, expected) 54 | 55 | TEST_CASES = [ 56 | (""" 57 | jQuery.fn = jQuery.prototype = { 58 | // For internal use only. 59 | _data: function( elem, name, data ) { 60 | return jQuery.data( elem, name, data, true ); 61 | } 62 | }; 63 | """, 64 | 'jQuery.fn=jQuery.prototype={_data:function(elem,name,data){return jQuery.data(elem,name,data,true);}};'), 65 | 66 | ('context = context instanceof jQuery ? context[0] : context;', 67 | 'context=context instanceof jQuery?context[0]:context;' 68 | ), 69 | 70 | (""" 71 | /* 72 | * A number of helper functions used for managing events. 73 | * Many of the ideas behind this code originated from 74 | * Dean Edwards' addEvent library. 75 | */ 76 | if ( elem && elem.parentNode ) { 77 | // Handle the case where IE and Opera return items 78 | // by name instead of ID 79 | if ( elem.id !== match[2] ) { 80 | return rootjQuery.find( selector ); 81 | } 82 | 83 | // Otherwise, we inject the element directly into the jQuery object 84 | this.length = 1; 85 | this[0] = elem; 86 | } 87 | """, 88 | 89 | 'if(elem&&elem.parentNode){if(elem.id!==match[2])return rootjQuery.find(selector);this.length=1;this[0]=elem;}' 90 | ), 91 | 92 | (""" 93 | var a = function( obj ) { 94 | for ( var name in obj ) { 95 | return false; 96 | } 97 | return true; 98 | }; 99 | """, 100 | 'var a=function(obj){for(var name in obj)return false;return true;};' 101 | ), 102 | 103 | (""" 104 | x = "string", y = 5; 105 | 106 | (x = 5) ? true : false; 107 | 108 | for (p in obj) 109 | ; 110 | 111 | if (true) 112 | val = null; 113 | else 114 | val = false; 115 | 116 | """, 117 | 'x="string",y=5;(x=5)?true:false;for(p in obj);if(true)val=null;else val=false;' 118 | ), 119 | 120 | # for loops + empty statement in loop body 121 | (""" 122 | for (x = 0; true; x++) 123 | ; 124 | for (; true; x++) 125 | ; 126 | for (x = 0, y = 5; true; x++) 127 | ; 128 | 129 | y = (x + 5) * 20; 130 | 131 | """, 132 | 'for(x=0;true;x++);for(;true;x++);for(x=0,y=5;true;x++);y=(x+5)*20;'), 133 | 134 | 135 | # unary expressions 136 | (""" 137 | delete x; 138 | typeof x; 139 | void x; 140 | x += (!y)++; 141 | """, 142 | 'delete x;typeof x;void x;x+=(!y)++;'), 143 | 144 | # label + break label + continue label 145 | (""" 146 | label: 147 | if ( i == 0 ) 148 | continue label; 149 | switch (day) { 150 | case 5: 151 | break ; 152 | default: 153 | break label; 154 | } 155 | """, 156 | 'label:if(i==0)continue label;switch(day){case 5:break;default:break label;}'), 157 | 158 | # break + continue: no labels 159 | (""" 160 | while (i <= 7) { 161 | if ( i == 3 ) 162 | continue; 163 | if ( i == 0 ) 164 | break; 165 | } 166 | """, 167 | 'while(i<=7){if(i==3)continue;if(i==0)break;}'), 168 | 169 | # regex + one line statements in if and if .. else 170 | (""" 171 | function a(x, y) { 172 | var re = /ab+c/; 173 | if (x == 1) 174 | return x + y; 175 | if (x == 3) 176 | return {x: 1}; 177 | else 178 | return; 179 | } 180 | """, 181 | 'function a(x,y){var re=/ab+c/;if(x==1)return x+y;if(x==3)return{x:1};else return;}'), 182 | 183 | # new 184 | ('return new jQuery.fn.init( selector, context, rootjQuery );', 185 | 'return new jQuery.fn.init(selector,context,rootjQuery);' 186 | ), 187 | 188 | # no space after 'else' when the next token is (, { 189 | (""" 190 | if (true) { 191 | x = true; 192 | y = 3; 193 | } else { 194 | x = false 195 | y = 5 196 | } 197 | """, 198 | 'if(true){x=true;y=3;}else{x=false;y=5;}'), 199 | 200 | (""" 201 | if (true) { 202 | x = true; 203 | y = 3; 204 | } else 205 | (x + ' qw').split(' '); 206 | """, 207 | "if(true){x=true;y=3;}else(x+' qw').split(' ');"), 208 | 209 | 210 | ############################################################## 211 | # Block braces removal 212 | ############################################################## 213 | 214 | # do while 215 | ('do { x += 1; } while(true);', 'do x+=1;while(true);'), 216 | # do while: multiple statements 217 | ('do { x += 1; y += 1;} while(true);', 'do{x+=1;y+=1;}while(true);'), 218 | 219 | # elision 220 | ('var a = [1, 2, 3, ,,,5];', 'var a=[1,2,3,,,,5];'), 221 | 222 | # with 223 | (""" 224 | with (obj) { 225 | a = b; 226 | } 227 | """, 228 | 'with(obj)a=b;'), 229 | 230 | # with: multiple statements 231 | (""" 232 | with (obj) { 233 | a = b; 234 | c = d; 235 | } 236 | """, 237 | 'with(obj){a=b;c=d;}'), 238 | 239 | # if else 240 | (""" 241 | if (true) { 242 | x = true; 243 | } else { 244 | x = false 245 | } 246 | """, 247 | 'if(true)x=true;else x=false;'), 248 | 249 | # if: multiple statements 250 | (""" 251 | if (true) { 252 | x = true; 253 | y = false; 254 | } else { 255 | x = false; 256 | y = true; 257 | } 258 | """, 259 | 'if(true){x=true;y=false;}else{x=false;y=true;}'), 260 | 261 | # try catch finally: one statement 262 | (""" 263 | try { 264 | throw "my_exception"; // generates an exception 265 | } 266 | catch (e) { 267 | // statements to handle any exceptions 268 | log(e); // pass exception object to error handler 269 | } 270 | finally { 271 | closefiles(); // always close the resource 272 | } 273 | """, 274 | 'try{throw "my_exception";}catch(e){log(e);}finally{closefiles();}' 275 | ), 276 | 277 | # try catch finally: no statements 278 | (""" 279 | try { 280 | } 281 | catch (e) { 282 | } 283 | finally { 284 | } 285 | """, 286 | 'try{}catch(e){}finally{}' 287 | ), 288 | 289 | # try catch finally: multiple statements 290 | (""" 291 | try { 292 | x = 3; 293 | y = 5; 294 | } 295 | catch (e) { 296 | log(e); 297 | log('e'); 298 | } 299 | finally { 300 | z = 7; 301 | log('z'); 302 | } 303 | """, 304 | "try{x=3;y=5;}catch(e){log(e);log('e');}finally{z=7;log('z');}" 305 | ), 306 | 307 | # tricky case with an 'if' nested in 'if .. else' 308 | # We need to preserve braces in the first 'if' otherwise 309 | # 'else' might get associated with nested 'if' instead 310 | (""" 311 | if ( obj ) { 312 | for ( n in obj ) { 313 | if ( v === false) { 314 | break; 315 | } 316 | } 317 | } else { 318 | for ( ; i < l; ) { 319 | if ( nv === false ) { 320 | break; 321 | } 322 | } 323 | } 324 | """, 325 | 'if(obj){for(n in obj)if(v===false)break;}else for(;i foo.bar 370 | ('foo["bar"];', 'foo.bar;'), 371 | ("foo['bar'];", 'foo.bar;'), 372 | ("""foo['bar"']=42;""", """foo['bar"']=42;"""), 373 | ("""foo["bar'"]=42;""", """foo["bar'"]=42;"""), 374 | ('foo["bar bar"];', 'foo["bar bar"];'), 375 | ('foo["bar"+"bar"];', 'foo["bar"+"bar"];'), 376 | # https://github.com/rspivak/slimit/issues/34 377 | # test some reserved keywords 378 | ('foo["for"];', 'foo["for"];'), 379 | ('foo["class"];', 'foo["class"];'), 380 | 381 | 382 | # https://github.com/rspivak/slimit/issues/21 383 | # c||(c=393,a=323,b=2321); --> c||c=393,a=323,b=2321; ERROR 384 | ('c||(c=393);', 'c||(c=393);'), 385 | ('c||(c=393,a=323,b=2321);', 'c||(c=393,a=323,b=2321);'), 386 | 387 | # https://github.com/rspivak/slimit/issues/25 388 | ('for(a?b:c;d;)e=1;', 'for(a?b:c;d;)e=1;'), 389 | 390 | # https://github.com/rspivak/slimit/issues/26 391 | ('"begin"+ ++a+"end";', '"begin"+ ++a+"end";'), 392 | 393 | # https://github.com/rspivak/slimit/issues/28 394 | (""" 395 | (function($) { 396 | $.hello = 'world'; 397 | }(jQuery)); 398 | """, 399 | "(function($){$.hello='world';}(jQuery));"), 400 | 401 | # function call in FOR init 402 | ('for(o(); i < 3; i++) {}', 'for(o();i<3;i++){}'), 403 | 404 | # unary increment operator in FOR init 405 | ('for(i++; i < 3; i++) {}', 'for(i++;i<3;i++){}'), 406 | 407 | # unary decrement operator in FOR init 408 | ('for(i--; i < 3; i++) {}', 'for(i--;i<3;i++){}'), 409 | 410 | # issue-37, simple identifier in FOR init 411 | ('for(i; i < 3; i++) {}', 'for(i;i<3;i++){}'), 412 | 413 | # https://github.com/rspivak/slimit/issues/32 414 | (""" 415 | Name.prototype = { 416 | getPageProp: function Page_getPageProp(key) { 417 | return this.pageDict.get(key); 418 | }, 419 | 420 | get fullName() { 421 | return this.first + " " + this.last; 422 | }, 423 | 424 | set fullName(name) { 425 | var names = name.split(" "); 426 | this.first = names[0]; 427 | this.last = names[1]; 428 | } 429 | }; 430 | """, 431 | ('Name.prototype={getPageProp:function Page_getPageProp(key){' 432 | 'return this.pageDict.get(key);},' 433 | 'get fullName(){return this.first+" "+this.last;},' 434 | 'set fullName(name){var names=name.split(" ");this.first=names[0];' 435 | 'this.last=names[1];}};') 436 | ), 437 | 438 | # https://github.com/rspivak/slimit/issues/47 - might be a Python 3 439 | # related issue 440 | ('testObj[":"] = undefined; // Breaks', 'testObj[":"]=undefined;'), 441 | ('testObj["::"] = undefined; // Breaks', 'testObj["::"]=undefined;'), 442 | ('testObj["a:"] = undefined; // Breaks', 'testObj["a:"]=undefined;'), 443 | ('testObj["."] = undefined; // OK', 'testObj["."]=undefined;'), 444 | ('testObj["{"] = undefined; // OK', 'testObj["{"]=undefined;'), 445 | ('testObj["}"] = undefined; // OK', 'testObj["}"]=undefined;'), 446 | ('testObj["["] = undefined; // Breaks', 'testObj["["]=undefined;'), 447 | ('testObj["]"] = undefined; // Breaks', 'testObj["]"]=undefined;'), 448 | ('testObj["("] = undefined; // OK', 'testObj["("]=undefined;'), 449 | ('testObj[")"] = undefined; // OK', 'testObj[")"]=undefined;'), 450 | ('testObj["="] = undefined; // Breaks', 'testObj["="]=undefined;'), 451 | ('testObj["-"] = undefined; // OK', 'testObj["-"]=undefined;'), 452 | ('testObj["+"] = undefined; // OK', 'testObj["+"]=undefined;'), 453 | ('testObj["*"] = undefined; // OK', 'testObj["*"]=undefined;'), 454 | ('testObj["/"] = undefined; // OK', 'testObj["/"]=undefined;'), 455 | (r'testObj["\\"] = undefined; // Breaks', r'testObj["\\"]=undefined;'), 456 | ('testObj["%"] = undefined; // OK', 'testObj["%"]=undefined;'), 457 | ('testObj["<"] = undefined; // Breaks', 'testObj["<"]=undefined;'), 458 | ('testObj[">"] = undefined; // Breaks', 'testObj[">"]=undefined;'), 459 | ('testObj["!"] = undefined; // OK', 'testObj["!"]=undefined;'), 460 | ('testObj["?"] = undefined; // Breaks', 'testObj["?"]=undefined;'), 461 | ('testObj[","] = undefined; // OK', 'testObj[","]=undefined;'), 462 | ('testObj["@"] = undefined; // Breaks', 'testObj["@"]=undefined;'), 463 | ('testObj["#"] = undefined; // OK', 'testObj["#"]=undefined;'), 464 | ('testObj["&"] = undefined; // OK', 'testObj["&"]=undefined;'), 465 | ('testObj["|"] = undefined; // OK', 'testObj["|"]=undefined;'), 466 | ('testObj["~"] = undefined; // OK', 'testObj["~"]=undefined;'), 467 | ('testObj["`"] = undefined; // Breaks', 'testObj["`"]=undefined;'), 468 | ('testObj["."] = undefined; // OK', 'testObj["."]=undefined;'), 469 | ] 470 | 471 | -------------------------------------------------------------------------------- /src/slimit/tests/test_nodevisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import doctest 28 | import unittest 29 | 30 | 31 | def test_suite(): 32 | return unittest.TestSuite(( 33 | doctest.DocFileSuite( 34 | '../visitors/nodevisitor.py', 35 | optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS 36 | ), 37 | )) 38 | -------------------------------------------------------------------------------- /src/slimit/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011-2012 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import textwrap 28 | import unittest 29 | 30 | from slimit import ast 31 | from slimit.parser import Parser 32 | from slimit.visitors import nodevisitor 33 | 34 | 35 | def decorator(cls): 36 | def make_test_function(input, expected): 37 | 38 | def test_func(self): 39 | parser = Parser() 40 | result = parser.parse(input).to_ecma() 41 | self.assertMultiLineEqual(result, expected) 42 | 43 | return test_func 44 | 45 | for index, (input, expected) in enumerate(cls.TEST_CASES): 46 | input = textwrap.dedent(input).strip() 47 | expected = textwrap.dedent(expected).strip() 48 | func = make_test_function(input, expected) 49 | setattr(cls, 'test_case_%d' % index, func) 50 | 51 | return cls 52 | 53 | 54 | class ParserTestCase(unittest.TestCase): 55 | 56 | def test_line_terminator_at_the_end_of_file(self): 57 | parser = Parser() 58 | parser.parse('var $_ = function(x){}(window);\n') 59 | 60 | # XXX: function expression ? 61 | def _test_function_expression(self): 62 | text = """ 63 | if (true) { 64 | function() { 65 | foo; 66 | location = 'http://anywhere.com'; 67 | } 68 | } 69 | """ 70 | parser = Parser() 71 | parser.parse(text) 72 | 73 | def test_modify_tree(self): 74 | text = """ 75 | for (var i = 0; i < 10; i++) { 76 | var x = 5 + i; 77 | } 78 | """ 79 | parser = Parser() 80 | tree = parser.parse(text) 81 | for node in nodevisitor.visit(tree): 82 | if isinstance(node, ast.Identifier) and node.value == 'i': 83 | node.value = 'hello' 84 | self.assertMultiLineEqual( 85 | tree.to_ecma(), 86 | textwrap.dedent(""" 87 | for (var hello = 0; hello < 10; hello++) { 88 | var x = 5 + hello; 89 | } 90 | """).strip() 91 | ) 92 | 93 | def test_bug_no_semicolon_at_the_end_of_block_plus_newline_at_eof(self): 94 | # https://github.com/rspivak/slimit/issues/3 95 | text = textwrap.dedent(""" 96 | function add(x, y) { 97 | return x + y; 98 | } 99 | """) 100 | parser = Parser() 101 | tree = parser.parse(text) 102 | self.assertTrue(bool(tree.children())) 103 | 104 | def test_function_expression_is_part_of_member_expr_nobf(self): 105 | # https://github.com/rspivak/slimit/issues/22 106 | # The problem happened to be that function_expr was not 107 | # part of member_expr_nobf rule 108 | text = 'window.done_already || function () { return "slimit!" ; }();' 109 | self.assertTrue(bool(Parser().parse(text).children())) 110 | 111 | # https://github.com/rspivak/slimit/issues/29 112 | def test_that_parsing_eventually_stops(self): 113 | text = """var a; 114 | , b;""" 115 | parser = Parser() 116 | self.assertRaises(SyntaxError, parser.parse, text) 117 | 118 | 119 | @decorator 120 | class ASITestCase(unittest.TestCase): 121 | TEST_CASES = [ 122 | (""" 123 | switch (day) { 124 | case 1: 125 | result = 'Mon'; 126 | break 127 | case 2: 128 | break 129 | } 130 | """, 131 | """ 132 | switch (day) { 133 | case 1: 134 | result = 'Mon'; 135 | break; 136 | case 2: 137 | break; 138 | } 139 | """), 140 | 141 | (""" 142 | while (true) 143 | continue 144 | a = 1; 145 | """, 146 | """ 147 | while (true) continue; 148 | a = 1; 149 | """), 150 | 151 | (""" 152 | return 153 | a; 154 | """, 155 | """ 156 | return; 157 | a; 158 | """), 159 | # test 3 160 | (""" 161 | x = 5 162 | """, 163 | """ 164 | x = 5; 165 | """), 166 | 167 | (""" 168 | var a, b 169 | var x 170 | """, 171 | """ 172 | var a, b; 173 | var x; 174 | """), 175 | 176 | (""" 177 | var a, b 178 | var x 179 | """, 180 | """ 181 | var a, b; 182 | var x; 183 | """), 184 | 185 | # test 6 186 | (""" 187 | return 188 | a + b 189 | """, 190 | """ 191 | return; 192 | a + b; 193 | """), 194 | 195 | ('while (true) ;', 'while (true) ;'), 196 | 197 | (""" 198 | if (x) { 199 | y() 200 | } 201 | """, 202 | """ 203 | if (x) { 204 | y(); 205 | } 206 | """), 207 | 208 | # test 9 209 | (""" 210 | for ( ; i < length; i++) { 211 | } 212 | """, 213 | """ 214 | for ( ; i < length; i++) { 215 | 216 | } 217 | """), 218 | 219 | (""" 220 | var i; 221 | for (i; i < length; i++) { 222 | } 223 | """, 224 | """ 225 | var i; 226 | for (i; i < length; i++) { 227 | 228 | } 229 | """), 230 | ] 231 | 232 | def test_throw_statement(self): 233 | # expression is not optional in throw statement 234 | input = textwrap.dedent(""" 235 | throw 236 | 'exc'; 237 | """) 238 | parser = Parser() 239 | # ASI at lexer level should insert ';' after throw 240 | self.assertRaises(SyntaxError, parser.parse, input) 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/slimit/unicode_chars.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | # Reference - http://xregexp.com/plugins/#unicode 28 | # Adapted from https://github.com/mishoo/UglifyJS/blob/master/lib/parse-js.js 29 | 30 | # 'Uppercase letter (Lu)', 'Lowercase letter (Ll)', 31 | # 'Titlecase letter(Lt)', 'Modifier letter (Lm)', 'Other letter (Lo)' 32 | LETTER = ( 33 | u'[A-Za-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6' 34 | u'\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376' 35 | u'\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5' 36 | u'\u03f7-\u0481\u048a-\u0523\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea' 37 | u'\u05f0-\u05f2\u0621-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6' 38 | u'\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1' 39 | u'\u07ca-\u07ea\u07f4\u07f5\u07fa\u0904-\u0939\u093d\u0950\u0958-\u0961' 40 | u'\u0971\u0972\u097b-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8' 41 | u'\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1' 42 | u'\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32' 43 | u'\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74' 44 | u'\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3' 45 | u'\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10' 46 | u'\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d' 47 | u'\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99' 48 | u'\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0' 49 | u'\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d' 50 | u'\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8' 51 | u'\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05-\u0d0c' 52 | u'\u0d0e-\u0d10\u0d12-\u0d28\u0d2a-\u0d39\u0d3d\u0d60\u0d61\u0d7a-\u0d7f' 53 | u'\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30' 54 | u'\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d' 55 | u'\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab' 56 | u'\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc\u0edd\u0f00' 57 | u'\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8b\u1000-\u102a\u103f\u1050-\u1055' 58 | u'\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e' 59 | u'\u10a0-\u10c5\u10d0-\u10fa\u10fc\u1100-\u1159\u115f-\u11a2\u11a8-\u11f9' 60 | u'\u1200-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288' 61 | u'\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5' 62 | u'\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f' 63 | u'\u13a0-\u13f4\u1401-\u166c\u166f-\u1676\u1681-\u169a\u16a0-\u16ea' 64 | u'\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c' 65 | u'\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa' 66 | u'\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19a9\u19c1-\u19c7' 67 | u'\u1a00-\u1a16\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf' 68 | u'\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1d00-\u1dbf\u1e00-\u1f15' 69 | u'\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d' 70 | u'\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc' 71 | u'\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071' 72 | u'\u207f\u2090-\u2094\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124' 73 | u'\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e' 74 | u'\u2183\u2184\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2c6f\u2c71-\u2c7d' 75 | u'\u2c80-\u2ce4\u2d00-\u2d25\u2d30-\u2d65\u2d6f\u2d80-\u2d96\u2da0-\u2da6' 76 | u'\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce' 77 | u'\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005\u3006\u3031-\u3035\u303b\u303c' 78 | u'\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d' 79 | u'\u3131-\u318e\u31a0-\u31b7\u31f0-\u31ff\u3400\u4db5\u4e00\u9fc3' 80 | u'\ua000-\ua48c\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua65f' 81 | u'\ua662-\ua66e\ua67f-\ua697\ua717-\ua71f\ua722-\ua788\ua78b\ua78c' 82 | u'\ua7fb-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873' 83 | u'\ua882-\ua8b3\ua90a-\ua925\ua930-\ua946\uaa00-\uaa28\uaa40-\uaa42' 84 | u'\uaa44-\uaa4b\uac00\ud7a3\uf900-\ufa2d\ufa30-\ufa6a\ufa70-\ufad9' 85 | u'\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c' 86 | u'\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f' 87 | u'\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a' 88 | u'\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7' 89 | u'\uffda-\uffdc]' 90 | ) 91 | 92 | NON_SPACING_MARK = ( 93 | u'[\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5' 94 | u'\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7' 95 | u'\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3' 96 | u'\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c' 97 | u'\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09c1-\u09c4' 98 | u'\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48' 99 | u'\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5' 100 | u'\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41-\u0b44\u0b4d' 101 | u'\u0b56\u0b62\u0b63\u0b82\u0bc0\u0bcd\u0c3e-\u0c40\u0c46-\u0c48' 102 | u'\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd' 103 | u'\u0ce2\u0ce3\u0d41-\u0d44\u0d4d\u0d62\u0d63\u0dca\u0dd2-\u0dd4\u0dd6' 104 | u'\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc' 105 | u'\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84' 106 | u'\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037' 107 | u'\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082' 108 | u'\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753' 109 | u'\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9' 110 | u'\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56' 111 | u'\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03' 112 | u'\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5' 113 | u'\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0' 114 | u'\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u20d0-\u20dc\u20e1' 115 | u'\u20e5-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f' 116 | u'\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4' 117 | u'\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9' 118 | u'\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0' 119 | u'\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\ufb1e' 120 | u'\ufe00-\ufe0f\ufe20-\ufe26]' 121 | ) 122 | 123 | COMBINING_SPACING_MARK = ( 124 | u'[\u0903\u093e-\u0940\u0949-\u094c\u094e\u0982\u0983\u09be-\u09c0\u09c7' 125 | u'\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e-\u0a40\u0a83\u0abe-\u0ac0\u0ac9' 126 | u'\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57' 127 | u'\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcc\u0bd7\u0c01-\u0c03' 128 | u'\u0c41-\u0c44\u0c82\u0c83\u0cbe\u0cc0-\u0cc4\u0cc7\u0cc8\u0cca\u0ccb' 129 | u'\u0cd5\u0cd6\u0d02\u0d03\u0d3e-\u0d40\u0d46-\u0d48\u0d4a-\u0d4c\u0d57' 130 | u'\u0d82\u0d83\u0dcf-\u0dd1\u0dd8-\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f' 131 | u'\u102b\u102c\u1031\u1038\u103b\u103c\u1056\u1057\u1062-\u1064' 132 | u'\u1067-\u106d\u1083\u1084\u1087-\u108c\u108f\u109a-\u109c\u17b6' 133 | u'\u17be-\u17c5\u17c7\u17c8\u1923-\u1926\u1929-\u192b\u1930\u1931' 134 | u'\u1933-\u1938\u19b0-\u19c0\u19c8\u19c9\u1a19-\u1a1b\u1a55\u1a57\u1a61' 135 | u'\u1a63\u1a64\u1a6d-\u1a72\u1b04\u1b35\u1b3b\u1b3d-\u1b41\u1b43\u1b44' 136 | u'\u1b82\u1ba1\u1ba6\u1ba7\u1baa\u1c24-\u1c2b\u1c34\u1c35\u1ce1\u1cf2' 137 | u'\ua823\ua824\ua827\ua880\ua881\ua8b4-\ua8c3\ua952\ua953\ua983\ua9b4' 138 | u'\ua9b5\ua9ba\ua9bb\ua9bd-\ua9c0\uaa2f\uaa30\uaa33\uaa34\uaa4d\uaa7b' 139 | u'\uabe3\uabe4\uabe6\uabe7\uabe9\uabea\uabec]' 140 | ) 141 | 142 | COMBINING_MARK = u'%s|%s' % (NON_SPACING_MARK, COMBINING_SPACING_MARK) 143 | 144 | CONNECTOR_PUNCTUATION = u'[_\u203f\u2040\u2054\ufe33\ufe34\ufe4d-\ufe4f\uff3f]' 145 | 146 | DIGIT = ( 147 | u'[0-9\u0660-\u0669\u06f0-\u06f9\u07c0-\u07c9\u0966-\u096f' 148 | u'\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be6-\u0bef' # noqa: E501,W293 149 | u'\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9' 150 | u'\u0f20-\u0f29\u1040-\u1049\u1090-\u1099\u17e0-\u17e9\u1810-\u1819' 151 | u'\u1946-\u194f\u19d0-\u19da\u1a80-\u1a89\u1a90-\u1a99\u1b50-\u1b59' 152 | u'\u1bb0-\u1bb9\u1c40-\u1c49\u1c50-\u1c59\ua620-\ua629\ua8d0-\ua8d9' 153 | u'\ua900-\ua909\ua9d0-\ua9d9\uaa50-\uaa59\uabf0-\uabf9\uff10-\uff19]' 154 | ) 155 | -------------------------------------------------------------------------------- /src/slimit/visitors/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/slimit/visitors/ecmavisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | from slimit import ast 28 | 29 | 30 | class ECMAVisitor(object): 31 | 32 | def __init__(self): 33 | self.indent_level = 0 34 | 35 | def _make_indent(self): 36 | return ' ' * self.indent_level 37 | 38 | def visit(self, node): 39 | method = 'visit_%s' % node.__class__.__name__ 40 | return getattr(self, method, self.generic_visit)(node) 41 | 42 | def generic_visit(self, node): 43 | return 'GEN: %r' % node 44 | 45 | def visit_Program(self, node): 46 | return '\n'.join(self.visit(child) for child in node) 47 | 48 | def visit_Block(self, node): 49 | s = '{\n' 50 | self.indent_level += 2 51 | s += '\n'.join( 52 | self._make_indent() + self.visit(child) for child in node) 53 | self.indent_level -= 2 54 | s += '\n' + self._make_indent() + '}' 55 | return s 56 | 57 | def visit_VarStatement(self, node): 58 | s = 'var %s;' % ', '.join(self.visit(child) for child in node) 59 | return s 60 | 61 | def visit_VarDecl(self, node): 62 | output = [] 63 | output.append(self.visit(node.identifier)) 64 | if node.initializer is not None: 65 | output.append(' = %s' % self.visit(node.initializer)) 66 | return ''.join(output) 67 | 68 | def visit_Identifier(self, node): 69 | return node.value 70 | 71 | def visit_Assign(self, node): 72 | if node.op == ':': 73 | template = '%s%s %s' 74 | else: 75 | template = '%s %s %s' 76 | if getattr(node, '_parens', False): 77 | template = '(%s)' % template 78 | return template % ( 79 | self.visit(node.left), node.op, self.visit(node.right)) 80 | 81 | def visit_GetPropAssign(self, node): 82 | template = 'get %s() {\n%s\n%s}' 83 | if getattr(node, '_parens', False): 84 | template = '(%s)' % template 85 | self.indent_level += 2 86 | body = '\n'.join( 87 | (self._make_indent() + self.visit(el)) 88 | for el in node.elements 89 | ) 90 | self.indent_level -= 2 91 | tail = self._make_indent() 92 | return template % (self.visit(node.prop_name), body, tail) 93 | 94 | def visit_SetPropAssign(self, node): 95 | template = 'set %s(%s) {\n%s\n%s}' 96 | if getattr(node, '_parens', False): 97 | template = '(%s)' % template 98 | if len(node.parameters) > 1: 99 | raise SyntaxError( 100 | 'Setter functions must have one argument: %s' % node) 101 | params = ','.join(self.visit(param) for param in node.parameters) 102 | self.indent_level += 2 103 | body = '\n'.join( 104 | (self._make_indent() + self.visit(el)) 105 | for el in node.elements 106 | ) 107 | self.indent_level -= 2 108 | tail = self._make_indent() 109 | return template % (self.visit(node.prop_name), params, body, tail) 110 | 111 | def visit_Number(self, node): 112 | return node.value 113 | 114 | def visit_Comma(self, node): 115 | s = '%s, %s' % (self.visit(node.left), self.visit(node.right)) 116 | if getattr(node, '_parens', False): 117 | s = '(' + s + ')' 118 | return s 119 | 120 | def visit_EmptyStatement(self, node): 121 | return node.value 122 | 123 | def visit_If(self, node): 124 | s = 'if (' 125 | if node.predicate is not None: 126 | s += self.visit(node.predicate) 127 | s += ') ' 128 | s += self.visit(node.consequent) 129 | if node.alternative is not None: 130 | s += ' else ' 131 | s += self.visit(node.alternative) 132 | return s 133 | 134 | def visit_Boolean(self, node): 135 | return node.value 136 | 137 | def visit_For(self, node): 138 | s = 'for (' 139 | if node.init is not None: 140 | s += self.visit(node.init) 141 | if node.init is None: 142 | s += ' ; ' 143 | elif isinstance(node.init, (ast.Assign, ast.Comma, ast.FunctionCall, 144 | ast.UnaryOp, ast.Identifier, ast.BinOp, 145 | ast.Conditional, ast.Regex, ast.NewExpr)): 146 | s += '; ' 147 | else: 148 | s += ' ' 149 | if node.cond is not None: 150 | s += self.visit(node.cond) 151 | s += '; ' 152 | if node.count is not None: 153 | s += self.visit(node.count) 154 | s += ') ' + self.visit(node.statement) 155 | return s 156 | 157 | def visit_ForIn(self, node): 158 | if isinstance(node.item, ast.VarDecl): 159 | template = 'for (var %s in %s) ' 160 | else: 161 | template = 'for (%s in %s) ' 162 | s = template % (self.visit(node.item), self.visit(node.iterable)) 163 | s += self.visit(node.statement) 164 | return s 165 | 166 | def visit_BinOp(self, node): 167 | if getattr(node, '_parens', False): 168 | template = '(%s %s %s)' 169 | else: 170 | template = '%s %s %s' 171 | return template % ( 172 | self.visit(node.left), node.op, self.visit(node.right)) 173 | 174 | def visit_UnaryOp(self, node): 175 | s = self.visit(node.value) 176 | if node.postfix: 177 | s += node.op 178 | elif node.op in ('delete', 'void', 'typeof'): 179 | s = '%s %s' % (node.op, s) 180 | else: 181 | s = '%s%s' % (node.op, s) 182 | if getattr(node, '_parens', False): 183 | s = '(%s)' % s 184 | return s 185 | 186 | def visit_ExprStatement(self, node): 187 | return '%s;' % self.visit(node.expr) 188 | 189 | def visit_DoWhile(self, node): 190 | s = 'do ' 191 | s += self.visit(node.statement) 192 | s += ' while (%s);' % self.visit(node.predicate) 193 | return s 194 | 195 | def visit_While(self, node): 196 | s = 'while (%s) ' % self.visit(node.predicate) 197 | s += self.visit(node.statement) 198 | return s 199 | 200 | def visit_Null(self, node): 201 | return 'null' 202 | 203 | def visit_String(self, node): 204 | return node.value 205 | 206 | def visit_Continue(self, node): 207 | if node.identifier is not None: 208 | s = 'continue %s;' % self.visit_Identifier(node.identifier) 209 | else: 210 | s = 'continue;' 211 | return s 212 | 213 | def visit_Break(self, node): 214 | if node.identifier is not None: 215 | s = 'break %s;' % self.visit_Identifier(node.identifier) 216 | else: 217 | s = 'break;' 218 | return s 219 | 220 | def visit_Return(self, node): 221 | if node.expr is None: 222 | return 'return;' 223 | else: 224 | return 'return %s;' % self.visit(node.expr) 225 | 226 | def visit_With(self, node): 227 | s = 'with (%s) ' % self.visit(node.expr) 228 | s += self.visit(node.statement) 229 | return s 230 | 231 | def visit_Label(self, node): 232 | s = '%s: %s' % ( 233 | self.visit(node.identifier), self.visit(node.statement)) 234 | return s 235 | 236 | def visit_Switch(self, node): 237 | s = 'switch (%s) {\n' % self.visit(node.expr) 238 | self.indent_level += 2 239 | for case in node.cases: 240 | s += self._make_indent() + self.visit_Case(case) 241 | if node.default is not None: 242 | s += self.visit_Default(node.default) 243 | self.indent_level -= 2 244 | s += self._make_indent() + '}' 245 | return s 246 | 247 | def visit_Case(self, node): 248 | s = 'case %s:\n' % self.visit(node.expr) 249 | self.indent_level += 2 250 | elements = '\n'.join(self._make_indent() + self.visit(element) 251 | for element in node.elements) 252 | if elements: 253 | s += elements + '\n' 254 | self.indent_level -= 2 255 | return s 256 | 257 | def visit_Default(self, node): 258 | s = self._make_indent() + 'default:\n' 259 | self.indent_level += 2 260 | s += '\n'.join(self._make_indent() + self.visit(element) 261 | for element in node.elements) 262 | if node.elements is not None: 263 | s += '\n' 264 | self.indent_level -= 2 265 | return s 266 | 267 | def visit_Throw(self, node): 268 | s = 'throw %s;' % self.visit(node.expr) 269 | return s 270 | 271 | def visit_Debugger(self, node): 272 | return '%s;' % node.value 273 | 274 | def visit_Try(self, node): 275 | s = 'try ' 276 | s += self.visit(node.statements) 277 | if node.catch is not None: 278 | s += ' ' + self.visit(node.catch) 279 | if node.fin is not None: 280 | s += ' ' + self.visit(node.fin) 281 | return s 282 | 283 | def visit_Catch(self, node): 284 | s = 'catch (%s) %s' % ( 285 | self.visit(node.identifier), self.visit(node.elements)) 286 | return s 287 | 288 | def visit_Finally(self, node): 289 | s = 'finally %s' % self.visit(node.elements) 290 | return s 291 | 292 | def visit_FuncDecl(self, node): 293 | self.indent_level += 2 294 | elements = '\n'.join(self._make_indent() + self.visit(element) 295 | for element in node.elements) 296 | self.indent_level -= 2 297 | 298 | s = 'function %s(%s) {\n%s' % ( 299 | self.visit(node.identifier), 300 | ', '.join(self.visit(param) for param in node.parameters), 301 | elements, 302 | ) 303 | s += '\n' + self._make_indent() + '}' 304 | return s 305 | 306 | def visit_FuncExpr(self, node): 307 | self.indent_level += 2 308 | elements = '\n'.join(self._make_indent() + self.visit(element) 309 | for element in node.elements) 310 | self.indent_level -= 2 311 | 312 | ident = node.identifier 313 | ident = '' if ident is None else ' %s' % self.visit(ident) 314 | 315 | header = 'function%s(%s)' 316 | if getattr(node, '_parens', False): 317 | header = '(' + header 318 | s = (header + ' {\n%s') % ( 319 | ident, 320 | ', '.join(self.visit(param) for param in node.parameters), 321 | elements, 322 | ) 323 | s += '\n' + self._make_indent() + '}' 324 | if getattr(node, '_parens', False): 325 | s += ')' 326 | return s 327 | 328 | def visit_Conditional(self, node): 329 | if getattr(node, '_parens', False): 330 | template = '(%s ? %s : %s)' 331 | else: 332 | template = '%s ? %s : %s' 333 | 334 | s = template % ( 335 | self.visit(node.predicate), 336 | self.visit(node.consequent), self.visit(node.alternative)) 337 | return s 338 | 339 | def visit_Regex(self, node): 340 | if getattr(node, '_parens', False): 341 | return '(%s)' % node.value 342 | else: 343 | return node.value 344 | 345 | def visit_NewExpr(self, node): 346 | s = 'new %s(%s)' % ( 347 | self.visit(node.identifier), 348 | ', '.join(self.visit(arg) for arg in node.args) 349 | ) 350 | return s 351 | 352 | def visit_DotAccessor(self, node): 353 | if getattr(node, '_parens', False): 354 | template = '(%s.%s)' 355 | else: 356 | template = '%s.%s' 357 | s = template % (self.visit(node.node), self.visit(node.identifier)) 358 | return s 359 | 360 | def visit_BracketAccessor(self, node): 361 | s = '%s[%s]' % (self.visit(node.node), self.visit(node.expr)) 362 | return s 363 | 364 | def visit_FunctionCall(self, node): 365 | s = '%s(%s)' % (self.visit(node.identifier), 366 | ', '.join(self.visit(arg) for arg in node.args)) 367 | if getattr(node, '_parens', False): 368 | s = '(' + s + ')' 369 | return s 370 | 371 | def visit_Object(self, node): 372 | s = '{\n' 373 | self.indent_level += 2 374 | s += ',\n'.join(self._make_indent() + self.visit(prop) 375 | for prop in node.properties) 376 | self.indent_level -= 2 377 | if node.properties: 378 | s += '\n' 379 | s += self._make_indent() + '}' 380 | return s 381 | 382 | def visit_Array(self, node): 383 | s = '[' 384 | length = len(node.items) - 1 385 | for index, item in enumerate(node.items): 386 | if isinstance(item, ast.Elision): 387 | s += ',' 388 | elif index != length: 389 | s += self.visit(item) + ',' 390 | else: 391 | s += self.visit(item) 392 | s += ']' 393 | return s 394 | 395 | def visit_This(self, node): 396 | return 'this' 397 | 398 | -------------------------------------------------------------------------------- /src/slimit/visitors/minvisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | import re 28 | 29 | from slimit import ast 30 | from slimit.lexer import Lexer 31 | 32 | _HAS_ID_MATCH = re.compile('^%s$' % Lexer.identifier).match 33 | 34 | def _is_identifier(value): 35 | return _HAS_ID_MATCH(value) and value not in Lexer.keywords_dict 36 | 37 | 38 | class ECMAMinifier(object): 39 | 40 | def __init__(self): 41 | self.in_block = 0 42 | self.ifelse_stack = [] 43 | 44 | def visit(self, node): 45 | method = 'visit_%s' % node.__class__.__name__ 46 | return getattr(self, method, self.generic_visit)(node) 47 | 48 | def generic_visit(self, node): 49 | return 'GEN: %r' % node 50 | 51 | def visit_Program(self, node): 52 | return ''.join(self.visit(child) for child in node) 53 | 54 | def visit_Block(self, node): 55 | children = [self.visit(child) for child in node] 56 | if len(children) == 1: 57 | return children[0] 58 | else: 59 | return '{%s}' % ''.join(children) 60 | 61 | def visit_VarStatement(self, node): 62 | s = 'var %s;' % ','.join(self.visit(child) for child in node) 63 | return s 64 | 65 | def visit_VarDecl(self, node): 66 | output = [] 67 | output.append(self.visit(node.identifier)) 68 | if node.initializer is not None: 69 | output.append('=%s' % self.visit(node.initializer)) 70 | return ''.join(output) 71 | 72 | def visit_Identifier(self, node): 73 | return node.value 74 | 75 | def visit_Assign(self, node): 76 | template = '%s%s%s' 77 | if getattr(node, '_parens', False): 78 | template = '(%s)' % template 79 | return template % ( 80 | self.visit(node.left), node.op, self.visit(node.right)) 81 | 82 | def visit_GetPropAssign(self, node): 83 | template = 'get %s(){%s}' 84 | if getattr(node, '_parens', False): 85 | template = '(%s)' % template 86 | return template % ( 87 | self.visit(node.prop_name), 88 | ''.join(self.visit(element) for element in node.elements) 89 | ) 90 | 91 | def visit_SetPropAssign(self, node): 92 | template = 'set %s(%s){%s}' 93 | if getattr(node, '_parens', False): 94 | template = '(%s)' % template 95 | if len(node.parameters) > 1: 96 | raise SyntaxError( 97 | 'Setter functions must have one argument: %s' % node) 98 | return template % ( 99 | self.visit(node.prop_name), 100 | ''.join(self.visit(param) for param in node.parameters), 101 | ''.join(self.visit(element) for element in node.elements) 102 | ) 103 | 104 | def visit_Number(self, node): 105 | return node.value 106 | 107 | def visit_Comma(self, node): 108 | template = '%s,%s' 109 | if getattr(node, '_parens', False): 110 | template = '(%s)' % template 111 | return template % (self.visit(node.left), self.visit(node.right)) 112 | 113 | def visit_EmptyStatement(self, node): 114 | return node.value 115 | 116 | def visit_If(self, node): 117 | has_alternative = node.alternative is not None 118 | 119 | def _is_singleline_block(n): 120 | return isinstance(n, ast.Block) and (len(n.children()) == 1) 121 | 122 | s = 'if(' 123 | if node.predicate is not None: 124 | s += self.visit(node.predicate) 125 | s += ')' 126 | 127 | # if we are an 'if..else' statement and 'if' part contains only 128 | # one statement 129 | if has_alternative and _is_singleline_block(node.consequent): 130 | self.ifelse_stack.append({'if_in_ifelse': False}) 131 | consequent = self.visit(node.consequent) 132 | record = self.ifelse_stack.pop() 133 | if record['if_in_ifelse']: 134 | s += '{%s}' % consequent 135 | else: 136 | s += consequent 137 | elif has_alternative: 138 | # we are an 'if..else' statement and 'if' part contains 139 | # myltiple statements 140 | s += self.visit(node.consequent) 141 | else: 142 | # 'if' without alternative - mark it so that an enclosing 143 | # 'if..else' can act on it and add braces around 'if' part 144 | if self.ifelse_stack: 145 | self.ifelse_stack[-1]['if_in_ifelse'] = True 146 | s += self.visit(node.consequent) 147 | 148 | if has_alternative: 149 | alternative = self.visit(node.alternative) 150 | if alternative.startswith(('(', '{')): 151 | s += 'else%s' % alternative 152 | else: 153 | s += 'else %s' % alternative 154 | return s 155 | 156 | def visit_Boolean(self, node): 157 | return node.value 158 | 159 | def visit_For(self, node): 160 | s = 'for(' 161 | if node.init is not None: 162 | s += self.visit(node.init) 163 | if node.init is None: 164 | s += ';' 165 | elif isinstance(node.init, (ast.Assign, ast.Comma, ast.Conditional, 166 | ast.FunctionCall, ast.UnaryOp, 167 | ast.Identifier)): 168 | s += ';' 169 | else: 170 | s += '' 171 | if node.cond is not None: 172 | s += self.visit(node.cond) 173 | s += ';' 174 | if node.count is not None: 175 | s += self.visit(node.count) 176 | s += ')' + self.visit(node.statement) 177 | return s 178 | 179 | def visit_ForIn(self, node): 180 | if isinstance(node.item, ast.VarDecl): 181 | template = 'for(var %s in %s)' 182 | else: 183 | template = 'for(%s in %s)' 184 | s = template % (self.visit(node.item), self.visit(node.iterable)) 185 | s += self.visit(node.statement) 186 | return s 187 | 188 | def visit_BinOp(self, node): 189 | if node.op in ('instanceof', 'in'): 190 | template = '%s %s %s' 191 | elif (node.op == '+' and 192 | isinstance(node.right, ast.UnaryOp) and 193 | node.right.op == '++' and not node.right.postfix 194 | ): 195 | # make a space between + and ++ 196 | # https://github.com/rspivak/slimit/issues/26 197 | template = '%s%s %s' 198 | else: 199 | template = '%s%s%s' 200 | if getattr(node, '_parens', False): 201 | template = '(%s)' % template 202 | return template % ( 203 | self.visit(node.left), node.op, self.visit(node.right)) 204 | 205 | def visit_UnaryOp(self, node): 206 | s = self.visit(node.value) 207 | if node.postfix: 208 | s += node.op 209 | elif node.op in ('delete', 'void', 'typeof'): 210 | s = '%s %s' % (node.op, s) 211 | else: 212 | s = '%s%s' % (node.op, s) 213 | if getattr(node, '_parens', False): 214 | s = '(%s)' % s 215 | return s 216 | 217 | def visit_ExprStatement(self, node): 218 | return '%s;' % self.visit(node.expr) 219 | 220 | def visit_DoWhile(self, node): 221 | statement = self.visit(node.statement) 222 | if statement.startswith(('{', '(')): 223 | s = 'do%s' % statement 224 | else: 225 | s = 'do %s' % statement 226 | s += 'while(%s);' % self.visit(node.predicate) 227 | return s 228 | 229 | def visit_While(self, node): 230 | s = 'while(%s)' % self.visit(node.predicate) 231 | s += self.visit(node.statement) 232 | return s 233 | 234 | def visit_Null(self, node): 235 | return 'null' 236 | 237 | def visit_String(self, node): 238 | return node.value 239 | 240 | def visit_Continue(self, node): 241 | if node.identifier is not None: 242 | s = 'continue %s;' % self.visit_Identifier(node.identifier) 243 | else: 244 | s = 'continue;' 245 | return s 246 | 247 | def visit_Break(self, node): 248 | if node.identifier is not None: 249 | s = 'break %s;' % self.visit_Identifier(node.identifier) 250 | else: 251 | s = 'break;' 252 | return s 253 | 254 | def visit_Return(self, node): 255 | if node.expr is None: 256 | return 'return;' 257 | 258 | expr_text = self.visit(node.expr) 259 | if expr_text.startswith(('(', '{')): 260 | return 'return%s;' % expr_text 261 | else: 262 | return 'return %s;' % expr_text 263 | 264 | def visit_With(self, node): 265 | s = 'with(%s)' % self.visit(node.expr) 266 | s += self.visit(node.statement) 267 | return s 268 | 269 | def visit_Label(self, node): 270 | s = '%s:%s' % ( 271 | self.visit(node.identifier), self.visit(node.statement)) 272 | return s 273 | 274 | def visit_Switch(self, node): 275 | s = 'switch(%s){' % self.visit(node.expr) 276 | for case in node.cases: 277 | s += self.visit_Case(case) 278 | if node.default is not None: 279 | s += self.visit_Default(node.default) 280 | s += '}' 281 | return s 282 | 283 | def visit_Case(self, node): 284 | s = 'case %s:' % self.visit(node.expr) 285 | elements = ''.join(self.visit(element) for element in node.elements) 286 | if elements: 287 | s += elements 288 | return s 289 | 290 | def visit_Default(self, node): 291 | s = 'default:' 292 | s += ''.join(self.visit(element) for element in node.elements) 293 | if node.elements is not None: 294 | s += '' 295 | return s 296 | 297 | def visit_Throw(self, node): 298 | s = 'throw %s;' % self.visit(node.expr) 299 | return s 300 | 301 | def visit_Debugger(self, node): 302 | return '%s;' % node.value 303 | 304 | def visit_Try(self, node): 305 | result = self.visit(node.statements) 306 | if result.startswith('{'): 307 | s = 'try%s' % result 308 | else: 309 | s = 'try{%s}' % result 310 | if node.catch is not None: 311 | s += self.visit(node.catch) 312 | if node.fin is not None: 313 | s += self.visit(node.fin) 314 | return s 315 | 316 | def visit_Catch(self, node): 317 | ident = self.visit(node.identifier) 318 | result = self.visit(node.elements) 319 | if result.startswith('{'): 320 | s = 'catch(%s)%s' % (ident, result) 321 | else: 322 | s = 'catch(%s){%s}' % (ident, result) 323 | return s 324 | 325 | def visit_Finally(self, node): 326 | result = self.visit(node.elements) 327 | if result.startswith('{'): 328 | s = 'finally%s' % result 329 | else: 330 | s = 'finally{%s}' % result 331 | return s 332 | 333 | def visit_FuncDecl(self, node): 334 | elements = ''.join(self.visit(element) for element in node.elements) 335 | s = 'function %s(%s){%s' % ( 336 | self.visit(node.identifier), 337 | ','.join(self.visit(param) for param in node.parameters), 338 | elements, 339 | ) 340 | s += '}' 341 | return s 342 | 343 | def visit_FuncExpr(self, node): 344 | elements = ''.join(self.visit(element) for element in node.elements) 345 | 346 | ident = node.identifier 347 | ident = '' if ident is None else ' %s' % self.visit(ident) 348 | 349 | header = 'function%s(%s)' 350 | if getattr(node, '_parens', False): 351 | header = '(' + header 352 | s = (header + '{%s') % ( 353 | ident, 354 | ','.join(self.visit(param) for param in node.parameters), 355 | elements, 356 | ) 357 | s += '}' 358 | if getattr(node, '_parens', False): 359 | s += ')' 360 | return s 361 | 362 | def visit_Conditional(self, node): 363 | if getattr(node, '_parens', False): 364 | template = '(%s?%s:%s)' 365 | else: 366 | template = '%s?%s:%s' 367 | 368 | s = template % ( 369 | self.visit(node.predicate), 370 | self.visit(node.consequent), self.visit(node.alternative)) 371 | return s 372 | 373 | def visit_Regex(self, node): 374 | if getattr(node, '_parens', False): 375 | return '(%s)' % node.value 376 | else: 377 | return node.value 378 | 379 | def visit_NewExpr(self, node): 380 | s = 'new %s(%s)' % ( 381 | self.visit(node.identifier), 382 | ','.join(self.visit(arg) for arg in node.args) 383 | ) 384 | return s 385 | 386 | def visit_DotAccessor(self, node): 387 | if getattr(node, '_parens', False): 388 | template = '(%s.%s)' 389 | else: 390 | template = '%s.%s' 391 | s = template % (self.visit(node.node), self.visit(node.identifier)) 392 | return s 393 | 394 | def visit_BracketAccessor(self, node): 395 | if isinstance(node.expr, ast.String): 396 | value = node.expr.value 397 | # remove single or double quotes around the value, but not both 398 | if value.startswith("'"): 399 | value = value.strip("'") 400 | elif value.startswith('"'): 401 | value = value.strip('"') 402 | if _is_identifier(value): 403 | s = '%s.%s' % (self.visit(node.node), value) 404 | return s 405 | 406 | s = '%s[%s]' % (self.visit(node.node), self.visit(node.expr)) 407 | return s 408 | 409 | def visit_FunctionCall(self, node): 410 | template = '%s(%s)' 411 | if getattr(node, '_parens', False): 412 | template = '(%s)' % template 413 | 414 | s = template % (self.visit(node.identifier), 415 | ','.join(self.visit(arg) for arg in node.args)) 416 | return s 417 | 418 | def visit_Object(self, node): 419 | s = '{%s}' % ','.join(self.visit(prop) for prop in node.properties) 420 | return s 421 | 422 | def visit_Array(self, node): 423 | s = '[' 424 | length = len(node.items) - 1 425 | for index, item in enumerate(node.items): 426 | if isinstance(item, ast.Elision): 427 | s += ',' 428 | elif index != length: 429 | s += self.visit(item) + ',' 430 | else: 431 | s += self.visit(item) 432 | s += ']' 433 | return s 434 | 435 | def visit_This(self, node): 436 | return 'this' 437 | 438 | -------------------------------------------------------------------------------- /src/slimit/visitors/nodevisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | 28 | class ASTVisitor(object): 29 | """Base class for custom AST node visitors. 30 | 31 | Example: 32 | 33 | >>> from __future__ import print_function 34 | >>> from slimit.parser import Parser 35 | >>> from slimit.visitors.nodevisitor import ASTVisitor 36 | >>> 37 | >>> text = ''' 38 | ... var x = { 39 | ... "key1": "value1", 40 | ... "key2": "value2" 41 | ... }; 42 | ... ''' 43 | >>> 44 | >>> class MyVisitor(ASTVisitor): 45 | ... def visit_Object(self, node): 46 | ... '''Visit object literal.''' 47 | ... for prop in node: 48 | ... left, right = prop.left, prop.right 49 | ... print('Property value: %s' % right.value) 50 | ... # visit all children in turn 51 | ... self.visit(prop) 52 | ... 53 | >>> 54 | >>> parser = Parser() 55 | >>> tree = parser.parse(text) 56 | >>> visitor = MyVisitor() 57 | >>> visitor.visit(tree) 58 | Property value: "value1" 59 | Property value: "value2" 60 | 61 | """ 62 | 63 | def visit(self, node): 64 | method = 'visit_%s' % node.__class__.__name__ 65 | return getattr(self, method, self.generic_visit)(node) 66 | 67 | def generic_visit(self, node): 68 | for child in node: 69 | self.visit(child) 70 | 71 | 72 | class NodeVisitor(object): 73 | """Simple node visitor.""" 74 | 75 | def visit(self, node): 76 | """Returns a generator that walks all children recursively.""" 77 | for child in node: 78 | yield child 79 | for subchild in self.visit(child): 80 | yield subchild 81 | 82 | 83 | def visit(node): 84 | visitor = NodeVisitor() 85 | for child in visitor.visit(node): 86 | yield child 87 | -------------------------------------------------------------------------------- /src/slimit/visitors/scopevisitor.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Copyright (c) 2011 Ruslan Spivak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | ############################################################################### 24 | 25 | __author__ = 'Ruslan Spivak ' 26 | 27 | from slimit import ast 28 | from slimit.scope import VarSymbol, FuncSymbol, LocalScope, SymbolTable 29 | 30 | 31 | class Visitor(object): 32 | def visit(self, node): 33 | method = 'visit_%s' % node.__class__.__name__ 34 | return getattr(self, method, self.generic_visit)(node) 35 | 36 | def generic_visit(self, node): 37 | if node is None: 38 | return 39 | if isinstance(node, list): 40 | for child in node: 41 | self.visit(child) 42 | else: 43 | for child in node.children(): 44 | self.visit(child) 45 | 46 | 47 | class ScopeTreeVisitor(Visitor): 48 | """Builds scope tree.""" 49 | 50 | def __init__(self, sym_table): 51 | self.sym_table = sym_table 52 | self.current_scope = sym_table.globals 53 | 54 | def visit_VarDecl(self, node): 55 | ident = node.identifier 56 | symbol = VarSymbol(name=ident.value) 57 | if symbol not in self.current_scope: 58 | self.current_scope.define(symbol) 59 | ident.scope = self.current_scope 60 | self.visit(node.initializer) 61 | 62 | def visit_Identifier(self, node): 63 | node.scope = self.current_scope 64 | 65 | def visit_FuncDecl(self, node): 66 | if node.identifier is not None: 67 | name = node.identifier.value 68 | self.visit_Identifier(node.identifier) 69 | else: 70 | name = None 71 | 72 | func_sym = FuncSymbol( 73 | name=name, enclosing_scope=self.current_scope) 74 | if name is not None: 75 | self.current_scope.define(func_sym) 76 | node.scope = self.current_scope 77 | 78 | # push function scope 79 | self.current_scope = func_sym 80 | for ident in node.parameters: 81 | self.current_scope.define(VarSymbol(ident.value)) 82 | ident.scope = self.current_scope 83 | 84 | for element in node.elements: 85 | self.visit(element) 86 | 87 | # pop the function scope 88 | self.current_scope = self.current_scope.get_enclosing_scope() 89 | 90 | # alias 91 | visit_FuncExpr = visit_FuncDecl 92 | 93 | def visit_Catch(self, node): 94 | # The catch identifier actually lives in a new scope, but additional 95 | # variables defined in the catch statement belong to the outer scope. 96 | # For the sake of simplicity we just reuse any existing variables 97 | # from the outer scope if they exist. 98 | ident = node.identifier 99 | existing_symbol = self.current_scope.symbols.get(ident.value) 100 | if existing_symbol is None: 101 | self.current_scope.define(VarSymbol(ident.value)) 102 | ident.scope = self.current_scope 103 | 104 | for element in node.elements: 105 | self.visit(element) 106 | 107 | class RefVisitor(Visitor): 108 | """Fill 'ref' attribute in scopes.""" 109 | 110 | def visit_Identifier(self, node): 111 | if self._is_id_in_expr(node): 112 | self._fill_scope_refs(node.value, node.scope) 113 | 114 | @staticmethod 115 | def _is_id_in_expr(node): 116 | """Return True if Identifier node is part of an expression.""" 117 | return ( 118 | getattr(node, 'scope', None) is not None and 119 | getattr(node, '_in_expression', False) 120 | ) 121 | 122 | @staticmethod 123 | def _fill_scope_refs(name, scope): 124 | """Put referenced name in 'ref' dictionary of a scope. 125 | 126 | Walks up the scope tree and adds the name to 'ref' of every scope 127 | up in the tree until a scope that defines referenced name is reached. 128 | """ 129 | symbol = scope.resolve(name) 130 | if symbol is None: 131 | return 132 | 133 | orig_scope = symbol.scope 134 | scope.refs[name] = orig_scope 135 | while scope is not orig_scope: 136 | scope = scope.get_enclosing_scope() 137 | scope.refs[name] = orig_scope 138 | 139 | 140 | def mangle_scope_tree(root, toplevel): 141 | """Walk over a scope tree and mangle symbol names. 142 | 143 | Args: 144 | toplevel: Defines if global scope should be mangled or not. 145 | """ 146 | def mangle(scope): 147 | # don't mangle global scope if not specified otherwise 148 | if scope.get_enclosing_scope() is None and not toplevel: 149 | return 150 | for name in scope.symbols: 151 | mangled_name = scope.get_next_mangled_name() 152 | scope.mangled[name] = mangled_name 153 | scope.rev_mangled[mangled_name] = name 154 | 155 | def visit(node): 156 | mangle(node) 157 | for child in node.children: 158 | visit(child) 159 | 160 | visit(root) 161 | 162 | 163 | def fill_scope_references(tree): 164 | """Fill 'ref' scope attribute with values.""" 165 | visitor = RefVisitor() 166 | visitor.visit(tree) 167 | 168 | 169 | class NameManglerVisitor(Visitor): 170 | """Mangles names. 171 | 172 | Walks over a parsed tree and changes ID values to corresponding 173 | mangled names. 174 | """ 175 | 176 | @staticmethod 177 | def _is_mangle_candidate(id_node): 178 | """Return True if Identifier node is a candidate for mangling. 179 | 180 | There are 5 cases when Identifier is a mangling candidate: 181 | 1. Function declaration identifier 182 | 2. Function expression identifier 183 | 3. Function declaration/expression parameter 184 | 4. Variable declaration identifier 185 | 5. Identifier is a part of an expression (primary_expr_no_brace rule) 186 | """ 187 | return getattr(id_node, '_mangle_candidate', False) 188 | 189 | def visit_Identifier(self, node): 190 | """Mangle names.""" 191 | if not self._is_mangle_candidate(node): 192 | return 193 | name = node.value 194 | symbol = node.scope.resolve(node.value) 195 | if symbol is None: 196 | return 197 | mangled = symbol.scope.mangled.get(name) 198 | if mangled is not None: 199 | node.value = mangled 200 | --------------------------------------------------------------------------------