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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/docs/source/_templates/sidebarlogo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/source/_theme/nature/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "basic/layout.html" %}
2 |
3 | {%- block extrahead %}
4 |
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'
27 |
28 | import doctest
29 | import unittest
30 | import difflib
31 | import pprint
32 |
33 | from slimit.lexer import Lexer
34 |
35 |
36 | def decorator(cls):
37 | def make_test_function(input, expected):
38 |
39 | def test_func(self):
40 | lexer = self._get_lexer()
41 | lexer.input(input)
42 | result = ['%s %s' % (token.type, token.value) for token in lexer]
43 | self.assertListEqual(result, expected)
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 | # The structure and some test cases are taken
54 | # from https://bitbucket.org/ned/jslex
55 | @decorator
56 | class LexerTestCase(unittest.TestCase):
57 |
58 | def _get_lexer(self):
59 | lexer = Lexer()
60 | return lexer
61 |
62 | def assertListEqual(self, first, second):
63 | """Assert that two lists are equal.
64 |
65 | Prints differences on error.
66 | This method is similar to that of Python 2.7 'assertListEqual'
67 | """
68 | if first != second:
69 | message = '\n'.join(
70 | difflib.ndiff(pprint.pformat(first).splitlines(),
71 | pprint.pformat(second).splitlines())
72 | )
73 | self.fail('Lists differ:\n' + message)
74 |
75 | def test_illegal_unicode_char_in_identifier(self):
76 | lexer = self._get_lexer()
77 | lexer.input(u'\u0036_tail')
78 | token = lexer.token()
79 | # \u0036_tail is the same as 6_tail and that's not a correct ID
80 | # Check that the token is NUMBER and not an ID
81 | self.assertEqual(token.type, 'NUMBER')
82 | self.assertEqual(token.value, '6')
83 |
84 | TEST_CASES = [
85 | # Identifiers
86 | ('i my_variable_name c17 _dummy $str $ _ CamelCase class2type',
87 | ['ID i', 'ID my_variable_name', 'ID c17', 'ID _dummy',
88 | 'ID $str', 'ID $', 'ID _', 'ID CamelCase', 'ID class2type']
89 | ),
90 | (u'\u03c0 \u03c0_tail var\ua67c',
91 | [u'ID \u03c0', u'ID \u03c0_tail', u'ID var\ua67c']),
92 | # https://github.com/rspivak/slimit/issues/2
93 | ('nullify truelie falsepositive',
94 | ['ID nullify', 'ID truelie', 'ID falsepositive']),
95 |
96 | # Keywords
97 | # ('break case ...', ['BREAK break', 'CASE case', ...])
98 | (' '.join(kw.lower() for kw in Lexer.keywords),
99 | ['%s %s' % (kw, kw.lower()) for kw in Lexer.keywords]
100 | ),
101 | ('break Break BREAK', ['BREAK break', 'ID Break', 'ID BREAK']),
102 |
103 | # Literals
104 | ('null true false Null True False',
105 | ['NULL null', 'TRUE true', 'FALSE false',
106 | 'ID Null', 'ID True', 'ID False']
107 | ),
108 |
109 | # Punctuators
110 | ('a /= b', ['ID a', 'DIVEQUAL /=', 'ID b']),
111 | (('= == != === !== < > <= >= || && ++ -- << >> '
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 |
--------------------------------------------------------------------------------