diff --git a/docs/conf.py b/docs/conf.py index e3a12d58..06558d58 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,256 +18,50 @@ import sphinx -ROOT_DIR = os.path.abspath(os.path.join(__file__, '..', '..')) +ROOT_DIR = os.path.abspath(os.path.join(__file__, "..", "..")) sys.path.insert(0, ROOT_DIR) -sys.path.insert(0, os.path.abspath('scripts')) - -import pscript +sys.path.insert(0, os.path.abspath("scripts")) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.3' +# needs_sphinx = '1.3' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. sphinxver = sphinx.version_info extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon' if sphinxver >= (1, 3) else 'sphinxcontrib.napoleon', - 'pscriptexample', + "sphinx.ext.autodoc", + "sphinx.ext.napoleon" if sphinxver >= (1, 3) else "sphinxcontrib.napoleon", + "pscriptexample", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'PScript' -copyright = '2015-2025, Almar Klein' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.5' -# The full version, including alpha/beta/rc tags. -release = '0.5' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +project = "PScript" +copyright = "2015-2025, Almar Klein" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False +exclude_patterns = ["_build"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -#html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PScriptdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'PScript.tex', 'PScript Documentation', - 'PScript contributors', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'PScript', 'PScript Documentation', - ['PScript contributors'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'PScript', 'PScript Documentation', - 'PScript contributors', 'PScript', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +pygments_style = "sphinx" diff --git a/docs/scripts/gencommonast.py b/docs/scripts/gencommonast.py index 135441bc..760264bd 100644 --- a/docs/scripts/gencommonast.py +++ b/docs/scripts/gencommonast.py @@ -1,58 +1,56 @@ -""" Generate docs for commonast. -""" +"""Generate docs for commonast.""" import os import sys -from types import ModuleType from pscript import commonast # Hack -sys.modules['commonast'] = commonast +sys.modules["commonast"] = commonast THIS_DIR = os.path.dirname(os.path.abspath(__file__)) -DOC_DIR = os.path.abspath(os.path.join(THIS_DIR, '..')) +DOC_DIR = os.path.abspath(os.path.join(THIS_DIR, "..")) OUTPUT_DIR = DOC_DIR created_files = [] + def main(): - # Create overview doc page - docs = 'Common AST' - docs += '\n' + '=' * len(docs) + '\n\n' - docs += '.. automodule:: commonast\n\n' - docs += '.. autofunction:: commonast.parse\n\n' - - docs += '----\n\n' - docs += 'The nodes\n---------\n\n' - - docs += '.. autoclass:: commonast.Node\n :members:\n\n' - - code = open(commonast.__file__, 'rb').read().decode() + docs = "Common AST" + docs += "\n" + "=" * len(docs) + "\n\n" + docs += ".. automodule:: commonast\n\n" + docs += ".. autofunction:: commonast.parse\n\n" + + docs += "----\n\n" + docs += "The nodes\n---------\n\n" + + docs += ".. autoclass:: commonast.Node\n :members:\n\n" + + code = open(commonast.__file__, "rb").read().decode() status = 0 for line in code.splitlines(): if status == 0: - if line.startswith('## --'): + if line.startswith("## --"): status = 1 elif status == 1: - if line.startswith('## --'): + if line.startswith("## --"): break - elif line.startswith('## '): + elif line.startswith("## "): title = line[3:].strip() - docs += '%s\n%s\n\n' % (title, '-' * len(title)) - elif line.startswith('class '): - clsname = line[6:].split('(')[0] - docs += '.. autoclass:: %s\n\n' % ('commonast.' + clsname) + docs += "%s\n%s\n\n" % (title, "-" * len(title)) + elif line.startswith("class "): + clsname = line[6:].split("(")[0] + docs += ".. autoclass:: %s\n\n" % ("commonast." + clsname) cls = getattr(commonast, clsname) - #cls.__doc__ = '%s(%s)\n%s' % (clsname, ', '.join(cls.__slots__), cls.__doc__) - cls.__doc__ = '%s()\n%s' % (clsname, cls.__doc__) - + # cls.__doc__ = '%s(%s)\n%s' % (clsname, ', '.join(cls.__slots__), cls.__doc__) + cls.__doc__ = "%s()\n%s" % (clsname, cls.__doc__) + # Write overview doc page - filename = os.path.join(OUTPUT_DIR, 'commonast.rst') + filename = os.path.join(OUTPUT_DIR, "commonast.rst") created_files.append(filename) - open(filename, 'wt', encoding='utf-8').write(docs) - - print(' generated commonast page') + open(filename, "wt", encoding="utf-8").write(docs) + + print(" generated commonast page") def clean(): diff --git a/docs/scripts/pscriptexample.py b/docs/scripts/pscriptexample.py index 16ee07bf..40477baf 100644 --- a/docs/scripts/pscriptexample.py +++ b/docs/scripts/pscriptexample.py @@ -2,9 +2,6 @@ Small sphinx extension to show PScript code and corresponding JS. """ -import os -import hashlib - from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter @@ -14,100 +11,112 @@ from pscript import py2js -pythonLexer, javaScriptLexer = get_lexer_by_name('py'), get_lexer_by_name('js') +pythonLexer, javaScriptLexer = get_lexer_by_name("py"), get_lexer_by_name("js") htmlFormatter = HtmlFormatter() -# +# # THIS_DIR = os.path.abspath(os.path.dirname(__file__)) # HTML_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '_build', 'html')) -# +# # if not os.path.isdir(HTML_DIR + '/ui'): # os.mkdir(HTML_DIR + '/ui') -# +# # if not os.path.isdir(HTML_DIR + '/ui/examples'): # os.mkdir(HTML_DIR + '/ui/examples') def my_highlight(code): - return highlight(code, PythonLexer(), HtmlFormatter()) + return highlight(code, PythonLexer(), HtmlFormatter()) # noqa + + +class pscript_example(nodes.raw): + pass -class pscript_example(nodes.raw): pass def visit_pscript_example_html(self, node): - # Fix for rtd - if not hasattr(node, 'code'): + if not hasattr(node, "code"): return - + # Get code - code = node.code.strip() + '\n' - + code = node.code.strip() + "\n" + # Split in parts (blocks between two newlines) lines = [line.rstrip() for line in code.splitlines()] - code = '\n'.join(lines) - parts = code.split('\n\n') - + code = "\n".join(lines) + parts = code.split("\n\n") + td_style1 = "style='vertical-align:top; min-width:300px;'" td_style2 = "style='vertical-align:top;'" pre_style = "style='margin:2px; padding: 2px; border:0px;'" - - - #self.body.append("\n") - self.body.append("\n") - + + # self.body.append("\n") + self.body.append( + "\n" + ) + self.body.append("") - #self.body.append("") - + # self.body.append("") + for py in parts: - - if py.strip().startswith('##'): - lines = [line.lstrip(' \n#\t') for line in py.splitlines()] - lines[0] = '%s
' % lines[0] - text = ''.join(lines) - self.body.append('' % - (td_style, text, td_style)) + if py.strip().startswith("##"): + lines = [line.lstrip(" \n#\t") for line in py.splitlines()] + lines[0] = "%s
" % lines[0] + text = "".join(lines) + self.body.append( + "" % (td_style1, text, td_style2) + ) continue - + js = py2js(py) - + py_html = highlight(py, pythonLexer, htmlFormatter) js_html = highlight(js, javaScriptLexer, htmlFormatter) - py_html = py_html.replace("
", '
' % pre_style)
-        js_html = js_html.replace("
", '
' % pre_style)
-        js_html = "
JS
%s
" % js_html - - #self.body.append("%s
  %s
" % (py_html, js_html)) - self.body.append("
" % - (td_style1, py_html, td_style2, js_html)) - + py_html = py_html.replace("
", "
" % pre_style)
+        js_html = js_html.replace("
", "
" % pre_style)
+        js_html = (
+            "
JS
%s
" + % js_html + ) + + # self.body.append("%s
  %s
" % (py_html, js_html)) + self.body.append( + "
" + % (td_style1, py_html, td_style2, js_html) + ) + self.body.append("
PScript      JS
PScript      JS
%s
%s
%s%s
%s%s
") - + + def depart_pscript_example_html(self, node): pass + class PscriptExampleDirective(Directive): - has_content = True - def run(self): - # Get code and extact height - code = '\n'.join(self.content) - try: - height = int(self.content[0]) - except Exception: - height = 300 - else: - code = code.split('\n', 1)[1].strip() - - # iframe - table = pscript_example('') - table.code = code - - return[table] + has_content = True + + def run(self): + # Get code and extact height + code = "\n".join(self.content) + try: + height = int(self.content[0]) + except Exception: + height = 300 # noqa + else: + code = code.split("\n", 1)[1].strip() + + # iframe + table = pscript_example("") + table.code = code + + return [table] def setup(Sphynx): - - #Sphynx.add_javascript('js-image-slider.js') - #Sphynx.add_stylesheet('js-image-slider.css') - - Sphynx.add_node(pscript_example, html=(visit_pscript_example_html, depart_pscript_example_html)) - Sphynx.add_directive('pscript_example', PscriptExampleDirective) + # Sphynx.add_javascript('js-image-slider.js') + # Sphynx.add_stylesheet('js-image-slider.css') + + Sphynx.add_node( + pscript_example, html=(visit_pscript_example_html, depart_pscript_example_html) + ) + Sphynx.add_directive("pscript_example", PscriptExampleDirective) diff --git a/examples/js_module.py b/examples/js_module.py index 39f4dc73..9390a6de 100644 --- a/examples/js_module.py +++ b/examples/js_module.py @@ -6,27 +6,27 @@ """ # This import is ignored by PScript, it allows using these variable -# names without triggering pyflakes (a static Python source analysis tool). +# names without triggering linters. from pscript import undefined, window # noqa + class Foo: - a_constant = 1, 2, 3 - + def ham(self, x): self.x = x - + def eggs(self, y): self.y = self.x * y hasattr(y, str) class Bar(Foo): - def bla(self, z): print(z) -if __name__ == '__main__': +if __name__ == "__main__": from pscript import script2js - script2js(__file__, 'mymodule') + + script2js(__file__, "mymodule") diff --git a/examples/transpiling.py b/examples/transpiling.py index bf0e8ba0..f2ccfb1e 100644 --- a/examples/transpiling.py +++ b/examples/transpiling.py @@ -4,24 +4,26 @@ from pscript import py2js, evaljs, evalpy + def foo(a, b=1, *args): print(a) return b + # Create jscode object jscode = py2js(foo) # Print some info that we have on the code -print(jscode.meta['filename']) -print(jscode.meta['pycode']) +print(jscode.meta["filename"]) +print(jscode.meta["pycode"]) print(jscode) # Convert strings of Python to JS -print(py2js('isinstance(x, str)')) -print(py2js('isinstance(x, Bar)')) +print(py2js("isinstance(x, str)")) +print(py2js("isinstance(x, Bar)")) # Evaluate js in nodejs -print(evaljs('10 * 10')) +print(evaljs("10 * 10")) # Evaluate PScript in nodejs -print(evalpy('10**10')) +print(evalpy("10**10")) diff --git a/pscript/__init__.py b/pscript/__init__.py index 1782d944..0ff9c2d0 100644 --- a/pscript/__init__.py +++ b/pscript/__init__.py @@ -241,7 +241,9 @@ """ -__version__ = '0.7.7' +# ruff: noqa: E402, F401, F403 + +__version__ = "0.7.7" import sys import logging diff --git a/pscript/base.py b/pscript/base.py index 1dbcbe5b..a1ad5579 100644 --- a/pscript/base.py +++ b/pscript/base.py @@ -3,26 +3,27 @@ class BasicParser(Parser2): - """ A parser without the Pythonic features for converting builtin + """A parser without the Pythonic features for converting builtin functions and common methods. """ + pass class Parser(Parser3): - """ Parser to convert Python to JavaScript. - + """Parser to convert Python to JavaScript. + Instantiate this class with the Python code. Retrieve the JS code using the dump() method. - + In a subclass, you can implement methods called "function_x" or "method_x", which will then be called during parsing when a function/method with name "x" is encountered. Several methods and functions are already implemented in this way. - + While working on ast parsing, this resource is very helpful: https://greentreesnakes.readthedocs.org - + Parameters: code (str): the Python source code. pysource (tuple): the filename and line number that contain the source. @@ -33,15 +34,17 @@ class Parser(Parser3): inline_stdlib (bool): whether the used stdlib functions are inlined (default True). Set to False if the stdlib is already loaded. """ + pass # Create stubs that mean something -Infinity = float('inf') -NaN = float('nan') +Infinity = float("inf") +NaN = float("nan") + def this_is_js(): - """ Function available in both JS and Py that returns whether the code is running + """Function available in both JS and Py that returns whether the code is running on Python or JavaScript. """ return False diff --git a/pscript/commonast.py b/pscript/commonast.py index b0c18610..0988a8d3 100644 --- a/pscript/commonast.py +++ b/pscript/commonast.py @@ -21,20 +21,17 @@ encodebytes = base64.encodestring decodebytes = base64.decodestring -pyversion = sys.version_info NoneType = None.__class__ _Ellipsis = Ellipsis -if pyversion >= (3, ): - basestring = str # noqa - # do some extra asserts when running tests, but not always, for speed -docheck = 'pytest' in sys.modules +docheck = "pytest" in sys.modules + def parse(code, comments=False): - """ Parse Python code to produce a common AST tree. - + """Parse Python code to produce a common AST tree. + Parameters: code (str): the Python code to parse comments (bool): if True, will include Comment nodes. Default False. @@ -44,150 +41,152 @@ def parse(code, comments=False): class Node(object): - """ Abstract base class for all Nodes. - """ - - __slots__ = ['lineno', 'col_offset'] - + """Abstract base class for all Nodes.""" + + __slots__ = ["lineno", "col_offset"] + class OPS: - """ Operator enums: """ + """Operator enums:""" + # Unary - UAdd = 'UAdd' - USub = 'USub' - Not = 'Not' - Invert = 'Invert' + UAdd = "UAdd" + USub = "USub" + Not = "Not" + Invert = "Invert" # Binary - Add = 'Add' - Sub = 'Sub' - Mult = 'Mult' - Div = 'Div' - FloorDiv = 'FloorDiv' - Mod = 'Mod' - Pow = 'Pow' - LShift = 'LShift' - RShift = 'RShift' - BitOr = 'BitOr' - BitXor = 'BitXor' - BitAnd = 'BitAnd' + Add = "Add" + Sub = "Sub" + Mult = "Mult" + Div = "Div" + FloorDiv = "FloorDiv" + Mod = "Mod" + Pow = "Pow" + LShift = "LShift" + RShift = "RShift" + BitOr = "BitOr" + BitXor = "BitXor" + BitAnd = "BitAnd" # Boolean - And = 'And' - Or = 'Or' - + And = "And" + Or = "Or" + class COMP: - """ Comparison enums: """ - Eq = 'Eq' - NotEq = 'NotEq' - Lt = 'Lt' - LtE = 'LtE' - Gt = 'Gt' - GtE = 'GtE' - Is = 'Is' - IsNot = 'IsNot' - In = 'In' - NotIn = 'NotIn' - + """Comparison enums:""" + + Eq = "Eq" + NotEq = "NotEq" + Lt = "Lt" + LtE = "LtE" + Gt = "Gt" + GtE = "GtE" + Is = "Is" + IsNot = "IsNot" + In = "In" + NotIn = "NotIn" + def __init__(self, *args): names = self.__slots__ # Checks assert len(args) == len(names) # check this always if docheck: - assert not hasattr(self, '__dict__'), 'Nodes must have __slots__' - assert self.__class__ is not Node, 'Node is an abstract class' + assert not hasattr(self, "__dict__"), "Nodes must have __slots__" + assert self.__class__ is not Node, "Node is an abstract class" for name, val in zip(names, args): assert not isinstance(val, ast.AST) - if name == 'name': - assert isinstance(val, (basestring, NoneType)), 'name not a string' - elif name == 'op': + if name == "name": + assert isinstance(val, (str, NoneType)), "name not a string" + elif name == "op": assert val in Node.OPS.__dict__ or val in Node.COMP.__dict__ - elif name.endswith('_node'): - assert isinstance(val, (Node, NoneType)), '%r is not a Node' % name - elif name.endswith('_nodes'): - islistofnodes = (isinstance(val, list) and - all(isinstance(n, Node) for n in val)) - assert islistofnodes, '%r is not a list of nodes' % name + elif name.endswith("_node"): + assert isinstance(val, (Node, NoneType)), "%r is not a Node" % name + elif name.endswith("_nodes"): + islistofnodes = isinstance(val, list) and all( + isinstance(n, Node) for n in val + ) + assert islistofnodes, "%r is not a list of nodes" % name else: - assert not isinstance(val, Node), '%r should not be a Node' % name - assert not (isinstance(val, list) and - all(isinstance(n, Node) for n in val)) + assert not isinstance(val, Node), "%r should not be a Node" % name + assert not ( + isinstance(val, list) and all(isinstance(n, Node) for n in val) + ) # Assign for name, val in zip(names, args): setattr(self, name, val) - + def tojson(self, indent=2): - """ Return a string with the JSON representatiom of this AST. + """Return a string with the JSON representatiom of this AST. Set indent to None for a more compact representation. """ return json.dumps(self._todict(), indent=indent, sort_keys=True) - + @classmethod def fromjson(cls, text): - """ Classmethod to create an AST tree from JSON. - """ + """Classmethod to create an AST tree from JSON.""" return Node._fromdict(json.loads(text)) - + @classmethod def _fromdict(cls, d): - assert '_type' in d - Cls = globals()[d['_type']] - + assert "_type" in d + Cls = globals()[d["_type"]] + args = [] for name in Cls.__slots__: val = d[name] if val is None: pass - elif name.endswith('_node'): + elif name.endswith("_node"): val = Node._fromdict(val) - elif name.endswith('_nodes'): + elif name.endswith("_nodes"): val = [Node._fromdict(x) for x in val] - elif isinstance(val, basestring): - if val.startswith('BYTES:'): - val = decodebytes(val[6:].encode('utf-8')) - elif val.startswith('COMPLEX:'): + elif isinstance(val, str): + if val.startswith("BYTES:"): + val = decodebytes(val[6:].encode("utf-8")) + elif val.startswith("COMPLEX:"): val = complex(val[8:]) - elif pyversion < (3, ): - val = unicode(val) # noqa args.append(val) return Cls(*args) - + def _todict(self): - """ Get a dict representing this AST. This is the basis for + """Get a dict representing this AST. This is the basis for creating JSON, but can be used to compare AST trees as well. """ d = {} - d['_type'] = self.__class__.__name__ + d["_type"] = self.__class__.__name__ for name in self.__slots__: val = getattr(self, name) if val is None: pass - elif name.endswith('_node'): + elif name.endswith("_node"): val = val._todict() - elif name.endswith('_nodes'): + elif name.endswith("_nodes"): val = [x._todict() for x in val] elif isinstance(self, Bytes) and isinstance(val, bytes): - val = 'BYTES:' + encodebytes(val).decode('utf-8').rstrip() + val = "BYTES:" + encodebytes(val).decode("utf-8").rstrip() elif isinstance(self, Num) and isinstance(val, complex): - val = 'COMPLEX:' + repr(val) + val = "COMPLEX:" + repr(val) d[name] = val return d - + def __eq__(self, other): if not isinstance(other, Node): - raise ValueError('Can only compare nodes to other nodes.') + raise ValueError("Can only compare nodes to other nodes.") return self._todict() == other._todict() - + def __repr__(self): - names = ', '.join([repr(x) for x in self.__slots__]) - return '<%s with %s at 0x%x>' % (self.__class__.__name__, names, id(self)) - + names = ", ".join([repr(x) for x in self.__slots__]) + return "<%s with %s at 0x%x>" % (self.__class__.__name__, names, id(self)) + def __str__(self): return self.tojson() try: - Node.OPS.__doc__ += ', '.join([x for x in sorted(Node.OPS.__dict__) - if not x.startswith('_')]) - Node.COMP.__doc__ += ', '.join([x for x in sorted(Node.COMP.__dict__) - if not x.startswith('_')]) + Node.OPS.__doc__ += ", ".join( + [x for x in sorted(Node.OPS.__dict__) if not x.startswith("_")] + ) + Node.COMP.__doc__ += ", ".join( + [x for x in sorted(Node.COMP.__dict__) if not x.startswith("_")] + ) except AttributeError: # pragma: no cover pass # Py < 3.3 @@ -196,42 +195,52 @@ def __str__(self): ## General + class Comment(Node): """ Attributes: value: the comment string. """ - __slots__ = 'value', + + __slots__ = ("value",) + class Module(Node): - """ Each code that an AST is created for gets wrapped in a Module node. - + """Each code that an AST is created for gets wrapped in a Module node. + Attributes: body_nodes: a list of nodes. """ - __slots__ = 'body_nodes', + + __slots__ = ("body_nodes",) + ## Literals + class Num(Node): """ Attributes: value: the number as a native Python object (int, float, or complex). """ - __slots__ = 'value', + + __slots__ = ("value",) + class Str(Node): """ Attributes: value: the native Python str object. """ - __slots__ = 'value', + + __slots__ = ("value",) + class FormattedValue(Node): - """ Node representing a single formatting field in an f-string. If the + """Node representing a single formatting field in an f-string. If the string contains a single formatting field and nothing else the node can be isolated, otherwise it appears in JoinedStr. - + Attributes: value_node: an expression (can be anything). conversion: a string, '' means no formatting, 's' means !s string @@ -241,43 +250,55 @@ class FormattedValue(Node): if no format was specified. Both conversion and format_node can be set at the same time. """ - __slots__ = 'value_node', 'conversion', 'format_node' + + __slots__ = "value_node", "conversion", "format_node" + class JoinedStr(Node): - """ An f-string, comprising a series of FormattedValue and Str nodes. - + """An f-string, comprising a series of FormattedValue and Str nodes. + Attributes: value_nodes: list of Str and FormattedValue nodes. """ - __slots__ = 'value_nodes', + + __slots__ = ("value_nodes",) + class Bytes(Node): """ Attributes: value: the native Python bytes object. """ - __slots__ = 'value', + + __slots__ = ("value",) + class List(Node): """ Attributes: element_nodes: the items in the list. """ - __slots__ = 'element_nodes', + + __slots__ = ("element_nodes",) + class Tuple(Node): """ Attributes: element_nodes: the items in the tuple. """ - __slots__ = 'element_nodes', + + __slots__ = ("element_nodes",) + class Set(Node): """ Attributes: element_nodes: the items in the set. """ - __slots__ = 'element_nodes', + + __slots__ = ("element_nodes",) + class Dict(Node): """ @@ -285,63 +306,79 @@ class Dict(Node): key_nodes: the keys of the dict. value_nodes: the corresponding values. """ - __slots__ = 'key_nodes', 'value_nodes' + + __slots__ = "key_nodes", "value_nodes" + class Ellipsis(Node): - """ Represents the ``...`` syntax for the Ellipsis singleton. - """ + """Represents the ``...`` syntax for the Ellipsis singleton.""" + __slots__ = () + class NameConstant(Node): """ Attributes: value: the corresponding native Python object like True, False or None. """ - __slots__ = 'value', + + __slots__ = ("value",) + ## Variables, attributes, indexing and slicing + class Name(Node): """ Attributes: name: the string name of this variable. """ - __slots__ = 'name', + + __slots__ = ("name",) + class Starred(Node): - """ A starred variable name, e.g. ``*foo``. Note that this isn't + """A starred variable name, e.g. ``*foo``. Note that this isn't used to define a function with ``*args`` - FunctionDef nodes have special fields for that. - + Attributes: value_node: the value that is starred, typically a Name node. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class Attribute(Node): - """ Attribute access, e.g. ``foo.bar``. - + """Attribute access, e.g. ``foo.bar``. + Attributes: value_node: The node to get/set an attribute of. Typically a Name node. attr: a string with the name of the attribute. """ - __slots__ = 'value_node', 'attr' + + __slots__ = "value_node", "attr" + class Subscript(Node): - """ Subscript access, e.g. ``foo[3]``. - + """Subscript access, e.g. ``foo[3]``. + Attributes: value_node: The node to get/set a subscript of. Typically a Name node. slice_node: An Index, Slice or ExtSlice node. """ - __slots__ = 'value_node', 'slice_node' + + __slots__ = "value_node", "slice_node" + class Index(Node): """ Attributes: value_node: Single index. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class Slice(Node): """ @@ -350,199 +387,238 @@ class Slice(Node): upper_node: end slice. step_node: slice step. """ - __slots__ = 'lower_node', 'upper_node', 'step_node' + + __slots__ = "lower_node", "upper_node", "step_node" + class ExtSlice(Node): """ Attributes: dim_nodes: list of Index and Slice nodes (of for each dimension). """ - __slots__ = 'dim_nodes', + + __slots__ = ("dim_nodes",) + ## Expressions + class Expr(Node): - """ When an expression, such as a function call, appears as a + """When an expression, such as a function call, appears as a statement by itself (an expression statement), with its return value not used or stored, it is wrapped in this container. - + Attributes: value_node: holds one of the other nodes in this section, or a literal, a Name, a Lambda, or a Yield or YieldFrom node. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class UnaryOp(Node): - """ A unary operation (e.g. ``-x``, ``not x``). - + """A unary operation (e.g. ``-x``, ``not x``). + Attributes: op: the operator (an enum from ``Node.OPS``). right_node: the operand at the right of the operator. """ - __slots__ = 'op', 'right_node' + + __slots__ = "op", "right_node" + class BinOp(Node): - """ A binary operation (e.g. ``a / b``, ``a + b``). - + """A binary operation (e.g. ``a / b``, ``a + b``). + Attributes: op: the operator (an enum from ``Node.OPS``). left_node: the node to the left of the operator. right_node: the node to the right of the operator. """ - __slots__ = 'op', 'left_node', 'right_node' + + __slots__ = "op", "left_node", "right_node" + class BoolOp(Node): - """ A boolean operator (``and``, ``or``, but not ``not``). - + """A boolean operator (``and``, ``or``, but not ``not``). + Attributes: op: the operator (an enum from ``Node.OPS``). - value_nodes: a list of nodes. ``a``, ``b`` and ``c`` in + value_nodes: a list of nodes. ``a``, ``b`` and ``c`` in ``a or b or c``. """ - __slots__ = 'op', 'value_nodes' + + __slots__ = "op", "value_nodes" + class Compare(Node): - """ A comparison of two or more values. - + """A comparison of two or more values. + Attributes: op: the comparison operator (an enum from ``Node.COMP``). left_node: the node to the left of the operator. right_node: the node to the right of the operator. """ - __slots__ = 'op', 'left_node', 'right_node' + + __slots__ = "op", "left_node", "right_node" + class Call(Node): - """ A function call. - + """A function call. + Attributes: func_node: Name, Attribute or SubScript node that represents the function. arg_nodes: list of nodes representing positional arguments. kwarg_nodes: list of Keyword nodes representing keyword arguments. - + Note that an argument ``*x`` would be specified as a Starred node in arg_nodes, and ``**y`` as a Keyword node with a name being ``None``. """ - __slots__ = ('func_node', 'arg_nodes', 'kwarg_nodes') + + __slots__ = ("func_node", "arg_nodes", "kwarg_nodes") + class Keyword(Node): - """ Keyword argument used in a Call. - + """Keyword argument used in a Call. + Attributes: name: the (string) name of the argument. Is None for ``**kwargs``. - value_node: the value of the arg. + value_node: the value of the arg. """ - __slots__ = ('name', 'value_node') + + __slots__ = ("name", "value_node") + class IfExp(Node): - """ An expression such as ``a if b else c``. - + """An expression such as ``a if b else c``. + Attributes: test_node: the ``b`` in the above. body_node: the ``a`` in the above. else_node: the ``c`` in the above. """ - __slots__ = 'test_node', 'body_node', 'else_node' + + __slots__ = "test_node", "body_node", "else_node" + class ListComp(Node): - """ List comprehension. - + """List comprehension. + Attributes: element_node: the part being evaluated for each item. comp_nodes: a list of Comprehension nodes. """ - __slots__ = 'element_node', 'comp_nodes' + + __slots__ = "element_node", "comp_nodes" + class SetComp(Node): - """ Set comprehension. See ListComp. - """ - __slots__ = 'element_node', 'comp_nodes' + """Set comprehension. See ListComp.""" + + __slots__ = "element_node", "comp_nodes" + class GeneratorExp(Node): - """ Generor expression. See ListComp. - """ - __slots__ = 'element_node', 'comp_nodes' + """Generor expression. See ListComp.""" + + __slots__ = "element_node", "comp_nodes" + class DictComp(Node): - """ Dict comprehension. - + """Dict comprehension. + Attributes: key_node: the key of the item being evaluated. value_node: the value of the item being evaluated. comp_nodes: a list of Comprehension nodes. """ - __slots__ = 'key_node', 'value_node', 'comp_nodes' + + __slots__ = "key_node", "value_node", "comp_nodes" + class Comprehension(Node): - """ Represents a single for-clause in a comprehension. - + """Represents a single for-clause in a comprehension. + Attributes: target_node: reference to use for each element, typically a Name or Tuple node. iter_node: the object to iterate over. if_nodes: a list of test expressions. """ - __slots__ = 'target_node', 'iter_node', 'if_nodes' + + __slots__ = "target_node", "iter_node", "if_nodes" ## Statements + class Assign(Node): - """ Assignment of a value to a variable. - + """Assignment of a value to a variable. + Attributes: target_nodes: variables to assign to, Name or SubScript. value_node: the object to assign. """ - __slots__ = 'target_nodes', 'value_node' + + __slots__ = "target_nodes", "value_node" + class AugAssign(Node): - """ Augmented assignment, such as ``a += 1``. - + """Augmented assignment, such as ``a += 1``. + Attributes: target_node: variable to assign to, Name or SubScript. op: operator enum (e.g. ``Node.OPS.Add``) value_node: the object to assign. - """ - __slots__ = 'target_node', 'op', 'value_node' + """ + + __slots__ = "target_node", "op", "value_node" class Raise(Node): - """ Raising an exception. - + """Raising an exception. + Attributes: exc_node: the exception object to be raised, normally a Call or Name, or None for a standalone raise. cause_node: the optional part for y in raise x from y. """ - __slots__ = 'exc_node', 'cause_node' + + __slots__ = "exc_node", "cause_node" + class Assert(Node): - """ An assertion. - + """An assertion. + Attributes: test_node: the condition to test. msg_node: the failure message (commonly a Str node) """ - __slots__ = 'test_node', 'msg_node' + + __slots__ = "test_node", "msg_node" + class Delete(Node): - """ A del statement. - + """A del statement. + Attributes: target_nodes: the variables to delete, such as Name, Attribute or Subscript nodes. """ - __slots__ = 'target_nodes', + + __slots__ = ("target_nodes",) + class Pass(Node): - """ Do nothing. - """ + """Do nothing.""" + __slots__ = () + class Import(Node): - """ An import statement. - + """An import statement. + Attributes: root: the name of the module to import from. None if this is not a from-import. @@ -550,69 +626,82 @@ class Import(Node): level: an integer indicating depth of import. Zero means absolute import. """ - __slots__ = 'root', 'names', 'level' + + __slots__ = "root", "names", "level" + ## Control flow + class If(Node): - """ An if-statement. - + """An if-statement. + Note that elif clauses don't have a special representation in the AST, but rather appear as extra If nodes within the else section of the previous one. - + Attributes: test_node: the test, e.g. a Compare node. body_nodes: the body of the if-statement. else_nodes: the body of the else-clause of the if-statement. """ - __slots__ = 'test_node', 'body_nodes', 'else_nodes' + + __slots__ = "test_node", "body_nodes", "else_nodes" + class For(Node): - """ A for-loop. - + """A for-loop. + Attributes: target_node: the variable(s) the loop assigns to. iter_node: the object to iterate over. body_nodes: the body of the for-loop. else_nodes: the body of the else-clause of the for-loop. """ - __slots__ = 'target_node', 'iter_node', 'body_nodes', 'else_nodes' + + __slots__ = "target_node", "iter_node", "body_nodes", "else_nodes" + class While(Node): - """ A while-loop. - + """A while-loop. + Attributes: test_node: the test to perform on each iteration. body_nodes: the body of the for-loop. else_nodes: the body of the else-clause of the for-loop. """ - __slots__ = 'test_node', 'body_nodes', 'else_nodes' + + __slots__ = "test_node", "body_nodes", "else_nodes" + class Break(Node): - """ Break from a loop. - """ + """Break from a loop.""" + __slots__ = () + class Continue(Node): - """ Continue with next iteration of a loop. - """ + """Continue with next iteration of a loop.""" + __slots__ = () + class Try(Node): - """ Try-block. - + """Try-block. + Attributes: body_nodes: the body of the try-block (i.e. the code to try). handler_nodes: a list of ExceptHandler instances. else_nodes: the body of the else-clause of the try-block. finally_nodes: the body of the finally-clause of the try-block. """ - __slots__ = 'body_nodes', 'handler_nodes', 'else_nodes', 'finally_nodes' + + __slots__ = "body_nodes", "handler_nodes", "else_nodes", "finally_nodes" + class ExceptHandler(Node): - """ Single except-clause. - + """Single except-clause. + Attributes: type_node: the type of exception to catch. Often a Name node or None to catch all. @@ -620,31 +709,38 @@ class ExceptHandler(Node): None otherwise. body_nodes: the body of the except-clause. """ - __slots__ = 'type_node', 'name', 'body_nodes' + + __slots__ = "type_node", "name", "body_nodes" + class With(Node): - """ A with-block (i.e. a context manager). - + """A with-block (i.e. a context manager). + Attributes: item_nodes: a list of WithItem nodes (i.e. context managers). body_nodes: the body of the with-block. """ - __slots__ = 'item_nodes', 'body_nodes' + + __slots__ = "item_nodes", "body_nodes" + class WithItem(Node): - """ A single context manager in a with block. - + """A single context manager in a with block. + Attributes: expr_node: the expression for the context manager. as_node: a Name, Tuple or List node representing the ``as foo`` part. """ - __slots__ = 'expr_node', 'as_node' + + __slots__ = "expr_node", "as_node" + ## Function and class definitions + class FunctionDef(Node): - """ A function definition. - + """A function definition. + Attributes: name: the (string) name of the function. decorator_nodes: the list of decorators to be applied, stored @@ -658,13 +754,22 @@ class FunctionDef(Node): kwargs_node: an Arg node representing ``**kwargs``. body_nodes: the body of the function. """ - __slots__ = ('name', 'decorator_nodes', 'annotation_node', - 'arg_nodes', 'kwarg_nodes', 'args_node', 'kwargs_node', - 'body_nodes') + + __slots__ = ( + "name", + "decorator_nodes", + "annotation_node", + "arg_nodes", + "kwarg_nodes", + "args_node", + "kwargs_node", + "body_nodes", + ) + class Lambda(Node): - """ Anonymous function definition. - + """Anonymous function definition. + Attributes: arg_nodes: list of Args nodes representing positional arguments. kwarg_nodes: list of Arg nodes representing keyword-only arguments. @@ -672,123 +777,146 @@ class Lambda(Node): kwargs_node: an Arg node representing ``**kwargs``. body_node: the body of the function (a single node). """ - __slots__ = ('arg_nodes', 'kwarg_nodes', 'args_node', 'kwargs_node', - 'body_node') + + __slots__ = ("arg_nodes", "kwarg_nodes", "args_node", "kwargs_node", "body_node") + class AsyncFunctionDef(Node): - """ Asynchronous function definition. - + """Asynchronous function definition. + Same as FunctionDef, but async. """ - __slots__ = ('name', 'decorator_nodes', 'annotation_node', - 'arg_nodes', 'kwarg_nodes', 'args_node', 'kwargs_node', - 'body_nodes') + + __slots__ = ( + "name", + "decorator_nodes", + "annotation_node", + "arg_nodes", + "kwarg_nodes", + "args_node", + "kwargs_node", + "body_nodes", + ) + class Arg(Node): - """ Function argument for a FunctionDef. - + """Function argument for a FunctionDef. + Attributes: name: the (string) name of the argument. value_node: the default value of this argument. Can be None. annotation_node: the annotation for this argument (Python3 only). """ - - __slots__ = ('name', 'value_node', 'annotation_node') + + __slots__ = ("name", "value_node", "annotation_node") + class Return(Node): """ Attributes: value_node: the value to return. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class Yield(Node): """ Attributes: value_node: the value to yield. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class YieldFrom(Node): """ Attributes: value_node: the value to yield. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class Await(Node): """ Attributes: value_node: the value to return. """ - __slots__ = 'value_node', + + __slots__ = ("value_node",) + class Global(Node): """ Attributes: names: a list of string names to declare global. """ - __slots__ = 'names', + + __slots__ = ("names",) + class Nonlocal(Node): """ Attributes: names: a list of string names to declare nonlocal. """ - __slots__ = 'names', + + __slots__ = ("names",) + class ClassDef(Node): - """ A class definition. - + """A class definition. + Attributes: name: a string for the class name. decorator_nodes: the list of decorators to be applied, as in FunctionDef. arg_nodes: list of nodes representing base classes. kwarg_nodes: list of Keyword nodes representing keyword-only arguments. body_nodes: the body of the class. - + Note that arg_nodes and kwarg_nodes are similar to those in the Call node. An argument ``*x`` would be specified as a Starred node in arg_nodes, and ``**y`` as a Keyword node with a name being ``None``. For more information on keyword arguments see https://www.python.org/dev/peps/pep-3115/. """ - __slots__ = ('name', 'decorator_nodes', 'arg_nodes', 'kwarg_nodes', 'body_nodes') + + __slots__ = ("name", "decorator_nodes", "arg_nodes", "kwarg_nodes", "body_nodes") + ## -- (end marker for doc generator) class NativeAstConverter: - """ Convert ast produced by Python's ast module to common ast. - """ - + """Convert ast produced by Python's ast module to common ast.""" + def __init__(self, code): self._root = ast.parse(code) - self._lines =code.splitlines() + self._lines = code.splitlines() self._stack = [] # contains tuple elements: (list_obj, native_nodes) - + def _add_comments(self, container, lineno): - """ Add comment nodes from the last point until the given line number. - """ + """Add comment nodes from the last point until the given line number.""" linenr1 = self._comment_pointer linenr2 = lineno self._comment_pointer = linenr2 + 1 # store for next time - + for i in range(linenr1, linenr2): - line = self._lines[i-1] # lineno's start from 1 - if line.lstrip().startswith('#'): - before, _, comment = line.partition('#') + line = self._lines[i - 1] # lineno's start from 1 + if line.lstrip().startswith("#"): + before, _, comment = line.partition("#") node = Comment(comment) node.lineno = i node.col_offset = len(before) container.append(node) - + def convert(self, comments=False): assert not self._stack self._comment_pointer = 1 - + result = self._convert(self._root) - + while self._stack: container, native_nodes = self._stack.pop(0) for native_node in native_nodes: @@ -796,39 +924,38 @@ def convert(self, comments=False): if comments: self._add_comments(container, node.lineno) container.append(node) - + return result - + def _convert(self, n): - # n is the native node produced by the ast module if n is None: return None # but some node attributes can be None assert isinstance(n, ast.AST) - + # Get converter function type = n.__class__.__name__ try: - converter = getattr(self, '_convert_' + type) + converter = getattr(self, "_convert_" + type) except AttributeError: # pragma: no cover - raise RuntimeError('Cannot convert %s nodes.' % type) + raise RuntimeError("Cannot convert %s nodes." % type) from None # Convert node val = converter(n) assert isinstance(val, Node) # Set its position - val.lineno = getattr(n, 'lineno', 1) - val.col_offset = getattr(n, 'col_offset', 0) + val.lineno = getattr(n, "lineno", 1) + val.col_offset = getattr(n, "col_offset", 0) return val - + def _convert_Module(self, n): node = Module([]) # Add back the "docstring" that Python removed; this may actually be # a code snippet and not a module. self._stack.append((node.body_nodes, n.body)) return node - + ## Literals - + def _convert_Constant(self, n): val = n.value if val is None or val is True or val is False: @@ -841,89 +968,68 @@ def _convert_Constant(self, n): return Bytes(val) if val is _Ellipsis: return Ellipsis() - raise RuntimeError('Cannot convert %s constants.' % type(val).__name__) - + raise RuntimeError("Cannot convert %s constants." % type(val).__name__) + def _convert_Num(self, n): - if pyversion < (3, ) and str(n.n).startswith('-'): - # -4 is a unary sub on 4, dont forget complex numbers - return UnaryOp(Node.OPS.USub, Num(-n.n)) return Num(n.n) - + def _convert_Str(self, n): # We check the string prefix here. We only really need it in Python 2, # because u is not needed in py3, and b and r are resolved by the lexer, # and f as well (resulting in JoinedStr or FormattedValue). # Note that the col_offset of the node seems 1 off when the string is # a key in a dict :/ (PScript issue #15) - if pyversion < (3, ): - line = self._lines[n.lineno-1] - i = n.col_offset - i = i - 1 if (i > 0 and line[i-1] in 'rufb"\'') else i - pre = '' - if line[i] not in '"\'': - pre += line[i] - if line[i + 1] not in '"\'': - pre += line[i + 1] - if 'b' in pre: - return Bytes(n.s) return Str(n.s) - + def _convert_JoinedStr(self, n): c = self._convert return JoinedStr([c(x) for x in n.values]) - + def _convert_FormattedValue(self, n): - conversion = '' if n.conversion < 0 else chr(n.conversion) - return FormattedValue(self._convert(n.value), conversion, - self._convert(n.format_spec)) - + conversion = "" if n.conversion < 0 else chr(n.conversion) + return FormattedValue( + self._convert(n.value), conversion, self._convert(n.format_spec) + ) + def _convert_Bytes(self, n): return Bytes(n.s) - + def _convert_List(self, n): c = self._convert return List([c(x) for x in n.elts]) - + def _convert_Tuple(self, n): c = self._convert return Tuple([c(x) for x in n.elts]) - + def _convert_Set(self, n): c = self._convert return Set([c(x) for x in n.elts]) - + def _convert_Dict(self, n): c = self._convert return Dict([c(x) for x in n.keys], [c(x) for x in n.values]) - + def _convert_Ellipsis(self, n): - if pyversion < (3, ): - return Index(Ellipsis()) # Ellipses must be wrapped in an index return Ellipsis() - + def _convert_NameConstant(self, n): return NameConstant(n.value) - + ## Variables, attributes, indexing and slicing - + def _convert_Name(self, n): - if pyversion < (3, 4): # pragma: no cover - M = {'None': None, 'False': False, 'True': True} - if n.id in M: - return NameConstant(M[n.id]) # Python < 3.4 - if pyversion < (3, ) and isinstance(n.ctx , ast.Param): - return Arg(n.id, None, None) return Name(n.id) - + def _convert_Starred(self, n): return Starred(self._convert(n.value)) - + def _convert_Attribute(self, n): return Attribute(self._convert(n.value), n.attr) - + def _convert_Subscript(self, n): return Subscript(self._convert(n.value), self._convert_index_like(n.slice)) - + def _convert_index_like(self, n): c = self._convert if isinstance(n, (ast.Slice, ast.Index, ast.ExtSlice, ast.Ellipsis)): @@ -934,39 +1040,36 @@ def _convert_index_like(self, n): return Tuple(dims) else: # Num, Unary, Name, or ... return c(n) - + def _convert_Index(self, n): return self._convert(n.value) - + def _convert_Slice(self, n): c = self._convert step = c(n.step) - if pyversion < (3, ) and isinstance(step, NameConstant) and step.value is None: - if not self._lines[n.step.lineno-1][n.step.col_offset:].startswith('None'): - step = None # silly Python 2 turns a[::] into a[::None] return Slice(c(n.lower), c(n.upper), step) - + def _convert_ExtSlice(self, n): return Tuple([self._convert_index_like(x) for x in n.dims]) - + ## Expressions - + def _convert_Expr(self, n): return Expr(self._convert(n.value)) - + def _convert_UnaryOp(self, n): op = n.op.__class__.__name__ return UnaryOp(op, self._convert(n.operand)) - + def _convert_BinOp(self, n): op = n.op.__class__.__name__ return BinOp(op, self._convert(n.left), self._convert(n.right)) - + def _convert_BoolOp(self, n): c = self._convert op = n.op.__class__.__name__ return BoolOp(op, [c(x) for x in n.values]) # list of value_nodes - + def _convert_Compare(self, n): c = self._convert # Get compares and ops @@ -976,7 +1079,7 @@ def _convert_Compare(self, n): # Create our comparison operators compares = [] for i in range(len(ops)): - co = Compare(ops[i], comps[i], comps[i+1]) + co = Compare(ops[i], comps[i], comps[i + 1]) compares.append(co) # Return single or wrapped in an AND assert compares @@ -984,53 +1087,46 @@ def _convert_Compare(self, n): return compares[0] else: return BoolOp(Node.OPS.And, compares) - + def _convert_Call(self, n): c = self._convert arg_nodes = [c(a) for a in n.args] kwarg_nodes = [c(a) for a in n.keywords] - - if pyversion < (3, 5): - if n.starargs: - arg_nodes.append(Starred(c(n.starargs))) - if n.kwargs: - kwarg_nodes.append(Keyword(None, c(n.kwargs))) - return Call(c(n.func), arg_nodes, kwarg_nodes) - + def _convert_keyword(self, n): return Keyword(n.arg, self._convert(n.value or None)) - + def _convert_IfExp(self, n): c = self._convert return IfExp(c(n.test), c(n.body), c(n.orelse)) - + def _convert_ListComp(self, n): c = self._convert return ListComp(c(n.elt), [c(x) for x in n.generators]) - + def _convert_SetComp(self, n): c = self._convert return SetComp(c(n.elt), [c(x) for x in n.generators]) - + def _convert_GeneratorExp(self, n): c = self._convert return GeneratorExp(c(n.elt), [c(x) for x in n.generators]) - + def _convert_DictComp(self, n): c = self._convert return DictComp(c(n.key), c(n.value), [c(x) for x in n.generators]) - + def _convert_comprehension(self, n): c = self._convert return Comprehension(c(n.target), c(n.iter), [c(x) for x in n.ifs]) - + ## Statements - + def _convert_Assign(self, n): c = self._convert return Assign([c(x) for x in n.targets], c(n.value)) - + def _convert_AugAssign(self, n): op = n.op.__class__.__name__ return AugAssign(self._convert(n.target), op, self._convert(n.value)) @@ -1040,7 +1136,7 @@ def _convert_AnnAssign(self, n): raise RuntimeError("Cannot convert AnnAssign nodes with no assignment!") c = self._convert return Assign([c(n.target)], c(n.value)) - + def _convert_Print(self, n): # pragma: no cover - Python 2.x compat c = self._convert if len(n.values) == 1 and isinstance(n.values[0], ast.Tuple): @@ -1049,75 +1145,71 @@ def _convert_Print(self, n): # pragma: no cover - Python 2.x compat arg_nodes = [c(x) for x in n.values] kwarg_nodes = [] if n.dest is not None: - kwarg_nodes.append(Keyword('dest', c(n.dest))) + kwarg_nodes.append(Keyword("dest", c(n.dest))) if not n.nl: - kwarg_nodes.append(Keyword('end', Str(''))) - return Expr(Call(Name('print'), arg_nodes, kwarg_nodes)) - + kwarg_nodes.append(Keyword("end", Str(""))) + return Expr(Call(Name("print"), arg_nodes, kwarg_nodes)) + def _convert_Exec(self, n): # pragma: no cover - Python 2.x compat c = self._convert arg_nodes = [c(n.body)] arg_nodes.append(c(n.globals) or NameConstant(None)) arg_nodes.append(c(n.locals) or NameConstant(None)) - return Expr(Call(Name('exec'), arg_nodes, [])) - + return Expr(Call(Name("exec"), arg_nodes, [])) + def _convert_Repr(self, n): # pragma: no cover - Python 2.x compat c = self._convert - return Call(Name('repr'), [c(n.value)], []) - + return Call(Name("repr"), [c(n.value)], []) + def _convert_Raise(self, n): - if pyversion < (3, ): - if n.inst or n.tback: - raise RuntimeError('Commonast does not support old raise syntax') - return Raise(self._convert(n.type), None) return Raise(self._convert(n.exc), self._convert(n.cause)) - + def _convert_Assert(self, n): return Assert(self._convert(n.test), self._convert(n.msg)) - + def _convert_Delete(self, n): c = self._convert return Delete([c(x) for x in n.targets]) - + def _convert_Pass(self, n): return Pass() - + def _convert_Import(self, n): return Import(None, [(x.name, x.asname) for x in n.names], 0) - + def _convert_ImportFrom(self, n): names = [(x.name, x.asname) for x in n.names] return Import(n.module, names, n.level) - + ## Control flow - + def _convert_If(self, n): c = self._convert node = If(c(n.test), [], []) self._stack.append((node.body_nodes, n.body)) self._stack.append((node.else_nodes, n.orelse)) return node - + def _convert_For(self, n): c = self._convert node = For(c(n.target), c(n.iter), [], []) self._stack.append((node.body_nodes, n.body)) self._stack.append((node.else_nodes, n.orelse)) return node - + def _convert_While(self, n): c = self._convert node = While(c(n.test), [], []) self._stack.append((node.body_nodes, n.body)) self._stack.append((node.else_nodes, n.orelse)) return node - + def _convert_Break(self, n): return Break() - + def _convert_Continue(self, n): return Continue() - + def _convert_Try(self, n): c = self._convert node = Try([], [c(x) for x in n.handlers], [], []) @@ -1125,10 +1217,10 @@ def _convert_Try(self, n): self._stack.append((node.else_nodes, n.orelse)) self._stack.append((node.finally_nodes, n.finalbody)) return node - + def _convert_TryFinally(self, n): # pragma: no cover - Py <= 3.2 c = self._convert - if (len(n.body) == 1) and n.body[0].__class__.__name__ == 'TryExcept': + if (len(n.body) == 1) and n.body[0].__class__.__name__ == "TryExcept": # un-nesting for try-except-finally n2 = n.body[0] node = Try([], [c(x) for x in n2.handlers], [], []) @@ -1140,24 +1232,24 @@ def _convert_TryFinally(self, n): # pragma: no cover - Py <= 3.2 self._stack.append((node.body_nodes, n.body)) self._stack.append((node.finally_nodes, n.finalbody)) return node - + def _convert_TryExcept(self, n): # pragma: no cover - Py <= 3.2 c = self._convert node = Try([], [c(x) for x in n.handlers], [], []) self._stack.append((node.body_nodes, n.body)) self._stack.append((node.else_nodes, n.orelse)) return node - + def _convert_ExceptHandler(self, n): c = self._convert name = n.name.id if isinstance(n.name, ast.Name) else n.name node = ExceptHandler(c(n.type), name, []) self._stack.append((node.body_nodes, n.body)) return node - + def _convert_With(self, n): c = self._convert - if hasattr(n, 'items'): + if hasattr(n, "items"): node = With([c(x) for x in n.items], []) else: # pragma: no cover - Py < 3.3 items = [WithItem(c(n.context_expr), c(n.optional_vars))] @@ -1167,111 +1259,104 @@ def _convert_With(self, n): node = With(items, []) self._stack.append((node.body_nodes, n.body)) return node - + def _convert_withitem(self, n): return WithItem(self._convert(n.context_expr), self._convert(n.optional_vars)) - + ## Function and class definitions - + def _convert_functiondefs(self, n, cls): c = self._convert args = n.args # Parse arg_nodes and kwarg_nodes arg_nodes = [c(x) for x in args.args] for i, default in enumerate(reversed(args.defaults)): - arg_node = arg_nodes[-1-i] + arg_node = arg_nodes[-1 - i] if isinstance(arg_node, Tuple): - raise RuntimeError('Tuple arguments in function def not supported.') + raise RuntimeError("Tuple arguments in function def not supported.") arg_node.value_node = c(default) - if pyversion < (3, ): - kwarg_nodes = [] - else: - kwarg_nodes = [c(x) for x in args.kwonlyargs] - for i, default in enumerate(reversed(args.kw_defaults)): - kwarg_nodes[-1-i].value_node = c(default) + kwarg_nodes = [c(x) for x in args.kwonlyargs] + for i, default in enumerate(reversed(args.kw_defaults)): + kwarg_nodes[-1 - i].value_node = c(default) # Parse args_node and kwargs_node - if pyversion < (3, ): - args_node = Arg(args.vararg, None, None) if args.vararg else None - kwargs_node = Arg(args.kwarg, None, None) if args.kwarg else None - elif pyversion < (3, 4): - args_node = kwargs_node = None - if args.vararg: - args_node = Arg(args.vararg, None, c(args.varargannotation)) - if args.kwarg: - kwargs_node = Arg(args.kwarg, None, c(args.kwargannotation)) - else: - args_node = c(args.vararg) - kwargs_node = c(args.kwarg) - - returns = None if pyversion < (3, ) else c(n.returns) - Cls = cls # noqa - node = Cls(n.name, [c(x) for x in n.decorator_list], returns, - arg_nodes, kwarg_nodes, args_node, kwargs_node, []) + args_node = c(args.vararg) + kwargs_node = c(args.kwarg) + + returns = c(n.returns) + Cls = cls + + node = Cls( + n.name, + [c(x) for x in n.decorator_list], + returns, + arg_nodes, + kwarg_nodes, + args_node, + kwargs_node, + [], + ) if docheck: assert isinstance(node.args_node, (NoneType, Arg)) assert isinstance(node.kwargs_node, (NoneType, Arg)) for x in node.arg_nodes + node.kwarg_nodes: assert isinstance(x, Arg) - + self._stack.append((node.body_nodes, n.body)) return node - + def _convert_FunctionDef(self, n): return self._convert_functiondefs(n, FunctionDef) - + def _convert_Lambda(self, n): c = self._convert args = n.args arg_nodes = [c(x) for x in args.args] for i, default in enumerate(reversed(args.defaults)): - arg_nodes[-1-i].value_node = c(default) - if pyversion < (3, ): - kwarg_nodes = [] - else: - kwarg_nodes = [c(x) for x in args.kwonlyargs] - for i, default in enumerate(reversed(args.kw_defaults)): - kwarg_nodes[-1-i].value_node = c(default) - - return Lambda(arg_nodes, kwarg_nodes, - c(args.vararg), c(args.kwarg), c(n.body)) - + arg_nodes[-1 - i].value_node = c(default) + kwarg_nodes = [c(x) for x in args.kwonlyargs] + for i, default in enumerate(reversed(args.kw_defaults)): + kwarg_nodes[-1 - i].value_node = c(default) + + return Lambda(arg_nodes, kwarg_nodes, c(args.vararg), c(args.kwarg), c(n.body)) + def _convert_AsyncFunctionDef(self, n): return self._convert_functiondefs(n, AsyncFunctionDef) - + def _convert_arg(self, n): # Value is initially None return Arg(n.arg or None, None, self._convert(n.annotation)) - + def _convert_Return(self, n): return Return(self._convert(n.value)) - + def _convert_Yield(self, n): return Yield(self._convert(n.value)) - + def _convert_YieldFrom(self, n): return YieldFrom(self._convert(n.value)) - + def _convert_Await(self, n): return Await(self._convert(n.value)) - + def _convert_Global(self, n): return Global(n.names) - + def _convert_Nonlocal(self, n): return Nonlocal(n.names) - + def _convert_ClassDef(self, n): c = self._convert arg_nodes = [c(a) for a in n.bases] - kwarg_nodes = [] if pyversion < (3, ) else [c(a) for a in n.keywords] - - if getattr(n, 'starargs', None): + kwarg_nodes = [c(a) for a in n.keywords] + + if getattr(n, "starargs", None): arg_nodes.append(Starred(self._convert(n.starargs))) - if getattr(n, 'kwargs', None): + if getattr(n, "kwargs", None): kwarg_nodes.append(Keyword(None, self._convert(n.kwargs))) - - node = ClassDef(n.name, [c(a) for a in n.decorator_list], - arg_nodes, kwarg_nodes, []) - + + node = ClassDef( + n.name, [c(a) for a in n.decorator_list], arg_nodes, kwarg_nodes, [] + ) + self._stack.append((node.body_nodes, n.body)) return node diff --git a/pscript/functions.py b/pscript/functions.py index 5d4d6b57..8e5e41c9 100644 --- a/pscript/functions.py +++ b/pscript/functions.py @@ -1,6 +1,5 @@ import re import os -import sys import types import inspect import hashlib @@ -13,14 +12,14 @@ class JSString(str): - """ A subclass of string, so we can add attributes to JS string objects. - """ + """A subclass of string, so we can add attributes to JS string objects.""" + pass def py2js(ob=None, new_name=None, **parser_options): - """ Convert Python to JavaScript. - + """Convert Python to JavaScript. + Parameters: ob (str, module, function, class): The code, function or class to transpile. @@ -31,11 +30,11 @@ def py2js(ob=None, new_name=None, **parser_options): into account in the process. parser_options: Additional options, see :class:`Parser class ` for details. - + Returns: str: The JavaScript code as a str object that has a ``meta`` attribute with the following fields: - + * filename (str): the name of the file that defines the object. * linenr (int): the starting linenr for the object definition. * pycode (str): the Python code used to generate the JS. @@ -46,65 +45,69 @@ def py2js(ob=None, new_name=None, **parser_options): * vars_global (set): names explicitly declared global. * std_functions (set): stdlib functions used in this code. * std_method (set): stdlib methods used in this code. - + Notes: The Python source code for a class is acquired by name. Therefore one should avoid decorating classes in modules where multiple classes with the same name are defined. This is a consequence of classes not having a corresponding code object (in contrast to functions). - + """ - + def py2js_(ob): if isinstance(ob, str): - thetype = 'str' + thetype = "str" pycode = ob filename = None linenr = 0 - elif isinstance(ob, types.ModuleType) and hasattr(ob, '__file__'): - thetype = 'str' + elif isinstance(ob, types.ModuleType) and hasattr(ob, "__file__"): + thetype = "str" filename = inspect.getsourcefile(ob) linenr = 0 - pycode = open(filename, 'rb').read().decode() - if pycode.startswith('# -*- coding:'): - pycode = '\n' + pycode.split('\n', 1)[-1] + pycode = open(filename, "rb").read().decode() + if pycode.startswith("# -*- coding:"): + pycode = "\n" + pycode.split("\n", 1)[-1] elif isinstance(ob, (type, types.FunctionType, types.MethodType)): - thetype = 'class' if isinstance(ob, type) else 'def' + thetype = "class" if isinstance(ob, type) else "def" # Get code try: filename = inspect.getsourcefile(ob) lines, linenr = inspect.getsourcelines(ob) except Exception as err: - raise ValueError('Could not get source code for object %r: %s' % - (ob, err)) - if getattr(ob, '__name__', '') in ('', ''): - raise ValueError('py2js() got anonymous function from ' - '"%s", line %i, %r.' % (filename, linenr, ob)) + raise ValueError( + "Could not get source code for object %r: %s" % (ob, err) + ) from None + if getattr(ob, "__name__", "") in ("", ""): + raise ValueError( + "py2js() got anonymous function from " + '"%s", line %i, %r.' % (filename, linenr, ob) + ) # Normalize indentation, based on first line indent = len(lines[0]) - len(lines[0].lstrip()) for i in range(len(lines)): line = lines[i] line_indent = len(line) - len(line.lstrip()) if line_indent < indent and line.strip(): - assert line.lstrip().startswith('#') # only possible for comments - lines[i] = indent * ' ' + line.lstrip() + assert line.lstrip().startswith("#") # only possible for comments + lines[i] = indent * " " + line.lstrip() else: lines[i] = line[indent:] # Skip any decorators - while not lines[0].lstrip().startswith((thetype, 'async ' + thetype)): + while not lines[0].lstrip().startswith((thetype, "async " + thetype)): lines.pop(0) # join lines and rename - pycode = ''.join(lines) + pycode = "".join(lines) else: - raise ValueError('py2js() only accepts non-builtin modules, ' - 'classes and functions.') - + raise ValueError( + "py2js() only accepts non-builtin modules, classes and functions." + ) + # Get hash, in case we ever want to cache JS accross sessions - h = hashlib.sha256('pscript version 1'.encode()) + h = hashlib.sha256("pscript version 1".encode()) h.update(pycode.encode()) hash = h.digest() - + # Get JS code if filename: p = Parser(pycode, (filename, linenr), **parser_options) @@ -112,137 +115,140 @@ def py2js_(ob): p = Parser(pycode, **parser_options) jscode = p.dump() if new_name: - if thetype not in ('class', 'def'): - raise TypeError('py2js() can only rename functions and classes.') + if thetype not in ("class", "def"): + raise TypeError("py2js() can only rename functions and classes.") jscode = js_rename(jscode, ob.__name__, new_name, thetype) - + # Collect undefined variables # vars_unknown = [name for name, s in p.vars.get_undefined()] vars_unknown = set() - for name, usages in p.vars.get_undefined(): + for _name, usages in p.vars.get_undefined(): for usage in usages: vars_unknown.add(usage) - + # todo: now that we have so much info in the meta, maybe we should # use use py2js everywhere where we now use Parser and move its docs here. - + # Wrap in JSString jscode = JSString(jscode) jscode.meta = {} - jscode.meta['filename'] = filename - jscode.meta['linenr'] = linenr - jscode.meta['pycode'] = pycode - jscode.meta['pyhash'] = hash - jscode.meta['std_functions'] = p._std_functions - jscode.meta['std_methods'] = p._std_methods - jscode.meta['vars_defined'] = p.vars.get_defined() - jscode.meta['vars_global'] = p.vars.get_globals() - jscode.meta['vars_unknown'] = vars_unknown + jscode.meta["filename"] = filename + jscode.meta["linenr"] = linenr + jscode.meta["pycode"] = pycode + jscode.meta["pyhash"] = hash + jscode.meta["std_functions"] = p._std_functions + jscode.meta["std_methods"] = p._std_methods + jscode.meta["vars_defined"] = p.vars.get_defined() + jscode.meta["vars_global"] = p.vars.get_globals() + jscode.meta["vars_unknown"] = vars_unknown return jscode - + if ob is None: return py2js_ # uses as a decorator with some options set return py2js_(ob) -re_sub1 = re.compile(r'this\.__(\w*?[a-zA-Z0-9](?!__)\W)', re.UNICODE) +re_sub1 = re.compile(r"this\.__(\w*?[a-zA-Z0-9](?!__)\W)", re.UNICODE) + def js_rename(jscode, cur_name, new_name, type=None): - """ Rename a function or class in a JavaScript code string. - + """Rename a function or class in a JavaScript code string. + The new name can be prefixed (i.e. have dots in it). Functions can be converted to methods by prefixing with a name that starts with a capital letter (and probably ".prototype"). Double-underscore-mangling is taken into account. - + Parameters: jscode (str): the JavaScript source code cur_name (str): the current name (must be an identifier, e.g. no dots). new_name (str): the name to replace the current name with type (str): the Python object type, can be 'class' or 'def'. If None, the type is inferred from the object name based on PEP8 - + Returns: str: the modified JavaScript source code """ - assert cur_name and '.' not in cur_name - + assert cur_name and "." not in cur_name + if type: - isclass = type == 'class' + isclass = type == "class" else: isclass = cur_name[0].lower() != cur_name[0] # For backward compat. if isclass: # cur_cls_name = cur_name - new_cls_name = new_name.split('.')[-1] + new_cls_name = new_name.split(".")[-1] else: - new_cls_name = '' - parts = new_name.split('.') - prefix = '.'.join(parts[:-1]) + new_cls_name = "" + parts = new_name.split(".") + prefix = ".".join(parts[:-1]) if prefix: - maybe_cls = parts[-3] if parts[-2] == 'prototype' else parts[-2] - maybe_cls = maybe_cls.strip('$') # allow special names + maybe_cls = parts[-3] if parts[-2] == "prototype" else parts[-2] + maybe_cls = maybe_cls.strip("$") # allow special names if maybe_cls[0].lower() != maybe_cls[0]: new_cls_name = maybe_cls - - cur_name_short = cur_name.split('.')[-1] - new_name_short = new_name.split('.')[-1] + + cur_name_short = cur_name.split(".")[-1] + new_name_short = new_name.split(".")[-1] if isclass: # If this is about a class ... - jscode = jscode.replace('.__name__ = "%s"' % cur_name_short, - '.__name__ = "%s"' % new_name_short) - jscode = jscode.replace('._%s__' % cur_name_short, - '._%s__' % new_name_short) - jscode = jscode.replace('%s.prototype' % cur_name, - '%s.prototype' % new_name) + jscode = jscode.replace( + '.__name__ = "%s"' % cur_name_short, '.__name__ = "%s"' % new_name_short + ) + jscode = jscode.replace("._%s__" % cur_name_short, "._%s__" % new_name_short) + jscode = jscode.replace("%s.prototype" % cur_name, "%s.prototype" % new_name) else: # If this is about a function / method - jscode = jscode.replace('function flx_%s' % cur_name_short, - 'function flx_%s' % new_name_short, 1) + jscode = jscode.replace( + "function flx_%s" % cur_name_short, "function flx_%s" % new_name_short, 1 + ) if new_cls_name: # use regexp to match double-underscore but no magics! - jscode = re_sub1.sub('this._%s__\\1' % new_cls_name, jscode) - #jscode = jscode.replace('this.__', 'this._%s__' % new_cls_name) - + jscode = re_sub1.sub("this._%s__\\1" % new_cls_name, jscode) + # jscode = jscode.replace('this.__', 'this._%s__' % new_cls_name) + # Always do this - jscode = jscode.replace('%s = function' % cur_name, - '%s = function' % new_name, 1) - jscode = jscode.replace('%s = async function' % cur_name, - '%s = async function' % new_name, 1) - if '.' in new_name: - jscode = jscode.replace('var %s;\n' % cur_name, '', 1) + jscode = jscode.replace("%s = function" % cur_name, "%s = function" % new_name, 1) + jscode = jscode.replace( + "%s = async function" % cur_name, "%s = async function" % new_name, 1 + ) + if "." in new_name: + jscode = jscode.replace("var %s;\n" % cur_name, "", 1) else: - jscode = jscode.replace('var %s;\n' % cur_name, - 'var %s;\n' % new_name, 1) - - if 'this._Component__properties__' in jscode: - 1/0 - + jscode = jscode.replace("var %s;\n" % cur_name, "var %s;\n" % new_name, 1) + + if "this._Component__properties__" in jscode: + 1 / 0 # noqa + return jscode NODE_EXE = None + + def get_node_exe(): - """ Small utility that provides the node exe. The first time this + """Small utility that provides the node exe. The first time this is called both 'nodejs' and 'node' are tried. To override the executable path, set the ``PSCRIPT_NODE_EXE`` environment variable. """ # This makes things work on Ubuntu's nodejs as well as other node # implementations, and allows users to set the node exe if necessary global NODE_EXE - NODE_EXE = os.getenv('PSCRIPT_NODE_EXE', os.getenv('FLEXX_NODE_EXE')) or NODE_EXE + NODE_EXE = os.getenv("PSCRIPT_NODE_EXE", os.getenv("FLEXX_NODE_EXE")) or NODE_EXE if NODE_EXE is None: - NODE_EXE = 'nodejs' + NODE_EXE = "nodejs" try: - subprocess.check_output([NODE_EXE, '-v']) + subprocess.check_output([NODE_EXE, "-v"]) except Exception: # pragma: no cover - NODE_EXE = 'node' + NODE_EXE = "node" return NODE_EXE _eval_count = 0 + def evaljs(jscode, whitespace=True, print_result=True, extra_nodejs_args=None): - """ Evaluate JavaScript code in Node.js. - + """Evaluate JavaScript code in Node.js. + Parameters: jscode (str): the JavaScript code to evaluate. whitespace (bool): if whitespace is False, the whitespace @@ -251,69 +257,71 @@ def evaljs(jscode, whitespace=True, print_result=True, extra_nodejs_args=None): Default True. If False, larger pieces of code can be evaluated because we can use file-mode. extra_nodejs_args (list): Extra command line args to pass to nodejs. - + Returns: str: the last result as a string. """ global _eval_count - + # Init command cmd = [get_node_exe()] if extra_nodejs_args: cmd.extend(extra_nodejs_args) - + # Prepare command if len(jscode) > 2**14: if print_result: # Strictly speaking, this is a limitation of Windows, but come-on! - raise RuntimeError('evaljs() wont send more than 16 kB of code ' - 'over the command line, but cannot use a file ' - 'unless print_result is False.') + raise RuntimeError( + "evaljs() wont send more than 16 kB of code " + "over the command line, but cannot use a file " + "unless print_result is False." + ) _eval_count += 1 - fname = 'pscript_%i_%i.js' % (os.getpid(), _eval_count) + fname = "pscript_%i_%i.js" % (os.getpid(), _eval_count) filename = os.path.join(tempfile.gettempdir(), fname) - with open(filename, 'wb') as f: + with open(filename, "wb") as f: f.write(jscode.encode()) - cmd += ['--use_strict', filename] + cmd += ["--use_strict", filename] else: filename = None - p_or_e = ['-p', '-e'] if print_result else ['-e'] - cmd += ['--use_strict'] + p_or_e + [jscode] - + p_or_e = ["-p", "-e"] if print_result else ["-e"] + cmd += ["--use_strict"] + p_or_e + [jscode] + # Call node try: res = subprocess.check_output(cmd, stderr=subprocess.STDOUT) except Exception as err: - if hasattr(err, 'output'): + if hasattr(err, "output"): err = err.output.decode() else: err = str(err) - err = err[:400] + '...' if len(err) > 400 else err - raise RuntimeError(err) + err = err[:400] + "..." if len(err) > 400 else err + raise RuntimeError(err) from None finally: if filename is not None: try: os.remove(filename) except Exception: pass - + # Process result res = res.decode().rstrip() - if print_result and res.endswith('undefined'): + if print_result and res.endswith("undefined"): res = res[:-9].rstrip() if not whitespace: - res = res.replace('\n', '').replace('\t', '').replace(' ', '') + res = res.replace("\n", "").replace("\t", "").replace(" ", "") return res def evalpy(pycode, whitespace=True): - """ Evaluate PScript code in Node.js (after translating to JS). - + """Evaluate PScript code in Node.js (after translating to JS). + Parameters: pycode (str): the PScript code to evaluate. whitespace (bool): if whitespace is False, the whitespace is removed from the result. Default True. - + Returns: str: the last result as a string. """ @@ -321,10 +329,11 @@ def evalpy(pycode, whitespace=True): return evaljs(py2js(pycode), whitespace) -def script2js(filename, namespace=None, target=None, module_type='umd', - **parser_options): - """ Export a .py file to a .js file. - +def script2js( + filename, namespace=None, target=None, module_type="umd", **parser_options +): + """Export a .py file to a .js file. + Parameters: filename (str): the filename of the .py file to transpile. namespace (str): the namespace for this module. (optional) @@ -336,21 +345,22 @@ def script2js(filename, namespace=None, target=None, module_type='umd', for details. """ # Import - assert filename.endswith('.py') - pycode = open(filename, 'rb').read().decode() + assert filename.endswith(".py") + pycode = open(filename, "rb").read().decode() # Convert parser = Parser(pycode, filename, **parser_options) - jscode = '/* Do not edit, autogenerated by pscript */\n\n' + parser.dump() + jscode = "/* Do not edit, autogenerated by pscript */\n\n" + parser.dump() # Wrap in module if namespace: - exports = [name for name in parser.vars.get_defined() - if not name.startswith('_')] + exports = [ + name for name in parser.vars.get_defined() if not name.startswith("_") + ] jscode = create_js_module(namespace, jscode, [], exports, module_type) # Export if target is None: dirname, fname = os.path.split(filename) - filename2 = os.path.join(dirname, fname[:-3] + '.js') + filename2 = os.path.join(dirname, fname[:-3] + ".js") else: filename2 = target - with open(filename2, 'wb') as f: + with open(filename2, "wb") as f: f.write(jscode.encode()) diff --git a/pscript/modules.py b/pscript/modules.py index 3e9a1b1b..2208dac3 100644 --- a/pscript/modules.py +++ b/pscript/modules.py @@ -76,15 +76,15 @@ def isidentifier(s): # http://stackoverflow.com/questions/2544972/ if not isinstance(s, str): return False - return re.match(r'^\w+$', s, re.UNICODE) and re.match(r'^[0-9]', s) is None + return re.match(r"^\w+$", s, re.UNICODE) and re.match(r"^[0-9]", s) is None -def create_js_module(name, code, imports, exports, type='umd'): - """ Wrap the given code in an AMD module. - +def create_js_module(name, code, imports, exports, type="umd"): + """Wrap the given code in an AMD module. + Note that "use strict" is added to the top of the module body. PScript does not deal with license strings; the caller should do that. - + Parameters: name (str): the name of the module. code (str): the JS code to wrap. @@ -98,66 +98,72 @@ def create_js_module(name, code, imports, exports, type='umd'): 'hidden', 'simple' (save module on root), 'amd' , 'amd-flexx' and 'umd' (case insensitive). Default 'umd'. """ - + # Check input args if not isinstance(name, str) or not name: - raise ValueError('create_module() name arg must be a (nonempty) string.') + raise ValueError("create_module() name arg must be a (nonempty) string.") if not isinstance(code, str): - raise ValueError('create_module() code arg must be a string.') + raise ValueError("create_module() code arg must be a string.") if not isinstance(imports, (tuple, list)): - raise ValueError('create_module() imports arg must be a string.') + raise ValueError("create_module() imports arg must be a string.") if not isinstance(exports, (str, tuple, list)): - raise ValueError('create_module() exports arg must be a string or list.') - + raise ValueError("create_module() exports arg must be a string or list.") + # Process imports deps, dep_names = [], [] for imp in imports: if not isinstance(imp, str): - raise ValueError('Elements in create_module() imports must be str.') - if ' as ' in imp: - dep, dep_name = imp.split(' as ', 1) + raise ValueError("Elements in create_module() imports must be str.") + if " as " in imp: + dep, dep_name = imp.split(" as ", 1) else: dep = dep_name = imp - if '.' in dep_name: + if "." in dep_name: raise ValueError('Import %r has dots, have you used "as"?' % dep_name) deps.append(dep) dep_names.append(dep_name) - + # Process exports if isinstance(exports, str): return_val = exports else: # list for exp in exports: if not isinstance(exp, str): - raise ValueError('Elements in create_module() exports must be str.') - return_val = ', '.join(['%s: %s' % (exp, exp) for exp in exports]) - return_val = '{' + return_val + '}' - + raise ValueError("Elements in create_module() exports must be str.") + return_val = ", ".join(["%s: %s" % (exp, exp) for exp in exports]) + return_val = "{" + return_val + "}" + # Process type -> select template - types = {'hidden': HIDDEN, 'simple': SIMPLE, - 'amd': AMD, 'umd': UMD, 'amd-flexx': AMD_FLEXX} + types = { + "hidden": HIDDEN, + "simple": SIMPLE, + "amd": AMD, + "umd": UMD, + "amd-flexx": AMD_FLEXX, + } if not isinstance(type, str): - raise ValueError('create_js_module() type must be str.') + raise ValueError("create_js_module() type must be str.") if type.lower() not in types: - raise ValueError('create_js_module() got invalid type %r' % type) + raise ValueError("create_js_module() got invalid type %r" % type) template = types[type.lower()] - + # Derived information needed to populate the module templates - save_name = lambda n: n.split('/')[-1].split('.')[0].replace('-', '_') + save_name = lambda n: n.split("/")[-1].split(".")[0].replace("-", "_") dep_strings = ['"%s"' % dep for dep in deps] - dep_fullnames = ['root.' + save_name(dep) for dep in deps] + dep_fullnames = ["root." + save_name(dep) for dep in deps] dep_requires = ['require("%s")' % dep for dep in deps] - + # Fill in the template - for key, val in [('{name}', name), - ('{save_name}', save_name(name)), - ('{exports}', return_val), - ('{dep_names}', ', '.join(dep_names)), - ('{dep_strings}', ', '.join(dep_strings)), - ('{dep_fullnames}', ', '.join(dep_fullnames)), - ('{dep_requires}', ', '.join(dep_requires)), - ('{code}', code), # last! - ]: + for key, val in [ + ("{name}", name), + ("{save_name}", save_name(name)), + ("{exports}", return_val), + ("{dep_names}", ", ".join(dep_names)), + ("{dep_strings}", ", ".join(dep_strings)), + ("{dep_fullnames}", ", ".join(dep_fullnames)), + ("{dep_requires}", ", ".join(dep_requires)), + ("{code}", code), # last! + ]: template = template.replace(key, val) - + return template diff --git a/pscript/parser0.py b/pscript/parser0.py index e2cf138c..35a71e4b 100644 --- a/pscript/parser0.py +++ b/pscript/parser0.py @@ -23,38 +23,38 @@ class JSError(Exception): - """ Exception raised when unable to convert Python to JS. - """ + """Exception raised when unable to convert Python to JS.""" + pass def unify(x): - """ Turn string or list of strings parts into string. Braces are + """Turn string or list of strings parts into string. Braces are placed around it if its not alphanumerical """ # Note that r'[\.\w]' matches anyting in 'ab_01.äé' - + if isinstance(x, (tuple, list)): - x = ''.join(x) - - if x[0] in '\'"' and x[0] == x[-1] and x.count(x[0]) == 2: + x = "".join(x) + + if x[0] in "'\"" and x[0] == x[-1] and x.count(x[0]) == 2: return x # string - elif re.match(r'^[\.\w]*$', x, re.UNICODE): + elif re.match(r"^[\.\w]*$", x, re.UNICODE): return x # words consisting of normal chars, numbers and dots - elif re.match(r'^[\.\w]*\(.*\)$', x, re.UNICODE) and x.count(')') == 1: + elif re.match(r"^[\.\w]*\(.*\)$", x, re.UNICODE) and x.count(")") == 1: return x # function calls (e.g. 'super()' or 'foo.bar(...)') - elif re.match(r'^[\.\w]*\[.*\]$', x, re.UNICODE) and x.count(']') == 1: + elif re.match(r"^[\.\w]*\[.*\]$", x, re.UNICODE) and x.count("]") == 1: return x # indexing - elif re.match(r'^\{.*\}$', x, re.UNICODE) and x.count('}') == 1: + elif re.match(r"^\{.*\}$", x, re.UNICODE) and x.count("}") == 1: return x # dicts else: - return '(%s)' % x + return "(%s)" % x class NameSpace(dict): - """ Representation of the namespace in a certain scope. It looks a bit like + """Representation of the namespace in a certain scope. It looks a bit like a set, but makes a distinction between used/defined and local/nonlocal. - + The value of an item in this dict can be: * 1: variable defined in this scope. * 2: nonlocal variable (set nonlocal in this scope). @@ -62,28 +62,28 @@ class NameSpace(dict): * 4: global variable (set in a subscope). * set: variable used here (or in a subscope) but not defined here. """ - + _pscript_overload = True - + def set_nonlocal(self, key): - """ Explicitly declare a name as nonlocal """ + """Explicitly declare a name as nonlocal""" self[key] = 2 # also if already exists - + def set_global(self, key): - """ Explicitly declare a name as global """ + """Explicitly declare a name as global""" self[key] = 3 # also if already exists # becomes 4 in parent scope - + def use(self, key, how): - """ Declare a name as used and how (the full name.foo.bar). The name + """Declare a name as used and how (the full name.foo.bar). The name may be defined in higher level, or it will end up in vars_unknown. """ hows = self.setdefault(key, set()) if isinstance(hows, set): hows.add(how) - + def add(self, key): - """ Declare a name as defined in this namespace """ + """Declare a name as defined in this namespace""" # If value is 4, the name is used as a global in a subscope. At this # point, we do not know whether this is the toplevel scope (also # because py2js() is often used to transpile snippets which are later @@ -91,13 +91,13 @@ def add(self, key): curval = self.get(key, 0) if curval not in (2, 3): # dont overwrite nonlocal or global self[key] = 1 - + def discard(self, key): - """ Discard name from this namespace """ + """Discard name from this namespace""" self.pop(key, None) - + def leak_stack(self, sub): - """ Leak a child namespace into the current one. Undefined variables + """Leak a child namespace into the current one. Undefined variables and nonlocals are moved upwards. """ for name in sub.get_globals(): @@ -110,26 +110,25 @@ def leak_stack(self, sub): sub.discard(name) for how in hows: self.use(name, how) - + def is_known(self, name): - """ Get whether the given name is defined or declared global/nonlocal + """Get whether the given name is defined or declared global/nonlocal in this scope. """ return self.get(name, 0) in (1, 2, 3) - + def get_defined(self): - """ Get list of variable names that the current scope defines. - """ + """Get list of variable names that the current scope defines.""" return set([name for name, val in self.items() if val == 1]) - + def get_globals(self): - """ Get list of variable names that are declared global in the + """Get list of variable names that are declared global in the current scope or its subscopes. """ return set([name for name, val in self.items() if val in (3, 4)]) - + def get_undefined(self): - """ Get (name, set) tuples for variables that are used, but not + """Get (name, set) tuples for variables that are used, but not defined. The set contains the ways in which the variable is used (e.g. name.foo.bar). """ @@ -137,68 +136,69 @@ def get_undefined(self): class Parser0: - """ The Base parser class. Implements the basic mechanism to allow + """The Base parser class. Implements the basic mechanism to allow parsing to work, but does not implement any parsing on its own. - + For details see the Parser class. """ - + # Developer notes: # The parse_x() functions are called by parse() with the node of # type x. They should return a string or a list of strings. parse() # always returns a list of strings. - + NAME_MAP = { - 'True' : 'true', - 'False' : 'false', - 'None' : 'null', - 'unichr': 'chr', - 'xrange': 'range', - 'self': 'this', + "True": "true", + "False": "false", + "None": "null", + "unichr": "chr", + "xrange": "range", + "self": "this", } - + ATTRIBUTE_MAP = { - '__class__': 'Object.getPrototypeOf({})', + "__class__": "Object.getPrototypeOf({})", } - + BINARY_OP = { - 'Add' : '+', - 'Sub' : '-', - 'Mult' : '*', - 'Div' : '/', - 'Mod' : '%', - 'LShift' : '<<', - 'RShift' : '>>', - 'BitOr' : '|', - 'BitXor' : '^', - 'BitAnd' : '&', + "Add": "+", + "Sub": "-", + "Mult": "*", + "Div": "/", + "Mod": "%", + "LShift": "<<", + "RShift": ">>", + "BitOr": "|", + "BitXor": "^", + "BitAnd": "&", } - + UNARY_OP = { - 'Invert' : '~', - 'Not' : '!', - 'UAdd' : '+', - 'USub' : '-', + "Invert": "~", + "Not": "!", + "UAdd": "+", + "USub": "-", } - + BOOL_OP = { - 'And' : '&&', - 'Or' : '||', + "And": "&&", + "Or": "||", } - + COMP_OP = { - 'Eq' : "==", - 'NotEq' : "!=", - 'Lt' : "<", - 'LtE' : "<=", - 'Gt' : ">", - 'GtE' : ">=", - 'Is' : "===", - 'IsNot' : "!==", + "Eq": "==", + "NotEq": "!=", + "Lt": "<", + "LtE": "<=", + "Gt": ">", + "GtE": ">=", + "Is": "===", + "IsNot": "!==", } - - def __init__(self, code, pysource=None, indent=0, docstrings=True, - inline_stdlib=True): + + def __init__( + self, code, pysource=None, indent=0, docstrings=True, inline_stdlib=True + ): self._pycode = code # helpfull during debugging self._pysource = None if isinstance(pysource, str): @@ -206,37 +206,37 @@ def __init__(self, code, pysource=None, indent=0, docstrings=True, elif isinstance(pysource, tuple): self._pysource = str(pysource[0]), int(pysource[1]) elif pysource is not None: - logger.warning('Parser ignores pysource; it must be str or (str, int).') + logger.warning("Parser ignores pysource; it must be str or (str, int).") self._root = ast.parse(code) self._stack = [] self._indent = indent self._dummy_counter = 0 self._scope_prefix = [] # stack of name prefixes to simulate local scope - + # To keep track of std lib usage self._std_functions = set() self._std_methods = set() - + # To help distinguish classes from functions self._seen_func_names = set() self._seen_class_names = set() - + # Options self._docstrings = bool(docstrings) # whether to inclue docstrings - + # Collect function and method handlers self._functions, self._methods = {}, {} for name in dir(self.__class__): - if name.startswith('function_op_'): + if name.startswith("function_op_"): pass # special operator function that we use explicitly - elif name.startswith('function_'): + elif name.startswith("function_"): self._functions[name[9:]] = getattr(self, name) - elif name.startswith('method_'): + elif name.startswith("method_"): self._methods[name[7:]] = getattr(self, name) - + # Prepare - self.push_stack('module', '') - + self.push_stack("module", "") + # Parse try: self._parts = self.parse(self._root) @@ -246,36 +246,35 @@ def __init__(self, code, pysource=None, indent=0, docstrings=True, try: msg = self._better_js_error(tb) except Exception: # pragma: no cover - raise(err) + raise (err) from None else: - err.args = (msg + ':\n' + str(err), ) - raise(err) - + err.args = (msg + ":\n" + str(err),) + raise (err) + # Finish ns = self.vars # do not self.pop_stack() so caller can inspect module vars defined_names = ns.get_defined() if defined_names: self._parts.insert(0, self.get_declarations(ns)) - + # Add part of the stdlib that was actually used if inline_stdlib: - libcode = stdlib.get_partial_std_lib(self._std_functions, - self._std_methods, - self._indent) + libcode = stdlib.get_partial_std_lib( + self._std_functions, self._std_methods, self._indent + ) if libcode: self._parts.insert(0, libcode) - + # Post-process if self._parts: - self._parts[0] = ' ' * indent + self._parts[0].lstrip() - + self._parts[0] = " " * indent + self._parts[0].lstrip() + def dump(self): - """ Get the JS code as a string. - """ - return ''.join(self._parts) - + """Get the JS code as a string.""" + return "".join(self._parts) + def _better_js_error(self, tb): # pragma: no cover - """ If we get a JSError, we try to get the corresponding node + """If we get a JSError, we try to get the corresponding node and print the lineno as well as the function etc. """ node = None @@ -283,51 +282,49 @@ def _better_js_error(self, tb): # pragma: no cover funcNode = None while tb.tb_next: tb = tb.tb_next - node = tb.tb_frame.f_locals.get('node', node) + node = tb.tb_frame.f_locals.get("node", node) classNode = node if isinstance(node, ast.ClassDef) else classNode funcNode = node if isinstance(node, ast.FunctionDef) else funcNode - + # Get location as accurately as we can filename = None - lineno = getattr(node, 'lineno', -1) + lineno = getattr(node, "lineno", -1) if self._pysource: filename, lineno = self._pysource lineno += node.lineno - - msg = 'Error processing %s-node' % (node.__class__.__name__) + + msg = "Error processing %s-node" % (node.__class__.__name__) if classNode: msg += ' in class "%s"' % classNode.name if funcNode: msg += ' in function "%s"' % funcNode.name if filename: msg += ' in "%s"' % filename - if hasattr(node, 'lineno'): - msg += ', line %i, ' % lineno - if hasattr(node, 'col_offset'): - msg += 'col %i' % node.col_offset + if hasattr(node, "lineno"): + msg += ", line %i, " % lineno + if hasattr(node, "col_offset"): + msg += "col %i" % node.col_offset return msg - + def push_stack(self, type, name): - """ New namespace stack. Match a call to this with a call to + """New namespace stack. Match a call to this with a call to pop_stack() and process the resulting line to declare the used variables. type must be 'module', 'class' or 'function'. """ - assert type in ('module', 'class', 'function') + assert type in ("module", "class", "function") self._stack.append((type, name, NameSpace())) - + def pop_stack(self): - """ Pop the current stack and return the namespace. - """ + """Pop the current stack and return the namespace.""" # Pop nstype, nsname, ns = self._stack.pop(-1) self.vars.leak_stack(ns) return ns - + def get_declarations(self, ns): - """ Get string with variable (and builtin-function) declarations. - """ + """Get string with variable (and builtin-function) declarations.""" if not ns: - return '' + return "" code = [] loose_vars = [] for name, value in sorted(ns.items()): @@ -335,95 +332,91 @@ def get_declarations(self, ns): loose_vars.append(name) # else: pass global/nonlocal or expected to be defined in outer scope if loose_vars: - code.insert(0, self.lf('var %s;' % ', '.join(loose_vars))) - return ''.join(code) - + code.insert(0, self.lf("var %s;" % ", ".join(loose_vars))) + return "".join(code) + def with_prefix(self, name, new=False): - """ Add class prefix to a variable name if necessary. - """ + """Add class prefix to a variable name if necessary.""" nstype, nsname, ns = self._stack[-1] - if nstype == 'class': - if name.startswith('__') and not name.endswith('__'): - name = '_' + nsname + name # Double underscore name mangling - return nsname + '.prototype.' + name + if nstype == "class": + if name.startswith("__") and not name.endswith("__"): + name = "_" + nsname + name # Double underscore name mangling + return nsname + ".prototype." + name else: return name - + @property def vars(self): - """ NameSpace instance for the current stack. """ + """NameSpace instance for the current stack.""" return self._stack[-1][2] - - def lf(self, code=''): - """ Line feed - create a new line with the correct indentation. - """ - return '\n' + self._indent * ' ' + code - - def dummy(self, name=''): - """ Get a unique name. The name is added to vars. - """ + + def lf(self, code=""): + """Line feed - create a new line with the correct indentation.""" + return "\n" + self._indent * " " + code + + def dummy(self, name=""): + """Get a unique name. The name is added to vars.""" self._dummy_counter += 1 - name = 'stub%i_%s' % (self._dummy_counter, name) + name = "stub%i_%s" % (self._dummy_counter, name) self.vars.add(name) return name - + def _handle_std_deps(self, code): nargs, function_deps, method_deps = stdlib.get_std_info(code) for dep in function_deps: self.use_std_function(dep, []) for dep in method_deps: - self.use_std_method('x', dep, []) - + self.use_std_method("x", dep, []) + def use_std_function(self, name, arg_nodes): - """ Use a function from the PScript standard library. - """ + """Use a function from the PScript standard library.""" self._handle_std_deps(stdlib.FUNCTIONS[name]) self._std_functions.add(name) mangled_name = stdlib.FUNCTION_PREFIX + name - args = [(a if isinstance(a, str) else unify(self.parse(a))) - for a in arg_nodes] - return '%s(%s)' % (mangled_name, ', '.join(args)) - + args = [(a if isinstance(a, str) else unify(self.parse(a))) for a in arg_nodes] + return "%s(%s)" % (mangled_name, ", ".join(args)) + def use_std_method(self, base, name, arg_nodes): - """ Use a method from the PScript standard library. - """ + """Use a method from the PScript standard library.""" self._handle_std_deps(stdlib.METHODS[name]) self._std_methods.add(name) mangled_name = stdlib.METHOD_PREFIX + name - args = [(a if isinstance(a, str) else unify(self.parse(a))) - for a in arg_nodes] - #return '%s.%s(%s)' % (base, mangled_name, ', '.join(args)) + args = [(a if isinstance(a, str) else unify(self.parse(a))) for a in arg_nodes] + # return '%s.%s(%s)' % (base, mangled_name, ', '.join(args)) args.insert(0, base) - return '%s.call(%s)' % (mangled_name, ', '.join(args)) - + return "%s.call(%s)" % (mangled_name, ", ".join(args)) + def pop_docstring(self, node): - """ If a docstring is present, in the body of the given node, + """If a docstring is present, in the body of the given node, remove that string node and return it as a string, corrected for indentation and stripped. If no docstring is present return empty string. """ - docstring = '' - if (node.body_nodes and isinstance(node.body_nodes[0], ast.Expr) and - isinstance(node.body_nodes[0].value_node, ast.Str)): + docstring = "" + if ( + node.body_nodes + and isinstance(node.body_nodes[0], ast.Expr) + and isinstance(node.body_nodes[0].value_node, ast.Str) + ): docstring = node.body_nodes.pop(0).value_node.value.strip() lines = docstring.splitlines() getindent = lambda x: len(x) - len(x.strip()) indent = min([getindent(x) for x in lines[1:]]) if (len(lines) > 1) else 0 if lines: - lines[0] = ' ' * indent + lines[0] + lines[0] = " " * indent + lines[0] lines = [line[indent:] for line in lines] - docstring = '\n'.join(lines) + docstring = "\n".join(lines) return docstring - + def parse(self, node): - """ Parse a node. Check node type and dispatch to one of the + """Parse a node. Check node type and dispatch to one of the specific parse functions. Raises error if we cannot parse this type of node. - + Returns a list of strings. """ nodeType = node.__class__.__name__ - parse_func = getattr(self, 'parse_' + nodeType, None) + parse_func = getattr(self, "parse_" + nodeType, None) if parse_func: res = parse_func(node) # Return as list also if a tuple or string was returned @@ -434,4 +427,4 @@ def parse(self, node): res = [res] return res else: - raise JSError('Cannot parse %s-nodes yet' % nodeType) + raise JSError("Cannot parse %s-nodes yet" % nodeType) diff --git a/pscript/parser1.py b/pscript/parser1.py index 74e63426..193dff84 100644 --- a/pscript/parser1.py +++ b/pscript/parser1.py @@ -7,17 +7,17 @@ equivalents. .. pscript_example:: - + # Simple operations 3 + 4 -1 3 * 7 / 9 5**2 pow(5, 2) 7 // 2 - + # Basic types [True, False, None] - + # Lists and dicts foo = [1, 2, 3] bar = {'a': 1, 'b': 2} @@ -32,12 +32,12 @@ foo = [1, 2, 3, 4, 5] foo[2:] foo[2:-2] - + # Slicing strings bar = 'abcdefghij' bar[2:] bar[2:-2] - + # Subscripting foo = {'bar': 3} foo['bar'] @@ -50,23 +50,23 @@ String formatting is supported in various forms. .. pscript_example:: - + # Old school "value: %g" % val "%s: %0.2f" % (name, val) - + # Modern "value: {:g}".format(val) "{}: {:3.2f}".format(name, val) - + # F-strings (python 3.6+) #f"value: {val:g}" #f"{name}: {val:3.2f}" - + # This also works t = "value: {:g}" t.format(val) - + # But this does not (because PScript cannot know whether t is str or float) t = "value: %g" t % val @@ -89,22 +89,22 @@ tuple packing and unpacking (a.k.a. destructuring assignment). .. pscript_example:: - + # Declare foo foo = 3 - + # But not here bar.foo = 3 - + # Pack items in an array a = 1, 2, 3 - + # And unpack them a1, a2, a3 = a - + # Deleting variables del bar.foo - + # Functions starting with a capital letter # are assumed constructors foo = Foo() @@ -114,23 +114,23 @@ ----------- .. pscript_example:: - + # Identity foo is bar - - # Equality + + # Equality foo == bar - + # But comparisons are deep (unlike JS) (2, 3, 4) == (2, 3, 4) (2, 3) in [(1,2), (2,3), (3,4)] # Test for null foo is None - + # Test for JS undefined foo is undefined - + # Testing for containment "foo" in "this has foo in it" 3 in [0, 1, 2, 3, 4] @@ -153,7 +153,7 @@ undefined [] {} - + # This still works a = [] a = a or [1] # a is now [1] @@ -166,10 +166,10 @@ ``null`` in JS). .. pscript_example:: - + # Business as usual foo(a, b) - + # Support for star args (but not **kwargs) foo(*a) @@ -189,57 +189,99 @@ from . import commonast as ast from . import stdlib -from .parser0 import Parser0, JSError, unify, reprs # noqa +from .parser0 import Parser0, JSError, unify, reprs # Define builtin stuff for which we know that it returns a bool or int -_bool_funcs = 'hasattr', 'all', 'any', 'op_contains', 'op_equals', 'truthy' -_bool_meths = ('count', 'isalnum', 'isalpha', 'isidentifier', 'islower', - 'isnumeric', 'isdigit', 'isdecimal', 'isspace', 'istitle', - 'isupper', 'startswith') -returning_bool = tuple([stdlib.FUNCTION_PREFIX + x + '(' for x in _bool_funcs] + - [stdlib.METHOD_PREFIX + x + '.' for x in _bool_meths]) +_bool_funcs = "hasattr", "all", "any", "op_contains", "op_equals", "truthy" +_bool_meths = ( + "count", + "isalnum", + "isalpha", + "isidentifier", + "islower", + "isnumeric", + "isdigit", + "isdecimal", + "isspace", + "istitle", + "isupper", + "startswith", +) +returning_bool = tuple( + [stdlib.FUNCTION_PREFIX + x + "(" for x in _bool_funcs] + + [stdlib.METHOD_PREFIX + x + "." for x in _bool_meths] +) # precompile regexp to help determine whether a string is an identifier -isidentifier1 = re.compile(r'^\w+$', re.UNICODE) +isidentifier1 = re.compile(r"^\w+$", re.UNICODE) reserved_names = ( - 'abstract', 'instanceof', 'boolean', 'enum', 'switch', 'export', - 'interface', 'synchronized', 'extends', 'let', 'case', 'throw', - 'catch', 'final', 'native', 'throws', 'new', 'transient', - 'const', 'package', 'function', 'private', 'typeof', 'debugger', 'goto', - 'protected', 'var', 'default', 'public', 'void', 'delete', 'implements', - 'volatile', 'do', 'static', + "abstract", + "instanceof", + "boolean", + "enum", + "switch", + "export", + "interface", + "synchronized", + "extends", + "let", + "case", + "throw", + "catch", + "final", + "native", + "throws", + "new", + "transient", + "const", + "package", + "function", + "private", + "typeof", + "debugger", + "goto", + "protected", + "var", + "default", + "public", + "void", + "delete", + "implements", + "volatile", + "do", + "static", # Commented, because are disallowed in Python too. # 'else', 'break', 'finally', 'class', 'for', 'try', 'continue', 'if', # 'return', 'import', 'while', 'in', 'with', # Commented for pragmatic reasons # 'super', 'float', 'this', 'int', 'byte', 'long', 'char', 'short', # 'double', 'null', 'true', 'false', - ) +) class Parser1(Parser0): - """ Parser that add basic functionality like assignments, + """Parser that add basic functionality like assignments, operations, function calls, and indexing. """ - + @property def _pscript_overload(self): - """ Whether pscript overloads add, mul, equals, and truthy. + """Whether pscript overloads add, mul, equals, and truthy. This setting applies per scope. """ return self._stack[-1][2]._pscript_overload - + ## Literals - + def parse_Num(self, node): return repr(node.value) - + def parse_Str(self, node): return reprs(node.value) - + def parse_JoinedStr(self, node): parts, value_nodes = [], [] for n in node.value_nodes: @@ -247,92 +289,94 @@ def parse_JoinedStr(self, node): parts.append(n.value) else: assert isinstance(n, ast.FormattedValue) - parts.append('{' + self._parse_FormattedValue_fmt(n) + '}') + parts.append("{" + self._parse_FormattedValue_fmt(n) + "}") value_nodes.append(n.value_node) - thestring = reprs(''.join(parts)) - return self.use_std_method(thestring, 'format', value_nodes) - + thestring = reprs("".join(parts)) + return self.use_std_method(thestring, "format", value_nodes) + def parse_FormattedValue(self, node): # can als be present standalone thestring = "{" + self._parse_FormattedValue_fmt(node) + "}" - return self.use_std_method(thestring, 'format', [node.value_node]) - + return self.use_std_method(thestring, "format", [node.value_node]) + def _parse_FormattedValue_fmt(self, node): - """ Return fmt for a FormattedValue node. - """ - fmt = '' + """Return fmt for a FormattedValue node.""" + fmt = "" if node.conversion: - fmt += '!' + node.conversion + fmt += "!" + node.conversion if node.format_node and len(node.format_node.value_nodes) > 0: if len(node.format_node.value_nodes) > 1: - raise JSError('String formatting only supports singleton format spec.') + raise JSError("String formatting only supports singleton format spec.") spec_node = node.format_node.value_nodes[0] if not isinstance(spec_node, ast.Str): - raise JSError('String formatting only supports string format spec.') - fmt += ':' + spec_node.value + raise JSError("String formatting only supports string format spec.") + fmt += ":" + spec_node.value return fmt - + def parse_Bytes(self, node): - raise JSError('No Bytes in JS') - + raise JSError("No Bytes in JS") + def parse_NameConstant(self, node): - M = {True: 'true', False: 'false', None: 'null'} + M = {True: "true", False: "false", None: "null"} return M[node.value] - + def parse_List(self, node): - code = ['['] + code = ["["] for child in node.element_nodes: code += self.parse(child) - code.append(', ') + code.append(", ") if node.element_nodes: code.pop(-1) # skip last comma - code.append(']') + code.append("]") return code - + def parse_Tuple(self, node): return self.parse_List(node) # tuple = ~ list in JS - + def parse_Dict(self, node): # Oh JS; without the outer braces, it would only be an Object if used # in an assignment ... use_make_dict_func = False - code = ['({'] + code = ["({"] for key, val in zip(node.key_nodes, node.value_nodes): if isinstance(key, (ast.Num, ast.NameConstant)): code += self.parse(key) - elif (isinstance(key, ast.Str) and isidentifier1.match(key.value) and - key.value[0] not in '0123456789'): + elif ( + isinstance(key, ast.Str) + and isidentifier1.match(key.value) + and key.value[0] not in "0123456789" + ): code += key.value else: use_make_dict_func = True break - code.append(': ') + code.append(": ") code += self.parse(val) - code.append(', ') + code.append(", ") if node.key_nodes: code.pop(-1) # skip last comma - code.append('})') - + code.append("})") + # Do we need to use the fallback? if use_make_dict_func: func_args = [] for key, val in zip(node.key_nodes, node.value_nodes): func_args += [unify(self.parse(key)), unify(self.parse(val))] - self.use_std_function('create_dict', []) - return stdlib.FUNCTION_PREFIX + 'create_dict(' + ', '.join(func_args) + ')' + self.use_std_function("create_dict", []) + return stdlib.FUNCTION_PREFIX + "create_dict(" + ", ".join(func_args) + ")" return code - + def parse_Set(self, node): - raise JSError('No Set in JS') - + raise JSError("No Set in JS") + ## Variables - + def push_scope_prefix(self, prefix): # To avoid name clashes e.g. in comprehensions, which have their own # scope in Python, but we want to apply these as a for loop in JS # where possible. assert prefix self._scope_prefix.append(prefix) - + def pop_scope_prefix(self): self._scope_prefix.pop(-1) @@ -340,7 +384,7 @@ def parse_Name(self, node, fullname=None): # node.ctx can be Load, Store, Del -> can be of use somewhere? name = node.name if name in reserved_names: - raise JSError('Cannot use reserved name %s as a variable name!' % name) + raise JSError("Cannot use reserved name %s as a variable name!" % name) if self.vars.is_known(name): return self.with_prefix(name) if self._scope_prefix: @@ -353,139 +397,153 @@ def parse_Name(self, node, fullname=None): if name in self.NAME_MAP: return self.NAME_MAP[name] # Else ... - if not (name in self._functions or name in ('undefined', 'window')): + if not (name in self._functions or name in ("undefined", "window")): # mark as used (not defined) - used_name = (name + '.' + fullname) if fullname else name + used_name = (name + "." + fullname) if fullname else name self.vars.use(name, used_name) return name - + def parse_Starred(self, node): # they're present in Call arguments, but we parse them there. - raise JSError('Starred args are not supported.') - + raise JSError("Starred args are not supported.") + ## Expressions - + def parse_Expr(self, node): # Expression (not stored in a variable) code = [self.lf()] code += self.parse(node.value_node) - code.append(';') + code.append(";") return code - + def parse_UnaryOp(self, node): if node.op == node.OPS.Not: - return '!', self._wrap_truthy(node.right_node) + return "!", self._wrap_truthy(node.right_node) else: op = self.UNARY_OP[node.op] right = unify(self.parse(node.right_node)) return op, right - + def parse_BinOp(self, node): if node.op == node.OPS.Mod and isinstance(node.left_node, ast.Str): # Modulo on a string is string formatting in Python return self._format_string(node) - + left = unify(self.parse(node.left_node)) right = unify(self.parse(node.right_node)) - + if node.op == node.OPS.Add: C = ast.Num, ast.Str if self._pscript_overload and not ( - isinstance(node.left_node, C) or - isinstance(node.right_node, C) or - (isinstance(node.left_node, ast.BinOp) and - node.left_node.op == node.OPS.Add and "op_add" not in left) or - (isinstance(node.right_node, ast.BinOp) and - node.right_node.op == node.OPS.Add and "op_add" not in right)): - return self.use_std_function('op_add', [left, right]) + isinstance(node.left_node, C) + or isinstance(node.right_node, C) + or ( + isinstance(node.left_node, ast.BinOp) + and node.left_node.op == node.OPS.Add + and "op_add" not in left + ) + or ( + isinstance(node.right_node, ast.BinOp) + and node.right_node.op == node.OPS.Add + and "op_add" not in right + ) + ): + return self.use_std_function("op_add", [left, right]) elif node.op == node.OPS.Mult: C = ast.Num if self._pscript_overload and not ( - isinstance(node.left_node, C) and - isinstance(node.right_node, C)): - return self.use_std_function('op_mult', [left, right]) + isinstance(node.left_node, C) and isinstance(node.right_node, C) + ): + return self.use_std_function("op_mult", [left, right]) elif node.op == node.OPS.Pow: return ["Math.pow(", left, ", ", right, ")"] elif node.op == node.OPS.FloorDiv: return ["Math.floor(", left, "/", right, ")"] - - op = ' %s ' % self.BINARY_OP[node.op] + + op = " %s " % self.BINARY_OP[node.op] return [left, op, right] - + def _format_string(self, node): # Get value_nodes if isinstance(node.right_node, (ast.Tuple, ast.List)): value_nodes = node.right_node.element_nodes else: value_nodes = [node.right_node] - + # Is the left side a string? If not, exit early # This works, but we cannot know whether the left was a string or number :P # if not isinstance(node.left_node, ast.Str): # thestring = unify(self.parse(node.left_node)) # thestring += ".replace(/%([0-9\.\+\-\#]*[srdeEfgGioxXc])/g, '{:$1}')" - # return self.use_std_method(thestring, 'format', value_nodes) - + # return self.use_std_method(thestring, 'format', value_nodes) + assert isinstance(node.left_node, ast.Str) - left = ''.join(self.parse(node.left_node)) + left = "".join(self.parse(node.left_node)) sep, left = left[0], left[1:-1] - + # Get matches - matches = list(re.finditer(r'%[0-9\.\+\-\#]*[srdeEfgGioxXc]', left)) + matches = list(re.finditer(r"%[0-9\.\+\-\#]*[srdeEfgGioxXc]", left)) if len(matches) != len(value_nodes): - raise JSError('In string formatting, number of placeholders ' - 'does not match number of replacements') + raise JSError( + "In string formatting, number of placeholders " + "does not match number of replacements" + ) # Format parts = [] start = 0 for m in matches: fmt = m.group(0) - fmt = {'%r': '!r', '%s': ''}.get(fmt, ':' + fmt[1:]) + fmt = {"%r": "!r", "%s": ""}.get(fmt, ":" + fmt[1:]) # Add the part in front of the match (and after prev match) - parts.append(left[start:m.start()]) + parts.append(left[start : m.start()]) parts.append("{%s}" % fmt) start = m.end() parts.append(left[start:]) - thestring = sep + ''.join(parts) + sep - return self.use_std_method(thestring, 'format', value_nodes) - + thestring = sep + "".join(parts) + sep + return self.use_std_method(thestring, "format", value_nodes) + def _wrap_truthy(self, node): - """ Wraps an operation in a truthy call, unless its not necessary. """ - eq_name = stdlib.FUNCTION_PREFIX + 'op_equals' - test = ''.join(self.parse(node)) + """Wraps an operation in a truthy call, unless its not necessary.""" + eq_name = stdlib.FUNCTION_PREFIX + "op_equals" + test = "".join(self.parse(node)) if not self._pscript_overload: return unify(test) elif ( - test.endswith('.length') or test.startswith('!') or - test.isnumeric() or test == 'true' or test == 'false' or - test.count('==') or test.count('>') or test.count('<') or - test.count(eq_name) or - test == '"this_is_js()"' or test.startswith('Array.isArray(') or - (test.startswith(returning_bool) and '||' not in test) + test.endswith(".length") + or test.startswith("!") + or test.isnumeric() + or test == "true" + or test == "false" + or test.count("==") + or test.count(">") + or test.count("<") + or test.count(eq_name) + or test == '"this_is_js()"' + or test.startswith("Array.isArray(") + or (test.startswith(returning_bool) and "||" not in test) ): return unify(test) else: - return self.use_std_function('truthy', [test]) - + return self.use_std_function("truthy", [test]) + def parse_BoolOp(self, node): - op = ' %s ' % self.BOOL_OP[node.op] - if node.op.lower() == 'or': # allow foo = bar or [] + op = " %s " % self.BOOL_OP[node.op] + if node.op.lower() == "or": # allow foo = bar or [] values = [unify(self._wrap_truthy(val)) for val in node.value_nodes[:-1]] values += [unify(self.parse(node.value_nodes[-1]))] else: values = [unify(self._wrap_truthy(val)) for val in node.value_nodes] return op.join(values) - + def parse_Compare(self, node): - left = unify(self.parse(node.left_node)) right = unify(self.parse(node.right_node)) - - if node.op in (node.COMP.Eq, node.COMP.NotEq) and not left.endswith('.length'): + + if node.op in (node.COMP.Eq, node.COMP.NotEq) and not left.endswith(".length"): if self._pscript_overload: - code = self.use_std_function('op_equals', [left, right]) + code = self.use_std_function("op_equals", [left, right]) if node.op == node.COMP.NotEq: - code = '!' + code + code = "!" + code else: if node.op == node.COMP.NotEq: code = [left, "!=", right] @@ -493,35 +551,34 @@ def parse_Compare(self, node): code = [left, "==", right] return code elif node.op in (node.COMP.In, node.COMP.NotIn): - self.use_std_function('op_equals', []) # trigger use of equals - code = self.use_std_function('op_contains', [left, right]) + self.use_std_function("op_equals", []) # trigger use of equals + code = self.use_std_function("op_contains", [left, right]) if node.op == node.COMP.NotIn: - code = '!' + code + code = "!" + code return code else: op = self.COMP_OP[node.op] return "%s %s %s" % (left, op, right) - + def parse_Call(self, node): - # Get full function name and method name if it exists - + if isinstance(node.func_node, ast.Attribute): # We dont want to parse twice, because it may add to the vars_unknown method_name = node.func_node.attr nameparts = self.parse(node.func_node) full_name = unify(nameparts) - nameparts[-1] = nameparts[-1].rsplit('.', 1)[0] + nameparts[-1] = nameparts[-1].rsplit(".", 1)[0] base_name = unify(nameparts) elif isinstance(node.func_node, ast.Subscript): base_name = unify(self.parse(node.func_node.value_node)) full_name = unify(self.parse(node.func_node)) - method_name = '' + method_name = "" else: # ast.Name - method_name = '' - base_name = '' + method_name = "" + base_name = "" full_name = unify(self.parse(node.func_node)) - + # Handle special functions and methods res = None if method_name in self._methods: @@ -530,35 +587,35 @@ def parse_Call(self, node): res = self._functions[full_name](node) if res is not None: return res - + # Handle normally - if base_name.endswith('._base_class') or base_name == 'super()': + if base_name.endswith("._base_class") or base_name == "super()": # super() was used, use "call" to pass "this" - return [full_name] + self._get_args(node, 'this', True) + return [full_name] + self._get_args(node, "this", True) else: code = [full_name] + self._get_args(node, base_name) # Insert "new" if this looks like a class - if base_name == 'this': + if base_name == "this": pass elif method_name: if method_name[0].lower() != method_name[0]: - code.insert(0, 'new ') + code.insert(0, "new ") else: fn = full_name if fn in self._seen_func_names and fn not in self._seen_class_names: pass elif fn not in self._seen_func_names and fn in self._seen_class_names: - code.insert(0, 'new ') + code.insert(0, "new ") elif full_name[0].lower() != full_name[0]: - code.insert(0, 'new ') + code.insert(0, "new ") return code - + def _get_args(self, node, base_name, use_call_or_apply=False): - """ Get arguments for function call. Does checking for keywords and + """Get arguments for function call. Does checking for keywords and handles starargs. The first element in the returned list is either "(" or ".apply(". """ - + # Can produce: # normal: foo(.., ..) # use_call_or_apply: foo.call(base_name, .., ..) @@ -566,92 +623,98 @@ def _get_args(self, node, base_name, use_call_or_apply=False): # or: foo.apply(base_name, [].concat([.., ..], vararg_name) # has_kwargs: foo({__args: [], __kwargs: {} }) # or: foo.apply(base_name, ({__args: [], __kwargs: {} }) - - base_name = base_name or 'null' - + + base_name = base_name or "null" + # Get arguments args_simple, args_array = self._get_positional_args(node) kwargs = self._get_keyword_args(node) - + if kwargs is not None: # Keyword arguments need a whole special treatment if use_call_or_apply: - start = ['.call(', base_name, ', '] + start = [".call(", base_name, ", "] else: - start = ['('] - return start + ['{', 'flx_args: ', args_array, - ', flx_kwargs: ', kwargs, '})'] + start = ["("] + return start + [ + "{", + "flx_args: ", + args_array, + ", flx_kwargs: ", + kwargs, + "})", + ] elif args_simple is None: # Need to use apply - return [".apply(", base_name, ', ', args_array, ")"] + return [".apply(", base_name, ", ", args_array, ")"] elif use_call_or_apply: # Need to use call (arg_simple can be empty string) if args_simple: - return [".call(", base_name, ', ', args_simple, ")"] + return [".call(", base_name, ", ", args_simple, ")"] else: return [".call(", base_name, ")"] - else: + else: # Normal function call return ["(", args_simple, ")"] - + def _get_positional_args(self, node): - """ Returns: + """Returns: * a string args_simple, which represents the positional args in comma separated form. Can be None if the args cannot be represented that way. Note that it can be empty string. * a string args_array representing the array with positional arguments. """ - + # Generate list of arg lists (has normal positional args and starargs) # Note that there can be multiple starargs and these can alternate. argswithcommas = [] arglists = [argswithcommas] for arg in node.arg_nodes: if isinstance(arg, ast.Starred): - starname = ''.join(self.parse(arg.value_node)) + starname = "".join(self.parse(arg.value_node)) arglists.append(starname) argswithcommas = [] arglists.append(argswithcommas) else: argswithcommas.extend(self.parse(arg)) - argswithcommas.append(', ') - + argswithcommas.append(", ") + # Clear empty lists and trailing commas for i in reversed(range(len(arglists))): arglist = arglists[i] if not arglist: arglists.pop(i) - elif arglist[-1] == ', ': + elif arglist[-1] == ", ": arglist.pop(-1) - + # Generate code for positional arguments if len(arglists) == 0: - return '', '[]' + return "", "[]" elif len(arglists) == 1 and isinstance(arglists[0], list): - args_simple = ''.join(argswithcommas) - return args_simple, '[' + args_simple + ']' + args_simple = "".join(argswithcommas) + return args_simple, "[" + args_simple + "]" elif len(arglists) == 1: assert isinstance(arglists[0], str) return None, arglists[0] else: - code = ['[].concat('] + code = ["[].concat("] for arglist in arglists: if isinstance(arglist, list): - code += ['['] + code += ["["] code += arglist - code += [']'] + code += ["]"] else: code += [arglist] - code += [', '] + code += [", "] code.pop(-1) - code += ')' - return None, ''.join(code) - + code += ")" + return None, "".join(code) + def _get_keyword_args(self, node): - """ Get a string that represents the dictionary of keyword arguments, + """Get a string that represents the dictionary of keyword arguments, or None if there are no keyword arguments (normal nor double-star). """ - + # Collect elements that will make up the total kwarg dict kwargs = [] for kwnode in node.kwarg_nodes: @@ -660,14 +723,15 @@ def _get_keyword_args(self, node): else: # foo=xx if not (kwargs and isinstance(kwargs[-1], list)): kwargs.append([]) - kwargs[-1].append('%s: %s' % (kwnode.name, - unify(self.parse(kwnode.value_node)))) - + kwargs[-1].append( + "%s: %s" % (kwnode.name, unify(self.parse(kwnode.value_node))) + ) + # Resolve sequneces of loose kwargs for i in range(len(kwargs)): if isinstance(kwargs[i], list): - kwargs[i] = '{' + ', '.join(kwargs[i]) + '}' - + kwargs[i] = "{" + ", ".join(kwargs[i]) + "}" + # Compose, easy if singleton, otherwise we need to merge if len(kwargs) == 0: return None @@ -675,11 +739,11 @@ def _get_keyword_args(self, node): return kwargs[0] else: # register use of merge_dicts(), but we build the string ourselves - self.use_std_function('merge_dicts', []) - return stdlib.FUNCTION_PREFIX + 'merge_dicts(' + ', '.join(kwargs) + ')' - + self.use_std_function("merge_dicts", []) + return stdlib.FUNCTION_PREFIX + "merge_dicts(" + ", ".join(kwargs) + ")" + def parse_Attribute(self, node, fullname=None): - fullname = node.attr + '.' + fullname if fullname else node.attr + fullname = node.attr + "." + fullname if fullname else node.attr if isinstance(node.value_node, ast.Name): base_name = self.parse_Name(node.value_node, fullname) elif isinstance(node.value_node, ast.Attribute): @@ -688,28 +752,28 @@ def parse_Attribute(self, node, fullname=None): base_name = unify(self.parse(node.value_node)) attr = node.attr # Double underscore name mangling - if attr.startswith('__') and not attr.endswith('__') and base_name == 'this': - for i in range(len(self._stack)-1, -1, -1): - if self._stack[i][0] == 'class': + if attr.startswith("__") and not attr.endswith("__") and base_name == "this": + for i in range(len(self._stack) - 1, -1, -1): + if self._stack[i][0] == "class": classname = self._stack[i][1] - attr = '_' + classname + attr + attr = "_" + classname + attr break if attr in self.ATTRIBUTE_MAP: - return self.ATTRIBUTE_MAP[attr].replace('{}', base_name) + return self.ATTRIBUTE_MAP[attr].replace("{}", base_name) else: return "%s.%s" % (base_name, attr) - + ## Statements - + def parse_Assign(self, node): - """ Variable assignment. """ + """Variable assignment.""" code = [self.lf()] - + # Set PScript behavior? Note that its reset on a function exit. if ( - len(node.target_nodes) == 1 and - isinstance(node.target_nodes[0], ast.Name) and - node.target_nodes[0].name == 'PSCRIPT_OVERLOAD' + len(node.target_nodes) == 1 + and isinstance(node.target_nodes[0], ast.Name) + and node.target_nodes[0].name == "PSCRIPT_OVERLOAD" ): if self._stack[-1][0] != "function": raise JSError("Can only set PSCRIPT_OVERLOAD inside a function") @@ -718,13 +782,13 @@ def parse_Assign(self, node): else: self._stack[-1][2]._pscript_overload = bool(node.value_node.value) return [] - + # Parse targets tuple = [] for target in node.target_nodes: - var = ''.join(self.parse(target)) + var = "".join(self.parse(target)) if isinstance(target, ast.Name): - if '.' in var: + if "." in var: code.append(var) else: self.vars.add(var) @@ -739,18 +803,18 @@ def parse_Assign(self, node): tuple = target.element_nodes else: raise JSError("Unsupported assignment type") - code.append(' = ') - + code.append(" = ") + # Parse right side if isinstance(node.value_node, ast.ListComp) and len(node.target_nodes) == 1: result_name = self.dummy() - code.append(result_name + ';') + code.append(result_name + ";") lc_code = self.parse_ListComp_funtionless(node.value_node, result_name) - code = [self.lf(), result_name + ' = [];'] + lc_code + code + code = [self.lf(), result_name + " = [];"] + lc_code + code else: code += self.parse(node.value_node) - code.append(';') - + code.append(";") + # Handle tuple unpacking if tuple: code.append(self.lf()) @@ -758,119 +822,127 @@ def parse_Assign(self, node): var = unify(self.parse(x)) if isinstance(x, ast.Name): # but not when attr or index self.vars.add(var) - code.append('%s = %s[%i];' % (var, dummy, i)) - + code.append("%s = %s[%i];" % (var, dummy, i)) + return code - + def parse_AugAssign(self, node): # -> x += 1 - target = ''.join(self.parse(node.target_node)) - value = ''.join(self.parse(node.value_node)) - + target = "".join(self.parse(node.target_node)) + value = "".join(self.parse(node.value_node)) + nl = self.lf() if ( - node.op == node.OPS.Add and - self._pscript_overload and - not isinstance(node.value_node, (ast.Num, ast.Str)) + node.op == node.OPS.Add + and self._pscript_overload + and not isinstance(node.value_node, (ast.Num, ast.Str)) ): - return [nl, target, ' = ', - self.use_std_function('op_add', [target, value]), ';'] + return [ + nl, + target, + " = ", + self.use_std_function("op_add", [target, value]), + ";", + ] elif node.op == node.OPS.Mult and self._pscript_overload: - return [nl, target, ' = ', - self.use_std_function('op_mult', [target, value]), ';'] + return [ + nl, + target, + " = ", + self.use_std_function("op_mult", [target, value]), + ";", + ] elif node.op == node.OPS.Pow: return [nl, target, " = Math.pow(", target, ", ", value, ");"] elif node.op == node.OPS.FloorDiv: return [nl, target, " = Math.floor(", target, "/", value, ");"] else: - op = ' %s= ' % self.BINARY_OP[node.op] - return [nl, target, op, value, ';'] - + op = " %s= " % self.BINARY_OP[node.op] + return [nl, target, op, value, ";"] + def parse_Delete(self, node): code = [] for target in node.target_nodes: - code.append(self.lf('delete ')) + code.append(self.lf("delete ")) code += self.parse(target) - code.append(';') + code.append(";") return code - + def parse_Pass(self, node): return [] ## Subscripting - + def parse_Subscript(self, node): - value_list = self.parse(node.value_node) slice_list = self.parse(node.slice_node) - + code = [] code += value_list - + if isinstance(node.slice_node, (ast.Slice, ast.Tuple)): - code.append('.slice(') + code.append(".slice(") code += slice_list - code.append(')') + code.append(")") else: - code.append('[') - if slice_list[0].startswith('-'): - code.append(unify(value_list) + '.length ') + code.append("[") + if slice_list[0].startswith("-"): + code.append(unify(value_list) + ".length ") code += slice_list - code.append(']') + code.append("]") return code - + def parse_Index(self, node): return self.parse(node.value_node) - + def parse_Slice(self, node): code = [] if node.step_node: - raise JSError('Slicing with step not supported.') + raise JSError("Slicing with step not supported.") if node.lower_node: code += self.parse(node.lower_node) else: - code.append('0') + code.append("0") if node.upper_node: - code.append(',') + code.append(",") code += self.parse(node.upper_node) return code - + def parse_ExtSlice(self, node): - raise JSError('Multidimensional slicing not supported in JS') - - ## Imports + raise JSError("Multidimensional slicing not supported in JS") + + ## Imports def parse_Import(self, node): - - if node.root and 'pscript' in node.root: + if node.root and "pscript" in node.root: # User is probably importing names from here to allow # writing the JS code and command to parse it in one module. # Ignore this import. return [] - if node.root and node.root == '__future__': + if node.root and node.root == "__future__": return [] # stuff to help the parser - if node.root == 'time': + if node.root == "time": return [] # PScript natively supports time() and perf_counter() - if node.root == 'typing': + if node.root == "typing": # User is probably importing type annotations. Ignore this import. return [] - raise JSError('PScript does not support imports.') - + raise JSError("PScript does not support imports.") + def parse_Module(self, node): # Module level. Every piece of code has a module as the root. # Just pass body. - - # Get docstring, but only if in module mode + + # Get docstring, but only if in module mode # module_mode = self._stack[0][1] # top stack has a name -> works no more module_mode = self._pysource and self._pysource[1] == 0 # line nr offset - docstring = '' + docstring = "" if self._docstrings and module_mode: docstring = self.pop_docstring(node) - + code = [] if docstring: for line in docstring.splitlines(): - code.append(self.lf('// ' + line)) - code.append('\n') + code.append(self.lf("// " + line)) + code.append("\n") for child in node.body_nodes: code += self.parse(child) return code diff --git a/pscript/parser2.py b/pscript/parser2.py index 2a9520ed..44f6e596 100644 --- a/pscript/parser2.py +++ b/pscript/parser2.py @@ -12,11 +12,11 @@ result = 1 else: result = 0 - + # One-line if result = 42 if truth else 0 - - + + Looping ------- @@ -38,14 +38,14 @@ # Using range() yields true for-loops for i in range(10): print(i) - + for i in range(100, 10, -2): print(i) - + # One way to iterate over an array for i in range(len(arr)): print(arr[i]) - + # But this is equally valid (and fast) for element in arr: print(element) @@ -58,14 +58,14 @@ # Plain iteration over a dict has a minor overhead for key in d: print(key) - + # Which is why we recommend using keys(), values(), or items() for key in d.keys(): print(key) - + for val in d.values(): print(val) - + for key, val in d.items(): print(key, val, sep=': ') @@ -77,28 +77,28 @@ # Strings for char in "foo bar": print(c) - + # More complex data structes for i, j in [[1, 2], [3, 4]]: print(i+j) -Builtin functions intended for iterations are supported too: +Builtin functions intended for iterations are supported too: enumerate, zip, reversed, sorted, filter, map. .. pscript_example:: for i, x in enumerate(foo): pass - + for a, b in zip(foo, bar): pass - + for x in reversed(sorted(foo)): pass - + for x in map(lambda x: x+1, foo): pass - + for x in filter(lambda x: x>0, foo): pass @@ -107,7 +107,7 @@ -------------- .. pscript_example:: - + # List comprehensions just work x = [i*2 for i in some_array if i>0] y = [i*j for i in a for j in b] @@ -120,18 +120,18 @@ def display(val): print(val) - + # Support for *args def foo(x, *values): bar(x+1, *values) - + # To write the function in raw JS, use the RawJS call def bar(a, b): RawJS(''' var c = 4; return a + b + c; ''') - + # Lambda expressions foo = lambda x: x**2 @@ -155,23 +155,23 @@ async def getresult(uri): Further, `super()` works just as in Python 3. .. pscript_example:: - + class Foo: a_class_attribute = 4 def __init__(self): self.x = 3 - + class Bar(Foo): def __init__(self): super.__init__() self.x += 1 def add1(self): self.x += 1 - + # Methods are bound functions, like in Python b = Bar() setTimeout(b.add1, 1000) - + # Functions defined in methods (and that do not start with self or this) # have ``this`` bound the the same object. class Spam(Bar): @@ -189,17 +189,17 @@ def add_later(self): you can only catch Error objects. .. pscript_example:: - + # Throwing/raising exceptions raise SomeError('asd') raise AnotherError() raise "In JS you can throw anything" raise 4 - + # Assertions work too assert foo == 3 assert bar == 4, "bar should be 4" - + # Catching exceptions try: raise IndexError('blabla') @@ -212,7 +212,7 @@ def add_later(self): -------------------- .. pscript_example:: - + a = 3 def foo(): global a @@ -225,260 +225,312 @@ def foo(): from . import commonast as ast from . import stdlib from . import logger -from .parser1 import Parser1, JSError, unify, reprs # noqa +from .parser1 import Parser1, JSError, unify, reprs -RAW_DOC_WARNING = ('Function %s only has a docstring, which used to be ' - 'intepreted as raw JS. Wrap a call to RawJS(...) around the ' - 'docstring, or add "pass" to the function body to prevent ' - 'this behavior.') +RAW_DOC_WARNING = ( + "Function %s only has a docstring, which used to be " + "intepreted as raw JS. Wrap a call to RawJS(...) around the " + 'docstring, or add "pass" to the function body to prevent ' + "this behavior." +) JS_RESERVED_WORDS = set() # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar -RESERVED = {'true', 'false', 'null', - # Reserved keywords as of ECMAScript 6 - 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', - 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', - 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', - 'return', 'super', 'switch', 'this', 'throw', 'try', 'typeof', - 'var', 'void', 'while', 'with', 'yield', - # Future reserved keywords - 'implements', 'interface', 'let', 'package', 'private', - 'protected', 'public', 'static', 'enum', - 'await', # only in module code - } - - +RESERVED = { + "true", + "false", + "null", + # Reserved keywords as of ECMAScript 6 + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "return", + "super", + "switch", + "this", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", + # Future reserved keywords + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + "enum", + "await", # only in module code +} + + class Parser2(Parser1): - """ Parser that adds control flow, functions, classes, and exceptions. - """ - + """Parser that adds control flow, functions, classes, and exceptions.""" + ## Exceptions - + def parse_Raise(self, node): # We raise the exception as an Error object - + if node.exc_node is None: - raise JSError('When raising, provide an error object.') + raise JSError("When raising, provide an error object.") if node.cause_node is not None: raise JSError('When raising, "cause" is not supported.') err_node = node.exc_node - + # Get cls and msg err_cls, err_msg = None, "''" if isinstance(err_node, ast.Name): if err_node.name[0].islower(): # raise an (error) object - return [self.lf("throw " + err_node.name + ';')] + return [self.lf("throw " + err_node.name + ";")] err_cls = err_node.name elif isinstance(err_node, ast.Call): err_cls = err_node.func_node.name - err_msg = ''.join([unify(self.parse(arg)) for arg in err_node.arg_nodes]) + err_msg = "".join([unify(self.parse(arg)) for arg in err_node.arg_nodes]) else: - err_msg = ''.join(self.parse(err_node)) - - err_name = 'err_%i' % self._indent + err_msg = "".join(self.parse(err_node)) + + err_name = "err_%i" % self._indent self.vars.add(err_name) - + # Build code to throw if err_cls: - code = self.use_std_function('op_error', - ["'%s'" % err_cls, err_msg or '""']) + code = self.use_std_function( + "op_error", ["'%s'" % err_cls, err_msg or '""'] + ) else: code = err_msg - return [self.lf('throw ' + code + ';')] - + return [self.lf("throw " + code + ";")] + def parse_Assert(self, node): - - test = ''.join(self.parse(node.test_node)) + test = "".join(self.parse(node.test_node)) msg = test if node.msg_node: - msg = ''.join(self.parse(node.msg_node)) - + msg = "".join(self.parse(node.msg_node)) + code = [] - code.append(self.lf('if (!(')) + code.append(self.lf("if (!(")) code += test - code.append(')) { throw ') - code.append(self.use_std_function('op_error', ["'AssertionError'", reprs(msg)])) + code.append(")) { throw ") + code.append(self.use_std_function("op_error", ["'AssertionError'", reprs(msg)])) code.append(";}") return code - + def parse_Try(self, node): if node.else_nodes: - raise JSError('No support for try-else clause.') - + raise JSError("No support for try-else clause.") + code = [] - + # Try if True: - code.append(self.lf('try {')) + code.append(self.lf("try {")) self._indent += 1 for n in node.body_nodes: code += self.parse(n) self._indent -= 1 - code.append(self.lf('}')) - + code.append(self.lf("}")) + # Except if node.handler_nodes: self._indent += 1 - err_name = 'err_%i' % self._indent - code.append(' catch(%s) {' % err_name) + err_name = "err_%i" % self._indent + code.append(" catch(%s) {" % err_name) subcode = [] for i, handler in enumerate(node.handler_nodes): if i == 0: - code.append(self.lf('')) + code.append(self.lf("")) else: - code.append(' else ') + code.append(" else ") subcode = self.parse(handler) code += subcode - + # Rethrow? - if subcode and subcode[0].startswith('if'): - code.append(' else { throw %s; }' % err_name) - + if subcode and subcode[0].startswith("if"): + code.append(" else { throw %s; }" % err_name) + self._indent -= 1 - code.append(self.lf('}')) # end catch - + code.append(self.lf("}")) # end catch + # Finally if node.finally_nodes: - code.append(' finally {') + code.append(" finally {") self._indent += 1 for n in node.finally_nodes: code += self.parse(n) self._indent -= 1 - code.append(self.lf('}')) # end finally - + code.append(self.lf("}")) # end finally + return code - + def parse_ExceptHandler(self, node): - err_name = 'err_%i' % self._indent - + err_name = "err_%i" % self._indent + # Setup the catch code = [] - err_type = unify(self.parse(node.type_node)) if node.type_node else '' + err_type = unify(self.parse(node.type_node)) if node.type_node else "" self.vars.discard(err_type) - if err_type and err_type != 'Exception': - code.append('if (%s instanceof Error && %s.name === "%s") {' % - (err_name, err_name, err_type)) + if err_type and err_type != "Exception": + code.append( + 'if (%s instanceof Error && %s.name === "%s") {' + % (err_name, err_name, err_type) + ) else: - code.append('{') + code.append("{") self._indent += 1 if node.name: - code.append(self.lf('%s = %s;' % (node.name, err_name))) + code.append(self.lf("%s = %s;" % (node.name, err_name))) self.vars.add(node.name) - + # Insert the body for n in node.body_nodes: code += self.parse(n) self._indent -= 1 - - code.append(self.lf('}')) + + code.append(self.lf("}")) return code - + def parse_With(self, node): code = [] - + if len(node.item_nodes) != 1: - raise JSError('With statement only supported for singleton contexts.') + raise JSError("With statement only supported for singleton contexts.") with_item = node.item_nodes[0] context_name = unify(self.parse(with_item.expr_node)) - + # Store context expression in a variable? - if '(' in context_name or '[' in context_name: - ctx = self.dummy('context') - code.append(self.lf(ctx + ' = ' + context_name + ';')) + if "(" in context_name or "[" in context_name: + ctx = self.dummy("context") + code.append(self.lf(ctx + " = " + context_name + ";")) context_name = ctx - - err_name1 = 'err_%i' % self._indent - err_name2 = self.dummy('err') - + + err_name1 = "err_%i" % self._indent + err_name2 = self.dummy("err") + # Enter # for with_item in node.item_nodes: ... if with_item.as_node is None: - code.append(self.lf('')) + code.append(self.lf("")) elif isinstance(with_item.as_node, ast.Name): self.vars.add(with_item.as_node.name) - code.append(self.lf(with_item.as_node.name + ' = ')) + code.append(self.lf(with_item.as_node.name + " = ")) elif isinstance(with_item.as_node, ast.Attribute): - code += [self.lf()] + self.parse(with_item.as_node) + [' = '] + code += [self.lf()] + self.parse(with_item.as_node) + [" = "] else: - raise JSError('The as-node in a with-statement must be a name or attr.') - code += [context_name, '.__enter__();'] - + raise JSError("The as-node in a with-statement must be a name or attr.") + code += [context_name, ".__enter__();"] + # Try - code.append(self.lf('try {')) + code.append(self.lf("try {")) self._indent += 1 for n in node.body_nodes: code += self.parse(n) self._indent -= 1 - code.append(self.lf('}')) - + code.append(self.lf("}")) + # Exit - code.append(' catch(%s) { %s=%s;' % (err_name1, err_name2, err_name1)) - code.append(self.lf('} finally {')) + code.append(" catch(%s) { %s=%s;" % (err_name1, err_name2, err_name1)) + code.append(self.lf("} finally {")) self._indent += 1 - code.append(self.lf() + 'if (%s) { ' - 'if (!%s.__exit__(%s.name || "error", %s, null)) ' - '{ throw %s; }' % - (err_name2, context_name, err_name2, err_name2, err_name2)) - code.append(self.lf() + '} else { %s.__exit__(null, null, null); }' % - context_name) + code.append( + self.lf() + "if (%s) { " + 'if (!%s.__exit__(%s.name || "error", %s, null)) ' + "{ throw %s; }" % (err_name2, context_name, err_name2, err_name2, err_name2) + ) + code.append( + self.lf() + "} else { %s.__exit__(null, null, null); }" % context_name + ) self._indent -= 1 - code.append(self.lf('}')) + code.append(self.lf("}")) return code - + # def parse_Withitem(self, node) -> handled in parse_With - + ## Control flow - + def parse_IfExp(self, node): # in "a if b else c" a = self.parse(node.body_node) b = self._wrap_truthy(node.test_node) c = self.parse(node.else_node) - + code = [] - code.append('(') + code.append("(") code += b - code.append(')? (') + code.append(")? (") code += a - code.append(') : (') + code.append(") : (") code += c - code.append(')') + code.append(")") return code - + def parse_If(self, node): - if (True and isinstance(node.test_node, ast.Compare) and - isinstance(node.test_node.left_node, ast.Name) and - node.test_node.left_node.name == '__name__'): + if ( + True + and isinstance(node.test_node, ast.Compare) + and isinstance(node.test_node.left_node, ast.Name) + and node.test_node.left_node.name == "__name__" + ): # Ignore ``__name__ == '__main__'``, since it may be # used inside a PScript file for the compiling. return [] - + # Shortcut for this_is_js() cases, discarting the else to reduce code - if (True and isinstance(node.test_node, ast.Call) and - isinstance(node.test_node.func_node, ast.Name) and - node.test_node.func_node.name == 'this_is_js'): - code = [self.lf('if ('), 'true', ') ', '{ /* if this_is_js() */'] + if ( + True + and isinstance(node.test_node, ast.Call) + and isinstance(node.test_node.func_node, ast.Name) + and node.test_node.func_node.name == "this_is_js" + ): + code = [self.lf("if ("), "true", ") ", "{ /* if this_is_js() */"] self._indent += 1 for stmt in node.body_nodes: code += self.parse(stmt) self._indent -= 1 - code.append(self.lf('}')) + code.append(self.lf("}")) return code - + # Disable body if "not this_is_js()" - if (True and isinstance(node.test_node, ast.UnaryOp) and - node.test_node.op == 'Not' and - isinstance(node.test_node.right_node, ast.Call) and - isinstance(node.test_node.right_node.func_node, ast.Name) and - node.test_node.right_node.func_node.name == 'this_is_js'): + if ( + True + and isinstance(node.test_node, ast.UnaryOp) + and node.test_node.op == "Not" + and isinstance(node.test_node.right_node, ast.Call) + and isinstance(node.test_node.right_node.func_node, ast.Name) + and node.test_node.right_node.func_node.name == "this_is_js" + ): node.body_nodes = [] - - code = [self.lf('if (')] # first part (popped in elif parsing) + + code = [self.lf("if (")] # first part (popped in elif parsing) code.append(self._wrap_truthy(node.test_node)) - code.append(') {') + code.append(") {") self._indent += 1 for stmt in node.body_nodes: code += self.parse(stmt) @@ -495,51 +547,58 @@ def parse_If(self, node): self._indent -= 1 code.append(self.lf("}")) # last part (popped in elif parsing) return code - + def parse_For(self, node): # Note that enumerate, reversed, sorted, filter, map are handled in parser3 - - METHODS = 'keys', 'values', 'items' - + + METHODS = "keys", "values", "items" + iter = None # what to iterate over sure_is_dict = False # flag to indicate that we're sure iter is a dict sure_is_range = False # dito for range - + # First see if this for-loop is something that we support directly if isinstance(node.iter_node, ast.Call): f = node.iter_node.func_node - if (isinstance(f, ast.Attribute) and - not node.iter_node.arg_nodes and f.attr in METHODS): + if ( + isinstance(f, ast.Attribute) + and not node.iter_node.arg_nodes + and f.attr in METHODS + ): sure_is_dict = f.attr - iter = ''.join(self.parse(f.value_node)) - elif isinstance(f, ast.Name) and f.name in ('xrange', 'range'): - sure_is_range = [''.join(self.parse(arg)) for arg in - node.iter_node.arg_nodes] - iter = 'range' # stub to prevent the parsing of iter_node below - + iter = "".join(self.parse(f.value_node)) + elif isinstance(f, ast.Name) and f.name in ("xrange", "range"): + sure_is_range = [ + "".join(self.parse(arg)) for arg in node.iter_node.arg_nodes + ] + iter = "range" # stub to prevent the parsing of iter_node below + # Otherwise we parse the iter if iter is None: - iter = ''.join(self.parse(node.iter_node)) - + iter = "".join(self.parse(node.iter_node)) + # Get target if isinstance(node.target_node, ast.Name): target = [node.target_node.name] - if sure_is_dict == 'values': + if sure_is_dict == "values": target.append(target[0]) - elif sure_is_dict == 'items': - raise JSError('Iteration over a dict with .items() ' - 'needs two iterators.') + elif sure_is_dict == "items": + raise JSError( + "Iteration over a dict with .items() needs two iterators." + ) elif isinstance(node.target_node, ast.Tuple): - target = [''.join(self.parse(t)) for t in node.target_node.element_nodes] + target = ["".join(self.parse(t)) for t in node.target_node.element_nodes] if sure_is_dict: - if not (sure_is_dict == 'items' and len(target) == 2): - raise JSError('Iteration over a dict needs one iterator, ' - 'or 2 when using .items()') + if not (sure_is_dict == "items" and len(target) == 2): + raise JSError( + "Iteration over a dict needs one iterator, " + "or 2 when using .items()" + ) elif sure_is_range: - raise JSError('Iterarion via range() needs one iterator.') + raise JSError("Iterarion via range() needs one iterator.") else: - raise JSError('Invalid iterator in for-loop') - + raise JSError("Invalid iterator in for-loop") + # Collect body and else-body for_body = [] for_else = [] @@ -549,53 +608,53 @@ def parse_For(self, node): for n in node.else_nodes: for_else += self.parse(n) self._indent -= 1 - + # Init code code = [] - + # Prepare variable to detect else if node.else_nodes: - else_dummy = self.dummy('els') - code.append(self.lf('%s = true;' % else_dummy)) - + else_dummy = self.dummy("els") + code.append(self.lf("%s = true;" % else_dummy)) + # Declare iteration variables if necessary for t in target: self.vars.add(t) - + if sure_is_range: # Explicit iteration # Get range args nums = sure_is_range # The range() arguments assert len(nums) in (1, 2, 3) if len(nums) == 1: - start, end, step = '0', nums[0], '1' + start, end, step = "0", nums[0], "1" elif len(nums) == 2: - start, end, step = nums[0], nums[1], '1' + start, end, step = nums[0], nums[1], "1" elif len(nums) == 3: start, end, step = nums[0], nums[1], nums[2] # Build for-loop in JS - t = 'for ({i} = {start}; {i} < {end}; {i} += {step})' - if step.lstrip('+-').isdecimal() and float(step) < 0: - t = t.replace('<', '>') + t = "for ({i} = {start}; {i} < {end}; {i} += {step})" + if step.lstrip("+-").isdecimal() and float(step) < 0: + t = t.replace("<", ">") assert len(target) == 1 - t = t.format(i=target[0], start=start, end=end, step=step) + ' {' + t = t.format(i=target[0], start=start, end=end, step=step) + " {" code.append(self.lf(t)) self._indent += 1 - + elif sure_is_dict: # Enumeration over an object (i.e. a dict) # Create dummy vars - d_seq = self.dummy('seq') - code.append(self.lf('%s = %s;' % (d_seq, iter))) + d_seq = self.dummy("seq") + code.append(self.lf("%s = %s;" % (d_seq, iter))) # The loop - code += self.lf(), 'for (', target[0], ' in ', d_seq, ') {' + code += self.lf(), "for (", target[0], " in ", d_seq, ") {" self._indent += 1 - code.append(self.lf('if (!%s.hasOwnProperty(%s)){ continue; }' % - (d_seq, target[0]))) + code.append( + self.lf("if (!%s.hasOwnProperty(%s)){ continue; }" % (d_seq, target[0])) + ) # Set second/alt iteration variable if len(target) > 1: - code.append(self.lf('%s = %s[%s];' % (target[1], d_seq, target[0]))) - + code.append(self.lf("%s = %s[%s];" % (target[1], d_seq, target[0]))) + else: # Enumeration - # We cannot know whether the thing to iterate over is an # array or a dict. We use a for-iterarion (otherwise we # cannot be sure of the element order for arrays). Before @@ -603,59 +662,66 @@ def parse_For(self, node): # not, we replace the sequence with the keys of that # sequence. Peformance for arrays should be good. For # objects probably slightly less. - + # Create dummy vars - d_seq = self.dummy('seq') - d_iter = self.dummy('itr') - d_target = target[0] if (len(target) == 1) else self.dummy('tgt') - + d_seq = self.dummy("seq") + d_iter = self.dummy("itr") + d_target = target[0] if (len(target) == 1) else self.dummy("tgt") + # Ensure our iterable is indeed iterable code.append(self._make_iterable(iter, d_seq)) - + # The loop - code.append(self.lf('for (%s = 0; %s < %s.length; %s += 1) {' % - (d_iter, d_iter, d_seq, d_iter))) + code.append( + self.lf( + "for (%s = 0; %s < %s.length; %s += 1) {" + % (d_iter, d_iter, d_seq, d_iter) + ) + ) self._indent += 1 - code.append(self.lf('%s = %s[%s];' % (d_target, d_seq, d_iter))) + code.append(self.lf("%s = %s[%s];" % (d_target, d_seq, d_iter))) if len(target) > 1: code.append(self.lf(self._iterator_assign(d_target, *target))) - + # The body of the loop code += for_body self._indent -= 1 - code.append(self.lf('}')) - + code.append(self.lf("}")) + # Handle else if node.else_nodes: - code.append(' if (%s) {' % else_dummy) + code.append(" if (%s) {" % else_dummy) code += for_else code.append(self.lf("}")) # Update all breaks to set the dummy. We overwrite the # "break;" so it will not be detected by a parent loop - ii = [i for i, part in enumerate(code) if part=='break;'] + ii = [i for i, part in enumerate(code) if part == "break;"] for i in ii: - code[i] = '%s = false; break;' % else_dummy - + code[i] = "%s = false; break;" % else_dummy + return code - + def _make_iterable(self, name1, name2, newlines=True): code = [] lf = self.lf if not newlines: # pragma: no cover lf = lambda x: x - + if name1 != name2: - code.append(lf('%s = %s;' % (name2, name1))) - code.append(lf('if ((typeof %s === "object") && ' - '(!Array.isArray(%s))) {' % (name2, name2))) - code.append(' %s = Object.keys(%s);' % (name2, name2)) - code.append('}') - return ''.join(code) - + code.append(lf("%s = %s;" % (name2, name1))) + code.append( + lf( + 'if ((typeof %s === "object") && ' + "(!Array.isArray(%s))) {" % (name2, name2) + ) + ) + code.append(" %s = Object.keys(%s);" % (name2, name2)) + code.append("}") + return "".join(code) + def parse_While(self, node): - - test = ''.join(self.parse(node.test_node)) - + test = "".join(self.parse(node.test_node)) + # Collect body and else-body for_body = [] for_else = [] @@ -665,240 +731,259 @@ def parse_While(self, node): for n in node.else_nodes: for_else += self.parse(n) self._indent -= 1 - + # Init code code = [] - + # Prepare variable to detect else if node.else_nodes: - else_dummy = self.dummy('els') - code.append(self.lf('%s = true;' % else_dummy)) - + else_dummy = self.dummy("els") + code.append(self.lf("%s = true;" % else_dummy)) + # The loop itself code.append(self.lf("while (%s) {" % test)) self._indent += 1 code += for_body self._indent -= 1 - code.append(self.lf('}')) - + code.append(self.lf("}")) + # Handle else if node.else_nodes: - code.append(' if (%s) {' % else_dummy) + code.append(" if (%s) {" % else_dummy) code += for_else code.append(self.lf("}")) # Update all breaks to set the dummy. We overwrite the # "break;" so it will not be detected by a parent loop - ii = [i for i, part in enumerate(code) if part=='break;'] + ii = [i for i, part in enumerate(code) if part == "break;"] for i in ii: - code[i] = '%s = false; break;' % else_dummy - + code[i] = "%s = false; break;" % else_dummy + return code - + def parse_Break(self, node): # Note that in parse_For, we detect breaks and modify them to # deal with the for-else clause - return [self.lf(), 'break;'] - + return [self.lf(), "break;"] + def parse_Continue(self, node): - return self.lf('continue;') - + return self.lf("continue;") + ## Comprehensions - + def parse_ListComp_funtionless(self, node, result_name): - prefix = result_name self.push_scope_prefix(prefix) code = [] - + for iter, comprehension in enumerate(node.comp_nodes): cc = [] # Get target (can be multiple vars) if isinstance(comprehension.target_node, ast.Tuple): - target = [namenode.name for namenode in - comprehension.target_node.element_nodes] + target = [ + namenode.name + for namenode in comprehension.target_node.element_nodes + ] else: target = [comprehension.target_node.name] for i in range(len(target)): if not self.vars.is_known(target[i]): target[i] = prefix + target[i] self.vars.add(target[i]) - self.vars.add(prefix + 'i%i' % iter) - self.vars.add(prefix + 'iter%i' % iter) - + self.vars.add(prefix + "i%i" % iter) + self.vars.add(prefix + "iter%i" % iter) + # comprehension(target_node, iter_node, if_nodes) - cc.append('iter# = %s;' % ''.join(self.parse(comprehension.iter_node))) - cc.append('if ((typeof iter# === "object") && ' - '(!Array.isArray(iter#))) {iter# = Object.keys(iter#);}') - cc.append('for (i#=0; i# 0: # first one is passed to function as an arg - cc.append('iter# = %s;' % ''.join(self.parse(comprehension.iter_node))) - vars.append('iter%i' % iter) - cc.append('if ((typeof iter# === "object") && ' - '(!Array.isArray(iter#))) {iter# = Object.keys(iter#);}') - cc.append('for (i#=0; i#= 1 and self._stack[-1][0] == 'function': + binder = "" # code to add to the end + if len(self._stack) >= 1 and self._stack[-1][0] == "function": if not has_self: - binder = ').bind(this)' - + binder = ").bind(this)" + # Init function definition # Non-anonymouse functions get a name so that they are debugged more # easily and resolve to the correct event labels in flexx.event. However, # we cannot use the exact name, since we don't want to actually *use* it. # Classes give their methods a __name__, so no need to name these. code = [] - func_name = '' + func_name = "" if not lambda_: if not has_self: - func_name = 'flx_' + node.name + func_name = "flx_" + node.name prefixed = self.with_prefix(node.name) if prefixed == node.name: # normal function vs method self.vars.add(node.name) self._seen_func_names.add(node.name) - code.append(self.lf('%s = ' % prefixed)) - code.append('%s%sfunction %s%s(' % ('(' if binder else '', - 'async ' if asyn else '', - func_name, - ' ' if func_name else '')) - + code.append(self.lf("%s = " % prefixed)) + code.append( + "%s%sfunction %s%s(" + % ( + "(" if binder else "", + "async " if asyn else "", + func_name, + " " if func_name else "", + ) + ) + # Collect args argnames = [] for arg in node.arg_nodes: # ast.Arg nodes name = self.NAME_MAP.get(arg.name, arg.name) - if name != 'this': + if name != "this": argnames.append(name) # Add code and comma code.append(name) - code.append(', ') + code.append(", ") if argnames: code.pop(-1) # pop last comma - + # Check if (not lambda_) and node.decorator_nodes: - if not (len(node.decorator_nodes) == 1 and - isinstance(node.decorator_nodes[0], ast.Name) and - node.decorator_nodes[0].name == 'staticmethod'): - raise JSError('No support for function decorators') - + if not ( + len(node.decorator_nodes) == 1 + and isinstance(node.decorator_nodes[0], ast.Name) + and node.decorator_nodes[0].name == "staticmethod" + ): + raise JSError("No support for function decorators") + # Prepare for content - code.append(') {') + code.append(") {") pre_code, code = code, [] self._indent += 1 - self.push_stack('function', '' if lambda_ else node.name) - + self.push_stack("function", "" if lambda_ else node.name) + # Add argnames to known vars for name in argnames: self.vars.add(name) - + # Prepare code for varargs - vararg_code1 = vararg_code2 = '' + vararg_code1 = vararg_code2 = "" if node.args_node: name = node.args_node.name # always an ast.Arg self.vars.add(name) if not argnames: # Make available under *arg name - #code.append(self.lf('%s = arguments;' % name)) - vararg_code1 = '%s = Array.prototype.slice.call(arguments);' % name - vararg_code2 = '%s = arguments[0].flx_args;' % name + # code.append(self.lf('%s = arguments;' % name)) + vararg_code1 = "%s = Array.prototype.slice.call(arguments);" % name + vararg_code2 = "%s = arguments[0].flx_args;" % name else: # Slice it x = name, len(argnames) - vararg_code1 = '%s = Array.prototype.slice.call(arguments, %i);' % x - vararg_code2 = '%s = arguments[0].flx_args.slice(%i);' % x - + vararg_code1 = "%s = Array.prototype.slice.call(arguments, %i);" % x + vararg_code2 = "%s = arguments[0].flx_args.slice(%i);" % x + # Handle keyword arguments and kwargs kw_argnames = set() # variables that come from keyword args, or helper vars if node.kwarg_nodes or node.kwargs_node: @@ -908,74 +993,82 @@ def parse_FunctionDef(self, node, lambda_=False, asyn=False): self.vars.add(arg.name) kw_argnames.add(arg.name) names.append("'%s'" % arg.name) - values.append(''.join(self.parse(arg.value_node))) + values.append("".join(self.parse(arg.value_node))) # Turn into string representation - names = '[' + ', '.join(names) + ']' - values = '[' + ', '.join(values) + ']' + names = "[" + ", ".join(names) + "]" + values = "[" + ", ".join(values) + "]" # Write code to prepare for kwargs if node.kwargs_node: - code.append(self.lf('%s = {};' % node.kwargs_node.name)) + code.append(self.lf("%s = {};" % node.kwargs_node.name)) if node.kwarg_nodes: - values_var = self.dummy('kw_values') + values_var = self.dummy("kw_values") kw_argnames.add(values_var) - code += [self.lf(values_var), ' = ', values, ';'] + code += [self.lf(values_var), " = ", values, ";"] else: values_var = values # Enter if to actually parse kwargs - code.append(self.lf( - "if (arguments.length == 1 && typeof arguments[0] == 'object' && " - "Object.keys(arguments[0]).toString() == 'flx_args,flx_kwargs') {")) + code.append( + self.lf( + "if (arguments.length == 1 && typeof arguments[0] == 'object' && " + "Object.keys(arguments[0]).toString() == 'flx_args,flx_kwargs') {" + ) + ) self._indent += 1 # Call function to parse args code += [self.lf()] if node.kwargs_node: kw_argnames.add(node.kwargs_node.name) self.vars.add(node.kwargs_node.name) - code += [node.kwargs_node.name, ' = '] - self.use_std_function('op_parse_kwargs', []) - code += [stdlib.FUNCTION_PREFIX + 'op_parse_kwargs(', - names, ', ', values_var, ', arguments[0].flx_kwargs'] + code += [node.kwargs_node.name, " = "] + self.use_std_function("op_parse_kwargs", []) + code += [ + stdlib.FUNCTION_PREFIX + "op_parse_kwargs(", + names, + ", ", + values_var, + ", arguments[0].flx_kwargs", + ] if not node.kwargs_node: - code.append(", '%s'" % func_name or 'anonymous') - code.append(');') + code.append(", '%s'" % func_name or "anonymous") + code.append(");") # Apply values of positional args # inside if, because standard arguments are invalid - args_var = 'arguments[0].flx_args' + args_var = "arguments[0].flx_args" if len(argnames) > 1: - args_var = self.dummy('args') - code.append(self.lf('%s = arguments[0].flx_args;' % args_var)) + args_var = self.dummy("args") + code.append(self.lf("%s = arguments[0].flx_args;" % args_var)) for i, name in enumerate(argnames): - code.append(self.lf('%s = %s[%i];' % (name, args_var, i))) + code.append(self.lf("%s = %s[%i];" % (name, args_var, i))) # End if if vararg_code2: code.append(self.lf(vararg_code2)) self._indent -= 1 - code.append(self.lf('}')) + code.append(self.lf("}")) if vararg_code1: - code += [' else {', vararg_code1, '}'] + code += [" else {", vararg_code1, "}"] # Apply values of keyword-only args # outside if, because these need to be assigned always # Note that we cannot use destructuring assignment because not all # browsers support it (meh IE and Safari!) for i, arg in enumerate(node.kwarg_nodes): - code.append(self.lf('%s = %s[%i];' % (arg.name, values_var, i))) + code.append(self.lf("%s = %s[%i];" % (arg.name, values_var, i))) else: if vararg_code1: code.append(self.lf(vararg_code1)) - + # Apply defaults of positional arguments for arg in node.arg_nodes: if arg.value_node is not None: name = arg.name - d = ''.join(self.parse(arg.value_node)) - x = '%s = (%s === undefined) ? %s: %s;' % (name, name, d, name) + d = "".join(self.parse(arg.value_node)) + x = "%s = (%s === undefined) ? %s: %s;" % (name, name, d, name) code.append(self.lf(x)) - + # Apply content if lambda_: - code.append('return ') + code.append("return ") code += self.parse(node.body_node) - code.append(';') + code.append(";") else: docstring = self.pop_docstring(node) if docstring and not node.body_nodes: @@ -987,13 +1080,13 @@ def parse_FunctionDef(self, node, lambda_=False, asyn=False): # Normal function if self._docstrings: for line in docstring.splitlines(): - code.append(self.lf('// ' + line)) + code.append(self.lf("// " + line)) for child in node.body_nodes: code += self.parse(child) - + # Wrap up if lambda_: - code.append('}%s' % binder) + code.append("}%s" % binder) # ns should only consist only of arg names (or helpers) for name in argnames: self.vars.discard(name) @@ -1003,129 +1096,128 @@ def parse_FunctionDef(self, node, lambda_=False, asyn=False): assert set(ns) == kw_argnames pre_code.append(self.get_declarations(ns)) else: - if not (code and code[-1].strip().startswith('return ')): - code.append(self.lf('return null;')) + if not (code and code[-1].strip().startswith("return ")): + code.append(self.lf("return null;")) # Declare vars, but exclude our argnames for name in argnames: self.vars.discard(name) ns = self.pop_stack() pre_code.append(self.get_declarations(ns)) - + self._indent -= 1 if not lambda_: - code.append(self.lf('}%s;\n' % binder)) + code.append(self.lf("}%s;\n" % binder)) return pre_code + code - + def parse_Lambda(self, node): return self.parse_FunctionDef(node, True) - + def parse_AsyncFunctionDef(self, node): return self.parse_FunctionDef(node, False, True) - + def parse_Return(self, node): if node.value_node is not None: - return self.lf('return %s;' % ''.join(self.parse(node.value_node))) + return self.lf("return %s;" % "".join(self.parse(node.value_node))) else: return self.lf("return null;") - + def parse_ClassDef(self, node): - # Checks if len(node.arg_nodes) > 1: - raise JSError('Multiple inheritance not (yet) supported.') + raise JSError("Multiple inheritance not (yet) supported.") if node.kwarg_nodes: - raise JSError('Metaclasses not supported.') + raise JSError("Metaclasses not supported.") if node.decorator_nodes: - raise JSError('Class decorators not supported.') - + raise JSError("Class decorators not supported.") + # Get base class (not the constructor) - base_class = 'Object' + base_class = "Object" if node.arg_nodes: - base_class = ''.join(self.parse(node.arg_nodes[0])) - if not base_class.replace('.', '_').isalnum(): - raise JSError('Base classes must be simple names') - elif base_class.lower() == 'object': # maybe Python "object" - base_class = 'Object' + base_class = "".join(self.parse(node.arg_nodes[0])) + if not base_class.replace(".", "_").isalnum(): + raise JSError("Base classes must be simple names") + elif base_class.lower() == "object": # maybe Python "object" + base_class = "Object" else: - base_class = base_class + '.prototype' - + base_class = base_class + ".prototype" + # Define function that acts as class constructor code = [] - docstring = self.pop_docstring(node) - docstring = docstring if self._docstrings else '' + docstring = self.pop_docstring(node) + docstring = docstring if self._docstrings else "" for line in get_class_definition(node.name, base_class, docstring): code.append(self.lf(line)) - self.use_std_function('op_instantiate', []) - + self.use_std_function("op_instantiate", []) + # Body ... self.vars.add(node.name) self._seen_class_names.add(node.name) - self.push_stack('class', node.name) + self.push_stack("class", node.name) for sub in node.body_nodes: code += self.parse(sub) - code.append('\n') + code.append("\n") self.pop_stack() # no need to declare variables, because they're prefixed - + return code - + def function_super(self, node): # allow using super() in methods # Note that in parse_Call() we ensure that a call using super # uses .call(this, ...) so that the instance is handled ok. - + if node.arg_nodes: - #raise JSError('super() accepts 0 or 1 arguments.') + # raise JSError('super() accepts 0 or 1 arguments.') pass # In Python 2, arg nodes are provided, and we ignore them if len(self._stack) < 3: # module, class, function - #raise JSError('can only use super() inside a method.') + # raise JSError('can only use super() inside a method.') # We just provide "super()" and hope that the user will # replace the code (as we do in the Model class). - return 'super()' - + return "super()" + # Find the class of this function. Using this._base_class would work # in simple situations, but not when there's two levels of super(). nstype1, nsname1, _ = self._stack[-1] nstype2, nsname2, _ = self._stack[-2] - if not (nstype1 == 'function' and nstype2 == 'class'): - raise JSError('can only use super() inside a method.') - + if not (nstype1 == "function" and nstype2 == "class"): + raise JSError("can only use super() inside a method.") + base_class = nsname2 - return '%s.prototype._base_class' % base_class - - #def parse_Yield - #def parse_YieldFrom - + return "%s.prototype._base_class" % base_class + + # def parse_Yield + # def parse_YieldFrom + def parse_Await(self, node): - return 'await %s' % ''.join(self.parse(node.value_node)) - + return "await %s" % "".join(self.parse(node.value_node)) + def parse_Global(self, node): for name in node.names: self.vars.set_global(name) - return '' - + return "" + def parse_Nonlocal(self, node): for name in node.names: self.vars.set_nonlocal(name) - return '' + return "" -def get_class_definition(name, base='Object', docstring=''): - """ Get a list of lines that defines a class in JS. +def get_class_definition(name, base="Object", docstring=""): + """Get a list of lines that defines a class in JS. Used in the parser as well as by flexx.app.Component. """ code = [] - - code.append('%s = function () {' % name) + + code.append("%s = function () {" % name) for line in docstring.splitlines(): - code.append(' // ' + line) - code.append(' %sop_instantiate(this, arguments);' % stdlib.FUNCTION_PREFIX) - code.append('}') - - if base != 'Object': - code.append('%s.prototype = Object.create(%s);' % (name, base)) - code.append('%s.prototype._base_class = %s;' % (name, base)) - code.append('%s.prototype.__name__ = %s;' % (name, reprs(name.split('.')[-1]))) - - code.append('') + code.append(" // " + line) + code.append(" %sop_instantiate(this, arguments);" % stdlib.FUNCTION_PREFIX) + code.append("}") + + if base != "Object": + code.append("%s.prototype = Object.create(%s);" % (name, base)) + code.append("%s.prototype._base_class = %s;" % (name, base)) + code.append("%s.prototype.__name__ = %s;" % (name, reprs(name.split(".")[-1]))) + + code.append("") return code diff --git a/pscript/parser3.py b/pscript/parser3.py index 1c49afe5..69f1aa19 100644 --- a/pscript/parser3.py +++ b/pscript/parser3.py @@ -16,33 +16,33 @@ # "self" is replaced with "this" self.foo - + # Printing just works print('some test') print(a, b, c, sep='-') - + # Getting the length of a string or array len(foo) - + # Rounding and abs round(foo) # round to nearest integer int(foo) # round towards 0 as in Python abs(foo) - + # min and max min(foo) min(a, b, c) max(foo) max(a, b, c) - + # divmod a, b = divmod(100, 7) # -> 14, 2 - + # Aggregation sum(foo) all(foo) any(foo) - + # Turning things into numbers, bools and strings str(s) float(x) @@ -50,7 +50,7 @@ int(z) # this rounds towards zero like in Python chr(65) # -> 'A' ord('A') # -> 65 - + # Turning things into lists and dicts dict([['foo', 1], ['bar', 2]]) # -> {'foo': 1, 'bar': 2} list('abc') # -> ['a', 'b', 'c'] @@ -72,22 +72,22 @@ isinstance([], list) isinstance({}, dict) isinstance(foo, types.FunctionType) - + # Can also use JS strings isinstance(3, 'number') isinstance('', 'string') isinstance([], 'array') isinstance({}, 'object') isinstance(foo, 'function') - + # You can use it on your own types too ... isinstance(x, MyClass) isinstance(x, 'MyClass') # equivalent isinstance(x, 'Object') # also yields true (subclass of Object) - + # issubclass works too issubclass(Foo, Bar) - + # As well as callable callable(foo) @@ -96,20 +96,20 @@ ------------------------------------- .. pscript_example:: - + a = {'foo': 1, 'bar': 2} - + hasattr(a, 'foo') # -> True hasattr(a, 'fooo') # -> False hasattr(null, 'foo') # -> False - + getattr(a, 'foo') # -> 1 getattr(a, 'fooo') # -> raise AttributeError getattr(a, 'fooo', 3) # -> 3 getattr(null, 'foo', 3) # -> 3 - + setattr(a, 'foo', 2) - + delattr(a, 'foo') @@ -117,16 +117,16 @@ ------------------ .. pscript_example:: - + range(10) range(2, 10, 2) range(100, 0, -1) - + reversed(foo) sorted(foo) enumerate(foo) zip(foo, bar) - + filter(func, foo) map(func, foo) @@ -138,7 +138,7 @@ # Call a.append() if it exists, otherwise a.push() a.append(x) - + # Similar for remove() a.remove(x) @@ -147,7 +147,7 @@ ------------ .. pscript_example:: - + a = {'foo': 3} a['foo'] a.get('foo', 0) @@ -170,14 +170,14 @@ When writing PScript inside Python modules, we recommend that where specific JavaScript functionality is used, that the references are -prefixed with ``window.`` Where ``window`` represents the global JS +prefixed with ``window.`` Where ``window`` represents the global JS namespace. All global JavaScript objects, functions, and variables automatically become members of the ``window`` object. This helps make it clear that the functionality is specific to JS, and also helps static code analysis tools like flake8. .. pscript_example:: - + from pscript import window # this is a stub def foo(a): return window.Math.cos(a) @@ -189,7 +189,7 @@ def foo(a): from . import commonast as ast from . import stdlib -from .parser2 import Parser2, JSError, unify # noqa +from .parser2 import Parser2, JSError, unify from .stubs import RawJS @@ -206,257 +206,284 @@ def foo(a): class Parser3(Parser2): - """ Parser to transcompile Python to JS, allowing more Pythonic + """Parser to transcompile Python to JS, allowing more Pythonic code, like ``self``, ``print()``, ``len()``, list methods, etc. """ - + def function_this_is_js(self, node): # Note that we handle this_is_js() shortcuts in the if-statement # directly. This replacement with a string is when this_is_js() # is used outside an if statement. if len(node.arg_nodes) != 0: - raise JSError('this_is_js() expects zero arguments.') - return ('"this_is_js()"') - + raise JSError("this_is_js() expects zero arguments.") + return '"this_is_js()"' + def function_RawJS(self, node): if len(node.arg_nodes) == 1: if not isinstance(node.arg_nodes[0], ast.Str): - raise JSError('RawJS needs a verbatim string (use multiple ' - 'args to bypass PScript\'s RawJS).') + raise JSError( + "RawJS needs a verbatim string (use multiple " + "args to bypass PScript's RawJS)." + ) lines = RawJS._str2lines(node.arg_nodes[0].value.strip()) - nl = '\n' + (self._indent * 4) * ' ' + nl = "\n" + (self._indent * 4) * " " return nl.join(lines) else: return None # maybe RawJS is a thing - + ## Python builtin functions - - + def function_isinstance(self, node): if len(node.arg_nodes) != 2: - raise JSError('isinstance() expects two arguments.') - + raise JSError("isinstance() expects two arguments.") + ob = unify(self.parse(node.arg_nodes[0])) cls = unify(self.parse(node.arg_nodes[1])) - if cls[0] in '"\'': + if cls[0] in "\"'": cls = cls[1:-1] # remove quotes - - BASIC_TYPES = ('number', 'boolean', 'string', 'function', 'array', - 'object', 'null', 'undefined') - - MAP = {'[int, float]': 'number', '[float, int]': 'number', 'float': 'number', - 'str': 'string', 'basestring': 'string', 'string_types': 'string', - 'bool': 'boolean', - 'FunctionType': 'function', 'types.FunctionType': 'function', - 'list': 'array', 'tuple': 'array', - '[list, tuple]': 'array', '[tuple, list]': 'array', - 'dict': 'object', + + BASIC_TYPES = ( + "number", + "boolean", + "string", + "function", + "array", + "object", + "null", + "undefined", + ) + + MAP = { + "[int, float]": "number", + "[float, int]": "number", + "float": "number", + "str": "string", + "string_types": "string", + "bool": "boolean", + "FunctionType": "function", + "types.FunctionType": "function", + "list": "array", + "tuple": "array", + "[list, tuple]": "array", + "[tuple, list]": "array", + "dict": "object", } - + cmp = MAP.get(cls, cls) - - if cmp == 'array': - return ['Array.isArray(', ob, ')'] + + if cmp == "array": + return ["Array.isArray(", ob, ")"] elif cmp.lower() in BASIC_TYPES: # Basic type, use Object.prototype.toString - return ["Object.prototype.toString.call(", ob , - ").slice(8,-1).toLowerCase() === '%s'" % cmp.lower()] + return [ + "Object.prototype.toString.call(", + ob, + ").slice(8,-1).toLowerCase() === '%s'" % cmp.lower(), + ] # In http://stackoverflow.com/questions/11108877 the following is # proposed, which might be better in theory, but is > 50% slower - return ["({}).toString.call(", - ob, - r").match(/\s([a-zA-Z]+)/)[1].toLowerCase() === ", - "'%s'" % cmp.lower() - ] + return [ + "({}).toString.call(", + ob, + r").match(/\s([a-zA-Z]+)/)[1].toLowerCase() === ", + "'%s'" % cmp.lower(), + ] else: # User defined type, use instanceof # http://tobyho.com/2011/01/28/checking-types-in-javascript/ cmp = unify(cls) - if cmp[0] == '(': - raise JSError('isinstance() can only compare to simple types') + if cmp[0] == "(": + raise JSError("isinstance() can only compare to simple types") return ob, " instanceof ", cmp - + def function_issubclass(self, node): # issubclass only needs to work on custom classes if len(node.arg_nodes) != 2: - raise JSError('issubclass() expects two arguments.') - + raise JSError("issubclass() expects two arguments.") + cls1 = unify(self.parse(node.arg_nodes[0])) cls2 = unify(self.parse(node.arg_nodes[1])) - if cls2 == 'object': - cls2 = 'Object' - return '(%s.prototype instanceof %s)' % (cls1, cls2) - + if cls2 == "object": + cls2 = "Object" + return "(%s.prototype instanceof %s)" % (cls1, cls2) + def function_print(self, node): # Process keywords - sep, end = '" "', '' + sep, end = '" "', "" for kw in node.kwarg_nodes: - if kw.name == 'sep': - sep = ''.join(self.parse(kw.value_node)) - elif kw.name == 'end': - end = ''.join(self.parse(kw.value_node)) - elif kw.name in ('file', 'flush'): - raise JSError('print() file and flush args not supported') + if kw.name == "sep": + sep = "".join(self.parse(kw.value_node)) + elif kw.name == "end": + end = "".join(self.parse(kw.value_node)) + elif kw.name in ("file", "flush"): + raise JSError("print() file and flush args not supported") else: - raise JSError('Invalid argument for print(): %r' % kw.name) - + raise JSError("Invalid argument for print(): %r" % kw.name) + # Combine args args = [unify(self.parse(arg)) for arg in node.arg_nodes] - end = (" + %s" % end) if (args and end and end != '\n') else '' - combiner = ' + %s + ' % sep + end = (" + %s" % end) if (args and end and end != "\n") else "" + combiner = " + %s + " % sep args_concat = combiner.join(args) or '""' - return 'console.log(' + args_concat + end + ')' - + return "console.log(" + args_concat + end + ")" + def function_len(self, node): if len(node.arg_nodes) == 1: - return unify(self.parse(node.arg_nodes[0])), '.length' + return unify(self.parse(node.arg_nodes[0])), ".length" else: return None # don't apply this feature - + def function_max(self, node): if len(node.arg_nodes) == 0: - raise JSError('max() needs at least one argument') + raise JSError("max() needs at least one argument") elif len(node.arg_nodes) == 1: - arg = ''.join(self.parse(node.arg_nodes[0])) - return 'Math.max.apply(null, ', arg, ')' + arg = "".join(self.parse(node.arg_nodes[0])) + return "Math.max.apply(null, ", arg, ")" else: - args = ', '.join([unify(self.parse(arg)) for arg in node.arg_nodes]) - return 'Math.max(', args, ')' - + args = ", ".join([unify(self.parse(arg)) for arg in node.arg_nodes]) + return "Math.max(", args, ")" + def function_min(self, node): if len(node.arg_nodes) == 0: - raise JSError('min() needs at least one argument') + raise JSError("min() needs at least one argument") elif len(node.arg_nodes) == 1: - arg = ''.join(self.parse(node.arg_nodes[0])) - return 'Math.min.apply(null, ', arg, ')' + arg = "".join(self.parse(node.arg_nodes[0])) + return "Math.min.apply(null, ", arg, ")" else: - args = ', '.join([unify(self.parse(arg)) for arg in node.arg_nodes]) - return 'Math.min(', args, ')' - + args = ", ".join([unify(self.parse(arg)) for arg in node.arg_nodes]) + return "Math.min(", args, ")" + def function_callable(self, node): if len(node.arg_nodes) == 1: arg = unify(self.parse(node.arg_nodes[0])) return '(typeof %s === "function")' % arg else: - raise JSError('callable() needs at least one argument') - + raise JSError("callable() needs at least one argument") + def function_chr(self, node): if len(node.arg_nodes) == 1: - arg = ''.join(self.parse(node.arg_nodes[0])) - return 'String.fromCharCode(%s)' % arg + arg = "".join(self.parse(node.arg_nodes[0])) + return "String.fromCharCode(%s)" % arg else: - raise JSError('chr() needs at least one argument') - + raise JSError("chr() needs at least one argument") + def function_ord(self, node): if len(node.arg_nodes) == 1: - arg = ''.join(self.parse(node.arg_nodes[0])) - return '%s.charCodeAt(0)' % arg + arg = "".join(self.parse(node.arg_nodes[0])) + return "%s.charCodeAt(0)" % arg else: - raise JSError('ord() needs at least one argument') - + raise JSError("ord() needs at least one argument") + def function_dict(self, node): if len(node.arg_nodes) == 0: - kwargs = ['%s:%s' % (arg.name, unify(self.parse(arg.value_node))) - for arg in node.kwarg_nodes] - return '{%s}' % ', '.join(kwargs) + kwargs = [ + "%s:%s" % (arg.name, unify(self.parse(arg.value_node))) + for arg in node.kwarg_nodes + ] + return "{%s}" % ", ".join(kwargs) if len(node.arg_nodes) == 1: - return self.use_std_function('dict', node.arg_nodes) + return self.use_std_function("dict", node.arg_nodes) else: - raise JSError('dict() needs at least one argument') - + raise JSError("dict() needs at least one argument") + def function_list(self, node): if len(node.arg_nodes) == 0: - return '[]' + return "[]" if len(node.arg_nodes) == 1: - return self.use_std_function('list', node.arg_nodes) + return self.use_std_function("list", node.arg_nodes) else: - raise JSError('list() needs at least one argument') - + raise JSError("list() needs at least one argument") + def function_tuple(self, node): return self.function_list(node) - + def function_range(self, node): if len(node.arg_nodes) == 1: args = ast.Num(0), node.arg_nodes[0], ast.Num(1) - return self.use_std_function('range', args) + return self.use_std_function("range", args) elif len(node.arg_nodes) == 2: args = node.arg_nodes[0], node.arg_nodes[1], ast.Num(1) - return self.use_std_function('range', args) + return self.use_std_function("range", args) elif len(node.arg_nodes) == 3: - return self.use_std_function('range', node.arg_nodes) + return self.use_std_function("range", node.arg_nodes) else: - raise JSError('range() needs 1, 2 or 3 arguments') - + raise JSError("range() needs 1, 2 or 3 arguments") + def function_sorted(self, node): if len(node.arg_nodes) == 1: - key, reverse = ast.Name('undefined'), ast.NameConstant(False) + key, reverse = ast.Name("undefined"), ast.NameConstant(False) for kw in node.kwarg_nodes: - if kw.name == 'key': + if kw.name == "key": key = kw.value_node - elif kw.name == 'reverse': + elif kw.name == "reverse": reverse = kw.value_node else: - raise JSError('Invalid keyword argument for sorted: %r' % kw.name) - return self.use_std_function('sorted', [node.arg_nodes[0], key, reverse]) + raise JSError("Invalid keyword argument for sorted: %r" % kw.name) + return self.use_std_function("sorted", [node.arg_nodes[0], key, reverse]) else: - raise JSError('sorted() needs one argument') - + raise JSError("sorted() needs one argument") + ## Methods of list/dict/str - + def method_sort(self, node, base): if len(node.arg_nodes) == 0: # sorts args are keyword-only - key, reverse = ast.Name('undefined'), ast.NameConstant(False) + key, reverse = ast.Name("undefined"), ast.NameConstant(False) for kw in node.kwarg_nodes: - if kw.name == 'key': + if kw.name == "key": key = kw.value_node - elif kw.name == 'reverse': + elif kw.name == "reverse": reverse = kw.value_node else: - raise JSError('Invalid keyword argument for sort: %r' % kw.name) - return self.use_std_method(base, 'sort', [key, reverse]) - + raise JSError("Invalid keyword argument for sort: %r" % kw.name) + return self.use_std_method(base, "sort", [key, reverse]) + def method_format(self, node, base): if node.kwarg_nodes: - raise JSError('Method format() does not support keyword args.') - return self.use_std_method(base, 'format', node.arg_nodes) + raise JSError("Method format() does not support keyword args.") + return self.use_std_method(base, "format", node.arg_nodes) # Add functions and methods to the class, using the stdib functions ... + def make_function(name, nargs, function_deps, method_deps): def function_X(self, node): if node.kwarg_nodes: - raise JSError('Function %s does not support keyword args.' % name) + raise JSError("Function %s does not support keyword args." % name) if len(node.arg_nodes) not in nargs: - raise JSError('Function %s needs #args in %r.' % (name, nargs)) + raise JSError("Function %s needs #args in %r." % (name, nargs)) for dep in function_deps: self.use_std_function(dep, []) for dep in method_deps: - self.use_std_method('x', dep, []) + self.use_std_method("x", dep, []) return self.use_std_function(name, node.arg_nodes) + return function_X + def make_method(name, nargs, function_deps, method_deps): def method_X(self, node, base): if node.kwarg_nodes: - raise JSError('Method %s does not support keyword args.' % name) + raise JSError("Method %s does not support keyword args." % name) if len(node.arg_nodes) not in nargs: return None # call as-is, don't use our variant for dep in function_deps: self.use_std_function(dep, []) for dep in method_deps: - self.use_std_method('x', dep, []) + self.use_std_method("x", dep, []) return self.use_std_method(base, name, node.arg_nodes) + return method_X + for name, code in stdlib.METHODS.items(): nargs, function_deps, method_deps = stdlib.get_std_info(code) - if nargs and not hasattr(Parser3, 'method_' + name): + if nargs and not hasattr(Parser3, "method_" + name): m = make_method(name, tuple(nargs), function_deps, method_deps) - setattr(Parser3, 'method_' + name, m) + setattr(Parser3, "method_" + name, m) for name, code in stdlib.FUNCTIONS.items(): nargs, function_deps, method_deps = stdlib.get_std_info(code) - if nargs and not hasattr(Parser3, 'function_' + name): + if nargs and not hasattr(Parser3, "function_" + name): m = make_function(name, tuple(nargs), function_deps, method_deps) - setattr(Parser3, 'function_' + name, m) + setattr(Parser3, "function_" + name, m) diff --git a/pscript/stdlib.py b/pscript/stdlib.py index 9bd21384..8d34784b 100644 --- a/pscript/stdlib.py +++ b/pscript/stdlib.py @@ -15,21 +15,21 @@ FUNCTIONS = {} METHODS = {} -FUNCTION_PREFIX = '_pyfunc_' -METHOD_PREFIX = '_pymeth_' +FUNCTION_PREFIX = "_pyfunc_" +METHOD_PREFIX = "_pymeth_" def get_std_info(code): - """ Given the JS code for a std function or method, determine the + """Given the JS code for a std function or method, determine the number of arguments, function_deps and method_deps. """ - _, _, nargs = code.splitlines()[0].partition('nargs:') - nargs = [int(i.strip()) for i in nargs.strip().replace(',', ' ').split(' ') if i] + _, _, nargs = code.splitlines()[0].partition("nargs:") + nargs = [int(i.strip()) for i in nargs.strip().replace(",", " ").split(" ") if i] # Collect dependencies on other funcs/methods sep = FUNCTION_PREFIX - function_deps = [part.split('(')[0].strip() for part in code.split(sep)[1:]] + function_deps = [part.split("(")[0].strip() for part in code.split(sep)[1:]] sep = METHOD_PREFIX - method_deps = [part.split('.')[0].strip() for part in code.split(sep)[1:]] + method_deps = [part.split(".")[0].strip() for part in code.split(sep)[1:]] # Reduce and sort function_deps = sorted(set(function_deps)) method_deps = sorted(set(method_deps)) @@ -45,14 +45,14 @@ def get_std_info(code): return nargs, sorted(function_deps), sorted(method_deps) + def _update_deps(code, function_deps, method_deps): - """ Given the code of a dependency, recursively resolve additional dependencies. - """ + """Given the code of a dependency, recursively resolve additional dependencies.""" # Collect deps sep = FUNCTION_PREFIX - new_function_deps = [part.split('(')[0].strip() for part in code.split(sep)[1:]] + new_function_deps = [part.split("(")[0].strip() for part in code.split(sep)[1:]] sep = METHOD_PREFIX - new_method_deps = [part.split('.')[0].strip() for part in code.split(sep)[1:]] + new_method_deps = [part.split(".")[0].strip() for part in code.split(sep)[1:]] # Update new_function_deps = set(new_function_deps).difference(function_deps) new_method_deps = set(new_method_deps).difference(method_deps) @@ -66,33 +66,34 @@ def _update_deps(code, function_deps, method_deps): return function_deps, method_deps -def get_partial_std_lib(func_names, method_names, indent=0, - func_prefix=None, method_prefix=None): - """ Get the code for the PScript standard library consisting of +def get_partial_std_lib( + func_names, method_names, indent=0, func_prefix=None, method_prefix=None +): + """Get the code for the PScript standard library consisting of the given function and method names. The given indent specifies how many sets of 4 spaces to prepend. """ - func_prefix = 'var ' + FUNCTION_PREFIX if (func_prefix is None) else func_prefix - method_prefix = 'var ' + METHOD_PREFIX if (method_prefix is None) else method_prefix + func_prefix = "var " + FUNCTION_PREFIX if (func_prefix is None) else func_prefix + method_prefix = "var " + METHOD_PREFIX if (method_prefix is None) else method_prefix lines = [] for name in sorted(func_names): code = FUNCTIONS[name].strip() - if '\n' not in code: - code = code.rsplit('//', 1)[0].rstrip() # strip comment from one-liners - lines.append('%s%s = %s;' % (func_prefix, name, code)) + if "\n" not in code: + code = code.rsplit("//", 1)[0].rstrip() # strip comment from one-liners + lines.append("%s%s = %s;" % (func_prefix, name, code)) for name in sorted(method_names): code = METHODS[name].strip() # lines.append('Object.prototype.%s%s = %s;' % (METHOD_PREFIX, name, code)) - lines.append('%s%s = %s;' % (method_prefix, name, code)) - code = '\n'.join(lines) + lines.append("%s%s = %s;" % (method_prefix, name, code)) + code = "\n".join(lines) if indent: - lines = [' '*indent + line for line in code.splitlines()] - code = '\n'.join(lines) + lines = [" " * indent + line for line in code.splitlines()] + code = "\n".join(lines) return code def get_full_std_lib(indent=0): - """ Get the code for the full PScript standard library. + """Get the code for the full PScript standard library. The given indent specifies how many sets of 4 spaces to prepend. If the full stdlib is made available in JavaScript, multiple @@ -105,26 +106,27 @@ def get_full_std_lib(indent=0): # todo: now that we have modules, we can have shorter/no prefixes, right? # -> though maybe we use them for string replacement somewhere? def get_all_std_names(): - """ Get list if function names and methods names in std lib. - """ - return ([FUNCTION_PREFIX + f for f in FUNCTIONS], - [METHOD_PREFIX + f for f in METHODS]) + """Get list if function names and methods names in std lib.""" + return ( + [FUNCTION_PREFIX + f for f in FUNCTIONS], + [METHOD_PREFIX + f for f in METHODS], + ) ## ----- Functions ## Special functions: not really in builtins, but important enough to support -FUNCTIONS['perf_counter'] = """function() { // nargs: 0 +FUNCTIONS["perf_counter"] = """function() { // nargs: 0 if (typeof(process) === "undefined"){return performance.now()*1e-3;} else {var t = process.hrtime(); return t[0] + t[1]*1e-9;} }""" # Work in nodejs and browser -FUNCTIONS['time'] = """function () {return Date.now() / 1000;} // nargs: 0""" +FUNCTIONS["time"] = """function () {return Date.now() / 1000;} // nargs: 0""" ## Hardcore functions -FUNCTIONS['op_instantiate'] = """function (ob, args) { // nargs: 2 +FUNCTIONS["op_instantiate"] = """function (ob, args) { // nargs: 2 if ((typeof ob === "undefined") || (typeof window !== "undefined" && window === ob) || (typeof global !== "undefined" && global === ob)) @@ -141,13 +143,13 @@ def get_all_std_names(): } }""" -FUNCTIONS['create_dict'] = """function () { +FUNCTIONS["create_dict"] = """function () { var d = {}; for (var i=0; i= 0) { @@ -297,60 +299,60 @@ def get_all_std_names(): ## Normal functions -FUNCTIONS['pow'] = 'Math.pow // nargs: 2' +FUNCTIONS["pow"] = "Math.pow // nargs: 2" -FUNCTIONS['sum'] = """function (x) { // nargs: 1 +FUNCTIONS["sum"] = """function (x) { // nargs: 1 return x.reduce(function(a, b) {return a + b;}); }""" -FUNCTIONS['round'] = 'Math.round // nargs: 1' +FUNCTIONS["round"] = "Math.round // nargs: 1" -FUNCTIONS['int'] = """function (x, base) { // nargs: 1 2 +FUNCTIONS["int"] = """function (x, base) { // nargs: 1 2 if(base !== undefined) return parseInt(x, base); return x<0 ? Math.ceil(x): Math.floor(x); }""" -FUNCTIONS['float'] = 'Number // nargs: 1' +FUNCTIONS["float"] = "Number // nargs: 1" -FUNCTIONS['str'] = 'String // nargs: 0 1' +FUNCTIONS["str"] = "String // nargs: 0 1" # Note use of "_IS_COMPONENT" to check for flexx.app component classes. -FUNCTIONS['repr'] = """function (x) { // nargs: 1 +FUNCTIONS["repr"] = """function (x) { // nargs: 1 var res; try { res = JSON.stringify(x); } catch (e) { res = undefined; } if (typeof res === 'undefined') { res = x._IS_COMPONENT ? x.id : String(x); } return res; }""" -FUNCTIONS['bool'] = """function (x) { // nargs: 1 +FUNCTIONS["bool"] = """function (x) { // nargs: 1 return Boolean(FUNCTION_PREFIXtruthy(x)); }""" -FUNCTIONS['abs'] = 'Math.abs // nargs: 1' +FUNCTIONS["abs"] = "Math.abs // nargs: 1" -FUNCTIONS['divmod'] = """function (x, y) { // nargs: 2 +FUNCTIONS["divmod"] = """function (x, y) { // nargs: 2 var m = x % y; return [(x-m)/y, m]; }""" -FUNCTIONS['all'] = """function (x) { // nargs: 1 +FUNCTIONS["all"] = """function (x) { // nargs: 1 for (var i=0; ib) {return 1;} return 0;}; @@ -381,13 +383,13 @@ def get_all_std_names(): return iter; }""" -FUNCTIONS['filter'] = """function (func, iter) { // nargs: 2 +FUNCTIONS["filter"] = """function (func, iter) { // nargs: 2 if (typeof func === "undefined" || func === null) {func = function(x) {return x;}} if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);} return iter.filter(func); }""" -FUNCTIONS['map'] = """function (func, iter) { // nargs: 2 +FUNCTIONS["map"] = """function (func, iter) { // nargs: 2 if (typeof func === "undefined" || func === null) {func = function(x) {return x;}} if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);} return iter.map(func); @@ -395,7 +397,7 @@ def get_all_std_names(): ## Other / Helper functions -FUNCTIONS['truthy'] = """function (v) { +FUNCTIONS["truthy"] = """function (v) { if (v === null || typeof v !== "object") {return v;} else if (v.length !== undefined) {return v.length ? v : false;} else if (v.byteLength !== undefined) {return v.byteLength ? v : false;} @@ -403,7 +405,7 @@ def get_all_std_names(): else {return Object.getOwnPropertyNames(v).length ? v : false;} }""" -FUNCTIONS['op_equals'] = """function op_equals (a, b) { // nargs: 2 +FUNCTIONS["op_equals"] = """function op_equals (a, b) { // nargs: 2 var a_type = typeof a; // If a (or b actually) is of type string, number or boolean, we don't need // to do all the other type checking below. @@ -426,7 +428,7 @@ def get_all_std_names(): } return a == b; }""" -FUNCTIONS['op_contains'] = """function op_contains (a, b) { // nargs: 2 +FUNCTIONS["op_contains"] = """function op_contains (a, b) { // nargs: 2 if (b == null) { } else if (Array.isArray(b)) { for (var i=0; ib) {return 1;} return 0;}; @@ -502,7 +504,7 @@ def get_all_std_names(): ## List and dict -METHODS['clear'] = """function () { // nargs: 0 +METHODS["clear"] = """function () { // nargs: 0 if (Array.isArray(this)) { this.splice(0, this.length); } else if (this.constructor === Object) { @@ -511,7 +513,7 @@ def get_all_std_names(): } else return this.KEY.apply(this, arguments); }""" -METHODS['copy'] = """function () { // nargs: 0 +METHODS["copy"] = """function () { // nargs: 0 if (Array.isArray(this)) { return this.slice(0); } else if (this.constructor === Object) { @@ -521,7 +523,7 @@ def get_all_std_names(): } else return this.KEY.apply(this, arguments); }""" -METHODS['pop'] = """function (i, d) { // nargs: 1 2 +METHODS["pop"] = """function (i, d) { // nargs: 1 2 if (Array.isArray(this)) { i = (i === undefined) ? -1 : i; i = (i < 0) ? (this.length + i) : i; @@ -539,7 +541,7 @@ def get_all_std_names(): ## List and str # start and stop nor supported for list on Python, but for simplicity, we do -METHODS['count'] = """function (x, start, stop) { // nargs: 1 2 3 +METHODS["count"] = """function (x, start, stop) { // nargs: 1 2 3 start = (start === undefined) ? 0 : start; stop = (stop === undefined) ? this.length : stop; start = Math.max(0, ((start < 0) ? this.length + start : start)); @@ -560,7 +562,7 @@ def get_all_std_names(): } else return this.KEY.apply(this, arguments); }""" -METHODS['index'] = """function (x, start, stop) { // nargs: 1 2 3 +METHODS["index"] = """function (x, start, stop) { // nargs: 1 2 3 start = (start === undefined) ? 0 : start; stop = (stop === undefined) ? this.length : stop; start = Math.max(0, ((start < 0) ? this.length + start : start)); @@ -580,26 +582,26 @@ def get_all_std_names(): # note: fromkeys is a classmethod, and we dont support it. -METHODS['get'] = """function (key, d) { // nargs: 1 2 +METHODS["get"] = """function (key, d) { // nargs: 1 2 if (this.constructor !== Object) return this.KEY.apply(this, arguments); if (this[key] !== undefined) {return this[key];} else if (d !== undefined) {return d;} else {return null;} }""" -METHODS['items'] = """function () { // nargs: 0 +METHODS["items"] = """function () { // nargs: 0 if (this.constructor !== Object) return this.KEY.apply(this, arguments); var key, keys = Object.keys(this), res = [] for (var i=0; i= 0; }""" -METHODS['expandtabs'] = """function (tabsize) { // nargs: 0 1 +METHODS["expandtabs"] = """function (tabsize) { // nargs: 0 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); tabsize = (tabsize === undefined) ? 8 : tabsize; return this.replace(/\\t/g, METHOD_PREFIXrepeat(' ', tabsize)); }""" -METHODS['find'] = """function (x, start, stop) { // nargs: 1 2 3 +METHODS["find"] = """function (x, start, stop) { // nargs: 1 2 3 if (this.constructor !== String) return this.KEY.apply(this, arguments); start = (start === undefined) ? 0 : start; stop = (stop === undefined) ? this.length : stop; @@ -688,7 +690,7 @@ def get_all_std_names(): return -1; }""" -METHODS['format'] = """function () { +METHODS["format"] = """function () { if (this.constructor !== String) return this.KEY.apply(this, arguments); var parts = [], i = 0, i1, i2; var itemnr = -1; @@ -713,28 +715,28 @@ def get_all_std_names(): return parts.join(''); }""" -METHODS['isalnum'] = """function () { // nargs: 0 +METHODS["isalnum"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return Boolean(/^[A-Za-z0-9]+$/.test(this)); }""" -METHODS['isalpha'] = """function () { // nargs: 0 +METHODS["isalpha"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return Boolean(/^[A-Za-z]+$/.test(this)); }""" -METHODS['isidentifier'] = """function () { // nargs: 0 +METHODS["isidentifier"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return Boolean(/^[A-Za-z_][A-Za-z0-9_]*$/.test(this)); }""" -METHODS['islower'] = """function () { // nargs: 0 +METHODS["islower"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); var low = this.toLowerCase(), high = this.toUpperCase(); return low != high && low == this; }""" -METHODS['isdecimal'] = """function () { // nargs: 0 +METHODS["isdecimal"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return Boolean(/^[0-9]+$/.test(this)); }""" @@ -751,43 +753,43 @@ def get_all_std_names(): # isnumeric with isdecimal's implementation, so we provide isnumeric # and isdigit as aliases for now. -METHODS['isnumeric'] = METHODS['isdigit'] = METHODS['isdecimal'] +METHODS["isnumeric"] = METHODS["isdigit"] = METHODS["isdecimal"] -METHODS['isspace'] = """function () { // nargs: 0 +METHODS["isspace"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return Boolean(/^\\s+$/.test(this)); }""" -METHODS['istitle'] = """function () { // nargs: 0 +METHODS["istitle"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); var low = this.toLowerCase(), title = METHOD_PREFIXtitle(this); return low != title && title == this; }""" -METHODS['isupper'] = """function () { // nargs: 0 +METHODS["isupper"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); var low = this.toLowerCase(), high = this.toUpperCase(); return low != high && high == this; }""" -METHODS['join'] = """function (x) { // nargs: 1 +METHODS["join"] = """function (x) { // nargs: 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); return x.join(this); // call join on the list instead of the string. }""" -METHODS['ljust'] = """function (w, fill) { // nargs: 1 2 +METHODS["ljust"] = """function (w, fill) { // nargs: 1 2 if (this.constructor !== String) return this.KEY.apply(this, arguments); fill = (fill === undefined) ? ' ' : fill; var tofill = Math.max(0, w - this.length); return this + METHOD_PREFIXrepeat(fill, tofill); }""" -METHODS['lower'] = """function () { // nargs: 0 +METHODS["lower"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); return this.toLowerCase(); }""" -METHODS['lstrip'] = """function (chars) { // nargs: 0 1 +METHODS["lstrip"] = """function (chars) { // nargs: 0 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); chars = (chars === undefined) ? ' \\t\\r\\n' : chars; for (var i=0; i= 0) return i; var e = Error(x); e.name='ValueError'; throw e; }""" -METHODS['rjust'] = """function (w, fill) { // nargs: 1 2 +METHODS["rjust"] = """function (w, fill) { // nargs: 1 2 if (this.constructor !== String) return this.KEY.apply(this, arguments); fill = (fill === undefined) ? ' ' : fill; var tofill = Math.max(0, w - this.length); return METHOD_PREFIXrepeat(fill, tofill) + this; }""" -METHODS['rpartition'] = """function (sep) { // nargs: 1 +METHODS["rpartition"] = """function (sep) { // nargs: 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); if (sep === '') {var e = Error('empty sep'); e.name='ValueError'; throw e;} var i1 = this.lastIndexOf(sep); @@ -855,7 +857,7 @@ def get_all_std_names(): return [this.slice(0, i1), this.slice(i1, i2), this.slice(i2)]; }""" -METHODS['rsplit'] = """function (sep, count) { // nargs: 1 2 +METHODS["rsplit"] = """function (sep, count) { // nargs: 1 2 if (this.constructor !== String) return this.KEY.apply(this, arguments); sep = (sep === undefined) ? /\\s/ : sep; count = Math.max(0, (count === undefined) ? 1e20 : count); @@ -866,7 +868,7 @@ def get_all_std_names(): return res; }""" -METHODS['rstrip'] = """function (chars) { // nargs: 0 1 +METHODS["rstrip"] = """function (chars) { // nargs: 0 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); chars = (chars === undefined) ? ' \\t\\r\\n' : chars; for (var i=this.length-1; i>=0; i--) { @@ -874,7 +876,7 @@ def get_all_std_names(): } return ''; }""" -METHODS['split'] = """function (sep, count) { // nargs: 0, 1 2 +METHODS["split"] = """function (sep, count) { // nargs: 0, 1 2 if (this.constructor !== String) return this.KEY.apply(this, arguments); if (sep === '') {var e = Error('empty sep'); e.name='ValueError'; throw e;} sep = (sep === undefined) ? /\\s/ : sep; @@ -891,7 +893,7 @@ def get_all_std_names(): return res; }""" -METHODS['splitlines'] = """function (keepends) { // nargs: 0 1 +METHODS["splitlines"] = """function (keepends) { // nargs: 0 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); keepends = keepends ? 1 : 0 var finder = /\\r\\n|\\r|\\n/g; @@ -908,12 +910,12 @@ def get_all_std_names(): return parts; }""" -METHODS['startswith'] = """function (x) { // nargs: 1 +METHODS["startswith"] = """function (x) { // nargs: 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); return this.indexOf(x) == 0; }""" -METHODS['strip'] = """function (chars) { // nargs: 0 1 +METHODS["strip"] = """function (chars) { // nargs: 0 1 if (this.constructor !== String) return this.KEY.apply(this, arguments); chars = (chars === undefined) ? ' \\t\\r\\n' : chars; var i, s1 = this, s2 = '', s3 = ''; @@ -924,7 +926,7 @@ def get_all_std_names(): } return s3; }""" -METHODS['swapcase'] = """function () { // nargs: 0 +METHODS["swapcase"] = """function () { // nargs: 0 if (this.constructor !== String) return this.KEY.apply(this, arguments); var c, res = []; for (var i=0; i' % (self.__class__.__name__, self.get_code(0)) else: - return '<%s with %i lines>' % (self.__class__.__name__, len(self._lines)) - + return "<%s with %i lines>" % (self.__class__.__name__, len(self._lines)) + def __str__(self): return self.get_code(0) - + @classmethod def _str2lines(cls, text): - """ Classmethod to split a text in lines, dedenting each line. + """Classmethod to split a text in lines, dedenting each line. The first line's indentation will assume the minimal indentation. """ - lines = text.replace('\r', '').split('\n') + lines = text.replace("\r", "").split("\n") lines[0] = lines[0].strip() # firts line is always detented if len(lines) > 1: # Get minimal indentation @@ -93,15 +93,15 @@ def _str2lines(cls, text): if not lines[0]: lines.pop(0) return lines - + def get_defined_name(self, suggestion=None): - """ Get the name by which this object is known in the module in which + """Get the name by which this object is known in the module in which it is defined. Only works if it is a global. Returns '' otherwise. If a suggestion is given and it is the correct name, this function performs faster. The resulting name is cached internally. """ if self._real_name is None: - self._real_name = '' # could be defined not in the globals + self._real_name = "" # could be defined not in the globals if suggestion and self._globals.get(suggestion, None) is self: self._real_name = suggestion else: @@ -110,35 +110,33 @@ def get_defined_name(self, suggestion=None): self._real_name = name break return self._real_name - + def get_code(self, indent=0): - """ Get the code with the given indentation. - """ - indent = indent * ' ' - return '\n'.join([indent + line for line in self._lines]) + """Get the code with the given indentation.""" + indent = indent * " " + return "\n".join([indent + line for line in self._lines]) class JSConstant: - """ Class to represent variables that are used in JS, and are considered + """Class to represent variables that are used in JS, and are considered global or otherwise available in a way that Python cannot know. """ - - def __init__(self, name='jsconstant'): + + def __init__(self, name="jsconstant"): self._name = name - + def __repr__(self): # pragma: no cover - return '<%s %s>' % (self.__class__.__name__, self._name) + return "<%s %s>" % (self.__class__.__name__, self._name) class Stubs: - __name__ = __name__ __file__ = __file__ JSConstant = JSConstant RawJS = RawJS - + def __getattr__(self, name): - if name in ('JSConstant', 'RawJS'): + if name in ("JSConstant", "RawJS"): return getattr(self, name) else: return self.JSConstant(name) diff --git a/pscript/testing.py b/pscript/testing.py index 8cdf4b02..97a9fda9 100644 --- a/pscript/testing.py +++ b/pscript/testing.py @@ -19,19 +19,21 @@ import pytest -PACKAGE_NAME = __name__.split('.')[0] +PACKAGE_NAME = __name__.split(".")[0] # Get project root dir THIS_DIR = os.path.abspath(os.path.dirname(__file__)) ROOT_DIR = THIS_DIR -for i in range(9): +for _i in range(9): ROOT_DIR = os.path.dirname(ROOT_DIR) if os.path.basename(ROOT_DIR) == PACKAGE_NAME: ROOT_DIR = os.path.dirname(ROOT_DIR) break else: - print('testing.py could not find project root dir, ' - 'using testing.py directory instead.') + print( + "testing.py could not find project root dir, " + "using testing.py directory instead." + ) ROOT_DIR = THIS_DIR @@ -42,42 +44,55 @@ def run_tests_if_main(show_coverage=False): - """ Run tests in a given file if it is run as a script + """Run tests in a given file if it is run as a script Coverage is reported for running this single test. Set show_coverage to launch the report in the web browser. """ local_vars = inspect.currentframe().f_back.f_locals - if not local_vars.get('__name__', '') == '__main__': + if not local_vars.get("__name__", "") == "__main__": return # we are in a "__main__" os.chdir(ROOT_DIR) - fname = str(local_vars['__file__']) + fname = str(local_vars["__file__"]) _clear_our_modules() _enable_faulthandler() - pytest.main(['-v', '-x', '--color=yes', '--cov', PACKAGE_NAME, - '--cov-config', '.coveragerc', '--cov-report', 'html', fname]) + pytest.main( + [ + "-v", + "-x", + "--color=yes", + "--cov", + PACKAGE_NAME, + "--cov-config", + ".coveragerc", + "--cov-report", + "html", + fname, + ] + ) if show_coverage: import webbrowser - fname = os.path.join(ROOT_DIR, 'htmlcov', 'index.html') + + fname = os.path.join(ROOT_DIR, "htmlcov", "index.html") webbrowser.open_new_tab(fname) def _enable_faulthandler(): - """ Enable faulthandler (if we can), so that we get tracebacks + """Enable faulthandler (if we can), so that we get tracebacks on segfaults. """ try: import faulthandler + faulthandler.enable() - print('Faulthandler enabled') + print("Faulthandler enabled") except Exception: - print('Could not enable faulthandler') + print("Could not enable faulthandler") def _clear_our_modules(): - """ Remove ourselves from sys.modules to force an import. - """ + """Remove ourselves from sys.modules to force an import.""" for key in list(sys.modules.keys()): - if key.startswith(PACKAGE_NAME) and 'testing' not in key: + if key.startswith(PACKAGE_NAME) and "testing" not in key: del sys.modules[key] diff --git a/pyproject.toml b/pyproject.toml index 485864c1..d9369bf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,11 +42,17 @@ build-backend = "flit_core.buildapi" [tool.ruff] line-length = 88 +exclude = ["tests/python_sample.py", "tests/python_sample3.py"] [tool.ruff.lint] select = ["F", "E", "W", "B", "RUF"] ignore = [ - # "RUF005", # Consider iterable unpacking instead of concatenation + "W291", # Trailing whitespace + "E501", # Line too long + "E731", # Do not assign a `lambda` expression, use a `def` + "RUF005", # Consider iterable unpacking instead of concatenation + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "RUF023", # __slots__ is not sorted -> our classes rely on slot order ] diff --git a/tests/python_sample2.bz2 b/tests/python_sample2.bz2 deleted file mode 100644 index 440ec456..00000000 Binary files a/tests/python_sample2.bz2 and /dev/null differ diff --git a/tests/python_sample2.py b/tests/python_sample2.py deleted file mode 100644 index f1c3cd90..00000000 --- a/tests/python_sample2.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Sample Python code with syntax that's only available in Python 2. -""" - -0xDeadBeefCafeL -0xDeadBeefCafel - -0123 -0177 - -# All of the print statements -print -print "x" -print "a", -print 1, 2 -print 1, 2, -print 1, 2, 93 -print >>x -print >>x, 1 -print >>x, 1, -print >>x, 9, 8 -print >>x, 9, 8, 7 - -exec "x=1" -exec "x=1" in x -exec "z=1" in z, y -exec "raise" in {}, globals() - -# #def spam((a) = c): pass # legal in Python 2.5, not 2.6 -# #def spam((((a))) = cc): pass # legal in Python 2.5, not 2.6 -# def spam((a,) = c): pass -# def spam((a,b) = c): pass -# def spam((a, (b, c)) = x96): pass -# def spam((a,b,c)=x332): pass -# def spam((a,b,c,d,e,f,g,h)=x12323): pass - -# backquotes (alias for repr) -# Terminal "," are not supported -`1` -`1,2` -`1,2,3` -`a,b,c,d,e,f,g,h` - -# Does not work in py3k -[x for x in 1,2,3,4] -[x for x in 1,2,3,4,] diff --git a/tests/test_commonast.py b/tests/test_commonast.py index 1b2191ad..96a8b05c 100644 --- a/tests/test_commonast.py +++ b/tests/test_commonast.py @@ -1,11 +1,10 @@ import os -import sys import bz2 import ast import json import time -from pscript.testing import run_tests_if_main, raises, skipif +from pscript.testing import run_tests_if_main, raises # sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) # import commonast @@ -13,9 +12,8 @@ dirname = os.path.dirname(__file__) -filename1 = os.path.join(dirname, 'python_sample.py') -filename2 = os.path.join(dirname, 'python_sample2.py') -filename3 = os.path.join(dirname, 'python_sample3.py') +filename1 = os.path.join(dirname, "python_sample.py") +filename3 = os.path.join(dirname, "python_sample3.py") def _export_python_sample_ast(): @@ -23,18 +21,18 @@ def _export_python_sample_ast(): filenames = filename1, filename3 # Write for filename in filenames: - filename_bz2 = filename[:-2] + 'bz2' - code = open(filename, 'rb').read().decode() + filename_bz2 = filename[:-2] + "bz2" + code = open(filename, "rb").read().decode() root = commonast.parse(code) ast_bin = bz2.compress(root.tojson(indent=None).encode()) - with open(filename_bz2, 'wb') as f: - n = f.write(ast_bin) or 'some' - print('wrote %s bytes to %s' % (n, filename_bz2)) + with open(filename_bz2, "wb") as f: + n = f.write(ast_bin) or "some" + print("wrote %s bytes to %s" % (n, filename_bz2)) def _get_ref_json(filename): - filename_bz2 = filename[:-2] + 'bz2' - js_ref = bz2.decompress(open(filename_bz2, 'rb').read()).decode() + filename_bz2 = filename[:-2] + "bz2" + js_ref = bz2.decompress(open(filename_bz2, "rb").read()).decode() return json.dumps(json.loads(js_ref), indent=2, sort_keys=True) @@ -42,105 +40,115 @@ def timeit(what, func, *args, **kwargs): t0 = time.perf_counter() func(*args, **kwargs) t1 = time.perf_counter() - print('%s took %1.2f s' % (what, (t1-t0))) + print("%s took %1.2f s" % (what, (t1 - t0))) def _performance_runner(): - """ Some simple perf tests ... + """Some simple perf tests ... (this function's name should not include 'test') """ - code = open(filename1, 'rb').read().decode() * 100 - timeit('sample with ast.parse', ast.parse, code) - timeit('sample with commonast.parse', commonast.parse, code) - timeit('sample with commonast.parse + comments', commonast.parse, code, comments=True) - + code = open(filename1, "rb").read().decode() * 100 + timeit("sample with ast.parse", ast.parse, code) + timeit("sample with commonast.parse", commonast.parse, code) + timeit( + "sample with commonast.parse + comments", commonast.parse, code, comments=True + ) + code = """if x < 9: def foo(a, *b): # done return a + b or 4 foo(3, 4*bar) """ - code = (code.strip() + '\n\n') * 10000 - + code = (code.strip() + "\n\n") * 10000 + print() - timeit('minisample with ast.parse', ast.parse, code) - timeit('minisample with commonast.parse', commonast.parse, code) - timeit('minisample with commonast.parse + comments', commonast.parse, code, comments=True) + timeit("minisample with ast.parse", ast.parse, code) + timeit("minisample with commonast.parse", commonast.parse, code) + timeit( + "minisample with commonast.parse + comments", + commonast.parse, + code, + comments=True, + ) def test_Node_creation(): - Node = commonast.Node - + class MyNodeWithoutSlots(Node): pass - + class MyStubNode(Node): __slots__ = () - + class MyNode(Node): - __slots__ = 'name', 'op', 'foo_node', 'foo_nodes', 'bar' - + __slots__ = "name", "op", "foo_node", "foo_nodes", "bar" + stubnode = MyStubNode() stubnodes = [MyStubNode(), MyStubNode(), MyStubNode()] - + # Node is an abstract class raises(AssertionError, Node) # Nodes must have slots (and no __dict__ to preserve memory) raises(AssertionError, MyNodeWithoutSlots) # number of names must match - raises(AssertionError, MyNode, ) + raises( + AssertionError, + MyNode, + ) raises(AssertionError, MyStubNode, 1) - raises(AssertionError, MyNode, 'a', 'Add', stubnode, stubnodes, 1, 2) - + raises(AssertionError, MyNode, "a", "Add", stubnode, stubnodes, 1, 2) + # These work - node = MyNode('a', 'Add', stubnode, stubnodes, 1) - node = MyNode('a', 'Add', None, stubnodes, 1) - node = MyNode('a', 'Add', stubnode, [], 1) - node = MyNode('a', 'Add', stubnode, stubnodes, 'bla') - node = MyNode('a', 'Add', stubnode, stubnodes, [1, 2, 3]) - node = MyNode('a', 'Mult', stubnode, stubnodes, 1) - node = MyNode('blas asdasd as', 'Mult', stubnode, stubnodes, 1) + _node = MyNode("a", "Add", stubnode, stubnodes, 1) + _node = MyNode("a", "Add", None, stubnodes, 1) + _node = MyNode("a", "Add", stubnode, [], 1) + _node = MyNode("a", "Add", stubnode, stubnodes, "bla") + _node = MyNode("a", "Add", stubnode, stubnodes, [1, 2, 3]) + _node = MyNode("a", "Mult", stubnode, stubnodes, 1) + _node = MyNode("blas asdasd as", "Mult", stubnode, stubnodes, 1) # Name must be a string - raises(AssertionError, MyNode, 1, 'Add', stubnode, stubnodes, 1) + raises(AssertionError, MyNode, 1, "Add", stubnode, stubnodes, 1) # op must be an existing operator - raises(AssertionError, MyNode, 'a', 'crap', stubnode, stubnodes, 1) + raises(AssertionError, MyNode, "a", "crap", stubnode, stubnodes, 1) # names ending with _node must be a node, and _nodes must be a list of nodes - raises(AssertionError, MyNode, 'a', 'Add', 1, stubnodes, 1) - raises(AssertionError, MyNode, 'a', 'Add', 'x', stubnodes, 1) - raises(AssertionError, MyNode, 'a', 'Add', stubnode, 'not a node', 1) - raises(AssertionError, MyNode, 'a', 'Add', stubnode, [1, 2], 1) + raises(AssertionError, MyNode, "a", "Add", 1, stubnodes, 1) + raises(AssertionError, MyNode, "a", "Add", "x", stubnodes, 1) + raises(AssertionError, MyNode, "a", "Add", stubnode, "not a node", 1) + raises(AssertionError, MyNode, "a", "Add", stubnode, [1, 2], 1) # bar can be anything, but not a Node or list of Nodes - raises(AssertionError, MyNode, 'a', 'Add', stubnode, stubnodes, stubnode) - raises(AssertionError, MyNode, 'a', 'Add', stubnode, stubnodes, stubnodes) + raises(AssertionError, MyNode, "a", "Add", stubnode, stubnodes, stubnode) + raises(AssertionError, MyNode, "a", "Add", stubnode, stubnodes, stubnodes) + def test_json_conversion(): from pscript.commonast import Node, Assign, Name, BinOp, Bytes, Num - + # Test json conversion - roota = Assign([Name('foo')], BinOp('Add', Name('a'), Num(3))) - rootb = Assign([Name('foo')], BinOp('Add', None, Num(3.2))) - rootc = Assign([Name('foo')], BinOp('Add', Bytes(b'xx'), Num(4j))) - + roota = Assign([Name("foo")], BinOp("Add", Name("a"), Num(3))) + rootb = Assign([Name("foo")], BinOp("Add", None, Num(3.2))) + rootc = Assign([Name("foo")], BinOp("Add", Bytes(b"xx"), Num(4j))) + for node1 in (roota, rootb, rootc): js = node1.tojson() node2 = Node.fromjson(js) # - assert js.count('BinOp') == 1 - assert js.count('Num') == 1 + assert js.count("BinOp") == 1 + assert js.count("Num") == 1 # assert node2.target_nodes[0].name == node1.target_nodes[0].name assert node2.value_node.op == node1.value_node.op assert node2.value_node.left_node == node1.value_node.left_node assert node2.value_node.right_node.value == node1.value_node.right_node.value # In fact, we can do - node1 == node2 - + assert node1 == node2 + assert roota != rootb assert roota != rootc with raises(ValueError): - roota == 5 - + roota == 5 # noqa + assert str(roota) == roota.tojson() assert len(repr(roota)) < 80 @@ -186,14 +194,14 @@ def foo(self): # cm12 bar """ - code = '\n'.join(x[4:] for x in code.splitlines()) - + code = "\n".join(x[4:] for x in code.splitlines()) + root = commonast.parse(code, comments=False) - assert 'cm' not in root.tojson() - + assert "cm" not in root.tojson() + root = commonast.parse(code, comments=True) for i in range(13): - assert ('cm%i' % i) in root.tojson() + assert ("cm%i" % i) in root.tojson() def _compare_large_strings(text1, text2): @@ -201,20 +209,22 @@ def _compare_large_strings(text1, text2): # to make fails easier to read. Invariant to newline-types in Str nodes. lines1, lines2 = text1.splitlines(), text2.splitlines() for i in range(0, max(len(lines1), len(lines2)), 4): - prefix = '# line %i\n' % i - sec1 = prefix + '\n'.join(lines1[i:i+8]) - sec2 = prefix + '\n'.join(lines2[i:i+8]).replace('\\r\\n', '\\n') # for pypy + prefix = "# line %i\n" % i + sec1 = prefix + "\n".join(lines1[i : i + 8]) + sec2 = prefix + "\n".join(lines2[i : i + 8]).replace( + "\\r\\n", "\\n" + ) # for pypy assert sec1 == sec2 def test_compare_print(): - ast = commonast.parse('print(foo, bar)') + ast = commonast.parse("print(foo, bar)") n = ast.body_nodes[0].value_node assert isinstance(n, commonast.Call) - assert n.func_node.name == 'print' + assert n.func_node.name == "print" assert len(n.arg_nodes) == 2 - assert n.arg_nodes[0].name == 'foo' - assert n.arg_nodes[1].name == 'bar' + assert n.arg_nodes[0].name == "foo" + assert n.arg_nodes[1].name == "bar" def test_consistent_ast0(): @@ -229,34 +239,22 @@ def test_consistent_ast0(): def test_consistent_ast1(): # Parse the sample file and export as a json string - code = open(filename1, 'rb').read().decode() + code = open(filename1, "rb").read().decode() root = commonast.parse(code) js = root.tojson() # Compare with ref _compare_large_strings(_get_ref_json(filename1), js) -@skipif(sys.version_info > (3,), reason='not Python 2.x') -def test_consistent_ast2(): - # Parse the sample file and export as a json string - code = open(filename2, 'rb').read().decode() - root = commonast.parse(code) - js = root.tojson() - # Compare with ref - _compare_large_strings(_get_ref_json(filename2), js) - - -@skipif(sys.version_info < (3,), reason='not Python 3.x') def test_consistent_ast3(): # Parse the sample file and export as a json string - code = open(filename3, 'rb').read().decode() + code = open(filename3, "rb").read().decode() root = commonast.parse(code) js = root.tojson() # Compare with ref _compare_large_strings(_get_ref_json(filename3), js) -@skipif(sys.version_info < (3,), reason='not Python 3.x') def test_functiondef_some_more(): code = """ def foo(a, b=3, *, c=4, d): @@ -264,19 +262,19 @@ def foo(a, b=3, *, c=4, d): def bar(a:[], b:(1,2), *c:'xx', **d:'yy') -> 'returns': pass """ - code = '\n'.join(x[4:] for x in code.splitlines()) - + code = "\n".join(x[4:] for x in code.splitlines()) + root = commonast.parse(code) assert len(root.body_nodes) == 2 - + f1, f2 = root.body_nodes assert isinstance(f1, commonast.FunctionDef) assert isinstance(f2, commonast.FunctionDef) - + # Test args of foo (focus on default values) assert len(f1.arg_nodes) == 2 assert len(f1.kwarg_nodes) == 2 - for arg, name in zip(f1.arg_nodes + f1.kwarg_nodes, 'abcd'): + for arg, name in zip(f1.arg_nodes + f1.kwarg_nodes, "abcd"): assert isinstance(arg, commonast.Arg) assert arg.name == name assert arg.annotation_node is None @@ -284,11 +282,11 @@ def bar(a:[], b:(1,2), *c:'xx', **d:'yy') -> 'returns': assert f1.arg_nodes[1].value_node.value == 3 # Num node assert f1.kwarg_nodes[0].value_node.value == 4 assert f1.kwarg_nodes[1].value_node is None - + # Test args of bar (focus on annotations) assert len(f2.arg_nodes) == 2 assert len(f2.kwarg_nodes) == 0 - for arg, name in zip(f2.arg_nodes + [f2.args_node, f2.kwargs_node], 'abcd'): + for arg, name in zip(f2.arg_nodes + [f2.args_node, f2.kwargs_node], "abcd"): assert isinstance(arg, commonast.Arg) assert arg.name == name assert f2.args_node.value_node is None @@ -301,71 +299,57 @@ def bar(a:[], b:(1,2), *c:'xx', **d:'yy') -> 'returns': def test_call_some_more(): - from pscript.commonast import Name, Num, Starred, Keyword - + from pscript.commonast import Name, Num, Starred + code = "foo(1, a, *b, c=3, **d)" node = commonast.parse(code).body_nodes[0].value_node # Call is in an Expr assert isinstance(node, commonast.Call) - + assert len(node.arg_nodes) == 3 assert len(node.kwarg_nodes) == 2 - for arg, cls in zip(node.arg_nodes + node.kwarg_nodes, - [Num, Name, Starred, Num, None.__class__]): + for arg, cls in zip( + node.arg_nodes + node.kwarg_nodes, [Num, Name, Starred, Num, None.__class__] + ): isinstance(arg, cls) - assert node.arg_nodes[2].value_node.name == 'b' + assert node.arg_nodes[2].value_node.name == "b" assert node.kwarg_nodes[1].name is None - assert node.kwarg_nodes[1].value_node.name == 'd' # keyword with emtpy name + assert node.kwarg_nodes[1].value_node.name == "d" # keyword with emtpy name -@skipif(sys.version_info < (3,5), reason='Need Python 3.5+') def test_call_even_some_more(): - from pscript.commonast import Name, Num, Starred, Keyword - + from pscript.commonast import Name, Starred, Keyword + code = "foo(a, *b, c, *d, **e, **f)" node = commonast.parse(code).body_nodes[0].value_node - + assert len(node.arg_nodes) == 4 assert len(node.kwarg_nodes) == 2 - for arg, cls in zip(node.arg_nodes + node.kwarg_nodes, - [Name, Starred, Name, Starred, Keyword, Keyword]): + for arg, cls in zip( + node.arg_nodes + node.kwarg_nodes, + [Name, Starred, Name, Starred, Keyword, Keyword], + ): isinstance(arg, cls) - -@skipif(sys.version_info < (3,), reason='not Python 3.x') + def test_classdef_some_more(): code = "class Foo(Bar, *bases, metaclass=X, **extra_kwargs): pass" node = commonast.parse(code).body_nodes[0] assert isinstance(node, commonast.ClassDef) - + assert len(node.arg_nodes) == 2 assert len(node.kwarg_nodes) == 2 # - assert node.arg_nodes[0].name == 'Bar' + assert node.arg_nodes[0].name == "Bar" assert isinstance(node.arg_nodes[1], commonast.Starred) - assert node.arg_nodes[1].value_node.name == 'bases' + assert node.arg_nodes[1].value_node.name == "bases" # kwarg nodes are Keywords - assert node.kwarg_nodes[0].name == 'metaclass' - assert node.kwarg_nodes[0].value_node.name == 'X' # Name node - assert node.kwarg_nodes[1].name is None - assert node.kwarg_nodes[1].value_node.name == 'extra_kwargs' - - -@skipif(sys.version_info > (3,), reason='not Python 2.x') -def test_python2_old_syntax(): - # We do not support tuple function arg; it would complicate things - with raises(RuntimeError): - commonast.parse('def foo((a,)=c):pass') - # Print statement becomes print function - assert commonast.parse('print(foo)') == commonast.parse('print foo') - # Exec statement becomes a d function - assert commonast.parse('exec(foo)') == commonast.parse('exec foo') - # Backticks becomes repr function - assert commonast.parse('repr(foo)') == commonast.parse('`foo`') + assert node.kwarg_nodes[0].name == "metaclass" + assert node.kwarg_nodes[0].value_node.name == "X" # Name node + assert node.kwarg_nodes[1].name is None + assert node.kwarg_nodes[1].value_node.name == "extra_kwargs" -@skipif(sys.version_info < (3,3), reason='Need Python 3.3+') def test_python_33_plus(): - # Yield from code = "def foo():\n yield from x" root = commonast.parse(code).body_nodes[0] @@ -373,7 +357,6 @@ def test_python_33_plus(): assert isinstance(node, commonast.YieldFrom) -@skipif(sys.version_info < (3,5), reason='Need Python 3.5+') def test_annotated_assignments(): # Verify that we treat annotated assignments as regular assingments code = "foo: int = 3" diff --git a/tests/test_functions.py b/tests/test_functions.py index 2494886b..b547fa5b 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,5 +1,6 @@ -""" Tests for PScript functions -""" +"""Tests for PScript functions""" + +# ruff: noqa: F841 import os import tempfile @@ -10,197 +11,197 @@ def test_dotted_unknowns(): - def func1(): - x = ui._layouts.SomeLayout() - y = ui.SomeLayout.YYY - z = ui.SomeOtherLayout - + x = ui._layouts.SomeLayout() # noqa + y = ui.SomeLayout.YYY # noqa + z = ui.SomeOtherLayout # noqa + js = py2js(func1) - assert js.meta['vars_unknown'] == set(['ui._layouts.SomeLayout', 'ui.SomeLayout.YYY', 'ui.SomeOtherLayout']) + assert js.meta["vars_unknown"] == set( + ["ui._layouts.SomeLayout", "ui.SomeLayout.YYY", "ui.SomeOtherLayout"] + ) def test_py2js_on_wrong_vals(): - raises(ValueError, py2js, []) raises(ValueError, py2js, {}) - + raises(ValueError, py2js, str) # cannot find source for str def test_py2js_on_strings(): # No need for extensive testing; we use this function extensively # in the other tests ... - assert py2js('3 + 3') == '3 + 3;' - assert py2js('list()') == '[];' + assert py2js("3 + 3") == "3 + 3;" + assert py2js("list()") == "[];" def test_evaljs(): - assert evaljs('3+4') == '7' - assert evaljs('var x = {}; x.doesnotexist') == '' # strip undefined + assert evaljs("3+4") == "7" + assert evaljs("var x = {}; x.doesnotexist") == "" # strip undefined def test_evalpy(): - assert evalpy('[3, 4]') == '[ 3, 4 ]' - assert evalpy('[3, 4]', False) == '[3,4]' + assert evalpy("[3, 4]") == "[ 3, 4 ]" + assert evalpy("[3, 4]", False) == "[3,4]" def test_py2js_on_function(): - def foo(): pass - + # normal jscode = py2js(foo) - assert jscode.startswith('var foo') - assert jscode.meta['pycode'].startswith('def foo') - + assert jscode.startswith("var foo") + assert jscode.meta["pycode"].startswith("def foo") + # renamed - jscode = py2js(foo, 'bar') - assert jscode.meta['pycode'].startswith('def foo') - assert 'foo' not in jscode - assert jscode.startswith('var bar') - assert 'bar = function ' in jscode - + jscode = py2js(foo, "bar") + assert jscode.meta["pycode"].startswith("def foo") + assert "foo" not in jscode + assert jscode.startswith("var bar") + assert "bar = function " in jscode + # renamed 2 - jscode = py2js(foo, 'bar.bla') - assert jscode.meta['pycode'].startswith('def foo') - assert 'foo' not in jscode - assert not 'var bar.bla' in jscode - assert 'bar.bla = function ' in jscode - - + jscode = py2js(foo, "bar.bla") + assert jscode.meta["pycode"].startswith("def foo") + assert "foo" not in jscode + assert "var bar.bla" not in jscode + assert "bar.bla = function " in jscode + # Skip decorators stub1 = lambda x: x stub2 = lambda x=None: stub1 - + @stub1 @stub1 def foo1(): pass - - @stub2( - ) + + @stub2() def foo2(): pass - + @py2js def foo3(): pass - + @py2js(indent=1) def foo4(): pass - + assert callable(foo1) assert callable(foo2) - assert py2js(foo1).meta['pycode'].startswith('def foo') - assert py2js(foo2).meta['pycode'].startswith('def foo') - assert foo3.startswith('var foo3') - assert foo4.startswith(' var foo4') + assert py2js(foo1).meta["pycode"].startswith("def foo") + assert py2js(foo2).meta["pycode"].startswith("def foo") + assert foo3.startswith("var foo3") + assert foo4.startswith(" var foo4") def test_py2js_on_class(): - class Foo1: X = 3 + def spam(): pass - + # normal jscode = py2js(Foo1, inline_stdlib=False) - assert jscode.startswith('var Foo1') - assert jscode.meta['pycode'].startswith('class Foo1') - + assert jscode.startswith("var Foo1") + assert jscode.meta["pycode"].startswith("class Foo1") + # renamed - jscode = py2js(Foo1, 'Bar', inline_stdlib=False) - assert jscode.meta['pycode'].startswith('class Foo') - assert 'Foo' not in jscode - assert jscode.startswith('var Bar') - + jscode = py2js(Foo1, "Bar", inline_stdlib=False) + assert jscode.meta["pycode"].startswith("class Foo") + assert "Foo" not in jscode + assert jscode.startswith("var Bar") + # renamed 2 - jscode = py2js(Foo1, 'Bar.bla', inline_stdlib=False) - assert jscode.meta['pycode'].startswith('class Foo') - assert 'Foo' not in jscode - assert not 'var Bar.bla' in jscode - assert 'Bar.bla = function ' in jscode + jscode = py2js(Foo1, "Bar.bla", inline_stdlib=False) + assert jscode.meta["pycode"].startswith("class Foo") + assert "Foo" not in jscode + assert "var Bar.bla" not in jscode + assert "Bar.bla = function " in jscode class Foo2: __x = 42 __y__ = 7 - + def res1(self): return self.__x - + def res2(self): return self.__y__ + def foo1(): return 42 + def foo2(self): return self.__x + self.__y__ + # Function name not compliant with PEP8 but that happens. def Foo3(): - return 'a' + return "a" def test_py2js_rename_class(): - - code = py2js(Foo2, 'Bar') - assert 'foo' not in code.lower() - assert evaljs(code + 'var m=new Bar(); [m.res1(), m.res2()];') == '[ 42, 7 ]' - - code = py2js(Foo2, 'xx.Bar') - assert 'foo' not in code.lower() - assert evaljs('var xx={};\n' + code + 'var m=new xx.Bar(); [m.res1(), m.res2()];') == '[ 42, 7 ]' + code = py2js(Foo2, "Bar") + assert "foo" not in code.lower() + assert evaljs(code + "var m=new Bar(); [m.res1(), m.res2()];") == "[ 42, 7 ]" + + code = py2js(Foo2, "xx.Bar") + assert "foo" not in code.lower() + assert ( + evaljs("var xx={};\n" + code + "var m=new xx.Bar(); [m.res1(), m.res2()];") + == "[ 42, 7 ]" + ) def test_py2s_rename_function(): - code = py2js(foo1, 'bar') - assert 'foo' not in code.lower() - assert evaljs(code + 'bar()') == '42' - - code = py2js(foo1, 'xx.bar') - assert 'foo' not in code.lower() - assert evaljs('var xx={};\n' + code + 'xx.bar();') == '42' - - code = py2js(Foo3, 'bar') - assert 'foo' not in code.lower() - assert evaljs(code + 'bar()') == 'a' - + code = py2js(foo1, "bar") + assert "foo" not in code.lower() + assert evaljs(code + "bar()") == "42" + + code = py2js(foo1, "xx.bar") + assert "foo" not in code.lower() + assert evaljs("var xx={};\n" + code + "xx.bar();") == "42" + + code = py2js(Foo3, "bar") + assert "foo" not in code.lower() + assert evaljs(code + "bar()") == "a" + def test_py2s_rename_function_to_method(): - - code1 = py2js(Foo2, 'Bar') - code = code1 + py2js(foo2, 'Bar.prototype.bar') - assert 'foo' not in code.lower() - assert evaljs(code + 'var m=new Bar(); m.bar();') == '49' - - code1 = py2js(Foo2, 'Bar') - code = code1 + '\nvar $Bar = Bar.prototype;\n' + py2js(foo2, '$Bar.bar') - assert 'foo' not in code.lower() - assert evaljs(code + 'var m=new Bar(); m.bar();') == '49' - - code1 = py2js(Foo2, 'xx.Bar') - code = code1 + py2js(foo2, 'xx.Bar.prototype.bar') - assert 'foo' not in code.lower() - assert evaljs('var xx={};\n' + code + 'var m=new xx.Bar(); m.bar();') == '49' - + code1 = py2js(Foo2, "Bar") + code = code1 + py2js(foo2, "Bar.prototype.bar") + assert "foo" not in code.lower() + assert evaljs(code + "var m=new Bar(); m.bar();") == "49" + + code1 = py2js(Foo2, "Bar") + code = code1 + "\nvar $Bar = Bar.prototype;\n" + py2js(foo2, "$Bar.bar") + assert "foo" not in code.lower() + assert evaljs(code + "var m=new Bar(); m.bar();") == "49" + + code1 = py2js(Foo2, "xx.Bar") + code = code1 + py2js(foo2, "xx.Bar.prototype.bar") + assert "foo" not in code.lower() + assert evaljs("var xx={};\n" + code + "var m=new xx.Bar(); m.bar();") == "49" + def test_raw_js(): - def func(a, b): """ var c = 3; return a + b + c; """ - + code = py2js(func) - assert evaljs(code + 'func(100, 10)') == '113' - assert evaljs(code + 'func("x", 10)') == 'x103' + assert evaljs(code + "func(100, 10)") == "113" + assert evaljs(code + 'func("x", 10)') == "x103" TEST_CODE = """ @@ -223,7 +224,7 @@ def func(a, b): """ # def test_clean_code(): -# +# # code = clean_code(TEST_CODE) # assert code.count('var foo =') == 1 # assert code.count('var bar =') == 2 @@ -231,45 +232,45 @@ def func(a, b): def test_scripts(): # Prepare - pycode = 'foo = 42; print(foo)' - pyname = os.path.join(tempfile.gettempdir(), 'pscript_test.py') - with open(pyname, 'wb') as f: + pycode = "foo = 42; print(foo)" + pyname = os.path.join(tempfile.gettempdir(), "pscript_test.py") + with open(pyname, "wb") as f: f.write(pycode.encode()) - jsname = pyname[:-3] + '.js' - + jsname = pyname[:-3] + ".js" + # Convert - plain file (no module) script2js(pyname) - + # Check result - jscode = open(jsname, 'rb').read().decode() - assert 'foo = 42;' in jscode - assert 'define(' not in jscode - + jscode = open(jsname, "rb").read().decode() + assert "foo = 42;" in jscode + assert "define(" not in jscode + # Convert - module light - script2js(pyname, 'mymodule', module_type='simple') - + script2js(pyname, "mymodule", module_type="simple") + # Check result - jscode = open(jsname, 'rb').read().decode() - assert 'foo = 42;' in jscode - assert 'define(' not in jscode - + jscode = open(jsname, "rb").read().decode() + assert "foo = 42;" in jscode + assert "define(" not in jscode + # Convert - module UMD - script2js(pyname, 'mymodule', module_type='umd') - + script2js(pyname, "mymodule", module_type="umd") + # Check result - jscode = open(jsname, 'rb').read().decode() - assert 'foo = 42;' in jscode - assert 'define(' in jscode - assert 'module.exports' in jscode - assert 'root.mymodule' in jscode - + jscode = open(jsname, "rb").read().decode() + assert "foo = 42;" in jscode + assert "define(" in jscode + assert "module.exports" in jscode + assert "root.mymodule" in jscode + # Convert - no module, explicit file script2js(pyname, None, jsname) - + # Check result - jscode = open(jsname, 'rb').read().decode() - assert 'foo = 42;' in jscode - assert 'define(' not in jscode + jscode = open(jsname, "rb").read().decode() + assert "foo = 42;" in jscode + assert "define(" not in jscode run_tests_if_main() diff --git a/tests/test_modules.py b/tests/test_modules.py index b70d6e22..e7b1bda3 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -1,11 +1,8 @@ -""" Tests for functionality to create JS modules. +"""Tests for functionality to create JS modules. Note that some tests in func also implicitly test this. """ -import os -import tempfile - from pscript.testing import run_tests_if_main, raises from pscript.modules import create_js_module @@ -18,83 +15,80 @@ def test_js_module_types(): - - code = create_js_module('baz.js', CODE, ['bb'], 'aa', 'hidden') - assert 'define' not in code - assert 'require' not in code - assert 'bb' not in code - assert 'aa' not in code - assert 'return' not in code - - code = create_js_module('baz.js', CODE, ['bb'], 'aa', 'simple') - assert 'define' not in code - assert 'require' not in code - assert 'bb' not in code - assert 'return aa' in code - - code = create_js_module('baz.js', CODE, ['bb'], 'aa', 'amd') - assert 'define' in code - assert 'require' not in code - assert 'bb' in code - assert 'return aa' in code - - code = create_js_module('baz.js', CODE, ['bb'], 'aa', 'umd') - assert 'define' in code - assert 'require' in code - assert 'bb' in code - assert 'return aa' in code - + code = create_js_module("baz.js", CODE, ["bb"], "aa", "hidden") + assert "define" not in code + assert "require" not in code + assert "bb" not in code + assert "aa" not in code + assert "return" not in code + + code = create_js_module("baz.js", CODE, ["bb"], "aa", "simple") + assert "define" not in code + assert "require" not in code + assert "bb" not in code + assert "return aa" in code + + code = create_js_module("baz.js", CODE, ["bb"], "aa", "amd") + assert "define" in code + assert "require" not in code + assert "bb" in code + assert "return aa" in code + + code = create_js_module("baz.js", CODE, ["bb"], "aa", "umd") + assert "define" in code + assert "require" in code + assert "bb" in code + assert "return aa" in code + with raises(ValueError): # type not a str - create_js_module('baz.js', CODE, ['bb'], 'aa', 3) - + create_js_module("baz.js", CODE, ["bb"], "aa", 3) + with raises(ValueError): # invalid type - create_js_module('baz.js', CODE, ['bb'], 'aa', 'not_known') + create_js_module("baz.js", CODE, ["bb"], "aa", "not_known") def test_js_module_names(): - with raises(ValueError): # name not a str - create_js_module(3, CODE, ['bb'], 'aa', 'simple') - + create_js_module(3, CODE, ["bb"], "aa", "simple") + with raises(ValueError): # name empty str - create_js_module('', CODE, ['bb'], 'aa', 'simple') - - code = create_js_module('foo.js', CODE, ['bb'], 'aa', 'simple') - assert '.foo =' in code # using safe names + create_js_module("", CODE, ["bb"], "aa", "simple") + + code = create_js_module("foo.js", CODE, ["bb"], "aa", "simple") + assert ".foo =" in code # using safe names def test_js_module_code(): with raises(ValueError): # code not a str - create_js_module('foo.js', 4, ['bb'], 'aa', 'simple') + create_js_module("foo.js", 4, ["bb"], "aa", "simple") def test_js_module_imports(): with raises(ValueError): # imports not a list - create_js_module('foo.js', CODE, 'bb', 'aa', 'simple') - + create_js_module("foo.js", CODE, "bb", "aa", "simple") + with raises(ValueError): # imports element not a str - create_js_module('foo.js', CODE, ['bb', 4], 'aa', 'simple') - - for type in ('amd', 'umd'): - code = create_js_module('foo.js', CODE, ['bb as cc', 'dd'], 'aa', type) + create_js_module("foo.js", CODE, ["bb", 4], "aa", "simple") + + for type in ("amd", "umd"): + code = create_js_module("foo.js", CODE, ["bb as cc", "dd"], "aa", type) assert '"bb"' in code assert '"dd"' in code assert '"cc"' not in code - assert 'cc, dd' in code + assert "cc, dd" in code def test_js_module_exports(): with raises(ValueError): # exports not a str or list - create_js_module('foo.js', CODE, ['bb'], 3, 'simple') + create_js_module("foo.js", CODE, ["bb"], 3, "simple") with raises(ValueError): # exports element not a str - create_js_module('foo.js', CODE, ['bb'], ['aa', 3], 'simple') - - code =create_js_module('foo.js', CODE, ['bb'], 'aa', 'simple') - assert 'return aa' in code - - code = create_js_module('foo.js', CODE, ['bb'], ['aa', 'bb'], 'simple') - assert 'return {aa: aa, bb: bb}' in code - - + create_js_module("foo.js", CODE, ["bb"], ["aa", 3], "simple") + + code = create_js_module("foo.js", CODE, ["bb"], "aa", "simple") + assert "return aa" in code + + code = create_js_module("foo.js", CODE, ["bb"], ["aa", "bb"], "simple") + assert "return {aa: aa, bb: bb}" in code + run_tests_if_main() diff --git a/tests/test_parser0.py b/tests/test_parser0.py index b1e0dcca..e410202c 100644 --- a/tests/test_parser0.py +++ b/tests/test_parser0.py @@ -1,48 +1,49 @@ -from pscript.testing import run_tests_if_main, raises +# ruff: noqa: F841 -from pscript.parser0 import JSError, unify +from pscript.testing import run_tests_if_main + +from pscript.parser0 import unify def test_unify(): - # Simple objects - assert unify('3') == '3' - assert unify('3.12') == '3.12' + assert unify("3") == "3" + assert unify("3.12") == "3.12" assert unify('"aa"') == '"aa"' assert unify("'aa'") == "'aa'" - + # Simple names - assert unify('foo') == 'foo' - assert unify('foo.bar') == 'foo.bar' - assert unify('foo_12') == 'foo_12' - + assert unify("foo") == "foo" + assert unify("foo.bar") == "foo.bar" + assert unify("foo_12") == "foo_12" + # Simple calls - assert unify('foo()') == 'foo()' - assert unify('bar.fo_o()') == 'bar.fo_o()' - + assert unify("foo()") == "foo()" + assert unify("bar.fo_o()") == "bar.fo_o()" + # Anything that already has braces or [] - assert unify('(foo)') == '(foo)' - assert unify('(3 + 3)') == '(3 + 3)' - assert unify('[2, 3]') == '[2, 3]' - + assert unify("(foo)") == "(foo)" + assert unify("(3 + 3)") == "(3 + 3)" + assert unify("[2, 3]") == "[2, 3]" + # Func calls with args (but no extra braces) - assert unify('xxxxx(some args bla)') == 'xxxxx(some args bla)' - assert unify('foo(3)') == 'foo(3)' - + assert unify("xxxxx(some args bla)") == "xxxxx(some args bla)" + assert unify("foo(3)") == "foo(3)" + # Indexing - assert unify('foo[1]') == 'foo[1]' - assert unify('bar.foo[1:2,3]') == 'bar.foo[1:2,3]' - + assert unify("foo[1]") == "foo[1]" + assert unify("bar.foo[1:2,3]") == "bar.foo[1:2,3]" + # Dict assert unify('{a:3, b:"5"}') == '{a:3, b:"5"}' - + # Otherwise ... braces! - assert unify('3+3') == '(3+3)' - assert unify('(3)+(3)') == '((3)+(3))' - assert unify('[3]+[3]') == '([3]+[3])' - assert unify('foo((3))') == '(foo((3)))' - assert unify('bar+foo(3)') == '(bar+foo(3))' - assert unify('b + {a:3}') == '(b + {a:3})' + assert unify("3+3") == "(3+3)" + assert unify("(3)+(3)") == "((3)+(3))" + assert unify("[3]+[3]") == "([3]+[3])" + assert unify("foo((3))") == "(foo((3)))" + assert unify("bar+foo(3)") == "(bar+foo(3))" + assert unify("b + {a:3}") == "(b + {a:3})" run_tests_if_main() diff --git a/tests/test_parser1.py b/tests/test_parser1.py index d1540295..2f8e7c8e 100644 --- a/tests/test_parser1.py +++ b/tests/test_parser1.py @@ -1,597 +1,612 @@ +# ruff: noqa: F841 + from pscript.testing import run_tests_if_main, raises -import sys import pscript from pscript import JSError, py2js, evaljs, evalpy, Parser def nowhitespace(s): - return s.replace('\n', '').replace('\t', '').replace(' ', '') + return s.replace("\n", "").replace("\t", "").replace(" ", "") class StubParser(Parser): - def function_foo_foo(self, node): - return 'xxx' - + return "xxx" + def method_bar_bar(self, node, base): return base class TestTheParser: - def test_special_functions(self): - assert StubParser("foo_foo()").dump() == 'xxx;' - assert StubParser("bar_bar()").dump() == 'bar_bar();' - - assert StubParser("xxx.bar_bar()").dump() == 'xxx;' - assert StubParser("xxx.foo_foo()").dump() == 'xxx.foo_foo();' - + assert StubParser("foo_foo()").dump() == "xxx;" + assert StubParser("bar_bar()").dump() == "bar_bar();" + + assert StubParser("xxx.bar_bar()").dump() == "xxx;" + assert StubParser("xxx.foo_foo()").dump() == "xxx.foo_foo();" + # def test_exceptions(self): # raises(JSError, py2js, "foo(**kwargs)") - + class TestExpressions: - """ Tests for single-line statements/expressions - """ - + """Tests for single-line statements/expressions""" + def test_special(self): - assert py2js('') == '' - assert py2js(' \n') == '' - + assert py2js("") == "" + assert py2js(" \n") == "" + def test_ops(self): # Test code - assert py2js('2+3') == '2 + 3;' # Binary - assert py2js('2/3') == '2 / 3;' - assert py2js('not 2') == '!2;' # Unary - assert py2js('-(2+3)') == '-(2 + 3);' - assert py2js('True and False') == 'true && false;' # Boolean - + assert py2js("2+3") == "2 + 3;" # Binary + assert py2js("2/3") == "2 / 3;" + assert py2js("not 2") == "!2;" # Unary + assert py2js("-(2+3)") == "-(2 + 3);" + assert py2js("True and False") == "true && false;" # Boolean + # No parentices around names, numbers and strings - assert py2js('foo - bar') == "foo - bar;" - assert py2js('_foo3 - _bar4') == "_foo3 - _bar4;" - assert py2js('3 - 4') == "3 - 4;" + assert py2js("foo - bar") == "foo - bar;" + assert py2js("_foo3 - _bar4") == "_foo3 - _bar4;" + assert py2js("3 - 4") == "3 - 4;" assert py2js('"abc" - "def"') == '"abc" - "def";' assert py2js("'abc' - 'def'") == '"abc" - "def";' - assert py2js("'\"abc\" - \"def\"'") == '"\\"abc\\" - \\"def\\"";' - + assert py2js('\'"abc" - "def"\'') == '"\\"abc\\" - \\"def\\"";' + # But they should be if it gets more complex - assert py2js('foo - bar > 4') == "(foo - bar) > 4;" - + assert py2js("foo - bar > 4") == "(foo - bar) > 4;" + # Test outcome - assert evalpy('2+3') == '5' # Binary - assert evalpy('6/3') == '2' - assert evalpy('4//3') == '1' - assert evalpy('2**8') == '256' - assert evalpy('not True') == 'false' # Unary - assert evalpy('0-3') == '-3' - assert evalpy('True and False') == 'false' # Boolean - assert evalpy('True or False') == 'true' + assert evalpy("2+3") == "5" # Binary + assert evalpy("6/3") == "2" + assert evalpy("4//3") == "1" + assert evalpy("2**8") == "256" + assert evalpy("not True") == "false" # Unary + assert evalpy("0-3") == "-3" + assert evalpy("True and False") == "false" # Boolean + assert evalpy("True or False") == "true" # Bug - assert evalpy('(9-3-3)/3') == '1' - + assert evalpy("(9-3-3)/3") == "1" + def test_string_formatting1(self): # string formatting that we already had - assert evalpy('"%s" % "bar"') == 'bar' - assert evalpy('"-%s-" % "bar"') == '-bar-' - assert evalpy('"foo %s foo" % "bar"') == 'foo bar foo' - assert evalpy('"x %i" % 6') == 'x 6' - assert evalpy('"x %g" % 6') == 'x 6' - assert evalpy('"%s: %f" % ("value", 6)') == 'value: 6.000000' + assert evalpy('"%s" % "bar"') == "bar" + assert evalpy('"-%s-" % "bar"') == "-bar-" + assert evalpy('"foo %s foo" % "bar"') == "foo bar foo" + assert evalpy('"x %i" % 6') == "x 6" + assert evalpy('"x %g" % 6') == "x 6" + assert evalpy('"%s: %f" % ("value", 6)') == "value: 6.000000" assert evalpy('"%r: %r" % ("value", 6)') == '"value": 6' - + def test_string_formatting2(self): - py2jslight = lambda x: py2js(x, inline_stdlib=False) - + # Verify that percent-formatting produces same JS as str.format assert py2jslight("'hi %i' % a") == py2jslight("'hi {:i}'.format(a)") - assert py2jslight("'hi %i %+i' % (a, b)") == py2jslight("'hi {:i} {:+i}'.format(a, b)") - assert py2jslight("'hi %f %1.2f' % (a, b)") == py2jslight("'hi {:f} {:1.2f}'.format(a, b)") - assert py2jslight("'hi %s %r' % (a, b)") == py2jslight("'hi {} {!r}'.format(a, b)") - + assert py2jslight("'hi %i %+i' % (a, b)") == py2jslight( + "'hi {:i} {:+i}'.format(a, b)" + ) + assert py2jslight("'hi %f %1.2f' % (a, b)") == py2jslight( + "'hi {:f} {:1.2f}'.format(a, b)" + ) + assert py2jslight("'hi %s %r' % (a, b)") == py2jslight( + "'hi {} {!r}'.format(a, b)" + ) + # Verify that f-string formatting produces same JS as str.format - Python 3.6+ assert py2jslight("f'hi {a:i}'") == py2jslight("'hi {:i}'.format(a)") assert py2js("f'hi {a:i} {b:+i}'") == py2js("'hi {:i} {:+i}'.format(a, b)") - assert py2jslight("f'hi {a:f} {b:1.2f}'") == py2jslight("'hi {:f} {:1.2f}'.format(a, b)") + assert py2jslight("f'hi {a:f} {b:1.2f}'") == py2jslight( + "'hi {:f} {:1.2f}'.format(a, b)" + ) assert py2jslight("f'hi {a} {b!r}'") == py2jslight("'hi {} {!r}'.format(a, b)") - + def test_string_formatting3(self): # Verify fancy formatting (mosly for numbers) # We don't support every kind of fortting that Python does. - + x = 'a = 3.1415926535; b = 7; c = "foo"; d = 314159265.35; e = 0.0031415926535;' # i formatting - assert evalpy(x + "'hi {:i}'.format(b)") == 'hi 7' - assert evalpy(x + "'hi {:03i} {:+03i}'.format(b, b)") == 'hi 007 +07' - assert evalpy(x + "'hi {:03} {:+03}'.format(b, b)") == 'hi 007 +07' + assert evalpy(x + "'hi {:i}'.format(b)") == "hi 7" + assert evalpy(x + "'hi {:03i} {:+03i}'.format(b, b)") == "hi 007 +07" + assert evalpy(x + "'hi {:03} {:+03}'.format(b, b)") == "hi 007 +07" # f formatting - assert evalpy(x + "'hi {:i} {:+i} {: i}'.format(b, b, b)") == 'hi 7 +7 7' - assert evalpy(x + "'hi {:f} {:1.0f} {:1.2f}'.format(a, a, a)") == 'hi 3.141593 3 3.14' - assert evalpy(x + "'hi {:05f} {:05.1f} {:+05.1f}'.format(a, a, a)") == 'hi 3.141593 003.1 +03.1' + assert evalpy(x + "'hi {:i} {:+i} {: i}'.format(b, b, b)") == "hi 7 +7 7" + assert ( + evalpy(x + "'hi {:f} {:1.0f} {:1.2f}'.format(a, a, a)") + == "hi 3.141593 3 3.14" + ) + assert ( + evalpy(x + "'hi {:05f} {:05.1f} {:+05.1f}'.format(a, a, a)") + == "hi 3.141593 003.1 +03.1" + ) # g formatting, these outputs are (manually) validated with Python - assert evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(a, a, a)") == 'hi 3.14159 3 3.14' - assert evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(d, d, d)") == 'hi 3.14159e+08 3e+08 3.14e+08' - assert evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(e, e, e)") == 'hi 0.00314159 0.003 0.00314' - assert evalpy(x + "'hi {:05g} {:05.1g} {:+05.1g}'.format(a, a, a)") == 'hi 3.14159 00003 +0003' + assert ( + evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(a, a, a)") == "hi 3.14159 3 3.14" + ) + assert ( + evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(d, d, d)") + == "hi 3.14159e+08 3e+08 3.14e+08" + ) + assert ( + evalpy(x + "'hi {:g} {:.1g} {:.3g}'.format(e, e, e)") + == "hi 0.00314159 0.003 0.00314" + ) + assert ( + evalpy(x + "'hi {:05g} {:05.1g} {:+05.1g}'.format(a, a, a)") + == "hi 3.14159 00003 +0003" + ) # String and repr formatting assert evalpy(x + "'hi {} {!s} {!r}'.format(c, c, c)") == 'hi foo foo "foo"' - + def test_string_formatting4(self): - - x = 'a = 3; b = 4; ' - + x = "a = 3; b = 4; " + # Setting positions in format string - assert evalpy(x + "'hi {1:g} {1:+g} {0}'.format(a, b)") == 'hi 4 +4 3' - + assert evalpy(x + "'hi {1:g} {1:+g} {0}'.format(a, b)") == "hi 4 +4 3" + # Using a predefined template string for .format() - assert evalpy(x + "t = 'hi {} {}'; t.format(a, b)") == 'hi 3 4' - + assert evalpy(x + "t = 'hi {} {}'; t.format(a, b)") == "hi 3 4" + # Using a predefined template string for % - we cannot do this, unfortunately! # assert evalpy(x + "t = 'hi %i %i'; t % (a, b)") == 'hi 3 4' - + def test_overloaded_list_ops(self): - assert evalpy('[1, 2] + [3, 4]') == '[ 1, 2, 3, 4 ]' - assert evalpy('[3, 4] + [1, 2]') == '[ 3, 4, 1, 2 ]' - assert evalpy('"ab" + "cd"') == 'abcd' - assert evalpy('[3, 4] * 2') == '[ 3, 4, 3, 4 ]' - assert evalpy('2 * [3, 4]') == '[ 3, 4, 3, 4 ]' - assert evalpy('"ab" * 2') == 'abab' - assert evalpy('2 * "ab"') == 'abab' - - assert evalpy('a = [1, 2]; a += [3, 4]; a') == '[ 1, 2, 3, 4 ]' - assert evalpy('a = [3, 4]; a += [1, 2]; a') == '[ 3, 4, 1, 2 ]' - assert evalpy('a = [3, 4]; a *= 2; a') == '[ 3, 4, 3, 4 ]' - assert evalpy('a = "ab"; a *= 2; a') == 'abab' - + assert evalpy("[1, 2] + [3, 4]") == "[ 1, 2, 3, 4 ]" + assert evalpy("[3, 4] + [1, 2]") == "[ 3, 4, 1, 2 ]" + assert evalpy('"ab" + "cd"') == "abcd" + assert evalpy("[3, 4] * 2") == "[ 3, 4, 3, 4 ]" + assert evalpy("2 * [3, 4]") == "[ 3, 4, 3, 4 ]" + assert evalpy('"ab" * 2') == "abab" + assert evalpy('2 * "ab"') == "abab" + + assert evalpy("a = [1, 2]; a += [3, 4]; a") == "[ 1, 2, 3, 4 ]" + assert evalpy("a = [3, 4]; a += [1, 2]; a") == "[ 3, 4, 1, 2 ]" + assert evalpy("a = [3, 4]; a *= 2; a") == "[ 3, 4, 3, 4 ]" + assert evalpy('a = "ab"; a *= 2; a') == "abab" + def test_raw_js_overloading(self): # more RawJS tests in test_parser3.py - s1 = 'a=3; b=4; c=1; a + b - c' + s1 = "a=3; b=4; c=1; a + b - c" s2 = 'a=3; b=4; c=1; RawJS("a + b") - c' - assert evalpy(s1) == '6' - assert evalpy(s2) == '6' - assert 'pyfunc' in py2js(s1) - assert 'pyfunc' not in py2js(s2) - + assert evalpy(s1) == "6" + assert evalpy(s2) == "6" + assert "pyfunc" in py2js(s1) + assert "pyfunc" not in py2js(s2) + def test_overload_funcs_dont_overload_real_funcs(self): - assert evalpy('def add(a, b): return a-b\n\nadd(4, 1)') == '3' - assert evalpy('def op_add(a, b): return a-b\n\nop_add(4, 1)') == '3' - + assert evalpy("def add(a, b): return a-b\n\nadd(4, 1)") == "3" + assert evalpy("def op_add(a, b): return a-b\n\nop_add(4, 1)") == "3" + def test_comparisons(self): - - assert py2js('4 > 3') == '4 > 3;' - assert py2js('4 is 3') == '4 === 3;' - - assert evalpy('4 > 4') == 'false' - assert evalpy('4 >= 4') == 'true' - assert evalpy('4 < 3') == 'false' - assert evalpy('4 <= 4') == 'true' - assert evalpy('4 == 3') == 'false' - assert evalpy('4 != 3') == 'true' - - assert evalpy('4 == "4"') == 'true' # yuck! - assert evalpy('4 is "4"') == 'false' - assert evalpy('4 is not "4"') == 'true' - - assert evalpy('"c" in "abcd"') == 'true' - assert evalpy('"x" in "abcd"') == 'false' - assert evalpy('"x" not in "abcd"') == 'true' - - assert evalpy('3 in [1,2,3,4]') == 'true' - assert evalpy('9 in [1,2,3,4]') == 'false' - assert evalpy('9 not in [1,2,3,4]') == 'true' - - assert evalpy('"bar" in {"foo": 3}') == 'false' - assert evalpy('"foo" in {"foo": 3}') == 'true' - + assert py2js("4 > 3") == "4 > 3;" + assert py2js("4 is 3") == "4 === 3;" + + assert evalpy("4 > 4") == "false" + assert evalpy("4 >= 4") == "true" + assert evalpy("4 < 3") == "false" + assert evalpy("4 <= 4") == "true" + assert evalpy("4 == 3") == "false" + assert evalpy("4 != 3") == "true" + + assert evalpy('4 == "4"') == "true" # yuck! + assert evalpy('4 is "4"') == "false" + assert evalpy('4 is not "4"') == "true" + + assert evalpy('"c" in "abcd"') == "true" + assert evalpy('"x" in "abcd"') == "false" + assert evalpy('"x" not in "abcd"') == "true" + + assert evalpy("3 in [1,2,3,4]") == "true" + assert evalpy("9 in [1,2,3,4]") == "false" + assert evalpy("9 not in [1,2,3,4]") == "true" + + assert evalpy('"bar" in {"foo": 3}') == "false" + assert evalpy('"foo" in {"foo": 3}') == "true" + # was a bug - assert evalpy('not (1 is null and 1 is null)') == 'true' - + assert evalpy("not (1 is null and 1 is null)") == "true" + def test_deep_comparisons(self): # List - arr = '[(1,2), (3,4), (5,6), (1,2), (7,8)]\n' - assert evalpy('a=' + arr + '(1,2) in a') == 'true' - assert evalpy('a=' + arr + '(7,8) in a') == 'true' - assert evalpy('a=' + arr + '(3,5) in a') == 'false' - assert evalpy('a=' + arr + '3 in a') == 'false' - - assert evalpy('(2, 3) == (2, 3)') == 'true' - assert evalpy('[2, 3] == [2, 3]') == 'true' - assert evalpy('a=' + arr + 'b=' + arr + 'a==b') == 'true' - + arr = "[(1,2), (3,4), (5,6), (1,2), (7,8)]\n" + assert evalpy("a=" + arr + "(1,2) in a") == "true" + assert evalpy("a=" + arr + "(7,8) in a") == "true" + assert evalpy("a=" + arr + "(3,5) in a") == "false" + assert evalpy("a=" + arr + "3 in a") == "false" + + assert evalpy("(2, 3) == (2, 3)") == "true" + assert evalpy("[2, 3] == [2, 3]") == "true" + assert evalpy("a=" + arr + "b=" + arr + "a==b") == "true" + # Dict dct = '{"a":7, 3:"foo", "bar": 1, "9": 3}\n' - assert evalpy('d=' + dct + '"a" in d') == 'true' - assert evalpy('d=' + dct + '"3" in d') == 'true' - assert evalpy('d=' + dct + '3 in d') == 'true' - assert evalpy('d=' + dct + '"bar" in d') == 'true' - assert evalpy('d=' + dct + '9 in d') == 'true' - assert evalpy('d=' + dct + '"9" in d') == 'true' - assert evalpy('d=' + dct + '7 in d') == 'false' - assert evalpy('d=' + dct + '"1" in d') == 'false' - - assert evalpy('{2: 3} == {"2": 3}') == 'true' - assert evalpy('dict(foo=7) == {"foo": 7}') == 'true' - assert evalpy('a=' + dct + 'b=' + dct + 'a==b') == 'true' - assert evalpy('{"foo": 1, "bar": 2}=={"bar": 2, "foo": 1}') == 'true' - assert evalpy('{"bar": 2, "foo": 1}=={"foo": 1, "bar": 2}') == 'true' - + assert evalpy("d=" + dct + '"a" in d') == "true" + assert evalpy("d=" + dct + '"3" in d') == "true" + assert evalpy("d=" + dct + "3 in d") == "true" + assert evalpy("d=" + dct + '"bar" in d') == "true" + assert evalpy("d=" + dct + "9 in d") == "true" + assert evalpy("d=" + dct + '"9" in d') == "true" + assert evalpy("d=" + dct + "7 in d") == "false" + assert evalpy("d=" + dct + '"1" in d') == "false" + + assert evalpy('{2: 3} == {"2": 3}') == "true" + assert evalpy('dict(foo=7) == {"foo": 7}') == "true" + assert evalpy("a=" + dct + "b=" + dct + "a==b") == "true" + assert evalpy('{"foo": 1, "bar": 2}=={"bar": 2, "foo": 1}') == "true" + assert evalpy('{"bar": 2, "foo": 1}=={"foo": 1, "bar": 2}') == "true" + # Deeper d1 = 'd1={"foo": [2, 3, {1:2,3:4,5:["aa", "bb"]}], "bar": None}\n' d2 = 'd2={"bar": None, "foo": [2, 3, {5:["aa", "bb"],1:2,3:4}]}\n' # same d3 = 'd3={"foo": [2, 3, {1:2,3:4,5:["aa", "b"]}], "bar": None}\n' # minus b - assert evalpy(d1+d2+d3+'d1 == d2') == 'true' - assert evalpy(d1+d2+d3+'d2 == d1') == 'true' - assert evalpy(d1+d2+d3+'d1 != d2') == 'false' - assert evalpy(d1+d2+d3+'d1 == d3') == 'false' - assert evalpy(d1+d2+d3+'d1 != d3') == 'true' + assert evalpy(d1 + d2 + d3 + "d1 == d2") == "true" + assert evalpy(d1 + d2 + d3 + "d2 == d1") == "true" + assert evalpy(d1 + d2 + d3 + "d1 != d2") == "false" + assert evalpy(d1 + d2 + d3 + "d1 == d3") == "false" + assert evalpy(d1 + d2 + d3 + "d1 != d3") == "true" # - assert evalpy(d1+d2+d3+'d2 in [2, d1, 4]') == 'true' - assert evalpy(d1+d2+d3+'d2 in ("xx", d2, None)') == 'true' - assert evalpy(d1+d2+d3+'d2 not in (1, d3, 2)') == 'true' - assert evalpy(d1+d2+d3+'4 in [2, d1, 4]') == 'true' - + assert evalpy(d1 + d2 + d3 + "d2 in [2, d1, 4]") == "true" + assert evalpy(d1 + d2 + d3 + 'd2 in ("xx", d2, None)') == "true" + assert evalpy(d1 + d2 + d3 + "d2 not in (1, d3, 2)") == "true" + assert evalpy(d1 + d2 + d3 + "4 in [2, d1, 4]") == "true" + def test_truthfulness_of_basic_types(self): # Numbers - assert evalpy('"T" if (1) else "F"') == 'T' - assert evalpy('"T" if (0) else "F"') == 'F' - + assert evalpy('"T" if (1) else "F"') == "T" + assert evalpy('"T" if (0) else "F"') == "F" + # Strings - assert evalpy('"T" if ("a") else "F"') == 'T' - assert evalpy('"T" if ("") else "F"') == 'F' - + assert evalpy('"T" if ("a") else "F"') == "T" + assert evalpy('"T" if ("") else "F"') == "F" + # None - undefined - assert evalpy('None is null') == 'true' - assert evalpy('None is undefined') == 'false' - assert evalpy('undefined is undefined') == 'true' - + assert evalpy("None is null") == "true" + assert evalpy("None is undefined") == "false" + assert evalpy("undefined is undefined") == "true" + def test_truthfulness_of_array_and_dict(self): - # Arrays - assert evalpy('bool([1])') == 'true' - assert evalpy('bool([])') == 'false' + assert evalpy("bool([1])") == "true" + assert evalpy("bool([])") == "false" # - assert evalpy('"T" if ([1, 2, 3]) else "F"') == 'T' - assert evalpy('"T" if ([]) else "F"') == 'F' + assert evalpy('"T" if ([1, 2, 3]) else "F"') == "T" + assert evalpy('"T" if ([]) else "F"') == "F" # - assert evalpy('if [1]: "T"\nelse: "F"') == 'T' - assert evalpy('if []: "T"\nelse: "F"') == 'F' + assert evalpy('if [1]: "T"\nelse: "F"') == "T" + assert evalpy('if []: "T"\nelse: "F"') == "F" # - assert evalpy('if [1] and 1: "T"\nelse: "F"') == 'T' - assert evalpy('if [] and 1: "T"\nelse: "F"') == 'F' - assert evalpy('if [] or 1: "T"\nelse: "F"') == 'T' + assert evalpy('if [1] and 1: "T"\nelse: "F"') == "T" + assert evalpy('if [] and 1: "T"\nelse: "F"') == "F" + assert evalpy('if [] or 1: "T"\nelse: "F"') == "T" # - assert evalpy('[2] or 42') == '[ 2 ]' - assert evalpy('[] or 42') == '42' - + assert evalpy("[2] or 42") == "[ 2 ]" + assert evalpy("[] or 42") == "42" + # Dicts - assert evalpy('bool({1:2})') == 'true' - assert evalpy('bool({})') == 'false' + assert evalpy("bool({1:2})") == "true" + assert evalpy("bool({})") == "false" # - assert evalpy('"T" if ({"foo": 3}) else "F"') == 'T' - assert evalpy('"T" if ({}) else "F"') == 'F' + assert evalpy('"T" if ({"foo": 3}) else "F"') == "T" + assert evalpy('"T" if ({}) else "F"') == "F" # - assert evalpy('if {1:2}: "T"\nelse: "F"') == 'T' - assert evalpy('if {}: "T"\nelse: "F"') == 'F' + assert evalpy('if {1:2}: "T"\nelse: "F"') == "T" + assert evalpy('if {}: "T"\nelse: "F"') == "F" # - assert evalpy('if {1:2} and 1: "T"\nelse: "F"') == 'T' - assert evalpy('if {} and 1: "T"\nelse: "F"') == 'F' - assert evalpy('if {} or 1: "T"\nelse: "F"') == 'T' + assert evalpy('if {1:2} and 1: "T"\nelse: "F"') == "T" + assert evalpy('if {} and 1: "T"\nelse: "F"') == "F" + assert evalpy('if {} or 1: "T"\nelse: "F"') == "T" # - assert evalpy('{1:2} or 42') == "{ '1': 2 }" - assert evalpy('{} or 42') == '42' - assert evalpy('{} or 0') == '0' - assert evalpy('None or []') == '[]' - + assert evalpy("{1:2} or 42") == "{ '1': 2 }" + assert evalpy("{} or 42") == "42" + assert evalpy("{} or 0") == "0" + assert evalpy("None or []") == "[]" + # Eval extra types - assert evalpy('null or 42') == '42' - assert evalpy('ArrayBuffer(4) or 42') != '42' - + assert evalpy("null or 42") == "42" + assert evalpy("ArrayBuffer(4) or 42") != "42" + # No bools - assert py2js('if foo: pass').count('_truthy') - assert py2js('if foo.length: pass').count('_truthy') == 0 - assert py2js('if 3: pass').count('_truthy') == 0 - assert py2js('if True: pass').count('_truthy') == 0 - assert py2js('if a == 3: pass').count('_truthy') == 0 - assert py2js('if a is 3: pass').count('_truthy') == 0 - - + assert py2js("if foo: pass").count("_truthy") + assert py2js("if foo.length: pass").count("_truthy") == 0 + assert py2js("if 3: pass").count("_truthy") == 0 + assert py2js("if True: pass").count("_truthy") == 0 + assert py2js("if a == 3: pass").count("_truthy") == 0 + assert py2js("if a is 3: pass").count("_truthy") == 0 + def test_indexing_and_slicing(self): - c = 'a = [1, 2, 3, 4, 5]\n' - + c = "a = [1, 2, 3, 4, 5]\n" + # Indexing - assert evalpy(c + 'a[2]') == '3' - assert evalpy(c + 'a[-2]') == '4' - + assert evalpy(c + "a[2]") == "3" + assert evalpy(c + "a[-2]") == "4" + # Slicing - assert evalpy(c + 'a[:]') == '[ 1, 2, 3, 4, 5 ]' - assert evalpy(c + 'a[1:-1]') == '[ 2, 3, 4 ]' - + assert evalpy(c + "a[:]") == "[ 1, 2, 3, 4, 5 ]" + assert evalpy(c + "a[1:-1]") == "[ 2, 3, 4 ]" + def test_assignments(self): - assert py2js('foo = 3') == 'var foo;\nfoo = 3;' # with var - assert py2js('foo.bar = 3') == 'foo.bar = 3;' # without var - assert py2js('foo[i] = 3') == 'foo[i] = 3;' # without var - - code = py2js('foo = 3; bar = 4') # define both - assert code.count('var') == 1 - code = py2js('foo = 3; foo = 4') # only define first time - assert code.count('var') == 1 - - code = py2js('foo = bar = 3') # multiple assignment - assert 'foo = bar = 3' in code - assert 'var bar, foo' in code # alphabetic order - + assert py2js("foo = 3") == "var foo;\nfoo = 3;" # with var + assert py2js("foo.bar = 3") == "foo.bar = 3;" # without var + assert py2js("foo[i] = 3") == "foo[i] = 3;" # without var + + code = py2js("foo = 3; bar = 4") # define both + assert code.count("var") == 1 + code = py2js("foo = 3; foo = 4") # only define first time + assert code.count("var") == 1 + + code = py2js("foo = bar = 3") # multiple assignment + assert "foo = bar = 3" in code + assert "var bar, foo" in code # alphabetic order + # self -> this - assert py2js('self') == 'this;' - assert py2js('self.foo') == 'this.foo;' - + assert py2js("self") == "this;" + assert py2js("self.foo") == "this.foo;" + # Indexing - assert evalpy('a=[0,0]\na[0]=2\na[1]=3\na', False) == '[2,3]' - + assert evalpy("a=[0,0]\na[0]=2\na[1]=3\na", False) == "[2,3]" + # Tuple unpacking - evalpy('x=[1,2,3]\na, b, c = x\nb', False) == '2' - evalpy('a,b,c = [1,2,3]\nc,b,a = a,b,c\n[a,b,c]', False) == '[3,2,1]' - + assert evalpy("x=[1,2,3]\na, b, c = x\nb", False) == "2" + assert evalpy("a,b,c = [1,2,3]\nc,b,a = a,b,c\n[a,b,c]", False) == "[3,2,1]" + # For unpacking, test that variables are declared, but not when attr or index - assert py2js('xx, yy = 3, 4').count('xx') == 2 - assert py2js('xx[0], yy[0] = 3, 4').count('xx') == 1 - assert py2js('xx.a, yy.a = 3, 4').count('xx') == 1 - + assert py2js("xx, yy = 3, 4").count("xx") == 2 + assert py2js("xx[0], yy[0] = 3, 4").count("xx") == 1 + assert py2js("xx.a, yy.a = 3, 4").count("xx") == 1 + # Class variables don't get a var - code = py2js('class Foo:\n bar=3\n bar = bar + 1') - assert code.count('bar') == 3 - assert code.count('Foo.prototype.bar') == 3 - + code = py2js("class Foo:\n bar=3\n bar = bar + 1") + assert code.count("bar") == 3 + assert code.count("Foo.prototype.bar") == 3 + def test_aug_assignments(self): # assign + bin op - assert evalpy('x=5; x+=1; x') == '6' - assert evalpy('x=5; x/=2; x') == '2.5' - assert evalpy('x=5; x**=2; x') == '25' - assert evalpy('x=5; x//=2; x') == '2' - + assert evalpy("x=5; x+=1; x") == "6" + assert evalpy("x=5; x/=2; x") == "2.5" + assert evalpy("x=5; x**=2; x") == "25" + assert evalpy("x=5; x//=2; x") == "2" + def test_basic_types(self): - assert py2js('True') == 'true;' - assert py2js('False') == 'false;' - assert py2js('None') == 'null;' - + assert py2js("True") == "true;" + assert py2js("False") == "false;" + assert py2js("None") == "null;" + assert py2js('"bla\\"bla"') == '"bla\\"bla";' - assert py2js('3') == '3;' - assert py2js('3.1415') == '3.1415;' - - assert py2js('[1,2,3]') == '[1, 2, 3];' - assert py2js('(1,2,3)') == '[1, 2, 3];' - - assert py2js('{"foo": 3, "bar": 4}') == '({foo: 3, bar: 4});' - assert evalpy('a={"foo": 3, "bar": 4};a') == '{ foo: 3, bar: 4 }' - + assert py2js("3") == "3;" + assert py2js("3.1415") == "3.1415;" + + assert py2js("[1,2,3]") == "[1, 2, 3];" + assert py2js("(1,2,3)") == "[1, 2, 3];" + + assert py2js('{"foo": 3, "bar": 4}') == "({foo: 3, bar: 4});" + assert evalpy('a={"foo": 3, "bar": 4};a') == "{ foo: 3, bar: 4 }" + def test_dict_literals(self): # JS has a different way to define dict literals, with limitation # (especially on IE), so we add some magic sause to make it work. - + def tester1(): - a = 'foo' - d = {a: 'bar1', 2: 'bar2', 'sp' + 'am': 'bar3'} + a = "foo" + d = {a: "bar1", 2: "bar2", "sp" + "am": "bar3"} print(d.foo, d[2], d.spam) - + js = py2js(tester1) - assert evaljs(js + 'tester1()') == 'bar1 bar2 bar3\nnull' - + assert evaljs(js + "tester1()") == "bar1 bar2 bar3\nnull" + def test_ignore_import_of_compiler(self): modname = pscript.__name__ - assert py2js('from %s import x, y, z\n42' % modname) == '42;' - + assert py2js("from %s import x, y, z\n42" % modname) == "42;" + def test_import(self): with raises(JSError): - py2js('import time') - + py2js("import time") + # But we do support special time funcs import time - assert abs(float(evalpy('time()')) - time.time()) < 0.5 - evalpy('t0=perf_counter(); t1=perf_counter(); (t1-t0)').startswith('0.0') - + + assert abs(float(evalpy("time()")) - time.time()) < 0.5 + evalpy("t0=perf_counter(); t1=perf_counter(); (t1-t0)").startswith("0.0") + def test_funcion_call(self): - jscode = 'var foo = function (x, y) {return x+y;};' - assert evaljs(jscode + py2js('foo(2,2)')) == '4' - assert evaljs(jscode + py2js('foo("so ", True)')) == 'so true' - assert evaljs(jscode + py2js('a=[1,2]; foo(*a)')) == '3' - assert evaljs(jscode + py2js('a=[1,2]; foo(7, *a)')) == '8' - + jscode = "var foo = function (x, y) {return x+y;};" + assert evaljs(jscode + py2js("foo(2,2)")) == "4" + assert evaljs(jscode + py2js('foo("so ", True)')) == "so true" + assert evaljs(jscode + py2js("a=[1,2]; foo(*a)")) == "3" + assert evaljs(jscode + py2js("a=[1,2]; foo(7, *a)")) == "8" + # Test super (is tested for real in test_parser3.py - assert evalpy('d={"_base_class": console};d._base_class.log(4)') == '4' - assert evalpy('d={"_base_class": console};d._base_class.log()') == '' - - jscode = 'var foo = function () {return this.val};' + assert evalpy('d={"_base_class": console};d._base_class.log(4)') == "4" + assert evalpy('d={"_base_class": console};d._base_class.log()') == "" + + jscode = "var foo = function () {return this.val};" jscode += 'var d = {"foo": foo, "val": 7};\n' - assert evaljs(jscode + py2js('d["foo"]()')) == '7' - assert evaljs(jscode + py2js('d["foo"](*[3, 4])')) == '7' - + assert evaljs(jscode + py2js('d["foo"]()')) == "7" + assert evaljs(jscode + py2js('d["foo"](*[3, 4])')) == "7" + def test_instantiation(self): # Test creating instances - assert 'new' in py2js('a = Bar()') - assert 'new' in py2js('a = x.Bar()') - assert 'new' not in py2js('a = foo()') - assert 'new' not in py2js('a = _foo()') - assert 'new' not in py2js('a = _Foo()') - assert 'new' not in py2js('a = this.Bar()') - assert 'new' not in py2js('a = JSON.stringify(x)') - - jscode = 'function Bar() {this.x = 3}\nvar x=1;\n' - assert evaljs(jscode + py2js('a=Bar()\nx')) == '1' - + assert "new" in py2js("a = Bar()") + assert "new" in py2js("a = x.Bar()") + assert "new" not in py2js("a = foo()") + assert "new" not in py2js("a = _foo()") + assert "new" not in py2js("a = _Foo()") + assert "new" not in py2js("a = this.Bar()") + assert "new" not in py2js("a = JSON.stringify(x)") + + jscode = "function Bar() {this.x = 3}\nvar x=1;\n" + assert evaljs(jscode + py2js("a=Bar()\nx")) == "1" + # Existing classes and functions are used to determine if a # call is an instantiation - assert 'new' in py2js('class foo:pass\na = foo()') - assert 'new' not in py2js('class foo:pass\ndef foo():pass\na = foo()') - assert 'new' not in py2js('def foo():pass\nclass foo:pass\na = foo()') + assert "new" in py2js("class foo:pass\na = foo()") + assert "new" not in py2js("class foo:pass\ndef foo():pass\na = foo()") + assert "new" not in py2js("def foo():pass\nclass foo:pass\na = foo()") # - assert 'new' not in py2js('def Bar():pass\na = Bar()') - assert 'new' in py2js('def Bar():pass\nclass Bar:pass\na = Bar()') - assert 'new' in py2js('class Bar:pass\ndef Bar():pass\na = Bar()') - + assert "new" not in py2js("def Bar():pass\na = Bar()") + assert "new" in py2js("def Bar():pass\nclass Bar:pass\na = Bar()") + assert "new" in py2js("class Bar:pass\ndef Bar():pass\na = Bar()") + def test_pass(self): - assert py2js('pass') == '' - + assert py2js("pass") == "" + def test_delete(self): - assert evalpy('d={}\nd.foo=3\n\nd') == "{ foo: 3 }" - assert evalpy('d={}\nd.foo=3\ndel d.foo\nd') == '{}' - assert evalpy('d={}\nd.foo=3\nd.bar=3\ndel d.foo\nd') == '{ bar: 3 }' - assert evalpy('d={}\nd.foo=3\nd.bar=3\ndel d.foo, d["bar"]\nd') == '{}' - + assert evalpy("d={}\nd.foo=3\n\nd") == "{ foo: 3 }" + assert evalpy("d={}\nd.foo=3\ndel d.foo\nd") == "{}" + assert evalpy("d={}\nd.foo=3\nd.bar=3\ndel d.foo\nd") == "{ bar: 3 }" + assert evalpy('d={}\nd.foo=3\nd.bar=3\ndel d.foo, d["bar"]\nd') == "{}" + class TestModules: - def test_module(self): - - code = Parser('"docstring"\nfoo=3;bar=4;_priv=0;', 'foo.py').dump() - + code = Parser('"docstring"\nfoo=3;bar=4;_priv=0;', "foo.py").dump() + # Has docstring - assert code.count('// docstring') == 1 + assert code.count("// docstring") == 1 class TestOverload: - def test_overload_add_and_mul(self): - def foo(): PSCRIPT_OVERLOAD = False a, b = 3, 4 return a + b * a - + js = py2js(foo) assert "PSCRIPT_OVERLOAD" not in js assert "pyfunc" not in js - assert evaljs(js + '\nfoo();') == '15' - + assert evaljs(js + "\nfoo();") == "15" + def bar(): PSCRIPT_OVERLOAD = False a, b = 3, 4 a += b a *= b return a - + js = py2js(bar) assert "PSCRIPT_OVERLOAD" not in js assert "pyfunc" not in js - assert evaljs(js + '\nbar();') == '28' - + assert evaljs(js + "\nbar();") == "28" + def test_overload_equals(self): - def foo(): PSCRIPT_OVERLOAD = False c = 4 print(c == 4) # we dont't *need* overloading here print(c == 5) # or here return [3, 4] == [3, 4] # but beware of this! - + js = py2js(foo) assert "PSCRIPT_OVERLOAD" not in js assert "pyfunc" not in js - assert evaljs(js + '\nfoo();') == 'true\nfalse\nfalse' - + assert evaljs(js + "\nfoo();") == "true\nfalse\nfalse" + def test_overload_truthy(self): - def foo(): PSCRIPT_OVERLOAD = False - for v in [true, 0, "a", "", [], {}]: + for v in [true, 0, "a", "", [], {}]: # noqa if v: - print('1') + print("1") else: - print('0') + print("0") return None or False - + js = py2js(foo) assert "PSCRIPT_OVERLOAD" not in js assert "pyfunc" not in js - ans = '1', '0', '1', '0', '1', '1', 'false' - assert evaljs(js + '\nfoo();') == '\n'.join(ans) - - + ans = "1", "0", "1", "0", "1", "1", "false" + assert evaljs(js + "\nfoo();") == "\n".join(ans) + def bar(): PSCRIPT_OVERLOAD = False - for v in [true, 0, "a", "", [], {}]: + for v in [true, 0, "a", "", [], {}]: # noqa if v: - print('1' + bool(v)) + print("1" + bool(v)) else: - print('0' + bool(v)) + print("0" + bool(v)) return None or False - + js = py2js(bar) assert "PSCRIPT_OVERLOAD" not in js # assert "pyfunc" not in js # bool() does pyfunc_truthy() - ans = '1true', '0false', '1true', '0false', '1false', '1false', 'false' - assert evaljs(js + '\nbar();') == '\n'.join(ans) + ans = "1true", "0false", "1true", "0false", "1false", "1false", "false" + assert evaljs(js + "\nbar();") == "\n".join(ans) - def test_overload_usage(self): - # Can only use in a function with raises(JSError) as err_info: py2js("PSCRIPT_OVERLOAD=False\n3+4") - assert 'PSCRIPT_OVERLOAD inside a function' in str(err_info.value) - + assert "PSCRIPT_OVERLOAD inside a function" in str(err_info.value) + # Can only use with a bool def foo(): PSCRIPT_OVERLOAD = 0 - return a + b - + return a + b # noqa + with raises(JSError) as err_info: py2js(foo) - assert 'PSCRIPT_OVERLOAD with a bool' in str(err_info.value) - + assert "PSCRIPT_OVERLOAD with a bool" in str(err_info.value) + def test_overload_scope(self): - # Can turn on and off def foo(): - print({} or 'x') + print({} or "x") PSCRIPT_OVERLOAD = False - print({} or 'x') + print({} or "x") PSCRIPT_OVERLOAD = True - print({} or 'x') + print({} or "x") + # js = py2js(foo) - assert evaljs(js + '\nfoo();').replace('\n', ' ') == 'x {} x null' - + assert evaljs(js + "\nfoo();").replace("\n", " ") == "x {} x null" + # Scoped per function def foo(): - def x1(): - print({} or 'x') + print({} or "x") + def x2(): PSCRIPT_OVERLOAD = False - print({} or 'x') + print({} or "x") + def x3(): - print({} or 'x') - - print({} or 'x') + print({} or "x") + + print({} or "x") x1() x2() x3() - print({} or 'x') + print({} or "x") + # js = py2js(foo) - assert evaljs(js + '\nfoo();').replace('\n', ' ') == 'x x {} x x null' - + assert evaljs(js + "\nfoo();").replace("\n", " ") == "x x {} x x null" + # Scope is maintained def foo(): PSCRIPT_OVERLOAD = False - + def x1(): - print({} or 'x') + print({} or "x") + def x2(): PSCRIPT_OVERLOAD = False - print({} or 'x') + print({} or "x") + def x3(): - print({} or 'x') - - print({} or 'x') + print({} or "x") + + print({} or "x") x1() x2() x3() - print({} or 'x') + print({} or "x") + # js = py2js(foo) - assert evaljs(js + '\nfoo();').replace('\n', ' ') == '{} x {} x {} null' + assert evaljs(js + "\nfoo();").replace("\n", " ") == "{} x {} x {} null" run_tests_if_main() diff --git a/tests/test_parser2.py b/tests/test_parser2.py index d44e5a93..979b2c95 100644 --- a/tests/test_parser2.py +++ b/tests/test_parser2.py @@ -1,794 +1,789 @@ -import sys +# ruff: noqa: F841 -from pscript.testing import run_tests_if_main, raises, skipif +from pscript.testing import run_tests_if_main, raises from pscript import RawJS, JSError, py2js, evaljs, evalpy +from pscript.stubs import undefined def nowhitespace(s): - return s.replace('\n', '').replace('\t', '').replace(' ', '') + return s.replace("\n", "").replace("\t", "").replace(" ", "") def normallist(s): - s = s.replace('[\n ', '[').replace('\n]', ']').replace('\n ', ' ') - s = s.replace('[ ', '[').replace('[ ', '[').replace(' ]', ']') - s = s.replace(', ', ', ').replace(', ', ', ') + s = s.replace("[\n ", "[").replace("\n]", "]").replace("\n ", " ") + s = s.replace("[ ", "[").replace("[ ", "[").replace(" ]", "]") + s = s.replace(", ", ", ").replace(", ", ", ") return s class TestConrolFlow: - def test_ignore_if_name_is_main(self): - assert py2js('if __name__ == "__main__":4') == '' - + assert py2js('if __name__ == "__main__":4') == "" + def test_if(self): # Normal if - assert evalpy('if True: 4\nelse: 5') == '4' - assert evalpy('if False: 4\nelse: 5') == '5' - assert evalpy('x=4\nif x>3: 13\nelif x > 2: 12\nelse: 10') == '13' - assert evalpy('x=3\nif x>3: 13\nelif x > 2: 12\nelse: 10') == '12' - assert evalpy('x=1\nif x>3: 13\nelif x > 2: 12\nelse: 10') == '10' - + assert evalpy("if True: 4\nelse: 5") == "4" + assert evalpy("if False: 4\nelse: 5") == "5" + assert evalpy("x=4\nif x>3: 13\nelif x > 2: 12\nelse: 10") == "13" + assert evalpy("x=3\nif x>3: 13\nelif x > 2: 12\nelse: 10") == "12" + assert evalpy("x=1\nif x>3: 13\nelif x > 2: 12\nelse: 10") == "10" + # One-line if - line = py2js('3 if True else 4').replace(')', '').replace('(', '') - assert line == 'true? 3 : 4;' + line = py2js("3 if True else 4").replace(")", "").replace("(", "") + assert line == "true? 3 : 4;" # - assert evalpy('4 if True else 5') == '4' - assert evalpy('4 if False else 5') == '5' - assert evalpy('3+1 if 0+2/1 else 4+1') == '4' - assert evalpy('3+1 if 4/2-2 else 4+1') == '5' - + assert evalpy("4 if True else 5") == "4" + assert evalpy("4 if False else 5") == "5" + assert evalpy("3+1 if 0+2/1 else 4+1") == "4" + assert evalpy("3+1 if 4/2-2 else 4+1") == "5" + # If with this_is_js() - assert '5' in py2js('if this_is_js_xx(): 4\nelse: 5') - assert '5' not in py2js('if this_is_js(): 4\nelse: 5') - + assert "5" in py2js("if this_is_js_xx(): 4\nelse: 5") + assert "5" not in py2js("if this_is_js(): 4\nelse: 5") + def test_for(self): - # Test all possible ranges - line = nowhitespace(py2js('for i in range(9): pass', inline_stdlib=False)) - assert line == 'vari;for(i=0;i<9;i+=1){}' - line = nowhitespace(py2js('for i in range(2, 99): pass', inline_stdlib=False)) - assert line == 'vari;for(i=2;i<99;i+=1){}' - line = nowhitespace(py2js('for i in range(100, 0, -1): pass', inline_stdlib=False)) - assert line == 'vari;for(i=100;i>0;i+=-1){}' - + line = nowhitespace(py2js("for i in range(9): pass", inline_stdlib=False)) + assert line == "vari;for(i=0;i<9;i+=1){}" + line = nowhitespace(py2js("for i in range(2, 99): pass", inline_stdlib=False)) + assert line == "vari;for(i=2;i<99;i+=1){}" + line = nowhitespace( + py2js("for i in range(100, 0, -1): pass", inline_stdlib=False) + ) + assert line == "vari;for(i=100;i>0;i+=-1){}" + # Test enumeration (code) - assert ' in ' not in py2js('for i in [1, 2, 3]: pass') - assert ' in ' not in py2js('for i in {1:2, 2:3}: pass') - + assert " in " not in py2js("for i in [1, 2, 3]: pass") + assert " in " not in py2js("for i in {1:2, 2:3}: pass") + # Test declaration of iteration variable - assert 'var aa' in py2js('for aa in x: pass') - assert 'var aa' in py2js('aa=""\nfor aa in x: pass') - assert 'var aa' in py2js('j=aa=""\nfor aa in x: pass') - + assert "var aa" in py2js("for aa in x: pass") + assert "var aa" in py2js('aa=""\nfor aa in x: pass') + assert "var aa" in py2js('j=aa=""\nfor aa in x: pass') + # Test output for range - assert evalpy('for i in range(3):\n print(i)') == '0\n1\n2' - assert evalpy('for i in range(1,6,2):\n print(i)') == '1\n3\n5' - + assert evalpy("for i in range(3):\n print(i)") == "0\n1\n2" + assert evalpy("for i in range(1,6,2):\n print(i)") == "1\n3\n5" + # Range with complex input - assert evalpy('for i in range(sum([2, 3])): print(i)') == '0\n1\n2\n3\n4' - + assert evalpy("for i in range(sum([2, 3])): print(i)") == "0\n1\n2\n3\n4" + # Test explicit for-array iteration - code = py2js('a=[7,8]\nfor i in range(len(a)):\n print(a[i])') - assert ' in ' not in code and evaljs(code) == '7\n8' + code = py2js("a=[7,8]\nfor i in range(len(a)):\n print(a[i])") + assert " in " not in code and evaljs(code) == "7\n8" # Test enumeration over arrays - should use actual for-loop - code = py2js('for k in [7, 8]:\n print(k)') - assert ' in ' not in code and evaljs(code) == '7\n8' + code = py2js("for k in [7, 8]:\n print(k)") + assert " in " not in code and evaljs(code) == "7\n8" # compile time tests - raises(JSError, py2js, 'for i, j in range(10): pass') - + raises(JSError, py2js, "for i, j in range(10): pass") + # Test enumeration over dicts # Python cannot see its a dict, and uses a for-loop - code = py2js('d = {3:7, 4:8}\nfor k in d:\n print(k)') - assert ' in ' not in code and evaljs(code) == '3\n4' - code = py2js('d = {3:7, 4:8}\nfor k in d:\n print(d[k])') - assert ' in ' not in code and evaljs(code) == '7\n8' + code = py2js("d = {3:7, 4:8}\nfor k in d:\n print(k)") + assert " in " not in code and evaljs(code) == "3\n4" + code = py2js("d = {3:7, 4:8}\nfor k in d:\n print(d[k])") + assert " in " not in code and evaljs(code) == "7\n8" # .keys() - code = py2js('d = {3:7, 4:8}\nfor k in d.keys():\n print(d[k])') - assert evaljs(code) == '7\n8' # and ' in ' in code + code = py2js("d = {3:7, 4:8}\nfor k in d.keys():\n print(d[k])") + assert evaljs(code) == "7\n8" # and ' in ' in code # .values() - code = py2js('d = {3:7, 4:8}\nfor v in d.values():\n print(v)') - assert ' in ' in code and evaljs(code) == '7\n8' + code = py2js("d = {3:7, 4:8}\nfor v in d.values():\n print(v)") + assert " in " in code and evaljs(code) == "7\n8" # .items() - code = py2js('d = {3:7, 4:8}\nfor k,v in d.items():\n print(k)') - assert ' in ' in code and evaljs(code) == '3\n4' - code = py2js('d = {3:7, 4:8}\nfor k,v in d.items():\n print(v)') - assert ' in ' in code and evaljs(code) == '7\n8' + code = py2js("d = {3:7, 4:8}\nfor k,v in d.items():\n print(k)") + assert " in " in code and evaljs(code) == "3\n4" + code = py2js("d = {3:7, 4:8}\nfor k,v in d.items():\n print(v)") + assert " in " in code and evaljs(code) == "7\n8" # compile time tests - raises(JSError, py2js, 'for i, j in x.keys(): pass') - raises(JSError, py2js, 'for i, j in x.values(): pass') - raises(JSError, py2js, 'for i in x.items(): pass') - raises(JSError, py2js, 'for i, j, k in x.items(): pass') - + raises(JSError, py2js, "for i, j in x.keys(): pass") + raises(JSError, py2js, "for i, j in x.values(): pass") + raises(JSError, py2js, "for i in x.items(): pass") + raises(JSError, py2js, "for i, j, k in x.items(): pass") + # Test iterate over strings code = py2js('for c in "foo":\n print(c)') - assert evaljs(code) == 'f\no\no' - + assert evaljs(code) == "f\no\no" + # Break and continue - for9 = 'for i in range(9):\n ' - assert evalpy(for9 + 'if i==4:break\n print(i)') == '0\n1\n2\n3' - assert evalpy(for9 + 'if i<5:continue\n print(i)') == '5\n6\n7\n8' - + for9 = "for i in range(9):\n " + assert evalpy(for9 + "if i==4:break\n print(i)") == "0\n1\n2\n3" + assert evalpy(for9 + "if i<5:continue\n print(i)") == "5\n6\n7\n8" + # Else - assert evalpy(for9 + 'if i==3:break\nelse: print(99)\n0') == '0' - assert evalpy(for9 + 'if i==30:break\nelse: print(99)\n0') == '99\n0' - + assert evalpy(for9 + "if i==3:break\nelse: print(99)\n0") == "0" + assert evalpy(for9 + "if i==30:break\nelse: print(99)\n0") == "99\n0" + # Nested loops correct else code = py2js(self.method_for) - assert evaljs('%s method_for()' % code) == 'ok\nok\nnull' - + assert evaljs("%s method_for()" % code) == "ok\nok\nnull" + # Tuple iterators - assert evalpy('for i, j in [[1, 2], [3, 4]]: print(i+j)') == '3\n7' - assert evalpy('for i, j, k in [[1, 2, 3], [3, 4, 5]]: print(i+j+k)') == '6\n12' - - + assert evalpy("for i, j in [[1, 2], [3, 4]]: print(i+j)") == "3\n7" + assert evalpy("for i, j, k in [[1, 2, 3], [3, 4, 5]]: print(i+j+k)") == "6\n12" + def method_for(self): - for i in range(5): + for _i in range(5): for j in range(5): if j == 4: break else: - print('this should not show') + print("this should not show") else: - print('ok') - + print("ok") + for i in range(5): if i == 1: break - for j in range(5): + for _j in range(5): pass else: - print('ok') + print("ok") else: - print('this should not show') - - + print("this should not show") + def test_while(self): - # Test code output - line = nowhitespace(py2js('while(True): pass')) - assert line == 'while(true){}' - line = nowhitespace(py2js('while(not ok): pass')) - assert 'while' in line - + line = nowhitespace(py2js("while(True): pass")) + assert line == "while(true){}" + line = nowhitespace(py2js("while(not ok): pass")) + assert "while" in line + # Test break and continue - for9 = 'i=-1\nwhile(i<8):\n i+=1\n ' - assert evalpy(for9 + 'if i==4:break\n print(i)\n0') == '0\n1\n2\n3\n0' - assert evalpy(for9 + 'if i<6:continue\n print(i)\n0') == '6\n7\n8\n0' + for9 = "i=-1\nwhile(i<8):\n i+=1\n " + assert evalpy(for9 + "if i==4:break\n print(i)\n0") == "0\n1\n2\n3\n0" + assert evalpy(for9 + "if i<6:continue\n print(i)\n0") == "6\n7\n8\n0" # Test else - assert evalpy(for9 + 'if i==3:break\nelse: print(99)\n0') == '0' - assert evalpy(for9 + 'if i==30:break\nelse: print(99)\n0') == '99\n0' - - + assert evalpy(for9 + "if i==3:break\nelse: print(99)\n0") == "0" + assert evalpy(for9 + "if i==30:break\nelse: print(99)\n0") == "99\n0" + def test_list_comprehensions(self): - # Simple - code1 = '[i for i in [-1, -2, 1, 2, 3]]' + code1 = "[i for i in [-1, -2, 1, 2, 3]]" assert str(eval(code1)) == normallist(evalpy(code1)) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # With assignment - code1 = 'a = 3\na = [i for i in range(a)]\na' - assert evalpy(code1) == '[ 0, 1, 2 ]' - + code1 = "a = 3\na = [i for i in range(a)]\na" + assert evalpy(code1) == "[ 0, 1, 2 ]" + # With ifs - code1 = '[i for i in [-1, -2, 1, 2, 3] if i > 0 and i < 3]' + code1 = "[i for i in [-1, -2, 1, 2, 3] if i > 0 and i < 3]" assert str(eval(code1)) == normallist(evalpy(code1)) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - - code1 = '[i for i in [-1, -2, 1, 2, 3] if i > 0 if i < 3]' + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + + code1 = "[i for i in [-1, -2, 1, 2, 3] if i > 0 if i < 3]" assert str(eval(code1)) == normallist(evalpy(code1)) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # Double - code1 = '[i*j for i in [-1, -2, 1, 2, 3] if i > 0 for j in [1, 10, 100] if j<100]' + code1 = ( + "[i*j for i in [-1, -2, 1, 2, 3] if i > 0 for j in [1, 10, 100] if j<100]" + ) assert str(eval(code1)) == normallist(evalpy(code1)) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # Triple - code1 = '[i*j*k for i in [1, 2, 3] for j in [1, 10] for k in [5, 7]]' + code1 = "[i*j*k for i in [1, 2, 3] for j in [1, 10] for k in [5, 7]]" assert str(eval(code1)) == normallist(evalpy(code1)) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # Double args - code1 = '[(i, j) for i in [1, 2, 3] for j in [1, 10]]' - assert str(eval(code1)).replace('(', '[').replace(')', ']') == normallist(evalpy(code1)) + code1 = "[(i, j) for i in [1, 2, 3] for j in [1, 10]]" + assert str(eval(code1)).replace("(", "[").replace(")", "]") == normallist( + evalpy(code1) + ) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # Double iters - code1 = '[(i, j) for i, j in [[1, 2], [3, 4], [5, 6]]]' - assert str(eval(code1)).replace('(', '[').replace(')', ']') == normallist(evalpy(code1)) + code1 = "[(i, j) for i, j in [[1, 2], [3, 4], [5, 6]]]" + assert str(eval(code1)).replace("(", "[").replace(")", "]") == normallist( + evalpy(code1) + ) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + # Double, dependent iter - code1 = '[i for j in [1,2,3] for i in range(j)]' - assert str(eval(code1)).replace('(', '[').replace(')', ']') == normallist(evalpy(code1)) + code1 = "[i for j in [1,2,3] for i in range(j)]" + assert str(eval(code1)).replace("(", "[").replace(")", "]") == normallist( + evalpy(code1) + ) # - code2 = 'a = ' + code1 + '; a == ' + code1 - assert evalpy(code2) == 'true' - - + code2 = "a = " + code1 + "; a == " + code1 + assert evalpy(code2) == "true" + def test_listcomp_regressions(self): - - code1 = 'a = [i for i in range(the_iter)]' + code1 = "a = [i for i in range(the_iter)]" js = py2js(code1) - assert 'the_iter' in js.meta['vars_unknown'] - assert 'i' not in js.meta['vars_unknown'] - - code1 = 'corners = [[(p[i] + 0.5) for i in range(3)] for p in corners_local]' + assert "the_iter" in js.meta["vars_unknown"] + assert "i" not in js.meta["vars_unknown"] + + code1 = "corners = [[(p[i] + 0.5) for i in range(3)] for p in corners_local]" js = py2js(code1) - assert 'p' not in js.meta['vars_unknown'] - + assert "p" not in js.meta["vars_unknown"] + def foo1(): i = 3 x = [i for i in range(4, 7)] return x + def foo2(): x = [i for i in range(4, 7)] return x - js1 = py2js(foo1) + '\nfoo1()' - js2 = py2js(foo2) + '\nfoo2()' - assert evaljs(js1) == '[ 4, 5, 6 ]' - assert evaljs(js2) == '[ 4, 5, 6 ]' - + + js1 = py2js(foo1) + "\nfoo1()" + js2 = py2js(foo2) + "\nfoo2()" + assert evaljs(js1) == "[ 4, 5, 6 ]" + assert evaljs(js2) == "[ 4, 5, 6 ]" + def xx_test_list_comprehension_speed(self): # https://developers.google.com/speed/articles/optimizing-javascript # ~ 0.029 when comprehension transpile to closures # ~ 0.023 when comprehensions transpile to inner function s, plus smaller chance on closures # ~ 0.017 when comprehensions transpile to inner function s, plus no chance on closures - code = 't0 = perf_counter()\nfor i in range(100000): a = [j for j in range(10)]\n\nperf_counter()-t0' + code = "t0 = perf_counter()\nfor i in range(100000): a = [j for j in range(10)]\n\nperf_counter()-t0" t1 = evalpy(code) print(t1) class TestExceptions: - def test_raise(self): - - assert 'throw' in py2js('raise MyException("foo")') - assert 'MyException' in py2js('raise MyException("foo")') - assert 'foo' in py2js('raise MyException("foo")') - - catcher = 'try { %s } catch(err) { console.log(err); }' - assert evaljs(catcher % py2js('raise "foo"')) == 'foo' - assert evaljs(catcher % py2js('raise 42')) == '42' - assert evaljs(catcher % py2js('raise ValueError')).count('ValueError') - assert evaljs(catcher % py2js('raise ValueError("foo")')).count('foo') - assert evaljs(catcher % py2js('xx = "bar"; raise xx;')).count('bar') - + assert "throw" in py2js('raise MyException("foo")') + assert "MyException" in py2js('raise MyException("foo")') + assert "foo" in py2js('raise MyException("foo")') + + catcher = "try { %s } catch(err) { console.log(err); }" + assert evaljs(catcher % py2js('raise "foo"')) == "foo" + assert evaljs(catcher % py2js("raise 42")) == "42" + assert evaljs(catcher % py2js("raise ValueError")).count("ValueError") + assert evaljs(catcher % py2js('raise ValueError("foo")')).count("foo") + assert evaljs(catcher % py2js('xx = "bar"; raise xx;')).count("bar") + def test_assert(self): - - assert 'throw' in py2js('assert True') - evalpy('assert true; 7') == '7' - evalpy('assert true, "msg"; 7') == '7' - - catcher = 'try { %s } catch(err) { console.log(err); }' - assert evaljs(catcher % py2js('assert false')).count('AssertionError') - assert evaljs(catcher % py2js('assert false, "foo"')).count('foo') - + assert "throw" in py2js("assert True") + assert evalpy("assert true; 7") == "7" + assert evalpy('assert true, "msg"; 7') == "7" + + catcher = "try { %s } catch(err) { console.log(err); }" + assert evaljs(catcher % py2js("assert false")).count("AssertionError") + assert evaljs(catcher % py2js('assert false, "foo"')).count("foo") + def test_assert_catch(self): - def catchtest(x): try: - assert False + assert False # noqa except AssertionError: - print('assertion-error') + print("assertion-error") return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)') == 'assertion-error' - + + assert evaljs(py2js(catchtest, "f") + "f(1)") == "assertion-error" + def test_catching(self): - def catchtest(x): try: if x == 1: - raise ValueError('foo') + raise ValueError("foo") elif x == 2: - raise RuntimeError('foo') + raise RuntimeError("foo") else: - raise "oh crap" + raise "oh crap" # noqa except ValueError: - print('value-error') + print("value-error") except RuntimeError: - print('runtime-error') + print("runtime-error") except Exception: - print('other-error') + print("other-error") return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)') == 'value-error' - assert evaljs(py2js(catchtest, 'f') + 'f(2)') == 'runtime-error' - assert evaljs(py2js(catchtest, 'f') + 'f(3)') == 'other-error' - + + assert evaljs(py2js(catchtest, "f") + "f(1)") == "value-error" + assert evaljs(py2js(catchtest, "f") + "f(2)") == "runtime-error" + assert evaljs(py2js(catchtest, "f") + "f(3)") == "other-error" + def test_catching2(self): - def catchtest(x): try: - raise ValueError('foo') + raise ValueError("foo") except Exception as err: print(err.message) return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)').endswith('foo') - + + assert evaljs(py2js(catchtest, "f") + "f(1)").endswith("foo") + def test_catching3(self): - def catchtest(x): try: try: - raise ValueError('foo') + raise ValueError("foo") except AttributeError: - print('not here') + print("not here") except IndexError: - print('not here either') + print("not here either") except Exception: - print('ok') + print("ok") return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)') == 'ok' - + + assert evaljs(py2js(catchtest, "f") + "f(1)") == "ok" + def test_finally(self): - def catchtest(x): try: - try: - raise ValueError('foo') + raise ValueError("foo") finally: - print('xx') - + print("xx") + except Exception as err: - print('yy') + print("yy") return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)').endswith('xx\nyy') - + + assert evaljs(py2js(catchtest, "f") + "f(1)").endswith("xx\nyy") + def test_finally2(self): # Avoid regression similar to #57 def catchtest(x): try: - try: return undefined finally: - print('xx') - + print("xx") + except Exception as err: - print('yy') + print("yy") return undefined - - assert evaljs(py2js(catchtest, 'f') + 'f(1)').endswith('xx') + + assert evaljs(py2js(catchtest, "f") + "f(1)").endswith("xx") class TestContextManagers: - def test_with_simple(self): - def contexttest(): - c = dict(__enter__=lambda: print('enter'), - __exit__=lambda: print('exit')) + c = dict(__enter__=lambda: print("enter"), __exit__=lambda: print("exit")) with c: print(42) - print('.') + print(".") return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f()') == 'enter\n42\nexit\n.' - + + assert evaljs(py2js(contexttest, "f") + "f()") == "enter\n42\nexit\n." + def test_with_as1(self): - def contexttest(): - c = dict(__enter__=lambda: 7, - __exit__=lambda: print('exit')) + c = dict(__enter__=lambda: 7, __exit__=lambda: print("exit")) with c as item: print(42) print(item) print(43) - print('.') + print(".") return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f()') == '42\n7\n43\nexit\n.' + + assert evaljs(py2js(contexttest, "f") + "f()") == "42\n7\n43\nexit\n." def test_with_as2(self): - def contexttest(): - c = dict(__enter__=lambda: 7, - __exit__=lambda: print('exit')) + c = dict(__enter__=lambda: 7, __exit__=lambda: print("exit")) with c as c.item: print(42) print(c.item) print(43) print(c.item) - print('.') + print(".") return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f()') == '42\n7\n43\nexit\n7\n.' - + + assert evaljs(py2js(contexttest, "f") + "f()") == "42\n7\n43\nexit\n7\n." + def test_with_calculated_context(self): - def contexttest(): def get_ctx(): - print('making') - return dict(__enter__=lambda: 7, - __exit__=lambda: print('exit')) + print("making") + return dict(__enter__=lambda: 7, __exit__=lambda: print("exit")) + with get_ctx() as item: print(item) print(42) return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f()') == 'making\n7\n42\nexit' - + + assert evaljs(py2js(contexttest, "f") + "f()") == "making\n7\n42\nexit" + def test_with_exception(self): - def contexttest(x): - c = dict(__enter__=lambda: print('enter'), - __exit__=lambda et, ev, tb: print(et)) + c = dict( + __enter__=lambda: print("enter"), __exit__=lambda et, ev, tb: print(et) + ) try: with c: print(42) if x != 1: - raise AttributeError('fooerror') + raise AttributeError("fooerror") print(43) except Exception as e: print(e.message) - print('.') + print(".") return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f(1)') == 'enter\n42\n43\nnull\n.' - s = 'enter\n42\nAttributeError\nAttributeError: fooerror\n.' - assert evaljs(py2js(contexttest, 'f') + 'f(0)') == s - + + assert evaljs(py2js(contexttest, "f") + "f(1)") == "enter\n42\n43\nnull\n." + s = "enter\n42\nAttributeError\nAttributeError: fooerror\n." + assert evaljs(py2js(contexttest, "f") + "f(0)") == s + def test_with_return(self): # Avoid regression of #57 def contexttest(): - c = dict(__enter__=lambda: print('enter'), - __exit__=lambda et, ev, tb: print("exit")) + c = dict( + __enter__=lambda: print("enter"), + __exit__=lambda et, ev, tb: print("exit"), + ) with c: print(42) return undefined print(43) - print('.') + print(".") return undefined - - assert evaljs(py2js(contexttest, 'f') + 'f(1)') == 'enter\n42\nexit' - + + assert evaljs(py2js(contexttest, "f") + "f(1)") == "enter\n42\nexit" + def func1(): return 2 + 3 class TestFunctions: - def test_func_default_return_null(self): - assert evalpy('def foo():pass\nprint(foo(), 1)') == 'null 1' - assert evalpy('def foo():return\nprint(foo(), 1)') == 'null 1' - + assert evalpy("def foo():pass\nprint(foo(), 1)") == "null 1" + assert evalpy("def foo():return\nprint(foo(), 1)") == "null 1" + def test_func_call_compilation(self): - assert py2js('foo()') == 'foo();' - assert py2js('foo(3, 4)') == 'foo(3, 4);' - assert py2js('foo(3, 4+1)') == 'foo(3, 4 + 1);' - assert py2js('foo(3, *args)') # JS is complex, just test it compiles - assert py2js('a.foo(3, *args)') # JS is complex, just test it compiles + assert py2js("foo()") == "foo();" + assert py2js("foo(3, 4)") == "foo(3, 4);" + assert py2js("foo(3, 4+1)") == "foo(3, 4 + 1);" + assert py2js("foo(3, *args)") # JS is complex, just test it compiles + assert py2js("a.foo(3, *args)") # JS is complex, just test it compiles def test_simple_funcs_dont_parse_kwargs(self): - # Simplest function does not parse kwargs - code = py2js('def foo(): pass') - assert 'parse_kwargs' not in code - assert 'kw_values' not in code - + code = py2js("def foo(): pass") + assert "parse_kwargs" not in code + assert "kw_values" not in code + # Also not with positional args - code = py2js('def foo(a, b, c): pass') - assert 'parse_kwargs' not in code - assert 'kw_values' not in code - + code = py2js("def foo(a, b, c): pass") + assert "parse_kwargs" not in code + assert "kw_values" not in code + # Also not with varargs - code = py2js('def foo(a, *c): pass') - assert 'parse_kwargs' not in code - assert 'kw_values' not in code - + code = py2js("def foo(a, *c): pass") + assert "parse_kwargs" not in code + assert "kw_values" not in code + # Also not with positional args that have defaults code = py2js('def foo(a, b=1, c="foo"): pass') - assert 'parse_kwargs' not in code - assert 'kw_values' not in code - + assert "parse_kwargs" not in code + assert "kw_values" not in code + def test_when_funcs_do_parse_kwargs(self): - # We do for **kwargs - code = py2js('def foo(a, **c): pass') - assert 'parse_kwargs' in code - assert 'kw_values' not in code - + code = py2js("def foo(a, **c): pass") + assert "parse_kwargs" in code + assert "kw_values" not in code + # We do for keyword only args code = py2js('def foo(a, *, b=1, c="foo"): pass') - assert 'parse_kwargs' in code - assert 'kw_values' in code - + assert "parse_kwargs" in code + assert "kw_values" in code + # We do for keyword only args and **kwargs code = py2js('def foo(a, *, b=1, c="foo", **d): pass') - assert 'parse_kwargs' in code - assert 'kw_values' in code - + assert "parse_kwargs" in code + assert "kw_values" in code + def test_func1(self): code = py2js(func1) - lines = [line for line in code.split('\n') if line] - + lines = [line for line in code.split("\n") if line] + assert len(lines) == 4 # only three lines + definition - assert lines[1] == 'func1 = function flx_func1 () {' # no args - assert lines[2].startswith(' ') # indented - assert lines[3] == '};' # dedented - - + assert lines[1] == "func1 = function flx_func1 () {" # no args + assert lines[2].startswith(" ") # indented + assert lines[3] == "};" # dedented + def test_function_call_simple(self): code = "def foo(x): return x + 1\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(3)') == '4' - assert evalpy(code + 'd.foo(3)') == '4' - + assert evalpy(code + "foo(3)") == "4" + assert evalpy(code + "d.foo(3)") == "4" + def test_function_call_varargs(self): code = "def foo(x, *xx): return x + sum(xx)\nd = {'foo':foo}\nfive=[2, 3]\n" - assert evalpy(code + 'foo(1, 2, 3)') == '6' - assert evalpy(code + 'd.foo(1, 2, 3)') == '6' + assert evalpy(code + "foo(1, 2, 3)") == "6" + assert evalpy(code + "d.foo(1, 2, 3)") == "6" # - assert evalpy(code + 'foo(1, *five)') == '6' - assert evalpy(code + 'd.foo(1, *five)') == '6' - + assert evalpy(code + "foo(1, *five)") == "6" + assert evalpy(code + "d.foo(1, *five)") == "6" + def test_function_call_default_args(self): code = "def foo(a=2, b=3, c=4): return a+b+c;\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(1, 2, 3)') == '6' - assert evalpy(code + 'd.foo(1, 2, 3)') == '6' + assert evalpy(code + "foo(1, 2, 3)") == "6" + assert evalpy(code + "d.foo(1, 2, 3)") == "6" # - assert evalpy(code + 'foo(1, 2)') == '7' - assert evalpy(code + 'd.foo(1, 2)') == '7' + assert evalpy(code + "foo(1, 2)") == "7" + assert evalpy(code + "d.foo(1, 2)") == "7" # - assert evalpy(code + 'foo(1)') == '8' - assert evalpy(code + 'd.foo(1)') == '8' + assert evalpy(code + "foo(1)") == "8" + assert evalpy(code + "d.foo(1)") == "8" # - assert evalpy(code + 'foo()') == '9' - assert evalpy(code + 'd.foo()') == '9' - + assert evalpy(code + "foo()") == "9" + assert evalpy(code + "d.foo()") == "9" + def test_function_call_keyword_only_args(self): code = "def foo(*, a=2, b=3, c=4): return a+b+c;\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(a=1, b=2, c=3)') == '6' - assert evalpy(code + 'd.foo(a=1, b=2, c=3)') == '6' + assert evalpy(code + "foo(a=1, b=2, c=3)") == "6" + assert evalpy(code + "d.foo(a=1, b=2, c=3)") == "6" # - assert evalpy(code + 'foo(b=10)') == '16' - assert evalpy(code + 'd.foo(b=10)') == '16' - + assert evalpy(code + "foo(b=10)") == "16" + assert evalpy(code + "d.foo(b=10)") == "16" + # Also with varargs code = "def foo(*x, a=2, b=3, c=4): return (sum(x) if x else 0) + a+b+c;\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(a=1, b=2, c=3)') == '6' - assert evalpy(code + 'd.foo(a=1, b=2, c=3)') == '6' + assert evalpy(code + "foo(a=1, b=2, c=3)") == "6" + assert evalpy(code + "d.foo(a=1, b=2, c=3)") == "6" # - assert evalpy(code + 'foo(b=10)') == '16' - assert evalpy(code + 'd.foo(b=10)') == '16' + assert evalpy(code + "foo(b=10)") == "16" + assert evalpy(code + "d.foo(b=10)") == "16" # - assert evalpy(code + 'foo(100, 200, b=10)') == '316' - assert evalpy(code + 'd.foo(100, 200, b=10)') == '316' - + assert evalpy(code + "foo(100, 200, b=10)") == "316" + assert evalpy(code + "d.foo(100, 200, b=10)") == "316" + # Cannot pass invalid kwargs - assert evalpy(code + 'try:\n foo(d=4)\nexcept TypeError:\n print("ha")') == 'ha' - assert evalpy(code + 'try:\n d.foo(d=4)\nexcept TypeError:\n print("ha")') == 'ha' - + assert ( + evalpy(code + 'try:\n foo(d=4)\nexcept TypeError:\n print("ha")') == "ha" + ) + assert ( + evalpy(code + 'try:\n d.foo(d=4)\nexcept TypeError:\n print("ha")') + == "ha" + ) + # NOTE: if we use kwargs on a simple func, we just get weird args. # Checking for this case adds overhead, and quite a bit of boilerplate code code = "def foo(a=2): return a\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(a=3)') == '{ flx_args: [], flx_kwargs: { a: 3 } }' - assert evalpy(code + 'd.foo(a=3)') == '{ flx_args: [], flx_kwargs: { a: 3 } }' - + assert evalpy(code + "foo(a=3)") == "{ flx_args: [], flx_kwargs: { a: 3 } }" + assert evalpy(code + "d.foo(a=3)") == "{ flx_args: [], flx_kwargs: { a: 3 } }" + def test_function_call_kwargs(self): code = "def foo(a, b=9, **x): return repr([a, b]) + repr(x);\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(1, 2)') == '[1,2]{}' - assert evalpy(code + 'foo(1, 2, 3)') == '[1,2]{}' # 3d arg is ignored - assert evalpy(code + 'foo(1, b=3)') == '[1,9]{"b":3}' - assert evalpy(code + 'foo(1, b=3, c=4)') == '[1,9]{"b":3,"c":4}' + assert evalpy(code + "foo(1, 2)") == "[1,2]{}" + assert evalpy(code + "foo(1, 2, 3)") == "[1,2]{}" # 3d arg is ignored + assert evalpy(code + "foo(1, b=3)") == '[1,9]{"b":3}' + assert evalpy(code + "foo(1, b=3, c=4)") == '[1,9]{"b":3,"c":4}' # - assert evalpy(code + 'd.foo(1, 2)') == '[1,2]{}' - assert evalpy(code + 'd.foo(1, b=3, c=4)') == '[1,9]{"b":3,"c":4}' - + assert evalpy(code + "d.foo(1, 2)") == "[1,2]{}" + assert evalpy(code + "d.foo(1, b=3, c=4)") == '[1,9]{"b":3,"c":4}' + def test_function_call_args_and_kwargs(self): # Func returns args code = "def foo(*args, **kwargs): return args;\n" - assert evalpy(code + 'foo(1, 2, 3)') == '[ 1, 2, 3 ]' - assert evalpy(code + 'foo(1, 2, 3, a=3)') == '[ 1, 2, 3 ]' - assert evalpy(code + 'foo(1, 2, 3, **{"b":4})') == '[ 1, 2, 3 ]' - assert evalpy(code + 'foo(a=3, **{"b":4})') == '[]' - + assert evalpy(code + "foo(1, 2, 3)") == "[ 1, 2, 3 ]" + assert evalpy(code + "foo(1, 2, 3, a=3)") == "[ 1, 2, 3 ]" + assert evalpy(code + 'foo(1, 2, 3, **{"b":4})') == "[ 1, 2, 3 ]" + assert evalpy(code + 'foo(a=3, **{"b":4})') == "[]" + # Func return kwargs code = "def foo(*args, **kwargs): return kwargs;\n" - assert evalpy(code + 'foo(1, 2, 3)') == '{}' - assert evalpy(code + 'foo(1, 2, 3, a=3)') == '{ a: 3 }' - assert evalpy(code + 'foo(1, 2, 3, **{"b":4})') == '{ b: 4 }' - assert evalpy(code + 'foo(a=3, **{"b":4})') == '{ a: 3, b: 4 }' - + assert evalpy(code + "foo(1, 2, 3)") == "{}" + assert evalpy(code + "foo(1, 2, 3, a=3)") == "{ a: 3 }" + assert evalpy(code + 'foo(1, 2, 3, **{"b":4})') == "{ b: 4 }" + assert evalpy(code + 'foo(a=3, **{"b":4})') == "{ a: 3, b: 4 }" + def test_function_call_keyword_only_args_and_kwargs(self): code = "def foo(*, a=3, b=4, **x): return repr([a, b]) + repr(x);\nd = {'foo':foo}\n" - assert evalpy(code + 'foo(1)') == '[3,4]{}' - assert evalpy(code + 'foo(a=1, b=2)') == '[1,2]{}' - assert evalpy(code + 'foo(a=1, b=2, c=5)') == '[1,2]{"c":5}' + assert evalpy(code + "foo(1)") == "[3,4]{}" + assert evalpy(code + "foo(a=1, b=2)") == "[1,2]{}" + assert evalpy(code + "foo(a=1, b=2, c=5)") == '[1,2]{"c":5}' # - assert evalpy(code + 'd.foo(1)') == '[3,4]{}' - assert evalpy(code + 'd.foo(a=1, b=2, c=5)') == '[1,2]{"c":5}' - + assert evalpy(code + "d.foo(1)") == "[3,4]{}" + assert evalpy(code + "d.foo(a=1, b=2, c=5)") == '[1,2]{"c":5}' + # All the args code = "def foo(a, b=2, *, c=3, **x): return repr([a, b, c]) + repr(x);\n" - assert evalpy(code + 'foo(1)') == '[1,2,3]{}' - assert evalpy(code + 'foo(1, b=8)') == '[1,2,3]{"b":8}' # this one might be surprising - assert evalpy(code + 'foo(1, c=8)') == '[1,2,8]{}' - assert evalpy(code + 'foo(1, d=8)') == '[1,2,3]{"d":8}' - + assert evalpy(code + "foo(1)") == "[1,2,3]{}" + assert ( + evalpy(code + "foo(1, b=8)") == '[1,2,3]{"b":8}' + ) # this one might be surprising + assert evalpy(code + "foo(1, c=8)") == "[1,2,8]{}" + assert evalpy(code + "foo(1, d=8)") == '[1,2,3]{"d":8}' + def method1(self): return - + def test_method1(self): code = py2js(self.method1) - lines = [line for line in code.split('\n') if line] - + lines = [line for line in code.split("\n") if line] + assert len(lines) == 4 # only three lines + definition - assert lines[1] == 'method1 = function () {' # no args, no self/this - assert lines[2].startswith(' ') # indented - assert lines[3] == '};' # dedented - + assert lines[1] == "method1 = function () {" # no args, no self/this + assert lines[2].startswith(" ") # indented + assert lines[3] == "};" # dedented + def test_default_args(self): - def func(self, foo, bar=2): return foo - bar - + code = py2js(func) - lines = [line for line in code.split('\n') if line] - - assert lines[1] == 'func = function (foo, bar) {' - assert '2' in code - - assert evaljs(code + 'func(2)') == '0' - assert evaljs(code + 'func(4, 3)') == '1' - assert evaljs(code + 'func(0, 0)') == '0' - + lines = [line for line in code.split("\n") if line] + + assert lines[1] == "func = function (foo, bar) {" + assert "2" in code + + assert evaljs(code + "func(2)") == "0" + assert evaljs(code + "func(4, 3)") == "1" + assert evaljs(code + "func(0, 0)") == "0" + def test_var_args1(self): - def func(self, *args): return args - + code1 = py2js(func) # lines = [line for line in code1.split('\n') if line] - - code2 = py2js('func(2, 3)') - assert evaljs(code1 + code2, False) == '[2,3]' - code2 = py2js('func()') - assert evaljs(code1 + code2, False) == '[]' - code2 = py2js('a=[2,3]\nfunc(*a)') - assert evaljs(code1 + code2, False) == '[2,3]' - code2 = py2js('a=[2,3]\nfunc(1,2,*a)') - assert evaljs(code1 + code2, False) == '[1,2,2,3]' - + + code2 = py2js("func(2, 3)") + assert evaljs(code1 + code2, False) == "[2,3]" + code2 = py2js("func()") + assert evaljs(code1 + code2, False) == "[]" + code2 = py2js("a=[2,3]\nfunc(*a)") + assert evaljs(code1 + code2, False) == "[2,3]" + code2 = py2js("a=[2,3]\nfunc(1,2,*a)") + assert evaljs(code1 + code2, False) == "[1,2,2,3]" + def test_var_args2(self): - def func(self, foo, *args): return args - + code1 = py2js(func) - #lines = [line for line in code1.split('\n') if line] - - code2 = py2js('func(0, 2, 3)') - assert evaljs(code1 + code2, False) == '[2,3]' - code2 = py2js('func(0)') - assert evaljs(code1 + code2, False) == '[]' - code2 = py2js('a=[0,2,3]\nfunc(*a)') - assert evaljs(code1 + code2, False) == '[2,3]' - code2 = py2js('a=[2,3]\nfunc(0,1,2,*a)') - assert evaljs(code1 + code2, False) == '[1,2,2,3]' - + # lines = [line for line in code1.split('\n') if line] + + code2 = py2js("func(0, 2, 3)") + assert evaljs(code1 + code2, False) == "[2,3]" + code2 = py2js("func(0)") + assert evaljs(code1 + code2, False) == "[]" + code2 = py2js("a=[0,2,3]\nfunc(*a)") + assert evaljs(code1 + code2, False) == "[2,3]" + code2 = py2js("a=[2,3]\nfunc(0,1,2,*a)") + assert evaljs(code1 + code2, False) == "[1,2,2,3]" + def test_self_becomes_this(self): - def func(self): return self.foo - + code = py2js(func) - lines = [line.strip() for line in code.split('\n') if line] - assert 'return this.foo;' in lines - + lines = [line.strip() for line in code.split("\n") if line] + assert "return this.foo;" in lines + def test_lambda(self): - assert evalpy('f=lambda x:x+1\nf(2)') == '3' - assert evalpy('(lambda x:x+1)(2)') == '3' - + assert evalpy("f=lambda x:x+1\nf(2)") == "3" + assert evalpy("(lambda x:x+1)(2)") == "3" + def test_scope(self): - def func(self): def foo(z): y = 2 - stub = False # noqa - only_here = 1 # noqa + stub = False + only_here = 1 return x + y + z + x = 1 y = 0 - y = 1 # noqa - z = 1 # noqa + y = 1 + z = 1 res = foo(3) - stub = True # noqa + stub = True return res + y # should return 1+2+3+1 == 7 - + # Find function start code = py2js(func) - i = code.splitlines().index('var func;') + i = code.splitlines().index("var func;") assert i >= 0 - + # Find first lines of functions, where the vars are defined - vars1 = code.splitlines()[i+2] - vars2 = code.splitlines()[i+4] - assert vars1.strip().startswith('var ') - assert vars2.strip().startswith('var ') - - assert 'y' in vars1 and 'y' in vars2 - assert 'stub' in vars1 and 'stub' in vars2 - assert 'only_here' in vars2 and 'only_here' not in vars1 - assert evaljs(code + 'func()') == '7' - + vars1 = code.splitlines()[i + 2] + vars2 = code.splitlines()[i + 4] + assert vars1.strip().startswith("var ") + assert vars2.strip().startswith("var ") + + assert "y" in vars1 and "y" in vars2 + assert "stub" in vars1 and "stub" in vars2 + assert "only_here" in vars2 and "only_here" not in vars1 + assert evaljs(code + "func()") == "7" + def test_scope2(self): # Avoid regression for bug with lambda and scoping - + def func1(self): x = 1 - + def func2(self): x = 1 - y = lambda : None - + y = lambda: None + def func3(self): x = 1 + def y(): pass - - assert 'var x' in py2js(func1) - assert 'var x' in py2js(func2) - assert 'var x' in py2js(func3) - + + assert "var x" in py2js(func1) + assert "var x" in py2js(func2) + assert "var x" in py2js(func3) + def test_recursion(self=None): - - code = 'def f(i): i *= 2; return i if i > 10 else f(i)\n\n' - assert evalpy(code + 'f(1)') == '16' - - clscode = 'class G:\n def __init__(self): self.i = 1\n\n' - code = clscode + ' def f(self): self.i *= 2; return self.i if self.i > 10 else self.f()\n\n' - assert evalpy(code + 'g = G(); g.f()') == '16' - - code = clscode + ' def f(self):\n def h(): self.i *= 2; return self.i if self.i > 10 else h()\n\n' - assert evalpy(code + ' return h()\n\ng = G(); g.f()') == '16' - + code = "def f(i): i *= 2; return i if i > 10 else f(i)\n\n" + assert evalpy(code + "f(1)") == "16" + + clscode = "class G:\n def __init__(self): self.i = 1\n\n" + code = ( + clscode + + " def f(self): self.i *= 2; return self.i if self.i > 10 else self.f()\n\n" + ) + assert evalpy(code + "g = G(); g.f()") == "16" + + code = ( + clscode + + " def f(self):\n def h(): self.i *= 2; return self.i if self.i > 10 else h()\n\n" + ) + assert evalpy(code + " return h()\n\ng = G(); g.f()") == "16" + def test_global(self): - assert py2js('global foo;foo = 3').strip() == 'foo = 3;' - + assert py2js("global foo;foo = 3").strip() == "foo = 3;" + def func1(): def inner(): x = 3 + x = 2 inner() return x - + def func2(): def inner(): global x x = 3 + x = 2 inner() return x - - assert evaljs(py2js(func1)+'func1()') == '2' - assert evaljs(py2js(func2)+'func2()') == '3' - + + assert evaljs(py2js(func1) + "func1()") == "2" + assert evaljs(py2js(func2) + "func2()") == "3" + def test_nonlocal(self): - assert py2js('nonlocal foo;foo = 3').strip() == 'foo = 3;' - + assert py2js("nonlocal foo;foo = 3").strip() == "foo = 3;" + func3_code = """def func3(): def inner(): nonlocal x @@ -797,17 +792,17 @@ def inner(): inner() return x """ - assert evaljs(py2js(func3_code)+'func3()') == '3' - + assert evaljs(py2js(func3_code) + "func3()") == "3" + def test_global_vs_nonlocal(self): - js1 = py2js('global foo;foo = 3') - js2 = py2js('nonlocal foo;foo = 3') - - assert js1.meta['vars_unknown'] == set() - assert js2.meta['vars_unknown'] == set() - assert js1.meta['vars_global'] == set(['foo']) - assert js2.meta['vars_global'] == set() - + js1 = py2js("global foo;foo = 3") + js2 = py2js("nonlocal foo;foo = 3") + + assert js1.meta["vars_unknown"] == set() + assert js2.meta["vars_unknown"] == set() + assert js1.meta["vars_global"] == set(["foo"]) + assert js2.meta["vars_global"] == set() + code = """if True: x = 1 y = 1 @@ -825,263 +820,297 @@ def inner2(): # In Python, this produces "1 2", but its hard to replicate that in JS, and # probably not worth the effort. # assert evalpy(code) == '1 2' - + def test_raw_js(self): - def func(a, b): RawJS(""" var c = 3; return a + b + c; """) - + code = py2js(func) - assert evaljs(code + 'func(100, 10)') == '113' - assert evaljs(code + 'func("x", 10)') == 'x103' - + assert evaljs(code + "func(100, 10)") == "113" + assert evaljs(code + 'func("x", 10)') == "x103" + def test_docstring(self): # And that its not interpreted as raw js - + def func(a, b): - """ docstring """ + """docstring""" return a + b - + code = py2js(func) - assert evaljs(code + 'func(100, 10)') == '110' - assert evaljs(code + 'func("x", 10)') == 'x10' - - assert code.count('// docstring') == 1 - + assert evaljs(code + "func(100, 10)") == "110" + assert evaljs(code + 'func("x", 10)') == "x10" + + assert code.count("// docstring") == 1 + def test_async_and_await(self): - - foo = py2js('async def foo(): return 42\n\n') - spam = py2js('async def spam(): print(await foo())\n\n') - eggs = py2js('async def eggs(): return await foo()\n\n') + foo = py2js("async def foo(): return 42\n\n") + spam = py2js("async def spam(): print(await foo())\n\n") + eggs = py2js("async def eggs(): return await foo()\n\n") js = foo + spam + eggs - - assert 'Promise' in evaljs(js + 'foo()') - assert 'Promise' in evaljs(js + 'spam()') - assert 'Promise' in evaljs(js + 'eggs()') - - assert '42' in evaljs(js + 'spam()') - assert '42' not in evaljs(js + 'eggs()') + + assert "Promise" in evaljs(js + "foo()") + assert "Promise" in evaljs(js + "spam()") + assert "Promise" in evaljs(js + "eggs()") + + assert "42" in evaljs(js + "spam()") + assert "42" not in evaljs(js + "eggs()") class TestClasses: - - def test_class(self): - class MyClass: - """ docstring """ + """docstring""" + foo = 7 foo = foo + 1 - + def __init__(self): self.bar = 7 + def addOne(self): self.bar += 1 - - code = py2js(MyClass) + 'var m = new MyClass();' - - assert code.count('// docstring') == 1 - assert evaljs(code + 'm.bar;') == '7' - assert evaljs(code + 'm.addOne();m.bar;') == '8' - + + code = py2js(MyClass) + "var m = new MyClass();" + + assert code.count("// docstring") == 1 + assert evaljs(code + "m.bar;") == "7" + assert evaljs(code + "m.addOne();m.bar;") == "8" + # class vars - assert evaljs(code + 'm.foo;') == '8' - + assert evaljs(code + "m.foo;") == "8" + def test_class_name_mangling1(self): - class FooMang1: __y = 12 + def __init__(self): self.__x = 30 + def __foo(self): return self.__x + self.__y + def bar(self): return self.__foo() - + code = py2js(FooMang1) - code += 'var m=new FooMang1();' - - assert '._FooMang1__foo' in code - assert '._FooMang1__x' in code - assert '._FooMang1__y' in code - - assert evaljs(code + 'm.bar();') == '42' - assert evaljs(code + 'm.__x;') == '' - assert evaljs(code + 'm.__y;') == '' - + code += "var m=new FooMang1();" + + assert "._FooMang1__foo" in code + assert "._FooMang1__x" in code + assert "._FooMang1__y" in code + + assert evaljs(code + "m.bar();") == "42" + assert evaljs(code + "m.__x;") == "" + assert evaljs(code + "m.__y;") == "" + def test_class_name_mangling2(self): # work well with nested functions class FooMang2: __y = 42 + def bar(self): def x1(): def x2(): return self.__y + return x2() + return x1() - + code = py2js(FooMang2) - code += 'var m=new FooMang2();' - assert evaljs(code + 'm.bar();') == '42' - + code += "var m=new FooMang2();" + assert evaljs(code + "m.bar();") == "42" + def test_inheritance_and_super(self): - class MyClass1: def __init__(self): self.bar = 7 + def add(self, x=1): self.bar += x + def addTwo(self): self.bar += 2 - + class MyClass2(MyClass1): def addTwo(self): super().addTwo() self.bar += 1 # haha, we add three! - + class MyClass3(MyClass2): def addTwo(self): super().addTwo() self.bar += 1 # haha, we add four! + def addFour(self): super(MyClass3, self).add(4) # Use older syntax - + code = py2js(MyClass1) + py2js(MyClass2) + py2js(MyClass3) - code += 'var m1=new MyClass1(), m2=new MyClass2(), m3=new MyClass3();' - + code += "var m1=new MyClass1(), m2=new MyClass2(), m3=new MyClass3();" + # m1 - assert evaljs(code + 'm1.bar;') == '7' - assert evaljs(code + 'm1.add();m1.bar;') == '8' - assert evaljs(code + 'm1.addTwo();m1.bar;') == '9' + assert evaljs(code + "m1.bar;") == "7" + assert evaljs(code + "m1.add();m1.bar;") == "8" + assert evaljs(code + "m1.addTwo();m1.bar;") == "9" # m2 - assert evaljs(code + 'm2.bar;') == '7' - assert evaljs(code + 'm2.add();m2.bar;') == '8' - assert evaljs(code + 'm2.addTwo();m2.bar;') == '10' + assert evaljs(code + "m2.bar;") == "7" + assert evaljs(code + "m2.add();m2.bar;") == "8" + assert evaljs(code + "m2.addTwo();m2.bar;") == "10" # m3 - assert evaljs(code + 'm3.bar;') == '7' - assert evaljs(code + 'm3.add();m3.bar;') == '8' - assert evaljs(code + 'm3.addTwo();m3.bar;') == '11' - assert evaljs(code + 'm3.addFour();m3.bar;') == '11' # super with args - + assert evaljs(code + "m3.bar;") == "7" + assert evaljs(code + "m3.add();m3.bar;") == "8" + assert evaljs(code + "m3.addTwo();m3.bar;") == "11" + assert evaljs(code + "m3.addFour();m3.bar;") == "11" # super with args + # Inhertance m1 - assert evaljs(code + 'm1 instanceof MyClass3;') == 'false' - assert evaljs(code + 'm1 instanceof MyClass2;') == 'false' - assert evaljs(code + 'm1 instanceof MyClass1;') == 'true' - assert evaljs(code + 'm1 instanceof Object;') == 'true' - + assert evaljs(code + "m1 instanceof MyClass3;") == "false" + assert evaljs(code + "m1 instanceof MyClass2;") == "false" + assert evaljs(code + "m1 instanceof MyClass1;") == "true" + assert evaljs(code + "m1 instanceof Object;") == "true" + # Inhertance m2 - assert evaljs(code + 'm2 instanceof MyClass3;') == 'false' - assert evaljs(code + 'm2 instanceof MyClass2;') == 'true' - assert evaljs(code + 'm2 instanceof MyClass1;') == 'true' - assert evaljs(code + 'm2 instanceof Object;') == 'true' - + assert evaljs(code + "m2 instanceof MyClass3;") == "false" + assert evaljs(code + "m2 instanceof MyClass2;") == "true" + assert evaljs(code + "m2 instanceof MyClass1;") == "true" + assert evaljs(code + "m2 instanceof Object;") == "true" + # Inhertance m3 - assert evaljs(code + 'm3 instanceof MyClass3;') == 'true' - assert evaljs(code + 'm3 instanceof MyClass2;') == 'true' - assert evaljs(code + 'm3 instanceof MyClass1;') == 'true' - assert evaljs(code + 'm3 instanceof Object;') == 'true' - + assert evaljs(code + "m3 instanceof MyClass3;") == "true" + assert evaljs(code + "m3 instanceof MyClass2;") == "true" + assert evaljs(code + "m3 instanceof MyClass1;") == "true" + assert evaljs(code + "m3 instanceof Object;") == "true" + def test_inheritance_super_more(self): - class MyClass4: def foo(self): return self - + class MyClass5(MyClass4): def foo(self, test): return super().foo() - + def foo(): return super().foo() - + code = py2js(MyClass4) + py2js(MyClass5) - code += py2js(foo).replace('super()', 'MyClass4.prototype') - code += 'var m4=new MyClass4(), m5=new MyClass5();' - - assert evaljs(code + 'm4.foo() === m4') == 'true' - assert evaljs(code + 'm4.foo() === m4') == 'true' - assert evaljs(code + 'foo.call(m4) === m4') == 'true' - + code += py2js(foo).replace("super()", "MyClass4.prototype") + code += "var m4=new MyClass4(), m5=new MyClass5();" + + assert evaljs(code + "m4.foo() === m4") == "true" + assert evaljs(code + "m4.foo() === m4") == "true" + assert evaljs(code + "foo.call(m4) === m4") == "true" + def test_calling_method_from_init(self): - # Note that all class names inside a module need to be unique # for js() to find the correct source. - + class MyClass11: def __init__(self): self._res = self.m1() + self.m2() + self.m3() + def m1(self): return 100 + def m2(self): return 10 - + class MyClass12(MyClass11): def m2(self): return 20 + def m3(self): return 2 - + code = py2js(MyClass11) + py2js(MyClass12) - assert evaljs(code + 'var m = new MyClass12(); m._res') == '122' - assert evaljs(code + 'var m = new MyClass12(); m.m1()') == '100' - + assert evaljs(code + "var m = new MyClass12(); m._res") == "122" + assert evaljs(code + "var m = new MyClass12(); m.m1()") == "100" + def test_ensure_use_new(self): class MyClass13: def __init__(self): - pass + pass + code = py2js(MyClass13) - err = 'Class constructor is called as a function.' - assert evaljs(code + 'try { var m = new MyClass13(); "ok"} catch (err) { err; }') == 'ok' - assert evaljs(code + 'try { var m = MyClass13();} catch (err) { err; }') == err - assert evaljs(code + 'try { MyClass13.apply(global);} catch (err) { err; }') == err - assert evaljs(code + 'var window = global;try { MyClass13.apply(window);} catch (err) { err; }') == err - - + err = "Class constructor is called as a function." + assert ( + evaljs(code + 'try { var m = new MyClass13(); "ok"} catch (err) { err; }') + == "ok" + ) + assert evaljs(code + "try { var m = MyClass13();} catch (err) { err; }") == err + assert ( + evaljs(code + "try { MyClass13.apply(global);} catch (err) { err; }") == err + ) + assert ( + evaljs( + code + + "var window = global;try { MyClass13.apply(window);} catch (err) { err; }" + ) + == err + ) + def test_bound_methods(self): - class MyClass14: def __init__(self): self.a = 1 + def add2(self): self.a += 2 - + class MyClass15(MyClass14): def add3(self): self.a += 3 - + code = py2js(MyClass14) + py2js(MyClass15) - assert evaljs(code + 'var m = new MyClass14(); m.add2(); m.add2(); m.a') == '5' - assert evaljs(code + 'var m = new MyClass14(); var f = m.add2; f(); f(); m.a') == '5' - assert evaljs(code + 'var m = new MyClass15(); var f = m.add3; f(); f(); m.a') == '7' - assert evaljs(code + 'var m = new MyClass15(); var f2 = m.add2, f3 = m.add3; f2(); f3(); m.a') == '6' - - + assert evaljs(code + "var m = new MyClass14(); m.add2(); m.add2(); m.a") == "5" + assert ( + evaljs(code + "var m = new MyClass14(); var f = m.add2; f(); f(); m.a") + == "5" + ) + assert ( + evaljs(code + "var m = new MyClass15(); var f = m.add3; f(); f(); m.a") + == "7" + ) + assert ( + evaljs( + code + + "var m = new MyClass15(); var f2 = m.add2, f3 = m.add3; f2(); f3(); m.a" + ) + == "6" + ) + def test_bound_funcs_in_methods(self): class MyClass16: def foo1(self): self.a = 3 f = lambda i: self.a return f() - + def foo2(self): self.a = 3 + def bar(): return self.a + return bar() - + def foo3(self): self.a = 3 + def bar(self): return self.a + return bar() - + code = py2js(MyClass16) - assert evaljs(code + 'var m = new MyClass16(); m.foo1()') == '3' - assert evaljs(code + 'var m = new MyClass16(); m.foo2()') == '3' - assert evaljs(code + 'var m = new MyClass16(); try {m.foo3();} catch (err) {"ok"}') == 'ok' + assert evaljs(code + "var m = new MyClass16(); m.foo1()") == "3" + assert evaljs(code + "var m = new MyClass16(); m.foo2()") == "3" + assert ( + evaljs(code + 'var m = new MyClass16(); try {m.foo3();} catch (err) {"ok"}') + == "ok" + ) run_tests_if_main() diff --git a/tests/test_parser3.py b/tests/test_parser3.py index 4a15af86..26b935dd 100644 --- a/tests/test_parser3.py +++ b/tests/test_parser3.py @@ -1,15 +1,14 @@ -import sys -from pscript.testing import run_tests_if_main, raises, skip - +# ruff: noqa: F841 +from pscript.testing import run_tests_if_main, raises from pscript import JSError, py2js, evaljs, evalpy, RawJS def nowhitespace(s): - return s.replace('\n', '').replace('\t', '').replace(' ', '') + return s.replace("\n", "").replace("\t", "").replace(" ", "") def foo(a, b): - x = RawJS('a + b') + x = RawJS("a + b") y = 0 RawJS(""" for (i=0; i<8; i++) { @@ -24,685 +23,738 @@ def foo(a, b): class TestSpecials: - def test_rawJS(self): - code = py2js(foo) - assert 'pyfunc' not in code - assert ' x =' in code - assert ' for' in code - assert ' y +=' in code - assert ' while' in code - assert ' y -=' in code + assert "pyfunc" not in code + assert " x =" in code + assert " for" in code + assert " y +=" in code + assert " while" in code + assert " y -=" in code class TestHardcoreBuiltins: - def test_isinstance(self): # The resulting code is not particularly pretty, so we just # test outcome - - assert evalpy('isinstance(3.0, list) == True') == 'false' - assert evalpy('isinstance(3.0, float) == True') == 'true' - - assert evalpy('x={}; isinstance(x.foo, "undefined")') == 'true' - - assert evalpy('isinstance(None, "null")') == 'true' - assert evalpy('isinstance(undefined, "undefined")') == 'true' + + assert evalpy("isinstance(3.0, list) == True") == "false" + assert evalpy("isinstance(3.0, float) == True") == "true" + + assert evalpy('x={}; isinstance(x.foo, "undefined")') == "true" + + assert evalpy('isinstance(None, "null")') == "true" + assert evalpy('isinstance(undefined, "undefined")') == "true" # - assert evalpy('isinstance(None, "undefined")') == 'false' - assert evalpy('isinstance(undefined, "null")') == 'false' - - assert evalpy('isinstance(3, float)') == 'true' - assert evalpy('isinstance(3, (int, float))') == 'true' - assert evalpy('isinstance(3, "number")') == 'true' + assert evalpy('isinstance(None, "undefined")') == "false" + assert evalpy('isinstance(undefined, "null")') == "false" + + assert evalpy("isinstance(3, float)") == "true" + assert evalpy("isinstance(3, (int, float))") == "true" + assert evalpy('isinstance(3, "number")') == "true" # - #assert evalpy('isinstance(3, int)') == 'false' # int is not defined - - assert evalpy('isinstance("", str)') == 'true' - assert evalpy('isinstance("", "string")') == 'true' + # assert evalpy('isinstance(3, int)') == 'false' # int is not defined + + assert evalpy('isinstance("", str)') == "true" + assert evalpy('isinstance("", "string")') == "true" # - assert evalpy('isinstance("", list)') == 'false' - - assert evalpy('isinstance(True, bool)') == 'true' - assert evalpy('isinstance(True, "boolean")') == 'true' + assert evalpy('isinstance("", list)') == "false" + + assert evalpy("isinstance(True, bool)") == "true" + assert evalpy('isinstance(True, "boolean")') == "true" # - assert evalpy('isinstance(True, float)') == 'false' - - assert evalpy('isinstance([], list)') == 'true' - assert evalpy('isinstance([], "array")') == 'true' + assert evalpy("isinstance(True, float)") == "false" + + assert evalpy("isinstance([], list)") == "true" + assert evalpy('isinstance([], "array")') == "true" # - assert evalpy('isinstance([], "object")') == 'false' - assert evalpy('isinstance([], "Object")') == 'false' - assert evalpy('isinstance([], dict)') == 'false' - - assert evalpy('isinstance({}, dict)') == 'true' - assert evalpy('isinstance({}, "object")') == 'true' + assert evalpy('isinstance([], "object")') == "false" + assert evalpy('isinstance([], "Object")') == "false" + assert evalpy("isinstance([], dict)") == "false" + + assert evalpy("isinstance({}, dict)") == "true" + assert evalpy('isinstance({}, "object")') == "true" # - assert evalpy('isinstance({}, list)') == 'false' - assert evalpy('isinstance({}, "array")') == 'false' - - assert evalpy('isinstance(eval, types.FunctionType)') == 'true' - assert evalpy('isinstance(eval, FunctionType)') == 'true' - assert evalpy('isinstance(3, types.FunctionType)') == 'false' - + assert evalpy("isinstance({}, list)") == "false" + assert evalpy('isinstance({}, "array")') == "false" + + assert evalpy("isinstance(eval, types.FunctionType)") == "true" + assert evalpy("isinstance(eval, FunctionType)") == "true" + assert evalpy("isinstance(3, types.FunctionType)") == "false" + # own class - code = 'function MyClass () {return this;}\nvar x = new MyClass();\n' - assert evaljs(code + py2js('isinstance(x, "object")')) == 'true' - assert evaljs(code + py2js('isinstance(x, "Object")')) == 'true' - assert evaljs(code + py2js('isinstance(x, "MyClass")')) == 'true' - assert evaljs(code + py2js('isinstance(x, MyClass)')) == 'true' - + code = "function MyClass () {return this;}\nvar x = new MyClass();\n" + assert evaljs(code + py2js('isinstance(x, "object")')) == "true" + assert evaljs(code + py2js('isinstance(x, "Object")')) == "true" + assert evaljs(code + py2js('isinstance(x, "MyClass")')) == "true" + assert evaljs(code + py2js("isinstance(x, MyClass)")) == "true" + def test_issubclass(self): - code = 'class Foo:pass\nclass Bar(Foo): pass\n' - assert evalpy(code + 'issubclass(Bar, Foo)') == 'true' - assert evalpy(code + 'issubclass(Foo, Bar)') == 'false' - assert evalpy(code + 'issubclass(Bar, object)') == 'true' - - + code = "class Foo:pass\nclass Bar(Foo): pass\n" + assert evalpy(code + "issubclass(Bar, Foo)") == "true" + assert evalpy(code + "issubclass(Foo, Bar)") == "false" + assert evalpy(code + "issubclass(Bar, object)") == "true" + def test_hasattr(self): code = 'a = {"foo":1, "bar":2};\n' - assert evalpy(code + 'hasattr(a, "foo")') == 'true' - assert evalpy(code + 'hasattr(a, "fooo")') == 'false' - + assert evalpy(code + 'hasattr(a, "foo")') == "true" + assert evalpy(code + 'hasattr(a, "fooo")') == "false" + def test_getattr(self): code = 'a = {"foo":1, "bar":2};\n' - assert evalpy(code + 'getattr(a, "foo")') == '1' - assert evalpy(code + 'getattr(a, "bar")') == '2' + assert evalpy(code + 'getattr(a, "foo")') == "1" + assert evalpy(code + 'getattr(a, "bar")') == "2" exc_att = 'except AttributeError: print("err")' - assert evalpy(code + 'try:\n getattr(a, "fooo")\n' + exc_att) == 'err' - assert evalpy(code + 'getattr(a, "fooo", 3)') == '3' - + assert evalpy(code + 'try:\n getattr(a, "fooo")\n' + exc_att) == "err" + assert evalpy(code + 'getattr(a, "fooo", 3)') == "3" + def test_setattr(self): code = 'a = {"foo":1};\n' assert evalpy(code + 'setattr(a, "foo", 2); a') == "{ foo: 2 }" - + def test_deltattr(self): code = 'a = {"foo":1};\n' - assert evalpy(code + 'delattr(a, "foo")\na') == '{}' - + assert evalpy(code + 'delattr(a, "foo")\na') == "{}" + def test_print(self): # Test code - assert py2js('print()') == 'console.log("");' - assert py2js('print(3)') == 'console.log(3);' - assert py2js('foo.print()') == 'foo.print();' - + assert py2js("print()") == 'console.log("");' + assert py2js("print(3)") == "console.log(3);" + assert py2js("foo.print()") == "foo.print();" + # Test single - assert evalpy('print(3)') == '3' - assert evalpy('print(3); print(3)') == '3\n3' - assert evalpy('print(); print(3)') == '\n3' # Test \n - assert evalpy('print("hello world")') == 'hello world' + assert evalpy("print(3)") == "3" + assert evalpy("print(3); print(3)") == "3\n3" + assert evalpy("print(); print(3)") == "\n3" # Test \n + assert evalpy('print("hello world")') == "hello world" # Test multiple args - assert evalpy('print(3, "hello")') == '3 hello' - assert evalpy('print(3+1, "hello", 3+1)') == '4 hello 4' + assert evalpy('print(3, "hello")') == "3 hello" + assert evalpy('print(3+1, "hello", 3+1)') == "4 hello 4" # Test sep and end - assert evalpy('print(3, 4, 5)') == '3 4 5' - assert evalpy('print(3, 4, 5, sep="")') == '345' - assert evalpy('print(3, 4, 5, sep="\\n")') == '3\n4\n5' - assert evalpy('print(3, 4, 5, sep="--")') == '3--4--5' - assert evalpy('print(3, 4, 5, end="-")') == '3 4 5-' - assert evalpy('print(3, 4, 5, end="\\n\\n-")') == '3 4 5\n\n-' - - raises(JSError, py2js, 'print(foo, file=x)') - raises(JSError, py2js, 'print(foo, bar=x)') - + assert evalpy("print(3, 4, 5)") == "3 4 5" + assert evalpy('print(3, 4, 5, sep="")') == "345" + assert evalpy('print(3, 4, 5, sep="\\n")') == "3\n4\n5" + assert evalpy('print(3, 4, 5, sep="--")') == "3--4--5" + assert evalpy('print(3, 4, 5, end="-")') == "3 4 5-" + assert evalpy('print(3, 4, 5, end="\\n\\n-")') == "3 4 5\n\n-" + + raises(JSError, py2js, "print(foo, file=x)") + raises(JSError, py2js, "print(foo, bar=x)") + def test_len(self): - assert py2js('len(a)') == 'a.length;' - assert py2js('len(a, b)') == 'len(a, b);' - + assert py2js("len(a)") == "a.length;" + assert py2js("len(a, b)") == "len(a, b);" + def test_min(self): - assert evalpy('min([3, 4, 1, 5, 2])') == '1' - assert evalpy('min(3, 4, 1, 5, 2)') == '1' - + assert evalpy("min([3, 4, 1, 5, 2])") == "1" + assert evalpy("min(3, 4, 1, 5, 2)") == "1" + def test_max(self): - assert evalpy('max([3, 4, 5, 1])') == '5' - assert evalpy('max(3, 4, 5, 1)') == '5' - + assert evalpy("max([3, 4, 5, 1])") == "5" + assert evalpy("max(3, 4, 5, 1)") == "5" + def test_callable(self): - assert evalpy('callable([])') == 'false' - assert evalpy('callable(3)') == 'false' - - assert evalpy('callable(Boolean)') == 'true' - assert evalpy('callable(eval)') == 'true' - assert evalpy('def foo():pass\ncallable(foo)') == 'true' - assert evalpy('foo = lambda x:1\ncallable(foo)') == 'true' + assert evalpy("callable([])") == "false" + assert evalpy("callable(3)") == "false" + + assert evalpy("callable(Boolean)") == "true" + assert evalpy("callable(eval)") == "true" + assert evalpy("def foo():pass\ncallable(foo)") == "true" + assert evalpy("foo = lambda x:1\ncallable(foo)") == "true" def test_chr_and_ord(self): - assert evalpy('chr(65)') == 'A' - assert evalpy('chr(65+32)') == 'a' - assert evalpy('ord("A")') == '65' - assert evalpy('ord("a")') == '97' - + assert evalpy("chr(65)") == "A" + assert evalpy("chr(65+32)") == "a" + assert evalpy('ord("A")') == "65" + assert evalpy('ord("a")') == "97" + def test_list(self): - assert py2js('list()') == '[];' + assert py2js("list()") == "[];" assert evalpy('list("abc")') == "[ 'a', 'b', 'c' ]" - assert evalpy('list({1:2, 3:4})') == "[ '1', '3' ]" - assert evalpy('tuple({1:2, 3:4})') == "[ '1', '3' ]" - + assert evalpy("list({1:2, 3:4})") == "[ '1', '3' ]" + assert evalpy("tuple({1:2, 3:4})") == "[ '1', '3' ]" + def test_dict(self): - assert py2js('dict()') == '{};' + assert py2js("dict()") == "{};" ok = "{ foo: 1, bar: 2 }", "{ bar: 2, foo: 1 }" assert evalpy('dict([["foo", 1], ["bar", 2]])') in ok assert evalpy('dict({"foo": 1, "bar": 2})') in ok - + def test_range(self): - assert evalpy('list(range(4))') == '[ 0, 1, 2, 3 ]' - assert evalpy('list(range(2, 4))') == '[ 2, 3 ]' - assert evalpy('list(range(2, 9, 2))') == '[ 2, 4, 6, 8 ]' - assert evalpy('list(range(10, 3, -2))') == '[ 10, 8, 6, 4 ]' + assert evalpy("list(range(4))") == "[ 0, 1, 2, 3 ]" + assert evalpy("list(range(2, 4))") == "[ 2, 3 ]" + assert evalpy("list(range(2, 9, 2))") == "[ 2, 4, 6, 8 ]" + assert evalpy("list(range(10, 3, -2))") == "[ 10, 8, 6, 4 ]" class TestOtherBuiltins: - # def test_allow_overload(self): # assert evalpy('sum([3, 4])') == '7' # assert evalpy('sum = lambda x:1\nsum([3, 4])') == '1' - + def test_pow(self): - assert evalpy('pow(2, 3)') == '8' - assert evalpy('pow(10, 2)') == '100' - + assert evalpy("pow(2, 3)") == "8" + assert evalpy("pow(10, 2)") == "100" + def test_sum(self): - assert evalpy('sum([3, 4, 1, 5, 2])') == '15' - + assert evalpy("sum([3, 4, 1, 5, 2])") == "15" + def test_round(self): - assert evalpy('round(3.4)') == '3' - assert evalpy('round(3.6)') == '4' - assert evalpy('round(-3.4)') == '-3' - assert evalpy('round(-3.6)') == '-4' - + assert evalpy("round(3.4)") == "3" + assert evalpy("round(3.6)") == "4" + assert evalpy("round(-3.4)") == "-3" + assert evalpy("round(-3.6)") == "-4" + def test_int(self): - assert evalpy('int(3.4)') == '3' - assert evalpy('int(3.6)') == '3' - assert evalpy('int(-3.4)') == '-3' - assert evalpy('int(-3.6)') == '-3' - assert evalpy('"5" + 2') == '52' - assert evalpy('int("5") + 2') == '7' # -> evaluate to number + assert evalpy("int(3.4)") == "3" + assert evalpy("int(3.6)") == "3" + assert evalpy("int(-3.4)") == "-3" + assert evalpy("int(-3.6)") == "-3" + assert evalpy('"5" + 2') == "52" + assert evalpy('int("5") + 2') == "7" # -> evaluate to number # Note: on Nodejs "5" * 2 also becomes 10 ... - + def test_float(self): - assert evalpy('"5.2" + 2') == '5.22' - assert evalpy('float("5") + 2') == '7' - assert evalpy('float("5.2") + 2') == '7.2' - + assert evalpy('"5.2" + 2') == "5.22" + assert evalpy('float("5") + 2') == "7" + assert evalpy('float("5.2") + 2') == "7.2" + def test_repr(self): # The [:] is to ensure the result is a string - assert evalpy('repr(5)[:]') == '5' + assert evalpy("repr(5)[:]") == "5" assert evalpy('repr("abc")') == '"abc"' - assert evalpy('repr([1, 2, 3])[:]') == "[1,2,3]" - assert evalpy('repr({1:2})[:]') == '{"1":2}' - + assert evalpy("repr([1, 2, 3])[:]") == "[1,2,3]" + assert evalpy("repr({1:2})[:]") == '{"1":2}' + def test_str(self): - assert evalpy('str(5) + 2') == '52' - assert evalpy('str("xx") + 2') == 'xx2' - + assert evalpy("str(5) + 2") == "52" + assert evalpy('str("xx") + 2') == "xx2" + def test_bool(self): - assert evalpy('bool(5)') == 'true' - assert evalpy('bool("xx")') == 'true' - assert evalpy('bool(0)') == 'false' - assert evalpy('bool("")') == 'false' + assert evalpy("bool(5)") == "true" + assert evalpy('bool("xx")') == "true" + assert evalpy("bool(0)") == "false" + assert evalpy('bool("")') == "false" # - assert evalpy('bool([1])') == 'true' - assert evalpy('bool({1:2})') == 'true' - assert evalpy('bool([])') == 'false' - assert evalpy('bool({})') == 'false' - + assert evalpy("bool([1])") == "true" + assert evalpy("bool({1:2})") == "true" + assert evalpy("bool([])") == "false" + assert evalpy("bool({})") == "false" + def test_abs(self): - assert evalpy('abs(5)') == '5' - assert evalpy('abs(0)') == '0' - assert evalpy('abs(-2)') == '2' - + assert evalpy("abs(5)") == "5" + assert evalpy("abs(0)") == "0" + assert evalpy("abs(-2)") == "2" + def test_divod(self): - assert evalpy('divmod(13, 3)') == '[ 4, 1 ]' - assert evalpy('a, b = divmod(100, 7); print(a); print(b)') == '14\n2' - + assert evalpy("divmod(13, 3)") == "[ 4, 1 ]" + assert evalpy("a, b = divmod(100, 7); print(a); print(b)") == "14\n2" + def test_all(self): - assert evalpy('all([1, 2, 3])') == 'true' - assert evalpy('all([0, 2, 3])') == 'false' - assert evalpy('all([])') == 'true' - - assert evalpy('all([3, [], 3])') == 'false' - assert evalpy('all([3, [1], 3])') == 'true' - + assert evalpy("all([1, 2, 3])") == "true" + assert evalpy("all([0, 2, 3])") == "false" + assert evalpy("all([])") == "true" + + assert evalpy("all([3, [], 3])") == "false" + assert evalpy("all([3, [1], 3])") == "true" + def test_any(self): - assert evalpy('any([1, 2, 3])') == 'true' - assert evalpy('any([0, 2, 0])') == 'true' - assert evalpy('any([0, 0, 0])') == 'false' - assert evalpy('any([])') == 'false' - - assert evalpy('any([0, [], 0])') == 'false' - assert evalpy('any([0, [1], 0])') == 'true' - + assert evalpy("any([1, 2, 3])") == "true" + assert evalpy("any([0, 2, 0])") == "true" + assert evalpy("any([0, 0, 0])") == "false" + assert evalpy("any([])") == "false" + + assert evalpy("any([0, [], 0])") == "false" + assert evalpy("any([0, [1], 0])") == "true" + def test_enumerate(self): - assert evalpy('for i, x in enumerate([10, 20, 30]): print(i*x)') == '0\n20\n60' - + assert evalpy("for i, x in enumerate([10, 20, 30]): print(i*x)") == "0\n20\n60" + def test_zip(self): - assert evalpy('for i, x in zip([1, 2, 3], [10, 20, 30]): print(i*x)') == '10\n40\n90' - res = '111\n222\n333' - assert evalpy('for a, b, c in zip([1, 2, 3], [10, 20, 30], [100, 200, 300]): print(a+b+c)') == res - + assert ( + evalpy("for i, x in zip([1, 2, 3], [10, 20, 30]): print(i*x)") + == "10\n40\n90" + ) + res = "111\n222\n333" + assert ( + evalpy( + "for a, b, c in zip([1, 2, 3], [10, 20, 30], [100, 200, 300]): print(a+b+c)" + ) + == res + ) + def test_reversed(self): - assert evalpy('for x in reversed([10, 20, 30]): print(x)') == '30\n20\n10' - + assert evalpy("for x in reversed([10, 20, 30]): print(x)") == "30\n20\n10" + def test_sorted(self): - assert evalpy('for x in sorted([1, 9, 3, 2, 7, 8, 4]): print(x)') == '1\n2\n3\n4\n7\n8\n9' - assert evalpy('for x in reversed(sorted([1, 9, 3, 2, 7, 8, 4])): print(x)') == '9\n8\n7\n4\n3\n2\n1' - assert evalpy('for x in sorted([1, 9, 3, 2, 7, 8, 4], key=lambda a: -a): print(x)') == '9\n8\n7\n4\n3\n2\n1' - assert evalpy('for x in sorted([1, 9, 3, 2, 7, 8, 4], reverse=True): print(x)') == '9\n8\n7\n4\n3\n2\n1' - - assert evalpy('for x in sorted(["bb", "aa", "mm", "dd"]): print(x)') == 'aa\nbb\ndd\nmm' - assert evalpy('for x in sorted(["bb", "aa", "mm", "dd"], key=lambda x: x): print(x)') == 'aa\nbb\ndd\nmm' - + assert ( + evalpy("for x in sorted([1, 9, 3, 2, 7, 8, 4]): print(x)") + == "1\n2\n3\n4\n7\n8\n9" + ) + assert ( + evalpy("for x in reversed(sorted([1, 9, 3, 2, 7, 8, 4])): print(x)") + == "9\n8\n7\n4\n3\n2\n1" + ) + assert ( + evalpy("for x in sorted([1, 9, 3, 2, 7, 8, 4], key=lambda a: -a): print(x)") + == "9\n8\n7\n4\n3\n2\n1" + ) + assert ( + evalpy("for x in sorted([1, 9, 3, 2, 7, 8, 4], reverse=True): print(x)") + == "9\n8\n7\n4\n3\n2\n1" + ) + + assert ( + evalpy('for x in sorted(["bb", "aa", "mm", "dd"]): print(x)') + == "aa\nbb\ndd\nmm" + ) + assert ( + evalpy( + 'for x in sorted(["bb", "aa", "mm", "dd"], key=lambda x: x): print(x)' + ) + == "aa\nbb\ndd\nmm" + ) + def test_filter(self): - assert list(filter(lambda x:x>0, [-1, -2, 1, 2])) == [1, 2] - - code = 'f1 = lambda x: x>0\n' - assert evalpy(code + 'for x in filter(f1, [-1, -2, 0, 1, 2]): print(x)') == '1\n2' - assert evalpy(code + 'for x in filter(None, [-1, -2, 0, 1, 2]): print(x)') == '-1\n-2\n1\n2' - + assert list(filter(lambda x: x > 0, [-1, -2, 1, 2])) == [1, 2] + + code = "f1 = lambda x: x>0\n" + assert ( + evalpy(code + "for x in filter(f1, [-1, -2, 0, 1, 2]): print(x)") == "1\n2" + ) + assert ( + evalpy(code + "for x in filter(None, [-1, -2, 0, 1, 2]): print(x)") + == "-1\n-2\n1\n2" + ) + def test_map(self): - code = 'f1 = lambda x: x+2\n' - assert evalpy(code + 'for x in map(f1, [-1, 0, 2]): print(x)') == '1\n2\n4' + code = "f1 = lambda x: x+2\n" + assert evalpy(code + "for x in map(f1, [-1, 0, 2]): print(x)") == "1\n2\n4" class TestListMethods: - def test_append(self): - assert nowhitespace(evalpy('a = [2]; a.append(3); a')) == '[2,3]' - + assert nowhitespace(evalpy("a = [2]; a.append(3); a")) == "[2,3]" + def test_remove(self): - code = 'a=[1,2,3,4,3,5];\n' - assert evalpy(code + 'a.remove(2); a') == '[ 1, 3, 4, 3, 5 ]' - assert evalpy(code + 'a.remove(3); a') == '[ 1, 2, 4, 3, 5 ]' - assert 'ValueError' in evalpy(code + 'try:\n a.remove(9);\nexcept Exception as e:\n e') - assert nowhitespace(evalpy('x = {"a":[2, 3]}; x.a.remove(2); x.a')) == '[3]' - - assert evalpy('a=[1,(2,3),4]; a.remove((2,3)); a') == '[ 1, 4 ]' - + code = "a=[1,2,3,4,3,5];\n" + assert evalpy(code + "a.remove(2); a") == "[ 1, 3, 4, 3, 5 ]" + assert evalpy(code + "a.remove(3); a") == "[ 1, 2, 4, 3, 5 ]" + assert "ValueError" in evalpy( + code + "try:\n a.remove(9);\nexcept Exception as e:\n e" + ) + assert nowhitespace(evalpy('x = {"a":[2, 3]}; x.a.remove(2); x.a')) == "[3]" + + assert evalpy("a=[1,(2,3),4]; a.remove((2,3)); a") == "[ 1, 4 ]" + def test_count(self): - assert evalpy('[1,2,3,4,5,3].count(9)') == '0' - assert evalpy('[1,2,3,4,5,3].count(2)') == '1' - assert evalpy('[1,2,3,4,5,3].count(3)') == '2' - - assert evalpy('a=[1,(2,3),4, (2,3), 5]; a.count((2,3))') == '2' - + assert evalpy("[1,2,3,4,5,3].count(9)") == "0" + assert evalpy("[1,2,3,4,5,3].count(2)") == "1" + assert evalpy("[1,2,3,4,5,3].count(3)") == "2" + + assert evalpy("a=[1,(2,3),4, (2,3), 5]; a.count((2,3))") == "2" + def test_extend(self): - assert evalpy('a=[1, 2]; b=[3, 4];a.extend(b); a') == '[ 1, 2, 3, 4 ]' - + assert evalpy("a=[1, 2]; b=[3, 4];a.extend(b); a") == "[ 1, 2, 3, 4 ]" + def test_index(self): - assert evalpy('[1,2,3,4,5,3].index(2)') == '1' - assert evalpy('[1,2,3,4,5,3].index(3)') == '2' - assert 'ValueError' in evalpy('try:\n [1,2,3,4,5,3].index(9);\nexcept Exception as e:\n e') - assert evalpy('[1,2,3,4,5,3].index(3, 4)') == '5' - assert evalpy('[1,2,3,4,5,3].index(3, -2)') == '5' - assert evalpy('[1,2,3,4,5,3].index(3, 0, -2)') == '2' - - assert evalpy('a=[1,(2,3),4, (2,3), 5]; a.index((2,3))') == '1' - assert evalpy('a=[1,(2,3),4, (2,3), 5]; a.index((2,3),2)') == '3' - + assert evalpy("[1,2,3,4,5,3].index(2)") == "1" + assert evalpy("[1,2,3,4,5,3].index(3)") == "2" + assert "ValueError" in evalpy( + "try:\n [1,2,3,4,5,3].index(9);\nexcept Exception as e:\n e" + ) + assert evalpy("[1,2,3,4,5,3].index(3, 4)") == "5" + assert evalpy("[1,2,3,4,5,3].index(3, -2)") == "5" + assert evalpy("[1,2,3,4,5,3].index(3, 0, -2)") == "2" + + assert evalpy("a=[1,(2,3),4, (2,3), 5]; a.index((2,3))") == "1" + assert evalpy("a=[1,(2,3),4, (2,3), 5]; a.index((2,3),2)") == "3" + def test_insert(self): - code = 'a=[1,2,3,4,5];' - assert evalpy(code + 'a.insert(2, 9); a') == '[ 1, 2, 9, 3, 4, 5 ]' - assert evalpy(code + 'a.insert(-1, 9); a') == '[ 1, 2, 3, 4, 9, 5 ]' - assert evalpy(code + 'a.insert(99, 9); a') == '[ 1, 2, 3, 4, 5, 9 ]' - + code = "a=[1,2,3,4,5];" + assert evalpy(code + "a.insert(2, 9); a") == "[ 1, 2, 9, 3, 4, 5 ]" + assert evalpy(code + "a.insert(-1, 9); a") == "[ 1, 2, 3, 4, 9, 5 ]" + assert evalpy(code + "a.insert(99, 9); a") == "[ 1, 2, 3, 4, 5, 9 ]" + def test_reverse(self): - assert evalpy('a=[1,2,3,4,5]; a.reverse(); a') == '[ 5, 4, 3, 2, 1 ]' - assert evalpy('a=[]; a.reverse(); a') == '[]' - + assert evalpy("a=[1,2,3,4,5]; a.reverse(); a") == "[ 5, 4, 3, 2, 1 ]" + assert evalpy("a=[]; a.reverse(); a") == "[]" + def test_sort(self): - assert evalpy('a=[3,1,4,2]; a.sort(); a') == '[ 1, 2, 3, 4 ]' - assert evalpy('a=[3,1,4,2]; a.sort(reverse=True); a') == '[ 4, 3, 2, 1 ]' - assert evalpy('a=[3,1,4,2]; a.sort(key=lambda x: -x); a') == '[ 4, 3, 2, 1 ]' - assert evalpy('a=[3,1,4,2]; a.sort(key=lambda x: -x, reverse=True); a') == '[ 1, 2, 3, 4 ]' - - assert evalpy('a=["bb", "aa", "mm", "dd"]; a.sort(); a') == "[ 'aa', 'bb', 'dd', 'mm' ]" - assert evalpy('a=["bb", "aa", "mm", "dd"]; a.sort(key=lambda x: x); a') == "[ 'aa', 'bb', 'dd', 'mm' ]" - + assert evalpy("a=[3,1,4,2]; a.sort(); a") == "[ 1, 2, 3, 4 ]" + assert evalpy("a=[3,1,4,2]; a.sort(reverse=True); a") == "[ 4, 3, 2, 1 ]" + assert evalpy("a=[3,1,4,2]; a.sort(key=lambda x: -x); a") == "[ 4, 3, 2, 1 ]" + assert ( + evalpy("a=[3,1,4,2]; a.sort(key=lambda x: -x, reverse=True); a") + == "[ 1, 2, 3, 4 ]" + ) + + assert ( + evalpy('a=["bb", "aa", "mm", "dd"]; a.sort(); a') + == "[ 'aa', 'bb', 'dd', 'mm' ]" + ) + assert ( + evalpy('a=["bb", "aa", "mm", "dd"]; a.sort(key=lambda x: x); a') + == "[ 'aa', 'bb', 'dd', 'mm' ]" + ) + def test_clear(self): - assert evalpy('a=[3,1,4,2]; a.clear(); a') == '[]' - + assert evalpy("a=[3,1,4,2]; a.clear(); a") == "[]" + def test_copy(self): - assert evalpy('a=[3,1,4,2]; b = a.copy(); a.push(1); b') == '[ 3, 1, 4, 2 ]' - + assert evalpy("a=[3,1,4,2]; b = a.copy(); a.push(1); b") == "[ 3, 1, 4, 2 ]" + def test_pop(self): - code = 'a=[1,2,3,4,5];\n' - assert evalpy(code + 'a.pop(2); a') == '[ 1, 2, 4, 5 ]' - assert evalpy(code + 'a.pop(0); a') == '[ 2, 3, 4, 5 ]' - assert evalpy(code + 'a.pop(); a') == '[ 1, 2, 3, 4 ]' - assert evalpy(code + 'a.pop(-1); a') == '[ 1, 2, 3, 4 ]' - assert 'IndexError' in evalpy(code + 'try:\n a.pop(9);\nexcept Exception as e:\n e') - + code = "a=[1,2,3,4,5];\n" + assert evalpy(code + "a.pop(2); a") == "[ 1, 2, 4, 5 ]" + assert evalpy(code + "a.pop(0); a") == "[ 2, 3, 4, 5 ]" + assert evalpy(code + "a.pop(); a") == "[ 1, 2, 3, 4 ]" + assert evalpy(code + "a.pop(-1); a") == "[ 1, 2, 3, 4 ]" + assert "IndexError" in evalpy( + code + "try:\n a.pop(9);\nexcept Exception as e:\n e" + ) + def test_no_list(self): - code = 'class Foo:\n def append(self): self.bar = 3\nfoo = Foo(); foo.append(2)\n' - assert evalpy(code + 'foo.bar') == '3' - code = 'class Foo:\n def clear(self): self.bar = 3\nfoo = Foo(); foo.clear()\n' - assert evalpy(code + 'foo.bar') == '3' - + code = ( + "class Foo:\n def append(self): self.bar = 3\nfoo = Foo(); foo.append(2)\n" + ) + assert evalpy(code + "foo.bar") == "3" + code = "class Foo:\n def clear(self): self.bar = 3\nfoo = Foo(); foo.clear()\n" + assert evalpy(code + "foo.bar") == "3" + def test_that_all_list_methods_are_tested(self): - tested = set([x.split('_')[1] for x in dir(self) if x.startswith('test_')]) - needed = set([x for x in dir(list) if not x.startswith('_')]) - ignore = '' - needed = needed.difference(ignore.split(' ')) - + tested = set([x.split("_")[1] for x in dir(self) if x.startswith("test_")]) + needed = set([x for x in dir(list) if not x.startswith("_")]) + ignore = "" + needed = needed.difference(ignore.split(" ")) + not_tested = needed.difference(tested) assert not not_tested class TestDictMethods: - def test_get(self): - assert evalpy('a = {"foo":3}; a.get("foo")') == '3' - assert evalpy('a = {"foo":3}; a.get("foo", 0)') == '3' - assert evalpy('a = {"foo":3}; a.get("bar")') == 'null' - assert evalpy('a = {"foo":3}; a.get("bar", 0)') == '0' + assert evalpy('a = {"foo":3}; a.get("foo")') == "3" + assert evalpy('a = {"foo":3}; a.get("foo", 0)') == "3" + assert evalpy('a = {"foo":3}; a.get("bar")') == "null" + assert evalpy('a = {"foo":3}; a.get("bar", 0)') == "0" # assert evalpy('{"foo":3}.get("foo")') == '3' # assert evalpy('{"foo":3}.get("bar", 0)') == '0' - + def test_items(self): - assert nowhitespace(evalpy("d={'a':1, 'b':2, 3:3}; d.items()")) == "[['3',3],['a',1],['b',2]]" + assert ( + nowhitespace(evalpy("d={'a':1, 'b':2, 3:3}; d.items()")) + == "[['3',3],['a',1],['b',2]]" + ) assert nowhitespace(evalpy("d={}; d.items()")) == "[]" - + def test_keys(self): - assert nowhitespace(evalpy("d={'a':1, 'b':2, 3:3}; d.keys()")) == "['3','a','b']" + assert ( + nowhitespace(evalpy("d={'a':1, 'b':2, 3:3}; d.keys()")) == "['3','a','b']" + ) assert nowhitespace(evalpy("d={}; d.keys()")) == "[]" - + def test_popitem(self): assert evalpy("d={'a': 1, 'b':2}; d.popitem()") == "[ 'a', 1 ]" - assert 'KeyError' in evalpy("d={}\ntry:\n d.popitem()\nexcept Exception as e:\n e") - + assert "KeyError" in evalpy( + "d={}\ntry:\n d.popitem()\nexcept Exception as e:\n e" + ) + def test_setdefault(self): - assert evalpy("a = {}; a.setdefault('a', 7)") == '7' - assert evalpy("a = {}; a.setdefault('a', 7); a.setdefault('a', 8)") == '7' - + assert evalpy("a = {}; a.setdefault('a', 7)") == "7" + assert evalpy("a = {}; a.setdefault('a', 7); a.setdefault('a', 8)") == "7" + def test_update(self): assert evalpy("a={}; b={'a':1, 'b':2}; a.update(b); a") == "{ a: 1, b: 2 }" assert evalpy("a={}; b={'a':1, 'b':2}; b.update(a); a") == "{}" - + def test_values(self): assert nowhitespace(evalpy("d={'a':1, 'b':2, 3:3};d.values()")) == "[3,1,2]" assert nowhitespace(evalpy("d={};d.values()")) == "[]" - + def test_clear(self): - assert evalpy("a={'a':1, 'b':2}; a.clear(); a") == '{}' - assert evalpy("a={}; a.clear(); a") == '{}' - + assert evalpy("a={'a':1, 'b':2}; a.clear(); a") == "{}" + assert evalpy("a={}; a.clear(); a") == "{}" + def test_copy(self): assert evalpy("a={'a':1, 'b':2}; b = a.copy(); b") == "{ a: 1, b: 2 }" assert evalpy("a={'a':1, 'b':2}; b = a.copy(); a['a']=9; b") == "{ a: 1, b: 2 }" assert evalpy("a={}; b = a.copy(); b") == "{}" - + def test_pop(self): - assert evalpy("a={'a':1, 'b':2}; a.pop('a')") == '1' - assert evalpy("a={'a':1, 'b':2}; a.pop('a', 9)") == '1' - assert evalpy("a={'a':1, 'b':2}; a.pop('z', 9)") == '9' + assert evalpy("a={'a':1, 'b':2}; a.pop('a')") == "1" + assert evalpy("a={'a':1, 'b':2}; a.pop('a', 9)") == "1" + assert evalpy("a={'a':1, 'b':2}; a.pop('z', 9)") == "9" assert evalpy("a={'a':1, 'b':2}; a.pop('a'); a") == "{ b: 2 }" - + def test_no_dict(self): - code = 'class Foo:\n def get(self): return 42\n' - assert evalpy(code + 'foo = Foo(); foo.get(1)') == '42' - - code = 'class Foo:\n def clear(self): self.bar = 42\n' - assert evalpy(code + 'foo = Foo(); foo.clear(); foo.bar') == '42' + code = "class Foo:\n def get(self): return 42\n" + assert evalpy(code + "foo = Foo(); foo.get(1)") == "42" + + code = "class Foo:\n def clear(self): self.bar = 42\n" + assert evalpy(code + "foo = Foo(); foo.clear(); foo.bar") == "42" def test_that_all_dict_methods_are_tested(self): - tested = set([x.split('_')[1] for x in dir(self) if x.startswith('test_')]) - needed = set([x for x in dir(dict) if not x.startswith('_')]) - ignore = 'fromkeys' - needed = needed.difference(ignore.split(' ')) - + tested = set([x.split("_")[1] for x in dir(self) if x.startswith("test_")]) + needed = set([x for x in dir(dict) if not x.startswith("_")]) + ignore = "fromkeys" + needed = needed.difference(ignore.split(" ")) + not_tested = needed.difference(tested) assert not not_tested class TestStrMethods: - def test_capitalize(self): assert evalpy('"".capitalize()') == "" assert evalpy('" _12".capitalize()') == " _12" assert evalpy('"_a".capitalize()') == "_a" assert evalpy('"foo bar".capitalize()') == "Foo bar" assert evalpy('"foo BAR".capitalize()') == "Foo bar" - + def test_title(self): assert evalpy('"".title()') == "" assert evalpy('" _12".title()') == " _12" assert evalpy('"_a".title()') == "_A" assert evalpy('"foo bar".title()') == "Foo Bar" assert evalpy('"foo BAR".title()') == "Foo Bar" - + def test_lower(self): assert evalpy('"".lower()') == "" assert evalpy('" _12".lower()') == " _12" assert evalpy('"foo bar".lower()') == "foo bar" assert evalpy('"foo BAR".lower()') == "foo bar" - + def test_upper(self): assert evalpy('"".upper()') == "" assert evalpy('" _12".upper()') == " _12" assert evalpy('"foo bar".upper()') == "FOO BAR" assert evalpy('"foo BAR".upper()') == "FOO BAR" - + def test_casefold(self): assert evalpy('"FoO bAr".casefold()') == "foo bar" - + def test_swapcase(self): assert evalpy('"".swapcase()') == "" assert evalpy('" _12".swapcase()') == " _12" assert evalpy('"foo bar".swapcase()') == "FOO BAR" assert evalpy('"foo BAR".swapcase()') == "FOO bar" - + def test_center(self): - assert evalpy('"foo".center(5) + "."') == ' foo .' - assert evalpy('"fo".center(5) + "."') == ' fo .' - assert evalpy('"foo".center(1) + "."') == 'foo.' - assert evalpy('"foo".center(5, "-")') == '-foo-' - + assert evalpy('"foo".center(5) + "."') == " foo ." + assert evalpy('"fo".center(5) + "."') == " fo ." + assert evalpy('"foo".center(1) + "."') == "foo." + assert evalpy('"foo".center(5, "-")') == "-foo-" + def test_ljust(self): - assert evalpy('"foo".ljust(5) + "."') == 'foo .' - assert evalpy('"fo".ljust(5) + "."') == 'fo .' - assert evalpy('"foo".ljust(1) + "."') == 'foo.' - assert evalpy('"foo".ljust(5, "-")') == 'foo--' - + assert evalpy('"foo".ljust(5) + "."') == "foo ." + assert evalpy('"fo".ljust(5) + "."') == "fo ." + assert evalpy('"foo".ljust(1) + "."') == "foo." + assert evalpy('"foo".ljust(5, "-")') == "foo--" + def test_rjust(self): - assert evalpy('"foo".rjust(5) + "."') == ' foo.' - assert evalpy('"fo".rjust(5) + "."') == ' fo.' - assert evalpy('"foo".rjust(1) + "."') == 'foo.' - assert evalpy('"foo".rjust(5, "-")') == '--foo' - + assert evalpy('"foo".rjust(5) + "."') == " foo." + assert evalpy('"fo".rjust(5) + "."') == " fo." + assert evalpy('"foo".rjust(1) + "."') == "foo." + assert evalpy('"foo".rjust(5, "-")') == "--foo" + def test_zfill(self): - assert evalpy('"foo".zfill(5) + "."') == '00foo.' - assert evalpy('"fo".zfill(5) + "."') == '000fo.' - assert evalpy('"foo".zfill(1) + "."') == 'foo.' - + assert evalpy('"foo".zfill(5) + "."') == "00foo." + assert evalpy('"fo".zfill(5) + "."') == "000fo." + assert evalpy('"foo".zfill(1) + "."') == "foo." + def test_count(self): - assert evalpy('"foo".count("o")') == '2' - assert evalpy('"foo".count("f")') == '1' - assert evalpy('"foo".count("x")') == '0' - assert evalpy('"foo".count("")') == '3' - - assert evalpy('"a--a--a".count("a")') == '3' - assert evalpy('"a--a--a".count("a", 0)') == '3' - assert evalpy('"a--a--a".count("a", 0, 99)') == '3' - assert evalpy('"a--a--a".count("a", 1)') == '2' - assert evalpy('"a--a--a".count("a", 0, 4)') == '2' - assert evalpy('"a--a--a".count("a", 1, 4)') == '1' - + assert evalpy('"foo".count("o")') == "2" + assert evalpy('"foo".count("f")') == "1" + assert evalpy('"foo".count("x")') == "0" + assert evalpy('"foo".count("")') == "3" + + assert evalpy('"a--a--a".count("a")') == "3" + assert evalpy('"a--a--a".count("a", 0)') == "3" + assert evalpy('"a--a--a".count("a", 0, 99)') == "3" + assert evalpy('"a--a--a".count("a", 1)') == "2" + assert evalpy('"a--a--a".count("a", 0, 4)') == "2" + assert evalpy('"a--a--a".count("a", 1, 4)') == "1" + def test_endswith(self): - assert evalpy('"blafoo".endswith("foo")') == 'true' - assert evalpy('"blafoo".endswith("")') == 'true' - assert evalpy('"foo".endswith("foo")') == 'true' - assert evalpy('"".endswith("foo")') == 'false' - assert evalpy('"".endswith("")') == 'true' - assert evalpy('"a".endswith("bb")') == 'false' # See issue #66 - + assert evalpy('"blafoo".endswith("foo")') == "true" + assert evalpy('"blafoo".endswith("")') == "true" + assert evalpy('"foo".endswith("foo")') == "true" + assert evalpy('"".endswith("foo")') == "false" + assert evalpy('"".endswith("")') == "true" + assert evalpy('"a".endswith("bb")') == "false" # See issue #66 + def test_startswith(self): - assert evalpy('"foobla".startswith("foo")') == 'true' - assert evalpy('"foobla".startswith("")') == 'true' - assert evalpy('"foo".startswith("foo")') == 'true' - assert evalpy('"".startswith("foo")') == 'false' - assert evalpy('"".startswith("")') == 'true' - + assert evalpy('"foobla".startswith("foo")') == "true" + assert evalpy('"foobla".startswith("")') == "true" + assert evalpy('"foo".startswith("foo")') == "true" + assert evalpy('"".startswith("foo")') == "false" + assert evalpy('"".startswith("")') == "true" + assert evalpy('("fo" + "obar").startswith("foo")') == "true" - + def test_expandtabs(self): - assert evalpy('"a\tb\t\tc".expandtabs()') == 'a b c' - assert evalpy('"a\tb\t\tc".expandtabs(2)') == 'a b c' - + assert evalpy('"a\tb\t\tc".expandtabs()') == "a b c" + assert evalpy('"a\tb\t\tc".expandtabs(2)') == "a b c" + def test_find(self): - assert evalpy('"abcdefgh".find("a")') == '0' - assert evalpy('"abcdefgh".find("h")') == '7' - assert evalpy('"abcdefgh".find("z")') == '-1' - assert evalpy('"abcdefgh".find("")') == '0' - - assert evalpy('"abcdefgh".find("cd")') == '2' - assert evalpy('"abcdefgh".find("def")') == '3' - - assert evalpy('"ab ab ab".find("ab", 0)') == '0' - assert evalpy('"ab ab ab".find("ab", 1)') == '3' - assert evalpy('"ab ab ab".find("ab", -2)') == '6' - assert evalpy('" ab".find("ab", 0, 4)') == '-1' - assert evalpy('" ab".find("ab", 0, -1)') == '-1' - + assert evalpy('"abcdefgh".find("a")') == "0" + assert evalpy('"abcdefgh".find("h")') == "7" + assert evalpy('"abcdefgh".find("z")') == "-1" + assert evalpy('"abcdefgh".find("")') == "0" + + assert evalpy('"abcdefgh".find("cd")') == "2" + assert evalpy('"abcdefgh".find("def")') == "3" + + assert evalpy('"ab ab ab".find("ab", 0)') == "0" + assert evalpy('"ab ab ab".find("ab", 1)') == "3" + assert evalpy('"ab ab ab".find("ab", -2)') == "6" + assert evalpy('" ab".find("ab", 0, 4)') == "-1" + assert evalpy('" ab".find("ab", 0, -1)') == "-1" + def test_index(self): # We know that the implementation is basded on find; no need to test all - assert evalpy('"abcdefgh".index("a")') == '0' - assert evalpy('"abcdefgh".index("h")') == '7' - assert evalpy('"abcdefgh".index("")') == '0' - assert 'ValueError' in evalpy('try:\n "abcdefgh".index("z")\nexcept Exception as e:\n e') - + assert evalpy('"abcdefgh".index("a")') == "0" + assert evalpy('"abcdefgh".index("h")') == "7" + assert evalpy('"abcdefgh".index("")') == "0" + assert "ValueError" in evalpy( + 'try:\n "abcdefgh".index("z")\nexcept Exception as e:\n e' + ) + def test_rfind(self): - assert evalpy('"abcdefgh".rfind("a")') == '0' - assert evalpy('"abcdefgh".rfind("h")') == '7' - assert evalpy('"abcdefgh".rfind("z")') == '-1' - assert evalpy('"abcdefgh".rfind("")') == '8' - - assert evalpy('"abcdefgh".rfind("cd")') == '2' - assert evalpy('"abcdefgh".rfind("def")') == '3' - - assert evalpy('"ab ab ab".rfind("ab", 0, -2)') == '3' - assert evalpy('"ab ab ab".rfind("ab", 0, 3)') == '0' - assert evalpy('"ab ".rfind("ab", 3)') == '-1' - + assert evalpy('"abcdefgh".rfind("a")') == "0" + assert evalpy('"abcdefgh".rfind("h")') == "7" + assert evalpy('"abcdefgh".rfind("z")') == "-1" + assert evalpy('"abcdefgh".rfind("")') == "8" + + assert evalpy('"abcdefgh".rfind("cd")') == "2" + assert evalpy('"abcdefgh".rfind("def")') == "3" + + assert evalpy('"ab ab ab".rfind("ab", 0, -2)') == "3" + assert evalpy('"ab ab ab".rfind("ab", 0, 3)') == "0" + assert evalpy('"ab ".rfind("ab", 3)') == "-1" + def test_rindex(self): # We know that the implementation is basded on find; no need to test all - assert evalpy('"abcdefghb".rindex("a")') == '0' - assert evalpy('"abcdefghb".rindex("h")') == '7' - assert evalpy('"abcdefghb".rindex("")') == '9' - assert evalpy('"abcdefghb".rindex("b")') == '8' - assert 'ValueError' in evalpy('try:\n "abcdefgh".rindex("z")\nexcept Exception as e:\n e') - + assert evalpy('"abcdefghb".rindex("a")') == "0" + assert evalpy('"abcdefghb".rindex("h")') == "7" + assert evalpy('"abcdefghb".rindex("")') == "9" + assert evalpy('"abcdefghb".rindex("b")') == "8" + assert "ValueError" in evalpy( + 'try:\n "abcdefgh".rindex("z")\nexcept Exception as e:\n e' + ) + def test_isalnum(self): - assert evalpy('"".isalnum()') == 'false' - assert evalpy('"012".isalnum()') == 'true' - assert evalpy('"abc".isalnum()') == 'true' - assert evalpy('"0a1b2c".isalnum()') == 'true' - assert evalpy('"0a_".isalnum()') == 'false' - + assert evalpy('"".isalnum()') == "false" + assert evalpy('"012".isalnum()') == "true" + assert evalpy('"abc".isalnum()') == "true" + assert evalpy('"0a1b2c".isalnum()') == "true" + assert evalpy('"0a_".isalnum()') == "false" + def test_isalpha(self): - assert evalpy('"".isalpha()') == 'false' - assert evalpy('"012".isalpha()') == 'false' - assert evalpy('"abc".isalpha()') == 'true' - assert evalpy('"0a1b2c".isalpha()') == 'false' - assert evalpy('"0a_".isalpha()') == 'false' - + assert evalpy('"".isalpha()') == "false" + assert evalpy('"012".isalpha()') == "false" + assert evalpy('"abc".isalpha()') == "true" + assert evalpy('"0a1b2c".isalpha()') == "false" + assert evalpy('"0a_".isalpha()') == "false" + def test_isdecimal(self): - assert evalpy('"".isdecimal()') == 'false' - assert evalpy('"012".isdecimal()') == 'true' - assert evalpy('"abc".isdecimal()') == 'false' - assert evalpy('"0a1b2c".isdecimal()') == 'false' - assert evalpy('"0a_".isdecimal()') == 'false' - + assert evalpy('"".isdecimal()') == "false" + assert evalpy('"012".isdecimal()') == "true" + assert evalpy('"abc".isdecimal()') == "false" + assert evalpy('"0a1b2c".isdecimal()') == "false" + assert evalpy('"0a_".isdecimal()') == "false" + def test_isdigit(self): - assert evalpy('"".isdigit()') == 'false' - assert evalpy('"012".isdigit()') == 'true' - assert evalpy('"abc".isdigit()') == 'false' - assert evalpy('"0a1b2c".isdigit()') == 'false' - assert evalpy('"0a_".isdigit()') == 'false' - + assert evalpy('"".isdigit()') == "false" + assert evalpy('"012".isdigit()') == "true" + assert evalpy('"abc".isdigit()') == "false" + assert evalpy('"0a1b2c".isdigit()') == "false" + assert evalpy('"0a_".isdigit()') == "false" + def test_isnumeric(self): - assert evalpy('"".isnumeric()') == 'false' - assert evalpy('"012".isnumeric()') == 'true' - assert evalpy('"abc".isnumeric()') == 'false' - assert evalpy('"0a1b2c".isnumeric()') == 'false' - assert evalpy('"0a_".isnumeric()') == 'false' - + assert evalpy('"".isnumeric()') == "false" + assert evalpy('"012".isnumeric()') == "true" + assert evalpy('"abc".isnumeric()') == "false" + assert evalpy('"0a1b2c".isnumeric()') == "false" + assert evalpy('"0a_".isnumeric()') == "false" + def test_isidentifier(self): - assert evalpy('"".isidentifier()') == 'false' - assert evalpy('"012".isidentifier()') == 'false' - assert evalpy('"abc".isidentifier()') == 'true' - assert evalpy('"0a1b2c".isidentifier()') == 'false' - assert evalpy('"a0a1b2c".isidentifier()') == 'true' - assert evalpy('"0a_".isidentifier()') == 'false' - assert evalpy('"_a".isidentifier()') == 'true' - assert evalpy('"_0".isidentifier()') == 'true' - + assert evalpy('"".isidentifier()') == "false" + assert evalpy('"012".isidentifier()') == "false" + assert evalpy('"abc".isidentifier()') == "true" + assert evalpy('"0a1b2c".isidentifier()') == "false" + assert evalpy('"a0a1b2c".isidentifier()') == "true" + assert evalpy('"0a_".isidentifier()') == "false" + assert evalpy('"_a".isidentifier()') == "true" + assert evalpy('"_0".isidentifier()') == "true" + def test_islower(self): - assert evalpy('"".islower()') == 'false' - assert evalpy('" ".islower()') == 'false' - assert evalpy('"aBc".islower()') == 'false' - assert evalpy('"aBc 01_".islower()') == 'false' - - assert evalpy('"abc".islower()') == 'true' - assert evalpy('"abc 01_".islower()') == 'true' - + assert evalpy('"".islower()') == "false" + assert evalpy('" ".islower()') == "false" + assert evalpy('"aBc".islower()') == "false" + assert evalpy('"aBc 01_".islower()') == "false" + + assert evalpy('"abc".islower()') == "true" + assert evalpy('"abc 01_".islower()') == "true" + def test_isupper(self): - assert evalpy('"".isupper()') == 'false' - assert evalpy('" ".isupper()') == 'false' - assert evalpy('"AbC".isupper()') == 'false' - assert evalpy('"AbC 01_".isupper()') == 'false' - - assert evalpy('"ABC".isupper()') == 'true' - assert evalpy('"ABC 01_".isupper()') == 'true' - + assert evalpy('"".isupper()') == "false" + assert evalpy('" ".isupper()') == "false" + assert evalpy('"AbC".isupper()') == "false" + assert evalpy('"AbC 01_".isupper()') == "false" + + assert evalpy('"ABC".isupper()') == "true" + assert evalpy('"ABC 01_".isupper()') == "true" + def test_isspace(self): - assert evalpy('"".isspace()') == 'false' - assert evalpy('" ".isspace()') == 'true' - assert evalpy('" \\t\\n".isspace()') == 'true' - assert evalpy('" _".isspace()') == 'false' - assert evalpy('" a".isspace()') == 'false' - assert evalpy('" 1".isspace()') == 'false' - + assert evalpy('"".isspace()') == "false" + assert evalpy('" ".isspace()') == "true" + assert evalpy('" \\t\\n".isspace()') == "true" + assert evalpy('" _".isspace()') == "false" + assert evalpy('" a".isspace()') == "false" + assert evalpy('" 1".isspace()') == "false" + def test_istitle(self): - assert evalpy('"".istitle()') == 'false' - assert evalpy('" ".istitle()') == 'false' - assert evalpy('"AbC".istitle()') == 'false' - assert evalpy('"Foo bar".istitle()') == 'false' - assert evalpy('"AbC 01_".istitle()') == 'false' - - assert evalpy('"Foo".istitle()') == 'true' - assert evalpy('"Foo Bar".istitle()') == 'true' - assert evalpy('"Foo 01_".istitle()') == 'true' - + assert evalpy('"".istitle()') == "false" + assert evalpy('" ".istitle()') == "false" + assert evalpy('"AbC".istitle()') == "false" + assert evalpy('"Foo bar".istitle()') == "false" + assert evalpy('"AbC 01_".istitle()') == "false" + + assert evalpy('"Foo".istitle()') == "true" + assert evalpy('"Foo Bar".istitle()') == "true" + assert evalpy('"Foo 01_".istitle()') == "true" + def test_join(self): - assert evalpy('"".join(["foo", "bar"])') == 'foobar' - assert evalpy('" ".join(["foo", "bar"])') == 'foo bar' - assert evalpy('"AA".join(["foo", "bar"])') == 'fooAAbar' - + assert evalpy('"".join(["foo", "bar"])') == "foobar" + assert evalpy('" ".join(["foo", "bar"])') == "foo bar" + assert evalpy('"AA".join(["foo", "bar"])') == "fooAAbar" + def test_lstrip(self): - assert evalpy('"".lstrip() + "."') == '.' - assert evalpy('" \\t\\r\\n".lstrip() + "."') == '.' - assert evalpy('" ab x cd ".lstrip() + "."') == 'ab x cd .' - assert evalpy('" ab x cd ".lstrip("x") + "."') == ' ab x cd .' - assert evalpy('" ab x cd ".lstrip(" x") + "."') == 'ab x cd .' - assert evalpy('"x x ab x cd x x".lstrip(" x") + "."') == 'ab x cd x x.' - + assert evalpy('"".lstrip() + "."') == "." + assert evalpy('" \\t\\r\\n".lstrip() + "."') == "." + assert evalpy('" ab x cd ".lstrip() + "."') == "ab x cd ." + assert evalpy('" ab x cd ".lstrip("x") + "."') == " ab x cd ." + assert evalpy('" ab x cd ".lstrip(" x") + "."') == "ab x cd ." + assert evalpy('"x x ab x cd x x".lstrip(" x") + "."') == "ab x cd x x." + def test_rstrip(self): - assert evalpy('"".rstrip() + "."') == '.' - assert evalpy('" \\t\\r\\n".rstrip() + "."') == '.' - assert evalpy('" ab x cd ".rstrip() + "."') == ' ab x cd.' - assert evalpy('" ab x cd ".rstrip("x") + "."') == ' ab x cd .' - assert evalpy('" ab x cd ".rstrip(" x") + "."') == ' ab x cd.' - assert evalpy('"x x ab x cd x x".rstrip(" x") + "."') == 'x x ab x cd.' - + assert evalpy('"".rstrip() + "."') == "." + assert evalpy('" \\t\\r\\n".rstrip() + "."') == "." + assert evalpy('" ab x cd ".rstrip() + "."') == " ab x cd." + assert evalpy('" ab x cd ".rstrip("x") + "."') == " ab x cd ." + assert evalpy('" ab x cd ".rstrip(" x") + "."') == " ab x cd." + assert evalpy('"x x ab x cd x x".rstrip(" x") + "."') == "x x ab x cd." + def test_strip(self): - assert evalpy('"".strip() + "."') == '.' - assert evalpy('" \\t\\r\\n".strip() + "."') == '.' - assert evalpy('" ab x cd ".strip() + "."') == 'ab x cd.' - assert evalpy('" ab x cd ".strip("x") + "."') == ' ab x cd .' - assert evalpy('" ab x cd ".strip(" x") + "."') == 'ab x cd.' - assert evalpy('"x x ab x cd x x".strip(" x") + "."') == 'ab x cd.' - + assert evalpy('"".strip() + "."') == "." + assert evalpy('" \\t\\r\\n".strip() + "."') == "." + assert evalpy('" ab x cd ".strip() + "."') == "ab x cd." + assert evalpy('" ab x cd ".strip("x") + "."') == " ab x cd ." + assert evalpy('" ab x cd ".strip(" x") + "."') == "ab x cd." + assert evalpy('"x x ab x cd x x".strip(" x") + "."') == "ab x cd." + def test_partition(self): assert evalpy('"".partition("-")') == "[ '', '', '' ]" assert evalpy('"abc".partition("-")') == "[ 'abc', '', '' ]" @@ -710,9 +762,11 @@ def test_partition(self): assert evalpy('"abc-".partition("-")') == "[ 'abc', '-', '' ]" assert evalpy('"-def".partition("-")') == "[ '', '-', 'def' ]" assert evalpy('"abc-def".partition("-")') == "[ 'abc', '-', 'def' ]" - - assert 'ValueError' in evalpy('try:\n "aa".partition("")\nexcept Exception as e:\n e') - + + assert "ValueError" in evalpy( + 'try:\n "aa".partition("")\nexcept Exception as e:\n e' + ) + def test_rpartition(self): assert evalpy('"".rpartition("-")') == "[ '', '', '' ]" assert evalpy('"abc".rpartition("-")') == "[ '', '', 'abc' ]" @@ -720,7 +774,7 @@ def test_rpartition(self): assert evalpy('"abc-".rpartition("-")') == "[ 'abc', '-', '' ]" assert evalpy('"-def".rpartition("-")') == "[ '', '-', 'def' ]" assert evalpy('"abc-def".rpartition("-")') == "[ 'abc', '-', 'def' ]" - + def test_split(self): assert evalpy('"".split("-")') == "[ '' ]" assert evalpy('"abc".split("-")') == "[ 'abc' ]" @@ -728,16 +782,18 @@ def test_split(self): assert evalpy('"abc-".split("-")') == "[ 'abc', '' ]" assert evalpy('"-def".split("-")') == "[ '', 'def' ]" assert evalpy('"abc-def".split("-")') == "[ 'abc', 'def' ]" - + assert evalpy('"a a a".split(" ", 0)') == "[ 'a a a' ]" assert evalpy('"a a a".split(" ", 1)') == "[ 'a', 'a a' ]" assert evalpy('"a a a".split(" ", 2)') == "[ 'a', 'a', 'a' ]" assert evalpy('"a a a".split(" ", 3)') == "[ 'a', 'a', 'a' ]" assert evalpy('"a a a".split(" ", 99)') == "[ 'a', 'a', 'a' ]" - + assert evalpy('"a a\\ta\\na".split()') == "[ 'a', 'a', 'a', 'a' ]" - assert 'ValueError' in evalpy('try:\n "aa".split("")\nexcept Exception as e:\n e') - + assert "ValueError" in evalpy( + 'try:\n "aa".split("")\nexcept Exception as e:\n e' + ) + def test_rsplit(self): assert evalpy('"".rsplit("-")') == "[ '' ]" assert evalpy('"abc".rsplit("-")') == "[ 'abc' ]" @@ -745,65 +801,69 @@ def test_rsplit(self): assert evalpy('"abc-".rsplit("-")') == "[ 'abc', '' ]" assert evalpy('"-def".rsplit("-")') == "[ '', 'def' ]" assert evalpy('"abc-def".rsplit("-")') == "[ 'abc', 'def' ]" - + assert evalpy('"a a a".rsplit(" ", 0)') == "[ 'a a a' ]" assert evalpy('"a a a".rsplit(" ", 1)') == "[ 'a a', 'a' ]" assert evalpy('"a a a".rsplit(" ", 2)') == "[ 'a', 'a', 'a' ]" assert evalpy('"a a a".rsplit(" ", 3)') == "[ 'a', 'a', 'a' ]" assert evalpy('"a a a".rsplit(" ", 99)') == "[ 'a', 'a', 'a' ]" - + assert evalpy('"a a\\ta\\na".split()') == "[ 'a', 'a', 'a', 'a' ]" - assert 'ValueError' in evalpy('try:\n "aa".split("")\nexcept Exception as e:\n e') - + assert "ValueError" in evalpy( + 'try:\n "aa".split("")\nexcept Exception as e:\n e' + ) + def test_splitlines(self): assert evalpy('"".splitlines()') == "[ '' ]" assert evalpy('"".splitlines(true)') == "[ '' ]" assert evalpy(r'"\n".splitlines()') == "[ '' ]" assert evalpy(r'"\n".splitlines(True)') == "[ '\\n' ]" - + assert evalpy(r'"abc def".splitlines()') == "[ 'abc def' ]" assert evalpy(r'"abc\ndef".splitlines()') == "[ 'abc', 'def' ]" assert evalpy(r'"abc\ndef\n".splitlines()') == "[ 'abc', 'def' ]" assert evalpy(r'"abc\rdef".splitlines()') == "[ 'abc', 'def' ]" assert evalpy(r'"abc\r\ndef".splitlines()') == "[ 'abc', 'def' ]" assert evalpy(r'"abc\n\rdef".splitlines()') == "[ 'abc', '', 'def' ]" - + assert evalpy(r'"abc def".splitlines(True)') == "[ 'abc def' ]" assert evalpy(r'"abc\ndef".splitlines(True)') == "[ 'abc\\n', 'def' ]" assert evalpy(r'"abc\ndef\n".splitlines(True)') == "[ 'abc\\n', 'def\\n' ]" assert evalpy(r'"abc\rdef".splitlines(True)') == "[ 'abc\\r', 'def' ]" assert evalpy(r'"abc\r\ndef".splitlines(True)') == "[ 'abc\\r\\n', 'def' ]" - - res = repr("X\n\nX\r\rX\r\n\rX\n\r\nX".splitlines(True)).replace(' ', '') + + res = repr("X\n\nX\r\rX\r\n\rX\n\r\nX".splitlines(True)).replace(" ", "") # res = res.replace('u"', '"').replace("u'", "'") # arg legacy py - assert nowhitespace(evalpy(r'"X\n\nX\r\rX\r\n\rX\n\r\nX".splitlines(true)')) == res - + assert ( + nowhitespace(evalpy(r'"X\n\nX\r\rX\r\n\rX\n\r\nX".splitlines(true)')) == res + ) + def test_replace(self): assert evalpy("'abcABC'.replace('a', 'x')") == "xbcABC" assert evalpy("'abcABC'.replace('C', 'x')") == "abcABx" assert evalpy("'abcABC'.replace('cA', 'x')") == "abxBC" - + assert evalpy("'abababab'.replace('a', 'x', 0)") == "abababab" assert evalpy("'abababab'.replace('a', 'x', 1)") == "xbababab" assert evalpy("'abababab'.replace('a', 'x', 3)") == "xbxbxbab" assert evalpy("'abababab'.replace('a', 'x', 99)") == "xbxbxbxb" assert evalpy("'abababab'.replace('b', 'x', 2)") == "axaxabab" - + def test_translate(self): code = "table = {'a':'x', 'b':'y', 'c': None}\n" assert evalpy(code + "'abcde'.translate(table)") == "xyde" - + def test_format(self): # Covered more extensively in test_parser1(), but we need the method # to pass the test below ... assert evalpy("'{:+0.2f}'.format(1.23456)") == "+1.23" - + def test_that_all_str_methods_are_tested(self): - tested = set([x.split('_')[1] for x in dir(self) if x.startswith('test_')]) - needed = set([x for x in dir(str) if not x.startswith('_')]) - ignore = 'encode decode format_map isprintable maketrans isascii removeprefix removesuffix' - needed = needed.difference(ignore.split(' ')) - + tested = set([x.split("_")[1] for x in dir(self) if x.startswith("test_")]) + needed = set([x for x in dir(str) if not x.startswith("_")]) + ignore = "encode decode format_map isprintable maketrans isascii removeprefix removesuffix" + needed = needed.difference(ignore.split(" ")) + not_tested = needed.difference(tested) assert not not_tested diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index 4cfdc2c8..5b0508c5 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -4,49 +4,51 @@ meta tests. """ -import sys +from pscript.testing import run_tests_if_main -from pscript.testing import run_tests_if_main, raises - -from pscript import py2js, evaljs, evalpy, Parser3, stdlib +from pscript import py2js, stdlib def test_stdlib_full_and_partial(): code = stdlib.get_full_std_lib() assert isinstance(code, str) - assert 'var %shasattr =' % stdlib.FUNCTION_PREFIX in code - assert 'var %slist =' % stdlib.FUNCTION_PREFIX in code - assert code.count('var') > 10 + assert "var %shasattr =" % stdlib.FUNCTION_PREFIX in code + assert "var %slist =" % stdlib.FUNCTION_PREFIX in code + assert code.count("var") > 10 - code = stdlib.get_partial_std_lib(['hasattr'], [], []) + code = stdlib.get_partial_std_lib(["hasattr"], [], []) assert isinstance(code, str) - assert 'var %shasattr =' % stdlib.FUNCTION_PREFIX in code - assert 'var %slist =' % stdlib.FUNCTION_PREFIX not in code - assert code.count('var') == 1 + assert "var %shasattr =" % stdlib.FUNCTION_PREFIX in code + assert "var %slist =" % stdlib.FUNCTION_PREFIX not in code + assert code.count("var") == 1 + + assert "_hasattr = function" in py2js('hasattr(x, "foo")') + assert "_hasattr = function" not in py2js('hasattr(x, "foo")', inline_stdlib=False) - assert '_hasattr = function' in py2js('hasattr(x, "foo")') - assert '_hasattr = function' not in py2js('hasattr(x, "foo")', inline_stdlib=False) def test_stdlib_has_all_list_methods(): - method_names = [m for m in dir(list) if not m.startswith('_')] + method_names = [m for m in dir(list) if not m.startswith("_")] for method_name in method_names: assert method_name in stdlib.METHODS + def test_stdlib_has_all_dict_methods(): - method_names = [m for m in dir(dict) if not m.startswith('_')] - ignore = 'fromkeys' - for name in ignore.split(' '): + method_names = [m for m in dir(dict) if not m.startswith("_")] + ignore = "fromkeys" + for name in ignore.split(" "): method_names.remove(name) for method_name in method_names: assert method_name in stdlib.METHODS + def test_stdlib_has_all_str_methods(): - method_names = [m for m in dir(str) if not m.startswith('_')] - ignore = 'encode format_map isprintable maketrans isascii removeprefix removesuffix' - for name in ignore.split(' '): + method_names = [m for m in dir(str) if not m.startswith("_")] + ignore = "encode format_map isprintable maketrans isascii removeprefix removesuffix" + for name in ignore.split(" "): if name in method_names: method_names.remove(name) for method_name in method_names: assert method_name in stdlib.METHODS + run_tests_if_main() diff --git a/tests/test_stubs.py b/tests/test_stubs.py index 2d46f64d..dfe5061d 100644 --- a/tests/test_stubs.py +++ b/tests/test_stubs.py @@ -1,82 +1,81 @@ from pscript.testing import run_tests_if_main, raises -import sys import pscript from pscript import RawJS def test_stubs(): from pscript.stubs import window, undefined, omgnotaname + assert isinstance(window, pscript.JSConstant) assert isinstance(undefined, pscript.JSConstant) assert isinstance(omgnotaname, pscript.JSConstant) def test_raw_js(): - with raises(TypeError): RawJS() with raises(TypeError): RawJS(3) - + # Empty - r1 = RawJS('') - assert str(r1) == '' - assert r1.get_code() == '' - assert r1.get_code(4) == '' - assert '0' in repr(r1) + r1 = RawJS("") + assert str(r1) == "" + assert r1.get_code() == "" + assert r1.get_code(4) == "" + assert "0" in repr(r1) assert r1.__module__.endswith(__name__) - + # Short single line r2 = RawJS('require("foobar")') - assert 'require(' in repr(r2) - assert 'require(' in str(r2) - assert r2.get_code().startswith('require') - assert r2.get_code(4).startswith(' require') - assert r2.get_code(2).startswith(' require') - assert '\n' not in r2.get_code() - + assert "require(" in repr(r2) + assert "require(" in str(r2) + assert r2.get_code().startswith("require") + assert r2.get_code(4).startswith(" require") + assert r2.get_code(2).startswith(" require") + assert "\n" not in r2.get_code() + # Long single line - r2b = RawJS('require("foobar")'*10) - assert 'require(' not in repr(r2b) - assert '1' in repr(r2b) - + r2b = RawJS('require("foobar")' * 10) + assert "require(" not in repr(r2b) + assert "1" in repr(r2b) + # Multiline, start at first line r3 = RawJS("""for ... { yyyy } """) - assert 'lines' in repr(r3) - assert 'for ...' in str(r3) - assert str(r3).endswith('}\n') - assert r3.get_code().count('\n') == 3 - assert r3.get_code().startswith('for') - assert r3.get_code(4).startswith(' for') - assert '\n yyyy\n' in r3.get_code(0) - assert '\n yyyy\n' in r3.get_code(4) - + assert "lines" in repr(r3) + assert "for ..." in str(r3) + assert str(r3).endswith("}\n") + assert r3.get_code().count("\n") == 3 + assert r3.get_code().startswith("for") + assert r3.get_code(4).startswith(" for") + assert "\n yyyy\n" in r3.get_code(0) + assert "\n yyyy\n" in r3.get_code(4) + # Multiline, exactly the same, but start at second line; same results r4 = RawJS(""" for ... { yyyy } """) - assert 'lines' in repr(r4) - assert 'for ...' in str(r4) - assert str(r4).endswith('}\n') - assert r4.get_code().count('\n') == 3 - assert r4.get_code().startswith('for') - assert r4.get_code(4).startswith(' for') - assert '\n yyyy\n' in r4.get_code(0) - assert '\n yyyy\n' in r4.get_code(4) - + assert "lines" in repr(r4) + assert "for ..." in str(r4) + assert str(r4).endswith("}\n") + assert r4.get_code().count("\n") == 3 + assert r4.get_code().startswith("for") + assert r4.get_code(4).startswith(" for") + assert "\n yyyy\n" in r4.get_code(0) + assert "\n yyyy\n" in r4.get_code(4) + # Multiline, now newline at the ned r5 = RawJS(""" for ... { yyyy }""") - assert r5.get_code().count('\n') == 2 - assert str(r5).endswith('}') + assert r5.get_code().count("\n") == 2 + assert str(r5).endswith("}") run_tests_if_main()