You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

465 lines
15 KiB

# -*- coding: utf-8 -*-
"""
Python Builtins
---------------
Most builtin functions (that make sense in JS) are automatically
translated to JavaScript: isinstance, issubclass, callable, hasattr,
getattr, setattr, delattr, print, len, max, min, chr, ord, dict, list,
tuple, range, pow, sum, round, int, float, str, bool, abs, divmod, all,
any, enumerate, zip, reversed, sorted, filter, map.
Further all methods for list, dict and str are implemented (except str
methods: encode, decode, format_map, isprintable, maketrans).
.. pscript_example::
# "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)
bool(y)
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']
dict(other_dict) # make a copy
list(other_list) # make copy
The isinstance function (and friends)
-------------------------------------
The ``isinstance()`` function works for all JS primitive types, but also
for user-defined classes.
.. pscript_example::
# Basic types
isinstance(3, float) # in JS there are no ints
isinstance('', str)
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)
hasattr, getattr, setattr and delattr
-------------------------------------
.. 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')
Creating sequences
------------------
.. 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)
List methods
------------
.. pscript_example::
# Call a.append() if it exists, otherwise a.push()
a.append(x)
# Similar for remove()
a.remove(x)
Dict methods
------------
.. pscript_example::
a = {'foo': 3}
a['foo']
a.get('foo', 0)
a.get('foo')
a.keys()
Str methods
-----------
.. pscript_example::
"foobar".startswith('foo')
"foobar".replace('foo', 'bar')
"foobar".upper()
Using JS specific functionality
-------------------------------
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
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)
Aside from ``window``, ``pscript`` also provides ``undefined``,
``Inifinity``, and ``NaN``.
"""
from __future__ import print_function, absolute_import, with_statement, unicode_literals, division
from . import commonast as ast
from . import stdlib
from .parser2 import Parser2, JSError, unify # noqa
from .stubs import RawJS
# This class has several `function_foo()` and `method_bar()` methods
# to implement corresponding functionality. Most of these are
# auto-generated from the stdlib. However, some methods need explicit
# implementation, e.g. to parse keyword arguments, or are inlined rather
# than implemented via the stlib.
#
# Note that when the number of arguments does not match, almost all
# functions raise a compile-time error. The methods, however, will
# bypass the stdlib in this case, because it is assumed that the user
# intended to call a special method on the object.
class Parser3(Parser2):
""" 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()"')
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).')
lines = RawJS._str2lines(node.arg_nodes[0].value.strip())
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.')
ob = unify(self.parse(node.arg_nodes[0]))
cls = unify(self.parse(node.arg_nodes[1]))
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',
}
cmp = MAP.get(cls, cls)
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()]
# 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()
]
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')
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.')
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)
def function_print(self, node):
# Process keywords
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')
else:
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
args_concat = combiner.join(args) or '""'
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'
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')
elif len(node.arg_nodes) == 1:
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, ')'
def function_min(self, node):
if len(node.arg_nodes) == 0:
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, ')'
else:
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')
def function_chr(self, node):
if len(node.arg_nodes) == 1:
arg = ''.join(self.parse(node.arg_nodes[0]))
return 'String.fromCharCode(%s)' % arg
else:
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
else:
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)
if len(node.arg_nodes) == 1:
return self.use_std_function('dict', node.arg_nodes)
else:
raise JSError('dict() needs at least one argument')
def function_list(self, node):
if len(node.arg_nodes) == 0:
return '[]'
if len(node.arg_nodes) == 1:
return self.use_std_function('list', node.arg_nodes)
else:
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)
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)
elif len(node.arg_nodes) == 3:
return self.use_std_function('range', node.arg_nodes)
else:
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)
for kw in node.kwarg_nodes:
if kw.name == 'key':
key = kw.value_node
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])
else:
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)
for kw in node.kwarg_nodes:
if kw.name == 'key':
key = kw.value_node
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])
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)
# 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)
if len(node.arg_nodes) not in 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, [])
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)
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, [])
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):
m = make_method(name, tuple(nargs), function_deps, method_deps)
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):
m = make_function(name, tuple(nargs), function_deps, method_deps)
setattr(Parser3, 'function_' + name, m)