core: update to subliminal_patch:head; replace cfscrape; add dependencies

This commit is contained in:
panni 2019-04-11 02:02:14 +02:00
parent c2a7c12470
commit 8cd8177455
152 changed files with 81870 additions and 314 deletions

View file

@ -1,279 +0,0 @@
import logging
import random
import time
import re
# based off of https://gist.github.com/doko-desuka/58d9212461f62583f8df9bc6387fade2
# and https://github.com/Anorov/cloudflare-scrape
# and https://github.com/VeNoMouS/cloudflare-scrape-js2py
'''''''''
Disables InsecureRequestWarning: Unverified HTTPS request is being made warnings.
'''''''''
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
''''''
from requests.sessions import Session
from copy import deepcopy
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
DEFAULT_USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/65.0.3325.181 Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0"
]
DEFAULT_USER_AGENT = random.choice(DEFAULT_USER_AGENTS)
BUG_REPORT = (
"Cloudflare may have changed their technique, or there may be a bug in the script.\n\nPlease read " "https://github.com/Anorov/cloudflare-scrape#updates, then file a "
"bug report at https://github.com/Anorov/cloudflare-scrape/issues.")
class CloudflareScraper(Session):
def __init__(self, *args, **kwargs):
super(CloudflareScraper, self).__init__(*args, **kwargs)
if "requests" in self.headers["User-Agent"]:
# Spoof Firefox on Linux if no custom User-Agent has been set
self.headers["User-Agent"] = random.choice(DEFAULT_USER_AGENTS)
def request(self, method, url, *args, **kwargs):
resp = super(CloudflareScraper, self).request(method, url, *args, **kwargs)
# Check if Cloudflare anti-bot is on
if (resp.status_code in (503, 429)
and resp.headers.get("Server", "").startswith("cloudflare")
and b"jschl_vc" in resp.content
and b"jschl_answer" in resp.content
):
return self.solve_cf_challenge(resp, **kwargs)
# Otherwise, no Cloudflare anti-bot detected
return resp
def solve_cf_challenge(self, resp, **original_kwargs):
body = resp.text
parsed_url = urlparse(resp.url)
domain = parsed_url.netloc
submit_url = "%s://%s/cdn-cgi/l/chk_jschl" % (parsed_url.scheme, domain)
cloudflare_kwargs = deepcopy(original_kwargs)
params = cloudflare_kwargs.setdefault("params", {})
headers = cloudflare_kwargs.setdefault("headers", {})
headers["Referer"] = resp.url
try:
cf_delay = float(re.search('submit.*?(\d+)', body, re.DOTALL).group(1)) / 1000.0
form_index = body.find('id="challenge-form"')
if form_index == -1:
raise Exception('CF form not found')
sub_body = body[form_index:]
s_match = re.search('name="s" value="(.+?)"', sub_body)
if s_match:
params["s"] = s_match.group(1) # On older variants this parameter is absent.
params["jschl_vc"] = re.search(r'name="jschl_vc" value="(\w+)"', sub_body).group(1)
params["pass"] = re.search(r'name="pass" value="(.+?)"', sub_body).group(1)
if body.find('id="cf-dn-', form_index) != -1:
extra_div_expression = re.search('id="cf-dn-.*?>(.+?)<', sub_body).group(1)
# Initial value.
js_answer = self.cf_parse_expression(
re.search('setTimeout\(function\(.*?:(.*?)}', body, re.DOTALL).group(1)
)
# Extract the arithmetic operations.
builder = re.search("challenge-form'\);\s*;(.*);a.value", body, re.DOTALL).group(1)
# Remove a function semicolon before splitting on semicolons, else it messes the order.
lines = builder.replace(' return +(p)}();', '', 1).split(';')
for line in lines:
if len(line) and '=' in line:
heading, expression = line.split('=', 1)
if 'eval(eval(atob' in expression:
# Uses the expression in an external <div>.
expression_value = self.cf_parse_expression(extra_div_expression)
elif '(function(p' in expression:
# Expression + domain sampling function.
expression_value = self.cf_parse_expression(expression, domain)
else:
expression_value = self.cf_parse_expression(expression)
js_answer = self.cf_arithmetic_op(heading[-1], js_answer, expression_value)
if '+ t.length' in body:
js_answer += len(domain) # Only older variants add the domain length.
params["jschl_answer"] = '%.10f' % js_answer
except Exception as e:
# Something is wrong with the page.
# This may indicate Cloudflare has changed their anti-bot
# technique. If you see this and are running the latest version,
# please open a GitHub issue so I can update the code accordingly.
logging.error("[!] %s Unable to parse Cloudflare anti-bots page. "
"Try upgrading cloudflare-scrape, or submit a bug report "
"if you are running the latest version. Please read "
"https://github.com/Anorov/cloudflare-scrape#updates "
"before submitting a bug report." % e)
raise
# Cloudflare requires a delay before solving the challenge.
# Always wait the full delay + 1s because of 'time.sleep()' imprecision.
time.sleep(cf_delay + 1.0)
# Requests transforms any request into a GET after a redirect,
# so the redirect has to be handled manually here to allow for
# performing other types of requests even as the first request.
method = resp.request.method
cloudflare_kwargs["allow_redirects"] = False
redirect = self.request(method, submit_url, **cloudflare_kwargs)
if 'Location' in redirect.headers:
redirect_location = urlparse(redirect.headers["Location"])
if not redirect_location.netloc:
redirect_url = "%s://%s%s" % (parsed_url.scheme, domain, redirect_location.path)
return self.request(method, redirect_url, **original_kwargs)
return self.request(method, redirect.headers["Location"], **original_kwargs)
else:
return redirect
def cf_sample_domain_function(self, func_expression, domain):
parameter_start_index = func_expression.find('}(') + 2
# Send the expression with the "+" char and enclosing parenthesis included, as they are
# stripped inside ".cf_parse_expression()'.
sample_index = self.cf_parse_expression(
func_expression[parameter_start_index: func_expression.rfind(')))')]
)
return ord(domain[int(sample_index)])
def cf_arithmetic_op(self, op, a, b):
if op == '+':
return a + b
elif op == '/':
return a / float(b)
elif op == '*':
return a * float(b)
elif op == '-':
return a - b
else:
raise Exception('Unknown operation')
def cf_parse_expression(self, expression, domain=None):
def _get_jsfuck_number(section):
digit_expressions = section.replace('!+[]', '1').replace('+!![]', '1').replace('+[]', '0').split('+')
return int(
# Form a number string, with each digit as the sum of the values inside each parenthesis block.
''.join(
str(sum(int(digit_char) for digit_char in digit_expression[1:-1])) # Strip the parenthesis.
for digit_expression in digit_expressions
)
)
if '/' in expression:
dividend, divisor = expression.split('/')
dividend = dividend[2:-1] # Strip the leading '+' char and the enclosing parenthesis.
if domain:
# 2019-04-02: At this moment, this extra domain sampling function always appears on the
# divisor side, at the end.
divisor_a, divisor_b = divisor.split('))+(')
divisor_a = _get_jsfuck_number(divisor_a[5:]) # Left-strip the sequence of "(+(+(".
divisor_b = self.cf_sample_domain_function(divisor_b, domain)
return _get_jsfuck_number(dividend) / float(divisor_a + divisor_b)
else:
divisor = divisor[2:-1]
return _get_jsfuck_number(dividend) / float(_get_jsfuck_number(divisor))
else:
return _get_jsfuck_number(expression[2:-1])
@classmethod
def create_scraper(cls, sess=None, **kwargs):
"""
Convenience function for creating a ready-to-go requests.Session (subclass) object.
"""
scraper = cls()
if sess:
attrs = ["auth", "cert", "cookies", "headers", "hooks", "params", "proxies", "data"]
for attr in attrs:
val = getattr(sess, attr, None)
if val:
setattr(scraper, attr, val)
return scraper
## Functions for integrating cloudflare-scrape with other applications and scripts
@classmethod
def get_tokens(cls, url, user_agent=None, **kwargs):
scraper = cls.create_scraper()
if user_agent:
scraper.headers["User-Agent"] = user_agent
try:
resp = scraper.get(url, **kwargs)
resp.raise_for_status()
except Exception as e:
logging.error("'%s' returned an error. Could not collect tokens." % url)
raise
domain = urlparse(resp.url).netloc
cookie_domain = None
for d in scraper.cookies.list_domains():
if d.startswith(".") and d in ("." + domain):
cookie_domain = d
break
else:
raise ValueError(
"Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM (\"I'm Under Attack Mode\") enabled?")
return ({
"__cfduid": scraper.cookies.get("__cfduid", "", domain=cookie_domain),
"cf_clearance": scraper.cookies.get("cf_clearance", "", domain=cookie_domain)
},
scraper.headers["User-Agent"]
)
def get_live_tokens(self, domain):
for d in self.cookies.list_domains():
if d.startswith(".") and d in ("." + domain):
cookie_domain = d
break
else:
raise ValueError(
"Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM (\"I'm Under Attack Mode\") enabled?")
return ({
"__cfduid": self.cookies.get("__cfduid", "", domain=cookie_domain),
"cf_clearance": self.cookies.get("cf_clearance", "", domain=cookie_domain)
},
self.headers["User-Agent"]
)
@classmethod
def get_cookie_string(cls, url, user_agent=None, **kwargs):
"""
Convenience function for building a Cookie HTTP header value.
"""
tokens, user_agent = cls.get_tokens(url, user_agent=user_agent, **kwargs)
return "; ".join("=".join(pair) for pair in tokens.items()), user_agent
create_scraper = CloudflareScraper.create_scraper
get_tokens = CloudflareScraper.get_tokens
get_cookie_string = CloudflareScraper.get_cookie_string

326
libs/cfscrape/__init__.py Normal file
View file

@ -0,0 +1,326 @@
import logging
import random
import re
import base64
from copy import deepcopy
from time import sleep
from collections import OrderedDict
from .jsfuck import jsunfuck
import js2py
from requests.sessions import Session
try:
from requests_toolbelt.utils import dump
except ImportError:
pass
try:
from urlparse import urlparse
from urlparse import urlunparse
except ImportError:
from urllib.parse import urlparse
from urllib.parse import urlunparse
__version__ = "2.0.3"
# Orignally written by https://github.com/Anorov/cloudflare-scrape
# Rewritten by VeNoMouS - <venom@gen-x.co.nz> for https://github.com/VeNoMouS/Sick-Beard - 24/3/2018 NZDT
DEFAULT_USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/65.0.3325.181 Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0",
]
BUG_REPORT = """\
Cloudflare may have changed their technique, or there may be a bug in the script.
"""
class CloudflareScraper(Session):
def __init__(self, *args, **kwargs):
self.delay = kwargs.pop('delay', 8)
self.debug = False
super(CloudflareScraper, self).__init__(*args, **kwargs)
if 'requests' in self.headers['User-Agent']:
# Set a random User-Agent if no custom User-Agent has been set
self.headers['User-Agent'] = random.choice(DEFAULT_USER_AGENTS)
def set_cloudflare_challenge_delay(self, delay):
if isinstance(delay, (int, float)) and delay > 0:
self.delay = delay
def is_cloudflare_challenge(self, resp):
if resp.headers.get('Server', '').startswith('cloudflare'):
if b'why_captcha' in resp.content or b'/cdn-cgi/l/chk_captcha' in resp.content:
raise ValueError('Captcha')
return (
resp.status_code in [429, 503]
and b"jschl_vc" in resp.content
and b"jschl_answer" in resp.content
)
return False
def debugRequest(self, req):
try:
print (dump.dump_all(req).decode('utf-8'))
except:
pass
def request(self, method, url, *args, **kwargs):
self.headers = (
OrderedDict(
[
('User-Agent', self.headers['User-Agent']),
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
('Accept-Language', 'en-US,en;q=0.5'),
('Accept-Encoding', 'gzip, deflate'),
('Connection', 'close'),
('Upgrade-Insecure-Requests', '1')
]
)
)
resp = super(CloudflareScraper, self).request(method, url, *args, **kwargs)
# Debug request
if self.debug:
self.debugRequest(resp)
# Check if Cloudflare anti-bot is on
if self.is_cloudflare_challenge(resp):
# Work around if the initial request is not a GET,
# Superseed with a GET then re-request the orignal METHOD.
if resp.request.method != 'GET':
self.request('GET', resp.url)
resp = self.request(method, url, *args, **kwargs)
else:
resp = self.solve_cf_challenge(resp, **kwargs)
return resp
def solve_cf_challenge(self, resp, **original_kwargs):
body = resp.text
# Cloudflare requires a delay before solving the challenge
if self.delay == 8:
try:
delay = float(re.search(r'submit\(\);\r?\n\s*},\s*([0-9]+)', body).group(1)) / float(1000)
if isinstance(delay, (int, float)):
self.delay = delay
except:
pass
sleep(self.delay)
parsed_url = urlparse(resp.url)
domain = parsed_url.netloc
submit_url = '{}://{}/cdn-cgi/l/chk_jschl'.format(parsed_url.scheme, domain)
cloudflare_kwargs = deepcopy(original_kwargs)
headers = cloudflare_kwargs.setdefault('headers', {'Referer': resp.url})
try:
params = cloudflare_kwargs.setdefault(
'params', OrderedDict(
[
('s', re.search(r'name="s"\svalue="(?P<s_value>[^"]+)', body).group('s_value')),
('jschl_vc', re.search(r'name="jschl_vc" value="(\w+)"', body).group(1)),
('pass', re.search(r'name="pass" value="(.+?)"', body).group(1)),
]
)
)
except Exception as e:
# Something is wrong with the page.
# This may indicate Cloudflare has changed their anti-bot
# technique. If you see this and are running the latest version,
# please open a GitHub issue so I can update the code accordingly.
raise ValueError("Unable to parse Cloudflare anti-bots page: {} {}".format(e.message, BUG_REPORT))
# Solve the Javascript challenge
params['jschl_answer'] = self.solve_challenge(body, domain)
# Requests transforms any request into a GET after a redirect,
# so the redirect has to be handled manually here to allow for
# performing other types of requests even as the first request.
method = resp.request.method
cloudflare_kwargs['allow_redirects'] = False
redirect = self.request(method, submit_url, **cloudflare_kwargs)
redirect_location = urlparse(redirect.headers['Location'])
if not redirect_location.netloc:
redirect_url = urlunparse(
(
parsed_url.scheme,
domain,
redirect_location.path,
redirect_location.params,
redirect_location.query,
redirect_location.fragment
)
)
return self.request(method, redirect_url, **original_kwargs)
return self.request(method, redirect.headers['Location'], **original_kwargs)
def solve_challenge(self, body, domain):
try:
js = re.search(
r"setTimeout\(function\(\){\s+(var s,t,o,p,b,r,e,a,k,i,n,g,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n",
body
).group(1)
except Exception:
raise ValueError("Unable to identify Cloudflare IUAM Javascript on website. {}".format(BUG_REPORT))
js = re.sub(r"a\.value = ((.+).toFixed\(10\))?", r"\1", js)
js = re.sub(r'(e\s=\sfunction\(s\)\s{.*?};)', '', js, flags=re.DOTALL|re.MULTILINE)
js = re.sub(r"\s{3,}[a-z](?: = |\.).+", "", js).replace("t.length", str(len(domain)))
js = js.replace('; 121', '')
# Strip characters that could be used to exit the string context
# These characters are not currently used in Cloudflare's arithmetic snippet
js = re.sub(r"[\n\\']", "", js)
if 'toFixed' not in js:
raise ValueError("Error parsing Cloudflare IUAM Javascript challenge. {}".format(BUG_REPORT))
try:
jsEnv = """
var t = "{domain}";
var g = String.fromCharCode;
o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
e = function(s) {{
s += "==".slice(2 - (s.length & 3));
var bm, r = "", r1, r2, i = 0;
for (; i < s.length;) {{
bm = o.indexOf(s.charAt(i++)) << 18 | o.indexOf(s.charAt(i++)) << 12 | (r1 = o.indexOf(s.charAt(i++))) << 6 | (r2 = o.indexOf(s.charAt(i++)));
r += r1 === 64 ? g(bm >> 16 & 255) : r2 === 64 ? g(bm >> 16 & 255, bm >> 8 & 255) : g(bm >> 16 & 255, bm >> 8 & 255, bm & 255);
}}
return r;
}};
function italics (str) {{ return '<i>' + this + '</i>'; }};
var document = {{
getElementById: function () {{
return {{'innerHTML': '{innerHTML}'}};
}}
}};
{js}
"""
innerHTML = re.search(
'<div(?: [^<>]*)? id="([^<>]*?)">([^<>]*?)<\/div>',
body,
re.MULTILINE | re.DOTALL
)
innerHTML = innerHTML.group(2).replace("'", r"\'") if innerHTML else ""
js = jsunfuck(jsEnv.format(domain=domain, innerHTML=innerHTML, js=js))
def atob(s):
return base64.b64decode('{}'.format(s)).decode('utf-8')
js2py.disable_pyimport()
context = js2py.EvalJs({'atob': atob})
result = context.eval(js)
except Exception:
logging.error("Error executing Cloudflare IUAM Javascript. {}".format(BUG_REPORT))
raise
try:
float(result)
except Exception:
raise ValueError("Cloudflare IUAM challenge returned unexpected answer. {}".format(BUG_REPORT))
return result
@classmethod
def create_scraper(cls, sess=None, **kwargs):
"""
Convenience function for creating a ready-to-go CloudflareScraper object.
"""
scraper = cls(**kwargs)
if sess:
attrs = ['auth', 'cert', 'cookies', 'headers', 'hooks', 'params', 'proxies', 'data']
for attr in attrs:
val = getattr(sess, attr, None)
if val:
setattr(scraper, attr, val)
return scraper
# Functions for integrating cloudflare-scrape with other applications and scripts
@classmethod
def get_tokens(cls, url, user_agent=None, debug=False, **kwargs):
scraper = cls.create_scraper()
scraper.debug = debug
if user_agent:
scraper.headers['User-Agent'] = user_agent
try:
resp = scraper.get(url, **kwargs)
resp.raise_for_status()
except Exception as e:
logging.error("'{}' returned an error. Could not collect tokens.".format(url))
raise
domain = urlparse(resp.url).netloc
cookie_domain = None
for d in scraper.cookies.list_domains():
if d.startswith('.') and d in ('.{}'.format(domain)):
cookie_domain = d
break
else:
raise ValueError("Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM (\"I'm Under Attack Mode\") enabled?")
return (
{
'__cfduid': scraper.cookies.get('__cfduid', '', domain=cookie_domain),
'cf_clearance': scraper.cookies.get('cf_clearance', '', domain=cookie_domain)
},
scraper.headers['User-Agent']
)
def get_live_tokens(self, domain):
for d in self.cookies.list_domains():
if d.startswith(".") and d in ("." + domain):
cookie_domain = d
break
else:
raise ValueError(
"Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM (\"I'm Under Attack Mode\") enabled?")
return ({
"__cfduid": self.cookies.get("__cfduid", "", domain=cookie_domain),
"cf_clearance": self.cookies.get("cf_clearance", "", domain=cookie_domain)
},
self.headers["User-Agent"]
)
@classmethod
def get_cookie_string(cls, url, user_agent=None, debug=False, **kwargs):
"""
Convenience function for building a Cookie HTTP header value.
"""
tokens, user_agent = cls.get_tokens(url, user_agent=user_agent, debug=debug, **kwargs)
return "; ".join("=".join(pair) for pair in tokens.items()), user_agent
create_scraper = CloudflareScraper.create_scraper
get_tokens = CloudflareScraper.get_tokens
get_cookie_string = CloudflareScraper.get_cookie_string

97
libs/cfscrape/jsfuck.py Normal file
View file

@ -0,0 +1,97 @@
MAPPING = {
'a': '(false+"")[1]',
'b': '([]["entries"]()+"")[2]',
'c': '([]["fill"]+"")[3]',
'd': '(undefined+"")[2]',
'e': '(true+"")[3]',
'f': '(false+"")[0]',
'g': '(false+[0]+String)[20]',
'h': '(+(101))["to"+String["name"]](21)[1]',
'i': '([false]+undefined)[10]',
'j': '([]["entries"]()+"")[3]',
'k': '(+(20))["to"+String["name"]](21)',
'l': '(false+"")[2]',
'm': '(Number+"")[11]',
'n': '(undefined+"")[1]',
'o': '(true+[]["fill"])[10]',
'p': '(+(211))["to"+String["name"]](31)[1]',
'q': '(+(212))["to"+String["name"]](31)[1]',
'r': '(true+"")[1]',
's': '(false+"")[3]',
't': '(true+"")[0]',
'u': '(undefined+"")[0]',
'v': '(+(31))["to"+String["name"]](32)',
'w': '(+(32))["to"+String["name"]](33)',
'x': '(+(101))["to"+String["name"]](34)[1]',
'y': '(NaN+[Infinity])[10]',
'z': '(+(35))["to"+String["name"]](36)',
'A': '(+[]+Array)[10]',
'B': '(+[]+Boolean)[10]',
'C': 'Function("return escape")()(("")["italics"]())[2]',
'D': 'Function("return escape")()([]["fill"])["slice"]("-1")',
'E': '(RegExp+"")[12]',
'F': '(+[]+Function)[10]',
'G': '(false+Function("return Date")()())[30]',
'I': '(Infinity+"")[0]',
'M': '(true+Function("return Date")()())[30]',
'N': '(NaN+"")[0]',
'O': '(NaN+Function("return{}")())[11]',
'R': '(+[]+RegExp)[10]',
'S': '(+[]+String)[10]',
'T': '(NaN+Function("return Date")()())[30]',
'U': '(NaN+Function("return{}")()["to"+String["name"]]["call"]())[11]',
' ': '(NaN+[]["fill"])[11]',
'"': '("")["fontcolor"]()[12]',
'%': 'Function("return escape")()([]["fill"])[21]',
'&': '("")["link"](0+")[10]',
'(': '(undefined+[]["fill"])[22]',
')': '([0]+false+[]["fill"])[20]',
'+': '(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[2]',
',': '([]["slice"]["call"](false+"")+"")[1]',
'-': '(+(.+[0000000001])+"")[2]',
'.': '(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]',
'/': '(false+[0])["italics"]()[10]',
':': '(RegExp()+"")[3]',
';': '("")["link"](")[14]',
'<': '("")["italics"]()[0]',
'=': '("")["fontcolor"]()[11]',
'>': '("")["italics"]()[2]',
'?': '(RegExp()+"")[2]',
'[': '([]["entries"]()+"")[0]',
']': '([]["entries"]()+"")[22]',
'{': '(true+[]["fill"])[20]',
'}': '([]["fill"]+"")["slice"]("-1")'
}
SIMPLE = {
'false': '![]',
'true': '!![]',
'undefined': '[][[]]',
'NaN': '+[![]]',
'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' # +"1e1000"
}
CONSTRUCTORS = {
'Array': '[]',
'Number': '(+[])',
'String': '([]+[])',
'Boolean': '(![])',
'Function': '[]["fill"]',
'RegExp': 'Function("return/"+false+"/")()'
}
def jsunfuck(jsfuckString):
for key in sorted(MAPPING, key=lambda k: len(MAPPING[k]), reverse=True):
if MAPPING.get(key) in jsfuckString:
jsfuckString = jsfuckString.replace(MAPPING.get(key), '"{}"'.format(key))
for key in sorted(SIMPLE, key=lambda k: len(SIMPLE[k]), reverse=True):
if SIMPLE.get(key) in jsfuckString:
jsfuckString = jsfuckString.replace(SIMPLE.get(key), '{}'.format(key))
#for key in sorted(CONSTRUCTORS, key=lambda k: len(CONSTRUCTORS[k]), reverse=True):
# if CONSTRUCTORS.get(key) in jsfuckString:
# jsfuckString = jsfuckString.replace(CONSTRUCTORS.get(key), '{}'.format(key))
return jsfuckString

75
libs/js2py/__init__.py Normal file
View file

@ -0,0 +1,75 @@
# The MIT License
#
# Copyright 2014, 2015 Piotr Dabkowski
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the 'Software'),
# to deal in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
""" This module allows you to translate and execute Javascript in pure python.
Basically its implementation of ECMAScript 5.1 in pure python.
Use eval_js method to execute javascript code and get resulting python object (builtin if possible).
EXAMPLE:
>>> import js2py
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
>>> add(1, 2) + 3
6
>>> add('1', 2, 3)
u'12'
>>> add.constructor
function Function() { [python code] }
Or use EvalJs to execute many javascript code fragments under same context - you would be able to get any
variable from the context!
>>> js = js2py.EvalJs()
>>> js.execute('var a = 10; function f(x) {return x*x};')
>>> js.f(9)
81
>>> js.a
10
Also you can use its console method to play with interactive javascript console.
Use parse_js to parse (syntax tree is just like in esprima.js) and translate_js to trasnlate JavaScript.
Finally, you can use pyimport statement from inside JS code to import and use python libraries.
>>> js2py.eval_js('pyimport urllib; urllib.urlopen("https://www.google.com")')
NOTE: This module is still not fully finished:
Date and JSON builtin objects are not implemented
Array prototype is not fully finished (will be soon)
Other than that everything should work fine.
"""
__author__ = 'Piotr Dabkowski'
__all__ = [
'EvalJs', 'translate_js', 'import_js', 'eval_js', 'parse_js',
'translate_file', 'run_file', 'disable_pyimport', 'eval_js6',
'translate_js6', 'PyJsException', 'get_file_contents',
'write_file_contents', 'require'
]
from .base import PyJsException
from .evaljs import *
from .translators import parse as parse_js
from .node_import import require

3280
libs/js2py/base.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'

View file

@ -0,0 +1,48 @@
from ..base import *
@Js
def Array():
if len(arguments) == 0 or len(arguments) > 1:
return arguments.to_list()
a = arguments[0]
if isinstance(a, PyJsNumber):
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js([])
temp.put('length', a)
return temp
return [a]
Array.create = Array
Array.own['length']['value'] = Js(1)
@Js
def isArray(arg):
return arg.Class == 'Array'
Array.define_own_property('isArray', {
'value': isArray,
'enumerable': False,
'writable': True,
'configurable': True
})
Array.define_own_property(
'prototype', {
'value': ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
ArrayPrototype.define_own_property('constructor', {
'value': Array,
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,41 @@
# this is based on jsarray.py
# todo check everything :)
from ..base import *
try:
import numpy
except:
pass
@Js
def ArrayBuffer():
a = arguments[0]
if isinstance(a, PyJsNumber):
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(bytearray([0] * length))
return temp
return Js(bytearray([0]))
ArrayBuffer.create = ArrayBuffer
ArrayBuffer.own['length']['value'] = Js(None)
ArrayBuffer.define_own_property(
'prototype', {
'value': ArrayBufferPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
ArrayBufferPrototype.define_own_property(
'constructor', {
'value': ArrayBuffer,
'enumerable': False,
'writable': False,
'configurable': True
})

View file

@ -0,0 +1,16 @@
from ..base import *
BooleanPrototype.define_own_property('constructor', {
'value': Boolean,
'enumerable': False,
'writable': True,
'configurable': True
})
Boolean.define_own_property(
'prototype', {
'value': BooleanPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,405 @@
from ..base import *
from .time_helpers import *
TZ_OFFSET = (time.altzone // 3600)
ABS_OFFSET = abs(TZ_OFFSET)
TZ_NAME = time.tzname[1]
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
@Js
def Date(year, month, date, hours, minutes, seconds, ms):
return now().to_string()
Date.Class = 'Date'
def now():
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype)
@Js
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
args = arguments
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js
def parse(string):
return PyJsDate(
TimeClip(parse_date(string.to_string().value)),
prototype=DatePrototype)
Date.define_own_property('now', {
'value': Js(now),
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('parse', {
'value': parse,
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('UTC', {
'value': UTC,
'enumerable': False,
'writable': True,
'configurable': True
})
class PyJsDate(PyJs):
Class = 'Date'
extensible = True
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def parse_date(py_string): # todo support all date string formats
try:
try:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
except:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
return MakeDate(
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
MakeTime(
Js(dt.hour), Js(dt.minute), Js(dt.second),
Js(dt.microsecond // 1000)))
except:
raise MakeError(
'TypeError',
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
% py_string)
def date_constructor(*args):
if len(args) >= 2:
return date_constructor2(*args)
elif len(args) == 1:
return date_constructor1(args[0])
else:
return date_constructor0()
def date_constructor0():
return now()
def date_constructor1(value):
v = value.to_primitive()
if v._type() == 'String':
v = parse_date(v.value)
else:
v = v.to_int()
return PyJsDate(TimeClip(v), prototype=DatePrototype)
def date_constructor2(*args):
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
return PyJsDate(t, prototype=DatePrototype)
Date.create = date_constructor
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
def check_date(obj):
if obj.Class != 'Date':
raise MakeError('TypeError', 'this is not a Date object')
class DateProto:
def toString():
check_date(this)
if this.value is NaN:
return 'Invalid Date'
offset = (UTCToLocal(this.value) - this.value) // msPerHour
return this.local_strftime(
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
offset, 2, True), GetTimeZoneName(this.value))
def toDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def toLocaleString():
check_date(this)
return this.local_strftime('%d %B %Y %H:%M:%S')
def toLocaleDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toLocaleTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def valueOf():
check_date(this)
return this.value
def getTime():
check_date(this)
return this.value
def getFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(UTCToLocal(this.value))
def getUTCFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(this.value)
def getMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(UTCToLocal(this.value))
def getDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(UTCToLocal(this.value))
def getUTCMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(this.value)
def getUTCDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(this.value)
def getDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(UTCToLocal(this.value))
def getUTCDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(this.value)
def getHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(UTCToLocal(this.value))
def getUTCHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(this.value)
def getMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(UTCToLocal(this.value))
def getUTCMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(this.value)
def getSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(UTCToLocal(this.value))
def getUTCSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(this.value)
def getMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(UTCToLocal(this.value))
def getUTCMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(this.value)
def getTimezoneOffset():
check_date(this)
if this.value is NaN:
return NaN
return (this.value - UTCToLocal(this.value)) // 60000
def setTime(time):
check_date(this)
this.value = TimeClip(time.to_number().to_int())
return this.value
def setMilliseconds(ms):
check_date(this)
t = UTCToLocal(this.value)
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u
return u
def setUTCMilliseconds(ms):
check_date(this)
t = this.value
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(MakeDate(Day(t), tim))
this.value = u
return u
# todo Complete all setters!
def toUTCString():
check_date(this)
return this.utc_strftime('%d %B %Y %H:%M:%S')
def toISOString():
check_date(this)
t = this.value
year = YearFromTime(t)
month, day, hour, minute, second, milli = pad(
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
HourFromTime(t)), pad(MinFromTime(t)), pad(
SecFromTime(t)), pad(msFromTime(t))
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
year, 6, True), month, day, hour, minute, second, milli)
def toJSON(key):
o = this.to_object()
tv = o.to_primitive('Number')
if tv.Class == 'Number' and not tv.is_finite():
return this.null
toISO = o.get('toISOString')
if not toISO.is_callable():
raise this.MakeError('TypeError', 'toISOString is not callable')
return toISO.call(o, ())
def pad(num, n=2, sign=False):
'''returns n digit string representation of the num'''
s = unicode(abs(num))
if len(s) < n:
s = '0' * (n - len(s)) + s
if not sign:
return s
if num >= 0:
return '+' + s
else:
return '-' + s
fill_prototype(DatePrototype, DateProto, default_attrs)
Date.define_own_property(
'prototype', {
'value': DatePrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
DatePrototype.define_own_property('constructor', {
'value': Date,
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Float32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.float32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.float32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.float32))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Float32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Float32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
array = numpy.frombuffer(
a.obj, dtype=numpy.float32, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.float32))
temp.put('length', Js(0))
return temp
Float32Array.create = Float32Array
Float32Array.own['length']['value'] = Js(3)
Float32Array.define_own_property(
'prototype', {
'value': Float32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Float32ArrayPrototype.define_own_property(
'constructor', {
'value': Float32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Float32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Float64Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.float64))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.float64))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.float64))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 8 != 0:
raise MakeError(
'RangeError',
'Byte length of Float64Array should be a multiple of 8')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 8 != 0:
raise MakeError(
'RangeError',
'Start offset of Float64Array should be a multiple of 8')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 8)
array = numpy.frombuffer(
a.obj, dtype=numpy.float64, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.float64))
temp.put('length', Js(0))
return temp
Float64Array.create = Float64Array
Float64Array.own['length']['value'] = Js(3)
Float64Array.define_own_property(
'prototype', {
'value': Float64ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Float64ArrayPrototype.define_own_property(
'constructor', {
'value': Float64Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Float64ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(8),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,52 @@
from ..base import *
try:
from ..translators.translator import translate_js
except:
pass
@Js
def Function():
# convert arguments to python list of strings
a = [e.to_string().value for e in arguments.to_list()]
body = ';'
args = ()
if len(a):
body = '%s;' % a[-1]
args = a[:-1]
# translate this function to js inline function
js_func = '(function (%s) {%s})' % (','.join(args), body)
# now translate js inline to python function
py_func = translate_js(js_func, '')
# add set func scope to global scope
# a but messy solution but works :)
globals()['var'] = PyJs.GlobalObject
# define py function and return it
temp = executor(py_func, globals())
temp.source = '{%s}' % body
temp.func_name = 'anonymous'
return temp
def executor(f, glob):
exec (f, globals())
return globals()['PyJs_anonymous_0_']
#new statement simply calls Function
Function.create = Function
#set constructor property inside FunctionPrototype
fill_in_props(FunctionPrototype, {'constructor': Function}, default_attrs)
#attach prototype to Function constructor
Function.define_own_property(
'prototype', {
'value': FunctionPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
#Fix Function length (its 0 and should be 1)
Function.own['length']['value'] = Js(1)

View file

@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int16Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int16))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int16))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int16))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 2 != 0:
raise MakeError(
'RangeError',
'Byte length of Int16Array should be a multiple of 2')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 2 != 0:
raise MakeError(
'RangeError',
'Start offset of Int16Array should be a multiple of 2')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 2)
array = numpy.frombuffer(
a.obj, dtype=numpy.int16, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int16))
temp.put('length', Js(0))
return temp
Int16Array.create = Int16Array
Int16Array.own['length']['value'] = Js(3)
Int16Array.define_own_property(
'prototype', {
'value': Int16ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int16ArrayPrototype.define_own_property(
'constructor', {
'value': Int16Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(2),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int32))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Int32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Int32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
array = numpy.frombuffer(
a.obj, dtype=numpy.int32, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int32))
temp.put('length', Js(0))
return temp
Int32Array.create = Int32Array
Int32Array.own['length']['value'] = Js(3)
Int32Array.define_own_property(
'prototype', {
'value': Int32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int32ArrayPrototype.define_own_property(
'constructor', {
'value': Int32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int8Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int8))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int8))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int8))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.int8, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int8))
temp.put('length', Js(0))
return temp
Int8Array.create = Int8Array
Int8Array.own['length']['value'] = Js(3)
Int8Array.define_own_property(
'prototype', {
'value': Int8ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int8ArrayPrototype.define_own_property(
'constructor', {
'value': Int8Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,157 @@
from ..base import *
import math
import random
Math = PyJsObject(prototype=ObjectPrototype)
Math.Class = 'Math'
CONSTANTS = {
'E': 2.7182818284590452354,
'LN10': 2.302585092994046,
'LN2': 0.6931471805599453,
'LOG2E': 1.4426950408889634,
'LOG10E': 0.4342944819032518,
'PI': 3.1415926535897932,
'SQRT1_2': 0.7071067811865476,
'SQRT2': 1.4142135623730951
}
for constant, value in CONSTANTS.items():
Math.define_own_property(
constant, {
'value': Js(value),
'writable': False,
'enumerable': False,
'configurable': False
})
class MathFunctions:
def abs(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return abs(a)
def acos(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.acos(a)
except:
return NaN
def asin(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.asin(a)
except:
return NaN
def atan(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.atan(a)
def atan2(y, x):
a = x.to_number().value
b = y.to_number().value
if a != a or b != b: # it must be a nan
return NaN
return math.atan2(b, a)
def ceil(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.ceil(a)
def floor(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.floor(a)
def round(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return round(a)
def sin(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.sin(a)
def cos(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.cos(a)
def tan(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.tan(a)
def log(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.log(a)
except:
return NaN
def exp(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.exp(a)
def pow(x, y):
a = x.to_number().value
b = y.to_number().value
if a != a or b != b: # it must be a nan
return NaN
try:
return a**b
except:
return NaN
def sqrt(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return a**0.5
except:
return NaN
def min():
if not len(arguments):
return Infinity
lis = tuple(e.to_number().value for e in arguments.to_list())
if any(e != e for e in lis): # we dont want NaNs
return NaN
return min(*lis)
def max():
if not len(arguments):
return -Infinity
lis = tuple(e.to_number().value for e in arguments.to_list())
if any(e != e for e in lis): # we dont want NaNs
return NaN
return max(*lis)
def random():
return random.random()
fill_prototype(Math, MathFunctions, default_attrs)

View file

@ -0,0 +1,23 @@
from ..base import *
CONSTS = {
'prototype': NumberPrototype,
'MAX_VALUE': 1.7976931348623157e308,
'MIN_VALUE': 5.0e-324,
'NaN': NaN,
'NEGATIVE_INFINITY': float('-inf'),
'POSITIVE_INFINITY': float('inf')
}
fill_in_props(Number, CONSTS, {
'enumerable': False,
'writable': False,
'configurable': False
})
NumberPrototype.define_own_property('constructor', {
'value': Number,
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,198 @@
from ..base import *
import six
#todo Double check everything is OK
@Js
def Object():
val = arguments.get('0')
if val.is_null() or val.is_undefined():
return PyJsObject(prototype=ObjectPrototype)
return val.to_object()
@Js
def object_constructor():
if len(arguments):
val = arguments.get('0')
if val.TYPE == 'Object':
#Implementation dependent, but my will simply return :)
return val
elif val.TYPE in ('Number', 'String', 'Boolean'):
return val.to_object()
return PyJsObject(prototype=ObjectPrototype)
Object.create = object_constructor
Object.own['length']['value'] = Js(1)
class ObjectMethods:
def getPrototypeOf(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.getPrototypeOf called on non-object')
return null if obj.prototype is None else obj.prototype
def getOwnPropertyDescriptor(obj, prop):
if not obj.is_object():
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return obj.own.get(
prop.to_string().
value) # will return undefined if we dont have this prop
def getOwnPropertyNames(obj):
if not obj.is_object():
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return obj.own.keys()
def create(obj):
if not (obj.is_object() or obj.is_null()):
raise MakeError('TypeError',
'Object prototype may only be an Object or null')
temp = PyJsObject(prototype=(None if obj.is_null() else obj))
if len(arguments) > 1 and not arguments[1].is_undefined():
if six.PY2:
ObjectMethods.defineProperties.__func__(temp, arguments[1])
else:
ObjectMethods.defineProperties(temp, arguments[1])
return temp
def defineProperty(obj, prop, attrs):
if not obj.is_object():
raise MakeError('TypeError',
'Object.defineProperty called on non-object')
name = prop.to_string().value
if not obj.define_own_property(name, ToPropertyDescriptor(attrs)):
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
return obj
def defineProperties(obj, properties):
if not obj.is_object():
raise MakeError('TypeError',
'Object.defineProperties called on non-object')
props = properties.to_object()
for name in props:
desc = ToPropertyDescriptor(props.get(name.value))
if not obj.define_own_property(name.value, desc):
raise MakeError(
'TypeError',
'Failed to define own property: %s' % name.value)
return obj
def seal(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.seal called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
obj.extensible = False
return obj
def freeze(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.freeze called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
if is_data_descriptor(desc):
desc['writable'] = False
obj.extensible = False
return obj
def preventExtensions(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.preventExtensions on non-object')
obj.extensible = False
return obj
def isSealed(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isSealed called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc['configurable']:
return False
return True
def isFrozen(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isFrozen called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc['configurable']:
return False
if is_data_descriptor(desc) and desc['writable']:
return False
return True
def isExtensible(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isExtensible called on non-object')
return obj.extensible
def keys(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.keys called on non-object')
return [e for e, d in six.iteritems(obj.own) if d.get('enumerable')]
# add methods attached to Object constructor
fill_prototype(Object, ObjectMethods, default_attrs)
# add constructor to prototype
fill_in_props(ObjectPrototype, {'constructor': Object}, default_attrs)
# add prototype property to the constructor.
Object.define_own_property(
'prototype', {
'value': ObjectPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
# some utility functions:
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
if obj.TYPE != 'Object':
raise MakeError('TypeError',
'Can\'t convert non-object to property descriptor')
desc = {}
if obj.has_property('enumerable'):
desc['enumerable'] = obj.get('enumerable').to_boolean().value
if obj.has_property('configurable'):
desc['configurable'] = obj.get('configurable').to_boolean().value
if obj.has_property('value'):
desc['value'] = obj.get('value')
if obj.has_property('writable'):
desc['writable'] = obj.get('writable').to_boolean().value
if obj.has_property('get'):
cand = obj.get('get')
if not (cand.is_undefined() or cand.is_callable()):
raise MakeError(
'TypeError',
'Invalid getter (it has to be a function or undefined)')
desc['get'] = cand
if obj.has_property('set'):
cand = obj.get('set')
if not (cand.is_undefined() or cand.is_callable()):
raise MakeError(
'TypeError',
'Invalid setter (it has to be a function or undefined)')
desc['set'] = cand
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise MakeError(
'TypeError',
'Invalid property. A property cannot both have accessors and be writable or have a value.'
)
return desc

View file

@ -0,0 +1,16 @@
from ..base import *
RegExpPrototype.define_own_property('constructor', {
'value': RegExp,
'enumerable': False,
'writable': True,
'configurable': True
})
RegExp.define_own_property(
'prototype', {
'value': RegExpPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,40 @@
from ..base import *
# python 3 support
import six
if six.PY3:
unichr = chr
@Js
def fromCharCode():
args = arguments.to_list()
res = u''
for e in args:
res += unichr(e.to_uint16())
return this.Js(res)
fromCharCode.own['length']['value'] = Js(1)
String.define_own_property(
'fromCharCode', {
'value': fromCharCode,
'enumerable': False,
'writable': True,
'configurable': True
})
String.define_own_property(
'prototype', {
'value': StringPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
StringPrototype.define_own_property('constructor', {
'value': String,
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint16Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint16))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint16))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint16))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 2 != 0:
raise MakeError(
'RangeError',
'Byte length of Uint16Array should be a multiple of 2')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 2 != 0:
raise MakeError(
'RangeError',
'Start offset of Uint16Array should be a multiple of 2')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 2)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint16, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint16))
temp.put('length', Js(0))
return temp
Uint16Array.create = Uint16Array
Uint16Array.own['length']['value'] = Js(3)
Uint16Array.define_own_property(
'prototype', {
'value': Uint16ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint16ArrayPrototype.define_own_property(
'constructor', {
'value': Uint16Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(2),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,95 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = len(array) - offset
temp = Js(
numpy.array(array[offset:offset + length], dtype=numpy.uint32))
temp.put('length', Js(length))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Uint32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Uint32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
temp = Js(
numpy.frombuffer(
a.obj, dtype=numpy.uint32, count=length, offset=offset))
temp.put('length', Js(length))
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint32))
temp.put('length', Js(0))
return temp
Uint32Array.create = Uint32Array
Uint32Array.own['length']['value'] = Js(3)
Uint32Array.define_own_property(
'prototype', {
'value': Uint32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint32ArrayPrototype.define_own_property(
'constructor', {
'value': Uint32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint8Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint8))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint8))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint8, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint8))
temp.put('length', Js(0))
return temp
Uint8Array.create = Uint8Array
Uint8Array.own['length']['value'] = Js(3)
Uint8Array.define_own_property(
'prototype', {
'value': Uint8ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint8ArrayPrototype.define_own_property(
'constructor', {
'value': Uint8Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint8ClampedArray():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint8), Clamped=True)
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint8, count=length, offset=offset)
temp = Js(array, Clamped=True)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(0))
return temp
Uint8ClampedArray.create = Uint8ClampedArray
Uint8ClampedArray.own['length']['value'] = Js(3)
Uint8ClampedArray.define_own_property(
'prototype', {
'value': Uint8ClampedArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint8ClampedArrayPrototype.define_own_property(
'constructor', {
'value': Uint8ClampedArray,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint8ClampedArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})

View file

@ -0,0 +1,207 @@
# NOTE: t must be INT!!!
import time
import datetime
import warnings
try:
from tzlocal import get_localzone
LOCAL_ZONE = get_localzone()
except: # except all problems...
warnings.warn(
'Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time'
)
class LOCAL_ZONE:
@staticmethod
def dst(*args):
return 1
from js2py.base import MakeError
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
msPerDay = 86400000
msPerYear = int(86400000 * 365.242)
msPerSecond = 1000
msPerMinute = 60000
msPerHour = 3600000
HoursPerDay = 24
MinutesPerHour = 60
SecondsPerMinute = 60
NaN = float('nan')
LocalTZA = -time.timezone * msPerSecond
def DaylightSavingTA(t):
if t is NaN:
return t
try:
return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
t // 1000)).seconds) * 1000
except:
warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...',
Warning)
return 1
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions')
def GetTimeZoneName(t):
return time.tzname[DaylightSavingTA(t) > 0]
def LocalToUTC(t):
return t - LocalTZA - DaylightSavingTA(t - LocalTZA)
def UTCToLocal(t):
return t + LocalTZA + DaylightSavingTA(t)
def Day(t):
return t // 86400000
def TimeWithinDay(t):
return t % 86400000
def DaysInYear(y):
if y % 4:
return 365
elif y % 100:
return 366
elif y % 400:
return 365
else:
return 366
def DayFromYear(y):
return 365 * (y - 1970) + (y - 1969) // 4 - (y - 1901) // 100 + (
y - 1601) // 400
def TimeFromYear(y):
return 86400000 * DayFromYear(y)
def YearFromTime(t):
guess = 1970 - t // 31556908800 # msPerYear
gt = TimeFromYear(guess)
if gt <= t:
while gt <= t:
guess += 1
gt = TimeFromYear(guess)
return guess - 1
else:
while gt > t:
guess -= 1
gt = TimeFromYear(guess)
return guess
def DayWithinYear(t):
return Day(t) - DayFromYear(YearFromTime(t))
def InLeapYear(t):
y = YearFromTime(t)
if y % 4:
return 0
elif y % 100:
return 1
elif y % 400:
return 0
else:
return 1
def MonthFromTime(t):
day = DayWithinYear(t)
leap = InLeapYear(t)
if day < 31:
return 0
day -= leap
if day < 59:
return 1
elif day < 90:
return 2
elif day < 120:
return 3
elif day < 151:
return 4
elif day < 181:
return 5
elif day < 212:
return 6
elif day < 243:
return 7
elif day < 273:
return 8
elif day < 304:
return 9
elif day < 334:
return 10
else:
return 11
def DateFromTime(t):
mon = MonthFromTime(t)
day = DayWithinYear(t)
return day - CUM[mon] - (1 if InLeapYear(t) and mon >= 2 else 0) + 1
def WeekDay(t):
# 0 == sunday
return (Day(t) + 4) % 7
def msFromTime(t):
return t % 1000
def SecFromTime(t):
return (t // 1000) % 60
def MinFromTime(t):
return (t // 60000) % 60
def HourFromTime(t):
return (t // 3600000) % 24
def MakeTime(hour, Min, sec, ms):
# takes PyJs objects and returns t
if not (hour.is_finite() and Min.is_finite() and sec.is_finite()
and ms.is_finite()):
return NaN
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int()
return h * 3600000 + m * 60000 + s * 1000 + milli
def MakeDay(year, month, date):
# takes PyJs objects and returns t
if not (year.is_finite() and month.is_finite() and date.is_finite()):
return NaN
y, m, dt = year.to_int(), month.to_int(), date.to_int()
y += m // 12
mn = m % 12
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y) == 366
and mn >= 2 else 0)
return d # ms per day
def MakeDate(day, time):
return 86400000 * day + time
def TimeClip(t):
if t != t or abs(t) == float('inf'):
return NaN
if abs(t) > 8.64 * 10**15:
return NaN
return int(t)

View file

@ -0,0 +1,41 @@
INITIALISED = False
babel = None
babelPresetEs2015 = None
def js6_to_js5(code):
global INITIALISED, babel, babelPresetEs2015
if not INITIALISED:
import signal, warnings, time
warnings.warn(
'\nImporting babel.py for the first time - this can take some time. \nPlease note that currently Javascript 6 in Js2Py is unstable and slow. Use only for tiny scripts!'
)
from .babel import babel as _babel
babel = _babel.Object.babel
babelPresetEs2015 = _babel.Object.babelPresetEs2015
# very weird hack. Somehow this helps babel to initialise properly!
try:
babel.transform('warmup', {'presets': {}})
signal.alarm(2)
def kill_it(a, b):
raise KeyboardInterrupt('Better work next time!')
signal.signal(signal.SIGALRM, kill_it)
babel.transform('stuckInALoop', {
'presets': babelPresetEs2015
}).code
for n in range(3):
time.sleep(1)
except:
print("Initialised babel!")
INITIALISED = True
return babel.transform(code, {'presets': babelPresetEs2015}).code
if __name__ == '__main__':
print(js6_to_js5('obj={}; obj.x = function() {return () => this}'))
print()
print(js6_to_js5('const a = 1;'))

6
libs/js2py/es6/babel.js Normal file
View file

@ -0,0 +1,6 @@
// run buildBabel in this folder to convert this code to python!
var babel = require("babel-core");
var babelPresetEs2015 = require("babel-preset-es2015");
Object.babelPresetEs2015 = babelPresetEs2015;
Object.babel = babel;

52077
libs/js2py/es6/babel.py Normal file

File diff suppressed because one or more lines are too long

12
libs/js2py/es6/buildBabel Normal file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# install all the dependencies and pack everything into one file babel_bundle.js (converted to es2015).
npm install babel-core babel-cli babel-preset-es2015 browserify
browserify babel.js -o babel_bundle.js -t [ babelify --presets [ es2015 ] ]
# translate babel_bundle.js using js2py -> generates babel.py
echo "Generating babel.py..."
python -c "import js2py;js2py.translate_file('babel_bundle.js', 'babel.py');"
rm babel_bundle.js

265
libs/js2py/evaljs.py Normal file
View file

@ -0,0 +1,265 @@
# coding=utf-8
from .translators import translate_js, DEFAULT_HEADER
from .es6 import js6_to_js5
import sys
import time
import json
import six
import os
import hashlib
import codecs
__all__ = [
'EvalJs', 'translate_js', 'import_js', 'eval_js', 'translate_file',
'eval_js6', 'translate_js6', 'run_file', 'disable_pyimport',
'get_file_contents', 'write_file_contents'
]
DEBUG = False
def disable_pyimport():
import pyjsparser.parser
pyjsparser.parser.ENABLE_PYIMPORT = False
def path_as_local(path):
if os.path.isabs(path):
return path
# relative to cwd
return os.path.join(os.getcwd(), path)
def import_js(path, lib_name, globals):
"""Imports from javascript source file.
globals is your globals()"""
with codecs.open(path_as_local(path), "r", "utf-8") as f:
js = f.read()
e = EvalJs()
e.execute(js)
var = e.context['var']
globals[lib_name] = var.to_python()
def get_file_contents(path_or_file):
if hasattr(path_or_file, 'read'):
js = path_or_file.read()
else:
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f:
js = f.read()
return js
def write_file_contents(path_or_file, contents):
if hasattr(path_or_file, 'write'):
path_or_file.write(contents)
else:
with open(path_as_local(path_or_file), 'w') as f:
f.write(contents)
def translate_file(input_path, output_path):
'''
Translates input JS file to python and saves the it to the output path.
It appends some convenience code at the end so that it is easy to import JS objects.
For example we have a file 'example.js' with: var a = function(x) {return x}
translate_file('example.js', 'example.py')
Now example.py can be easily importend and used:
>>> from example import example
>>> example.a(30)
30
'''
js = get_file_contents(input_path)
py_code = translate_js(js)
lib_name = os.path.basename(output_path).split('.')[0]
head = '__all__ = [%s]\n\n# Don\'t look below, you will not understand this Python code :) I don\'t.\n\n' % repr(
lib_name)
tail = '\n\n# Add lib to the module scope\n%s = var.to_python()' % lib_name
out = head + py_code + tail
write_file_contents(output_path, out)
def run_file(path_or_file, context=None):
''' Context must be EvalJS object. Runs given path as a JS program. Returns (eval_value, context).
'''
if context is None:
context = EvalJs()
if not isinstance(context, EvalJs):
raise TypeError('context must be the instance of EvalJs')
eval_value = context.eval(get_file_contents(path_or_file))
return eval_value, context
def eval_js(js):
"""Just like javascript eval. Translates javascript to python,
executes and returns python object.
js is javascript source code
EXAMPLE:
>>> import js2py
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
>>> add(1, 2) + 3
6
>>> add('1', 2, 3)
u'12'
>>> add.constructor
function Function() { [python code] }
NOTE: For Js Number, String, Boolean and other base types returns appropriate python BUILTIN type.
For Js functions and objects, returns Python wrapper - basically behaves like normal python object.
If you really want to convert object to python dict you can use to_dict method.
"""
e = EvalJs()
return e.eval(js)
def eval_js6(js):
return eval_js(js6_to_js5(js))
def translate_js6(js):
return translate_js(js6_to_js5(js))
class EvalJs(object):
"""This class supports continuous execution of javascript under same context.
>>> js = EvalJs()
>>> js.execute('var a = 10;function f(x) {return x*x};')
>>> js.f(9)
81
>>> js.a
10
context is a python dict or object that contains python variables that should be available to JavaScript
For example:
>>> js = EvalJs({'a': 30})
>>> js.execute('var x = a')
>>> js.x
30
You can run interactive javascript console with console method!"""
def __init__(self, context={}):
self.__dict__['_context'] = {}
exec (DEFAULT_HEADER, self._context)
self.__dict__['_var'] = self._context['var'].to_python()
if not isinstance(context, dict):
try:
context = context.__dict__
except:
raise TypeError(
'context has to be either a dict or have __dict__ attr')
for k, v in six.iteritems(context):
setattr(self._var, k, v)
def execute(self, js=None, use_compilation_plan=False):
"""executes javascript js in current context
During initial execute() the converted js is cached for re-use. That means next time you
run the same javascript snippet you save many instructions needed to parse and convert the
js code to python code.
This cache causes minor overhead (a cache dicts is updated) but the Js=>Py conversion process
is typically expensive compared to actually running the generated python code.
Note that the cache is just a dict, it has no expiration or cleanup so when running this
in automated situations with vast amounts of snippets it might increase memory usage.
"""
try:
cache = self.__dict__['cache']
except KeyError:
cache = self.__dict__['cache'] = {}
hashkey = hashlib.md5(js.encode('utf-8')).digest()
try:
compiled = cache[hashkey]
except KeyError:
code = translate_js(
js, '', use_compilation_plan=use_compilation_plan)
compiled = cache[hashkey] = compile(code, '<EvalJS snippet>',
'exec')
exec (compiled, self._context)
def eval(self, expression, use_compilation_plan=False):
"""evaluates expression in current context and returns its value"""
code = 'PyJsEvalResult = eval(%s)' % json.dumps(expression)
self.execute(code, use_compilation_plan=use_compilation_plan)
return self['PyJsEvalResult']
def execute_debug(self, js):
"""executes javascript js in current context
as opposed to the (faster) self.execute method, you can use your regular debugger
to set breakpoints and inspect the generated python code
"""
code = translate_js(js, '')
# make sure you have a temp folder:
filename = 'temp' + os.sep + '_' + hashlib.md5(
code.encode("utf-8")).hexdigest() + '.py'
try:
with open(filename, mode='w') as f:
f.write(code)
with open(filename, "r") as f:
pyCode = compile(f.read(), filename, 'exec')
exec(pyCode, self._context)
except Exception as err:
raise err
finally:
os.remove(filename)
try:
os.remove(filename + 'c')
except:
pass
def eval_debug(self, expression):
"""evaluates expression in current context and returns its value
as opposed to the (faster) self.execute method, you can use your regular debugger
to set breakpoints and inspect the generated python code
"""
code = 'PyJsEvalResult = eval(%s)' % json.dumps(expression)
self.execute_debug(code)
return self['PyJsEvalResult']
def __getattr__(self, var):
return getattr(self._var, var)
def __getitem__(self, var):
return getattr(self._var, var)
def __setattr__(self, var, val):
return setattr(self._var, var, val)
def __setitem__(self, var, val):
return setattr(self._var, var, val)
def console(self):
"""starts to interact (starts interactive console) Something like code.InteractiveConsole"""
while True:
if six.PY2:
code = raw_input('>>> ')
else:
code = input('>>>')
try:
print(self.eval(code))
except KeyboardInterrupt:
break
except Exception as e:
import traceback
if DEBUG:
sys.stderr.write(traceback.format_exc())
else:
sys.stderr.write('EXCEPTION: ' + str(e) + '\n')
time.sleep(0.01)
#print x
if __name__ == '__main__':
#with open('C:\Users\Piotrek\Desktop\esprima.js', 'rb') as f:
# x = f.read()
e = EvalJs()
e.execute('square(x)')
#e.execute(x)
e.console()

View file

View file

@ -0,0 +1,15 @@
from ..base import *
@Js
def console():
pass
@Js
def log():
print(arguments[0])
console.put('log', log)
console.put('debug', log)
console.put('info', log)
console.put('warn', log)
console.put('error', log)

View file

51
libs/js2py/host/jseval.py Normal file
View file

@ -0,0 +1,51 @@
from ..base import *
import inspect
try:
from js2py.translators.translator import translate_js
except:
pass
@Js
def Eval(code):
local_scope = inspect.stack()[3][0].f_locals['var']
global_scope = this.GlobalObject
# todo fix scope - we have to behave differently if called through variable other than eval
# we will use local scope (default)
globals()['var'] = local_scope
try:
py_code = translate_js(code.to_string().value, '')
except SyntaxError as syn_err:
raise MakeError('SyntaxError', str(syn_err))
lines = py_code.split('\n')
# a simple way to return value from eval. Will not work in complex cases.
has_return = False
for n in xrange(len(lines)):
line = lines[len(lines) - n - 1]
if line.strip():
if line.startswith(' '):
break
elif line.strip() == 'pass':
continue
elif any(
line.startswith(e)
for e in ['return ', 'continue ', 'break', 'raise ']):
break
else:
has_return = True
cand = 'EVAL_RESULT = (%s)\n' % line
try:
compile(cand, '', 'exec')
except SyntaxError:
break
lines[len(lines) - n - 1] = cand
py_code = '\n'.join(lines)
break
#print py_code
executor(py_code)
if has_return:
return globals()['EVAL_RESULT']
def executor(code):
exec (code, globals())

View file

@ -0,0 +1,176 @@
from ..base import *
from six.moves.urllib.parse import quote, unquote
RADIX_CHARS = {
'1': 1,
'0': 0,
'3': 3,
'2': 2,
'5': 5,
'4': 4,
'7': 7,
'6': 6,
'9': 9,
'8': 8,
'a': 10,
'c': 12,
'b': 11,
'e': 14,
'd': 13,
'g': 16,
'f': 15,
'i': 18,
'h': 17,
'k': 20,
'j': 19,
'm': 22,
'l': 21,
'o': 24,
'n': 23,
'q': 26,
'p': 25,
's': 28,
'r': 27,
'u': 30,
't': 29,
'w': 32,
'v': 31,
'y': 34,
'x': 33,
'z': 35,
'A': 10,
'C': 12,
'B': 11,
'E': 14,
'D': 13,
'G': 16,
'F': 15,
'I': 18,
'H': 17,
'K': 20,
'J': 19,
'M': 22,
'L': 21,
'O': 24,
'N': 23,
'Q': 26,
'P': 25,
'S': 28,
'R': 27,
'U': 30,
'T': 29,
'W': 32,
'V': 31,
'Y': 34,
'X': 33,
'Z': 35
}
@Js
def parseInt(string, radix):
string = string.to_string().value.lstrip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
r = radix.to_int32()
strip_prefix = True
if r:
if r < 2 or r > 36:
return NaN
if r != 16:
strip_prefix = False
else:
r = 10
if strip_prefix:
if len(string) >= 2 and string[:2] in ('0x', '0X'):
string = string[2:]
r = 16
n = 0
num = 0
while n < len(string):
cand = RADIX_CHARS.get(string[n])
if cand is None or not cand < r:
break
num = cand + num * r
n += 1
if not n:
return NaN
return sign * num
@Js
def parseFloat(string):
string = string.to_string().value.strip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
num = None
length = 1
max_len = None
failed = 0
while length <= len(string):
try:
num = float(string[:length])
max_len = length
failed = 0
except:
failed += 1
if failed > 4: # cant be a number anymore
break
length += 1
if num is None:
return NaN
return sign * float(string[:max_len])
@Js
def isNaN(number):
if number.to_number().is_nan():
return true
return false
@Js
def isFinite(number):
num = number.to_number()
if num.is_nan() or num.is_infinity():
return false
return true
# todo test them properly
@Js
def escape(text):
return quote(text.to_string().value)
@Js
def unescape(text):
return unquote(text.to_string().value)
@Js
def encodeURI(text):
return quote(text.to_string().value, safe='~@#$&()*!+=:;,.?/\'')
@Js
def decodeURI(text):
return unquote(text.to_string().value)
@Js
def encodeURIComponent(text):
return quote(text.to_string().value, safe='~()*!.\'')
@Js
def decodeURIComponent(text):
return unquote(text.to_string().value)

View file

View file

@ -0,0 +1,925 @@
from __future__ import unicode_literals
import re
import datetime
from desc import *
from simplex import *
from conversions import *
import six
from pyjsparser import PyJsParser
from itertools import izip
from conversions import *
from simplex import *
def Type(obj):
return obj.TYPE
# 8.6.2
class PyJs(object):
TYPE = 'Object'
IS_CONSTRUCTOR = False
prototype = None
Class = None
extensible = True
value = None
own = {}
def get_member(self, unconverted_prop):
return self.get(to_string(unconverted_prop))
def put_member(self, unconverted_prop, val):
return self.put(to_string(unconverted_prop), val)
def get(self, prop):
assert type(prop) == unicode
cand = self.get_property(prop)
if cand is None:
return undefined
if is_data_descriptor(cand):
return cand['value']
if is_undefined(cand['get']):
return undefined
return cand['get'].call(self)
def get_own_property(self, prop):
assert type(prop) == unicode
# takes py returns py
return self.own.get(prop)
def get_property(self, prop):
assert type(prop) == unicode
# take py returns py
cand = self.get_own_property(prop)
if cand:
return cand
if self.prototype is not None:
return self.prototype.get_property(prop)
def put(self, prop, val, throw=False):
assert type(prop) == unicode
# takes py, returns none
if not self.can_put(prop):
if throw:
raise MakeError('TypeError', 'Could not define own property')
return
own_desc = self.get_own_property(prop)
if is_data_descriptor(own_desc):
self.own[prop]['value'] = val
return
desc = self.get_property(prop)
if is_accessor_descriptor(desc):
desc['set'].call(
self, (val, )) # calling setter on own or inherited element
else: # new property
self.own[prop] = {
'value': val,
'writable': True,
'configurable': True,
'enumerable': True
}
def can_put(self, prop): # to check
assert type(prop) == unicode, type(prop)
# takes py returns py
desc = self.get_own_property(prop)
if desc: # if we have this property
if is_accessor_descriptor(desc):
return is_callable(
desc['set']) # Check if setter method is defined
else: # data desc
return desc['writable']
if self.prototype is None:
return self.extensible
inherited = self.prototype.get_property(prop)
if inherited is None:
return self.extensible
if is_accessor_descriptor(inherited):
return not is_undefined(inherited['set'])
elif self.extensible:
return inherited['writable'] # weird...
return False
def has_property(self, prop):
assert type(prop) == unicode
# takes py returns Py
return self.get_property(prop) is not None
def delete(self, prop, throw=False):
assert type(prop) == unicode
# takes py, returns py
desc = self.get_own_property(prop)
if desc is None:
return True
if desc['configurable']:
del self.own[prop]
return True
if throw:
raise MakeError('TypeError', 'Could not define own property')
return False
def default_value(self, hint=None):
order = ('valueOf', 'toString')
if hint == 'String' or (hint is None and self.Class == 'Date'):
order = ('toString', 'valueOf')
for meth_name in order:
method = self.get(meth_name)
if method is not None and is_callable(method):
cand = method.call(self, ())
if is_primitive(cand):
return cand
raise MakeError('TypeError',
'Cannot convert object to primitive value')
def define_own_property(
self, prop, desc,
throw): # Internal use only. External through Object
assert type(prop) == unicode
# takes Py, returns Py
# prop must be a Py string. Desc is either a descriptor or accessor.
# Messy method - raw translation from Ecma spec to prevent any bugs. # todo check this
current = self.get_own_property(prop)
extensible = self.extensible
if not current: # We are creating a new OWN property
if not extensible:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
# extensible must be True
if is_data_descriptor(desc) or is_generic_descriptor(desc):
DEFAULT_DATA_DESC = {
'value': undefined, # undefined
'writable': False,
'enumerable': False,
'configurable': False
}
DEFAULT_DATA_DESC.update(desc)
self.own[prop] = DEFAULT_DATA_DESC
else:
DEFAULT_ACCESSOR_DESC = {
'get': undefined, # undefined
'set': undefined, # undefined
'enumerable': False,
'configurable': False
}
DEFAULT_ACCESSOR_DESC.update(desc)
self.own[prop] = DEFAULT_ACCESSOR_DESC
return True
# therefore current exists!
if not desc or desc == current: # We don't need to change anything.
return True
configurable = current['configurable']
if not configurable: # Prevent changing params
if desc.get('configurable'):
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if 'enumerable' in desc and desc['enumerable'] != current[
'enumerable']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if is_generic_descriptor(desc):
pass
elif is_data_descriptor(current) != is_data_descriptor(desc):
# we want to change the current type of property
if not configurable:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if is_data_descriptor(current): # from data to setter
del current['value']
del current['writable']
current['set'] = undefined # undefined
current['get'] = undefined # undefined
else: # from setter to data
del current['set']
del current['get']
current['value'] = undefined # undefined
current['writable'] = False
elif is_data_descriptor(current) and is_data_descriptor(desc):
if not configurable:
if not current['writable'] and desc.get('writable'):
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if not current['writable'] and 'value' in desc and current[
'value'] != desc['value']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
elif is_accessor_descriptor(current) and is_accessor_descriptor(desc):
if not configurable:
if 'set' in desc and desc['set'] != current['set']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if 'get' in desc and desc['get'] != current['get']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
current.update(desc)
return True
def create(self, args, space):
'''Generally not a constructor, raise an error'''
raise MakeError('TypeError', '%s is not a constructor' % self.Class)
def get_member(
self, prop, space
): # general member getter, prop has to be unconverted prop. it is it can be any value
typ = type(self)
if typ not in PRIMITIVES: # most likely getter for object
return self.get_member(
prop
) # <- object can implement this to support faster prop getting. ex array.
elif typ == unicode: # then probably a String
if type(prop) == float and is_finite(prop):
index = int(prop)
if index == prop and 0 <= index < len(self):
return self[index]
s_prop = to_string(prop)
if s_prop == 'length':
return float(len(self))
elif s_prop.isdigit():
index = int(s_prop)
if 0 <= index < len(self):
return self[index]
# use standard string prototype
return space.StringPrototype.get(s_prop)
# maybe an index
elif typ == float:
# use standard number prototype
return space.NumberPrototype.get(to_string(prop))
elif typ == bool:
return space.BooleanPrototype.get(to_string(prop))
elif typ is UNDEFINED_TYPE:
raise MakeError('TypeError',
"Cannot read property '%s' of undefined" % prop)
elif typ is NULL_TYPE:
raise MakeError('TypeError',
"Cannot read property '%s' of null" % prop)
else:
raise RuntimeError('Unknown type! - ' + repr(typ))
def get_member_dot(self, prop, space):
# dot member getter, prop has to be unicode
typ = type(self)
if typ not in PRIMITIVES: # most likely getter for object
return self.get(prop)
elif typ == unicode: # then probably a String
if prop == 'length':
return float(len(self))
elif prop.isdigit():
index = int(prop)
if 0 <= index < len(self):
return self[index]
else:
# use standard string prototype
return space.StringPrototype.get(prop)
# maybe an index
elif typ == float:
# use standard number prototype
return space.NumberPrototype.get(prop)
elif typ == bool:
return space.BooleanPrototype.get(prop)
elif typ in (UNDEFINED_TYPE, NULL_TYPE):
raise MakeError('TypeError',
"Cannot read property '%s' of undefined" % prop)
else:
raise RuntimeError('Unknown type! - ' + repr(typ))
# Object
class PyJsObject(PyJs):
TYPE = 'Object'
Class = 'Object'
def __init__(self, prototype=None):
self.prototype = prototype
self.own = {}
def _init(self, props, vals):
i = 0
for prop, kind in props:
if prop in self.own: # just check... probably will not happen very often.
if is_data_descriptor(self.own[prop]):
if kind != 'i':
raise MakeError(
'SyntaxError',
'Invalid object initializer! Duplicate property name "%s"'
% prop)
else:
if kind == 'i' or (kind == 'g' and 'get' in self.own[prop]
) or (kind == 's'
and 'set' in self.own[prop]):
raise MakeError(
'SyntaxError',
'Invalid object initializer! Duplicate setter/getter of prop: "%s"'
% prop)
if kind == 'i': # init
self.own[prop] = {
'value': vals[i],
'writable': True,
'enumerable': True,
'configurable': True
}
elif kind == 'g': # get
self.define_own_property(prop, {
'get': vals[i],
'enumerable': True,
'configurable': True
}, False)
elif kind == 's': # get
self.define_own_property(prop, {
'get': vals[i],
'enumerable': True,
'configurable': True
}, False)
else:
raise RuntimeError(
'Invalid property kind - %s. Expected one of i, g, s.' %
repr(kind))
i += 1
def _set_props(self, prop_descs):
for prop, desc in six.iteritems(prop_descs):
self.define_own_property(prop, desc)
# Array
# todo Optimise Array - extremely slow due to index conversions from str to int and back etc.
# solution - make get and put methods callable with any type of prop and handle conversions from inside
# if not array then use to_string(prop). In array if prop is integer then just use it
# also consider removal of these stupid writable, enumerable etc for ints.
class PyJsArray(PyJs):
Class = 'Array'
def __init__(self, length, prototype=None):
self.prototype = prototype
self.own = {
'length': {
'value': float(length),
'writable': True,
'enumerable': False,
'configurable': False
}
}
def _init(self, elements):
for i, ele in enumerate(elements):
if ele is None: continue
self.own[unicode(i)] = {
'value': ele,
'writable': True,
'enumerable': True,
'configurable': True
}
def put(self, prop, val, throw=False):
assert type(val) != int
# takes py, returns none
if not self.can_put(prop):
if throw:
raise MakeError('TypeError', 'Could not define own property')
return
own_desc = self.get_own_property(prop)
if is_data_descriptor(own_desc):
self.define_own_property(prop, {'value': val}, False)
return
desc = self.get_property(prop)
if is_accessor_descriptor(desc):
desc['set'].call(
self, (val, )) # calling setter on own or inherited element
else: # new property
self.define_own_property(
prop, {
'value': val,
'writable': True,
'configurable': True,
'enumerable': True
}, False)
def define_own_property(self, prop, desc, throw):
assert type(desc.get('value')) != int
old_len_desc = self.get_own_property('length')
old_len = old_len_desc['value'] # value is js type so convert to py.
if prop == 'length':
if 'value' not in desc:
return PyJs.define_own_property(self, prop, desc, False)
new_len = to_uint32(desc['value'])
if new_len != to_number(desc['value']):
raise MakeError('RangeError', 'Invalid range!')
new_desc = dict((k, v) for k, v in six.iteritems(desc))
new_desc['value'] = float(new_len)
if new_len >= old_len:
return PyJs.define_own_property(self, prop, new_desc, False)
if not old_len_desc['writable']:
return False
if 'writable' not in new_desc or new_desc['writable'] == True:
new_writable = True
else:
new_writable = False
new_desc['writable'] = True
if not PyJs.define_own_property(self, prop, new_desc, False):
return False
if new_len < old_len:
# not very efficient for sparse arrays, so using different method for sparse:
if old_len > 30 * len(self.own):
for ele in self.own.keys():
if ele.isdigit() and int(ele) >= new_len:
if not self.delete(
ele
): # if failed to delete set len to current len and reject.
new_desc['value'] = old_len + 1.
if not new_writable:
new_desc['writable'] = False
PyJs.define_own_property(
self, prop, new_desc, False)
return False
old_len = new_len
else: # standard method
while new_len < old_len:
old_len -= 1
if not self.delete(
unicode(int(old_len))
): # if failed to delete set len to current len and reject.
new_desc['value'] = old_len + 1.
if not new_writable:
new_desc['writable'] = False
PyJs.define_own_property(self, prop, new_desc,
False)
return False
if not new_writable:
self.own['length']['writable'] = False
return True
elif prop.isdigit():
index = to_uint32(prop)
if index >= old_len and not old_len_desc['writable']:
return False
if not PyJs.define_own_property(self, prop, desc, False):
return False
if index >= old_len:
old_len_desc['value'] = index + 1.
return True
else:
return PyJs.define_own_property(self, prop, desc, False)
def to_list(self):
return [
self.get(str(e)) for e in xrange(self.get('length').to_uint32())
]
# database with compiled patterns. Js pattern -> Py pattern.
REGEXP_DB = {}
class PyJsRegExp(PyJs):
Class = 'RegExp'
def __init__(self, body, flags, prototype=None):
self.prototype = prototype
self.glob = True if 'g' in flags else False
self.ignore_case = re.IGNORECASE if 'i' in flags else 0
self.multiline = re.MULTILINE if 'm' in flags else 0
self.value = body
if (body, flags) in REGEXP_DB:
self.pat = REGEXP_DB[body, flags]
else:
comp = None
try:
# converting JS regexp pattern to Py pattern.
possible_fixes = [(u'[]', u'[\0]'), (u'[^]', u'[^\0]'),
(u'nofix1791', u'nofix1791')]
reg = self.value
for fix, rep in possible_fixes:
comp = PyJsParser()._interpret_regexp(reg, flags)
#print 'reg -> comp', reg, '->', comp
try:
self.pat = re.compile(
comp, self.ignore_case | self.multiline)
#print reg, '->', comp
break
except:
reg = reg.replace(fix, rep)
# print 'Fix', fix, '->', rep, '=', reg
else:
raise Exception()
REGEXP_DB[body, flags] = self.pat
except:
#print 'Invalid pattern...', self.value, comp
raise MakeError(
'SyntaxError',
'Invalid RegExp pattern: %s -> %s' % (repr(self.value),
repr(comp)))
# now set own properties:
self.own = {
'source': {
'value': self.value,
'enumerable': False,
'writable': False,
'configurable': False
},
'global': {
'value': self.glob,
'enumerable': False,
'writable': False,
'configurable': False
},
'ignoreCase': {
'value': bool(self.ignore_case),
'enumerable': False,
'writable': False,
'configurable': False
},
'multiline': {
'value': bool(self.multiline),
'enumerable': False,
'writable': False,
'configurable': False
},
'lastIndex': {
'value': 0.,
'enumerable': False,
'writable': True,
'configurable': False
}
}
def match(self, string, pos):
'''string is of course a py string'''
return self.pat.match(string, int(pos))
class PyJsError(PyJs):
Class = 'Error'
extensible = True
def __init__(self, message=None, prototype=None):
self.prototype = prototype
self.own = {}
if message is not None:
self.put('message', to_string(message))
self.own['message']['enumerable'] = False
class PyJsDate(PyJs):
Class = 'Date'
UTCToLocal = None # todo UTC to local should be imported!
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
self.UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
# Scope class it will hold all the variables accessible to user
class Scope(PyJs):
Class = 'Global'
extensible = True
IS_CHILD_SCOPE = True
THIS_BINDING = None
space = None
exe = None
# todo speed up!
# in order to speed up this very important class the top scope should behave differently than
# child scopes, child scope should not have this property descriptor thing because they cant be changed anyway
# they are all confugurable= False
def __init__(self, scope, space, parent=None):
"""Doc"""
self.space = space
self.prototype = parent
if type(scope) is not dict:
assert parent is not None, 'You initialised the WITH_SCOPE without a parent scope.'
self.own = scope
self.is_with_scope = True
else:
self.is_with_scope = False
if parent is None:
# global, top level scope
self.own = {}
for k, v in six.iteritems(scope):
# set all the global items
self.define_own_property(
k, {
'value': v,
'configurable': False,
'writable': False,
'enumerable': False
}, False)
else:
# not global, less powerful but faster closure.
self.own = scope # simple dictionary which maps name directly to js object.
self.par = super(Scope, self)
self.stack = []
def register(self, var):
# registered keeps only global registered variables
if self.prototype is None:
# define in global scope
if var in self.own:
self.own[var]['configurable'] = False
else:
self.define_own_property(
var, {
'value': undefined,
'configurable': False,
'writable': True,
'enumerable': True
}, False)
elif var not in self.own:
# define in local scope since it has not been defined yet
self.own[var] = undefined # default value
def registers(self, vars):
"""register multiple variables"""
for var in vars:
self.register(var)
def put(self, var, val, throw=False):
if self.prototype is None:
desc = self.own.get(var) # global scope
if desc is None:
self.par.put(var, val, False)
else:
if desc['writable']: # todo consider getters/setters
desc['value'] = val
else:
if self.is_with_scope:
if self.own.has_property(var):
return self.own.put(var, val, throw=throw)
else:
return self.prototype.put(var, val)
# trying to put in local scope
# we dont know yet in which scope we should place this var
elif var in self.own:
self.own[var] = val
return val
else:
# try to put in the lower scope since we cant put in this one (var wasn't registered)
return self.prototype.put(var, val)
def get(self, var, throw=False):
if self.prototype is not None:
if self.is_with_scope:
cand = None if not self.own.has_property(
var) else self.own.get(var)
else:
# fast local scope
cand = self.own.get(var)
if cand is None:
return self.prototype.get(var, throw)
return cand
# slow, global scope
if var not in self.own:
# try in ObjectPrototype...
if var in self.space.ObjectPrototype.own:
return self.space.ObjectPrototype.get(var)
if throw:
raise MakeError('ReferenceError', '%s is not defined' % var)
return undefined
cand = self.own[var].get('value')
return cand if cand is not None else self.own[var]['get'].call(self)
def delete(self, var, throw=False):
if self.prototype is not None:
if self.is_with_scope:
if self.own.has_property(var):
return self.own.delete(var)
elif var in self.own:
return False
return self.prototype.delete(var)
# we are in global scope here. Must exist and be configurable to delete
if var not in self.own:
# this var does not exist, why do you want to delete it???
return True
if self.own[var]['configurable']:
del self.own[var]
return True
# not configurable, cant delete
return False
def get_new_arguments_obj(args, space):
obj = space.NewObject()
obj.Class = 'Arguments'
obj.define_own_property(
'length', {
'value': float(len(args)),
'writable': True,
'enumerable': False,
'configurable': True
}, False)
for i, e in enumerate(args):
obj.put(unicode(i), e)
return obj
#Function
class PyJsFunction(PyJs):
Class = 'Function'
source = '{ [native code] }'
IS_CONSTRUCTOR = True
def __init__(self,
code,
ctx,
params,
name,
space,
is_declaration,
definitions,
prototype=None):
self.prototype = prototype
self.own = {}
self.code = code
if type(
self.code
) == int: # just a label pointing to the beginning of the code.
self.is_native = False
else:
self.is_native = True # python function
self.ctx = ctx
self.params = params
self.arguments_in_params = 'arguments' in params
self.definitions = definitions
# todo remove this check later
for p in params:
assert p in self.definitions
self.name = name
self.space = space
self.is_declaration = is_declaration
#set own property length to the number of arguments
self.own['length'] = {
'value': float(len(params)),
'writable': False,
'enumerable': False,
'configurable': False
}
if name:
self.own['name'] = {
'value': name,
'writable': False,
'enumerable': False,
'configurable': True
}
if not self.is_native: # set prototype for user defined functions
# constructor points to this function
proto = space.NewObject()
proto.own['constructor'] = {
'value': self,
'writable': True,
'enumerable': False,
'configurable': True
}
self.own['prototype'] = {
'value': proto,
'writable': True,
'enumerable': False,
'configurable': False
}
# todo set up throwers on callee and arguments if in strict mode
def call(self, this, args=()):
''' Dont use this method from inside bytecode to call other bytecode. '''
if self.is_native:
_args = SpaceTuple(
args
) # we have to do that unfortunately to pass all the necessary info to the funcs
_args.space = self.space
return self.code(
this, _args
) # must return valid js object - undefined, null, float, unicode, bool, or PyJs
else:
return self.space.exe._call(self, this,
args) # will run inside bytecode
def has_instance(self, other):
# I am not sure here so instanceof may not work lol.
if not is_object(other):
return False
proto = self.get('prototype')
if not is_object(proto):
raise MakeError(
'TypeError',
'Function has non-object prototype in instanceof check')
while True:
other = other.prototype
if not other: # todo make sure that the condition is not None or null
return False
if other is proto:
return True
def create(self, args, space):
proto = self.get('prototype')
if not is_object(proto):
proto = space.ObjectPrototype
new = PyJsObject(prototype=proto)
res = self.call(new, args)
if is_object(res):
return res
return new
def _generate_my_context(self, this, args):
my_ctx = Scope(
dict(izip(self.params, args)), self.space, parent=self.ctx)
my_ctx.registers(self.definitions)
my_ctx.THIS_BINDING = this
if not self.arguments_in_params:
my_ctx.own['arguments'] = get_new_arguments_obj(args, self.space)
if not self.is_declaration and self.name and self.name not in my_ctx.own:
my_ctx.own[
self.
name] = self # this should be immutable binding but come on!
return my_ctx
class SpaceTuple:
def __init__(self, tup):
self.tup = tup
def __len__(self):
return len(self.tup)
def __getitem__(self, item):
return self.tup[item]
def __iter__(self):
return iter(self.tup)

View file

@ -0,0 +1,752 @@
from code import Code
from simplex import MakeError
from opcodes import *
from operations import *
from trans_utils import *
SPECIAL_IDENTIFIERS = {'true', 'false', 'this'}
class ByteCodeGenerator:
def __init__(self, exe):
self.exe = exe
self.declared_continue_labels = {}
self.declared_break_labels = {}
self.implicit_breaks = []
self.implicit_continues = []
self.declared_vars = []
self.function_declaration_tape = []
self.states = []
def record_state(self):
self.states.append(
(self.declared_continue_labels, self.declared_break_labels,
self.implicit_breaks, self.implicit_continues, self.declared_vars,
self.function_declaration_tape))
self.declared_continue_labels, self.declared_break_labels, \
self.implicit_breaks, self.implicit_continues, \
self.declared_vars, self.function_declaration_tape = {}, {}, [], [], [], []
def restore_state(self):
self.declared_continue_labels, self.declared_break_labels, \
self.implicit_breaks, self.implicit_continues, \
self.declared_vars, self.function_declaration_tape = self.states.pop()
def ArrayExpression(self, elements, **kwargs):
for e in elements:
if e is None:
self.emit('LOAD_NONE')
else:
self.emit(e)
self.emit('LOAD_ARRAY', len(elements))
def AssignmentExpression(self, operator, left, right, **kwargs):
operator = operator[:-1]
if left['type'] == 'MemberExpression':
self.emit(left['object'])
if left['computed']:
self.emit(left['property'])
self.emit(right)
if operator:
self.emit('STORE_MEMBER_OP', operator)
else:
self.emit('STORE_MEMBER')
else:
self.emit(right)
if operator:
self.emit('STORE_MEMBER_DOT_OP', left['property']['name'],
operator)
else:
self.emit('STORE_MEMBER_DOT', left['property']['name'])
elif left['type'] == 'Identifier':
if left['name'] in SPECIAL_IDENTIFIERS:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
self.emit(right)
if operator:
self.emit('STORE_OP', left['name'], operator)
else:
self.emit('STORE', left['name'])
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
def BinaryExpression(self, operator, left, right, **kwargs):
self.emit(left)
self.emit(right)
self.emit('BINARY_OP', operator)
def BlockStatement(self, body, **kwargs):
self._emit_statement_list(body)
def BreakStatement(self, label, **kwargs):
if label is None:
self.emit('JUMP', self.implicit_breaks[-1])
else:
label = label.get('name')
if label not in self.declared_break_labels:
raise MakeError('SyntaxError',
'Undefined label \'%s\'' % label)
else:
self.emit('JUMP', self.declared_break_labels[label])
def CallExpression(self, callee, arguments, **kwargs):
if callee['type'] == 'MemberExpression':
self.emit(callee['object'])
if callee['computed']:
self.emit(callee['property'])
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL_METHOD')
else:
self.emit('CALL_METHOD_NO_ARGS')
else:
prop_name = to_key(callee['property'])
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL_METHOD_DOT', prop_name)
else:
self.emit('CALL_METHOD_DOT_NO_ARGS', prop_name)
else:
self.emit(callee)
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL')
else:
self.emit('CALL_NO_ARGS')
def ClassBody(self, body, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ClassDeclaration(self, id, superClass, body, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ClassExpression(self, id, superClass, body, **kwargs):
raise NotImplementedError('Classes not available in ECMA 5.1')
def ConditionalExpression(self, test, consequent, alternate, **kwargs):
alt = self.exe.get_new_label()
end = self.exe.get_new_label()
# ?
self.emit(test)
self.emit('JUMP_IF_FALSE', alt)
# first val
self.emit(consequent)
self.emit('JUMP', end)
# second val
self.emit('LABEL', alt)
self.emit(alternate)
# end of ?: statement
self.emit('LABEL', end)
def ContinueStatement(self, label, **kwargs):
if label is None:
self.emit('JUMP', self.implicit_continues[-1])
else:
label = label.get('name')
if label not in self.declared_continue_labels:
raise MakeError('SyntaxError',
'Undefined label \'%s\'' % label)
else:
self.emit('JUMP', self.declared_continue_labels[label])
def DebuggerStatement(self, **kwargs):
self.EmptyStatement(**kwargs)
def DoWhileStatement(self, body, test, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
initial_do = self.exe.get_new_label()
self.emit('JUMP', initial_do)
self.emit('LABEL', continue_label)
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
self.emit('LABEL', initial_do)
# translate the body, remember to add and afterwards remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def EmptyStatement(self, **kwargs):
# do nothing
pass
def ExpressionStatement(self, expression, **kwargs):
# change the final stack value
# pop the previous value and execute expression
self.emit('POP')
self.emit(expression)
def ForStatement(self, init, test, update, body, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
first_start = self.exe.get_new_label()
if init is not None:
self.emit(init)
if init['type'] != 'VariableDeclaration':
self.emit('POP')
# skip first update and go straight to test
self.emit('JUMP', first_start)
self.emit('LABEL', continue_label)
if update:
self.emit(update)
self.emit('POP')
self.emit('LABEL', first_start)
if test:
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
# translate the body, remember to add and afterwards to remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def ForInStatement(self, left, right, body, **kwargs):
# prepare the needed labels
body_start_label = self.exe.get_new_label()
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
# prepare the name
if left['type'] == 'VariableDeclaration':
if len(left['declarations']) != 1:
raise MakeError(
'SyntaxError',
' Invalid left-hand side in for-in loop: Must have a single binding.'
)
self.emit(left)
name = left['declarations'][0]['id']['name']
elif left['type'] == 'Identifier':
name = left['name']
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in for-loop')
# prepare the iterable
self.emit(right)
# emit ForIn Opcode
self.emit('FOR_IN', name, body_start_label, continue_label,
break_label)
# a special continue position
self.emit('LABEL', continue_label)
self.emit('NOP')
self.emit('LABEL', body_start_label)
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit('LOAD_UNDEFINED')
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('NOP')
self.emit('LABEL', break_label)
self.emit('NOP')
def FunctionDeclaration(self, id, params, defaults, body, **kwargs):
if defaults:
raise NotImplementedError('Defaults not available in ECMA 5.1')
# compile function
self.record_state(
) # cleans translator state and appends it to the stack so that it can be later restored
function_start = self.exe.get_new_label()
function_declarations = self.exe.get_new_label()
declarations_done = self.exe.get_new_label(
) # put jump to this place at the and of function tape!
function_end = self.exe.get_new_label()
# skip the function if encountered externally
self.emit('JUMP', function_end)
self.emit('LABEL', function_start)
# call is made with empty stack so load undefined to fill it
self.emit('LOAD_UNDEFINED')
# declare all functions
self.emit('JUMP', function_declarations)
self.emit('LABEL', declarations_done)
self.function_declaration_tape.append(LABEL(function_declarations))
self.emit(body)
self.ReturnStatement(None)
self.function_declaration_tape.append(JUMP(declarations_done))
self.exe.tape.extend(self.function_declaration_tape)
self.emit('LABEL', function_end)
declared_vars = self.declared_vars
self.restore_state()
# create function object and append to stack
name = id.get('name')
assert name is not None
self.declared_vars.append(name)
self.function_declaration_tape.append(
LOAD_FUNCTION(function_start, tuple(p['name'] for p in params),
name, True, tuple(declared_vars)))
self.function_declaration_tape.append(STORE(name))
self.function_declaration_tape.append(POP())
def FunctionExpression(self, id, params, defaults, body, **kwargs):
if defaults:
raise NotImplementedError('Defaults not available in ECMA 5.1')
# compile function
self.record_state(
) # cleans translator state and appends it to the stack so that it can be later restored
function_start = self.exe.get_new_label()
function_declarations = self.exe.get_new_label()
declarations_done = self.exe.get_new_label(
) # put jump to this place at the and of function tape!
function_end = self.exe.get_new_label()
# skip the function if encountered externally
self.emit('JUMP', function_end)
self.emit('LABEL', function_start)
# call is made with empty stack so load undefined to fill it
self.emit('LOAD_UNDEFINED')
# declare all functions
self.emit('JUMP', function_declarations)
self.emit('LABEL', declarations_done)
self.function_declaration_tape.append(LABEL(function_declarations))
self.emit(body)
self.ReturnStatement(None)
self.function_declaration_tape.append(JUMP(declarations_done))
self.exe.tape.extend(self.function_declaration_tape)
self.emit('LABEL', function_end)
declared_vars = self.declared_vars
self.restore_state()
# create function object and append to stack
name = id.get('name') if id else None
self.emit('LOAD_FUNCTION', function_start,
tuple(p['name'] for p in params), name, False,
tuple(declared_vars))
def Identifier(self, name, **kwargs):
if name == 'true':
self.emit('LOAD_BOOLEAN', 1)
elif name == 'false':
self.emit('LOAD_BOOLEAN', 0)
elif name == 'undefined':
self.emit('LOAD_UNDEFINED')
else:
self.emit('LOAD', name)
def IfStatement(self, test, consequent, alternate, **kwargs):
alt = self.exe.get_new_label()
end = self.exe.get_new_label()
# if
self.emit(test)
self.emit('JUMP_IF_FALSE', alt)
# consequent
self.emit(consequent)
self.emit('JUMP', end)
# alternate
self.emit('LABEL', alt)
if alternate is not None:
self.emit(alternate)
# end of if statement
self.emit('LABEL', end)
def LabeledStatement(self, label, body, **kwargs):
label = label['name']
if body['type'] in ('WhileStatement', 'DoWhileStatement',
'ForStatement', 'ForInStatement'):
# Continue label available... Simply take labels defined by the loop.
# It is important that they request continue label first
self.declared_continue_labels[label] = self.exe._label_count + 1
self.declared_break_labels[label] = self.exe._label_count + 2
self.emit(body)
del self.declared_break_labels[label]
del self.declared_continue_labels[label]
else:
# only break label available
lbl = self.exe.get_new_label()
self.declared_break_labels[label] = lbl
self.emit(body)
self.emit('LABEL', lbl)
del self.declared_break_labels[label]
def Literal(self, value, **kwargs):
if value is None:
self.emit('LOAD_NULL')
elif isinstance(value, bool):
self.emit('LOAD_BOOLEAN', int(value))
elif isinstance(value, basestring):
self.emit('LOAD_STRING', unicode(value))
elif isinstance(value, (float, int, long)):
self.emit('LOAD_NUMBER', float(value))
elif isinstance(value, tuple):
self.emit('LOAD_REGEXP', *value)
else:
raise RuntimeError('Unsupported literal')
def LogicalExpression(self, left, right, operator, **kwargs):
end = self.exe.get_new_label()
if operator == '&&':
# AND
self.emit(left)
self.emit('JUMP_IF_FALSE_WITHOUT_POP', end)
self.emit('POP')
self.emit(right)
self.emit('LABEL', end)
elif operator == '||':
# OR
self.emit(left)
self.emit('JUMP_IF_TRUE_WITHOUT_POP', end)
self.emit('POP')
self.emit(right)
self.emit('LABEL', end)
else:
raise RuntimeError("Unknown logical expression: %s" % operator)
def MemberExpression(self, computed, object, property, **kwargs):
if computed:
self.emit(object)
self.emit(property)
self.emit('LOAD_MEMBER')
else:
self.emit(object)
self.emit('LOAD_MEMBER_DOT', property['name'])
def NewExpression(self, callee, arguments, **kwargs):
self.emit(callee)
if arguments:
n = len(arguments)
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', n)
self.emit('NEW')
else:
self.emit('NEW_NO_ARGS')
def ObjectExpression(self, properties, **kwargs):
data = []
for prop in properties:
self.emit(prop['value'])
if prop['computed']:
raise NotImplementedError(
'ECMA 5.1 does not support computed object properties!')
data.append((to_key(prop['key']), prop['kind'][0]))
self.emit('LOAD_OBJECT', tuple(data))
def Program(self, body, **kwargs):
self.emit('LOAD_UNDEFINED')
self.emit(body)
# add function tape !
self.exe.tape = self.function_declaration_tape + self.exe.tape
def Pyimport(self, imp, **kwargs):
raise NotImplementedError(
'Not available for bytecode interpreter yet, use the Js2Py translator.'
)
def Property(self, kind, key, computed, value, method, shorthand,
**kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def RestElement(self, argument, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ReturnStatement(self, argument, **kwargs):
self.emit('POP') # pop result of expression statements
if argument is None:
self.emit('LOAD_UNDEFINED')
else:
self.emit(argument)
self.emit('RETURN')
def SequenceExpression(self, expressions, **kwargs):
for e in expressions:
self.emit(e)
self.emit('POP')
del self.exe.tape[-1]
def SwitchCase(self, test, consequent, **kwargs):
raise NotImplementedError('Already implemented in SwitchStatement')
def SwitchStatement(self, discriminant, cases, **kwargs):
self.emit(discriminant)
labels = [self.exe.get_new_label() for case in cases]
tests = [case['test'] for case in cases]
consequents = [case['consequent'] for case in cases]
end_of_switch = self.exe.get_new_label()
# translate test cases
for test, label in zip(tests, labels):
if test is not None:
self.emit(test)
self.emit('JUMP_IF_EQ', label)
else:
self.emit('POP')
self.emit('JUMP', label)
# this will be executed if none of the cases worked
self.emit('POP')
self.emit('JUMP', end_of_switch)
# translate consequents
self.implicit_breaks.append(end_of_switch)
for consequent, label in zip(consequents, labels):
self.emit('LABEL', label)
self._emit_statement_list(consequent)
self.implicit_breaks.pop()
self.emit('LABEL', end_of_switch)
def ThisExpression(self, **kwargs):
self.emit('LOAD_THIS')
def ThrowStatement(self, argument, **kwargs):
# throw with the empty stack
self.emit('POP')
self.emit(argument)
self.emit('THROW')
def TryStatement(self, block, handler, finalizer, **kwargs):
try_label = self.exe.get_new_label()
catch_label = self.exe.get_new_label()
finally_label = self.exe.get_new_label()
end_label = self.exe.get_new_label()
self.emit('JUMP', end_label)
# try block
self.emit('LABEL', try_label)
self.emit('LOAD_UNDEFINED')
self.emit(block)
self.emit(
'NOP'
) # needed to distinguish from break/continue vs some internal jumps
# catch block
self.emit('LABEL', catch_label)
self.emit('LOAD_UNDEFINED')
if handler:
self.emit(handler['body'])
self.emit('NOP')
# finally block
self.emit('LABEL', finally_label)
self.emit('LOAD_UNDEFINED')
if finalizer:
self.emit(finalizer)
self.emit('NOP')
self.emit('LABEL', end_label)
# give life to the code
self.emit('TRY_CATCH_FINALLY', try_label, catch_label,
handler['param']['name'] if handler else None, finally_label,
bool(finalizer), end_label)
def UnaryExpression(self, operator, argument, **kwargs):
if operator == 'typeof' and argument[
'type'] == 'Identifier': # todo fix typeof
self.emit('TYPEOF', argument['name'])
elif operator == 'delete':
if argument['type'] == 'MemberExpression':
self.emit(argument['object'])
if argument['property']['type'] == 'Identifier':
self.emit('LOAD_STRING',
unicode(argument['property']['name']))
else:
self.emit(argument['property'])
self.emit('DELETE_MEMBER')
elif argument['type'] == 'Identifier':
self.emit('DELETE', argument['name'])
else:
self.emit('LOAD_BOOLEAN', 1)
elif operator in UNARY_OPERATIONS:
self.emit(argument)
self.emit('UNARY_OP', operator)
else:
raise MakeError('SyntaxError',
'Unknown unary operator %s' % operator)
def UpdateExpression(self, operator, argument, prefix, **kwargs):
incr = int(operator == "++")
post = int(not prefix)
if argument['type'] == 'MemberExpression':
if argument['computed']:
self.emit(argument['object'])
self.emit(argument['property'])
self.emit('POSTFIX_MEMBER', post, incr)
else:
self.emit(argument['object'])
name = to_key(argument['property'])
self.emit('POSTFIX_MEMBER_DOT', post, incr, name)
elif argument['type'] == 'Identifier':
name = to_key(argument)
self.emit('POSTFIX', post, incr, name)
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
def VariableDeclaration(self, declarations, kind, **kwargs):
if kind != 'var':
raise NotImplementedError(
'Only var variable declaration is supported by ECMA 5.1')
for d in declarations:
self.emit(d)
def LexicalDeclaration(self, declarations, kind, **kwargs):
raise NotImplementedError('Not supported by ECMA 5.1')
def VariableDeclarator(self, id, init, **kwargs):
name = id['name']
if name in SPECIAL_IDENTIFIERS:
raise MakeError('Invalid left-hand side in assignment')
self.declared_vars.append(name)
if init is not None:
self.emit(init)
self.emit('STORE', name)
self.emit('POP')
def WhileStatement(self, test, body, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
self.emit('LABEL', continue_label)
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
# translate the body, remember to add and afterwards remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def WithStatement(self, object, body, **kwargs):
beg_label = self.exe.get_new_label()
end_label = self.exe.get_new_label()
# scope
self.emit(object)
# now the body
self.emit('JUMP', end_label)
self.emit('LABEL', beg_label)
self.emit('LOAD_UNDEFINED')
self.emit(body)
self.emit('NOP')
self.emit('LABEL', end_label)
# with statement implementation
self.emit('WITH', beg_label, end_label)
def _emit_statement_list(self, statements):
for statement in statements:
self.emit(statement)
def emit(self, what, *args):
''' what can be either name of the op, or node, or a list of statements.'''
if isinstance(what, basestring):
return self.exe.emit(what, *args)
elif isinstance(what, list):
self._emit_statement_list(what)
else:
return getattr(self, what['type'])(**what)
import os, codecs
def path_as_local(path):
if os.path.isabs(path):
return path
# relative to cwd
return os.path.join(os.getcwd(), path)
def get_file_contents(path_or_file):
if hasattr(path_or_file, 'read'):
js = path_or_file.read()
else:
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f:
js = f.read()
return js
def main():
from space import Space
import fill_space
from pyjsparser import parse
import json
a = ByteCodeGenerator(Code())
s = Space()
fill_space.fill_space(s, a)
a.exe.space = s
s.exe = a.exe
con = get_file_contents('internals/esprima.js')
d = parse(con + (
''';JSON.stringify(exports.parse(%s), 4, 4)''' % json.dumps(con)))
# d = parse('''
# function x(n) {
# log(n)
# return x(n+1)
# }
# x(0)
# ''')
# var v = 333333;
# while (v) {
# v--
#
# }
a.emit(d)
print a.declared_vars
print a.exe.tape
print len(a.exe.tape)
a.exe.compile()
def log(this, args):
print args[0]
return 999
print a.exe.run(a.exe.space.GlobalObj)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,197 @@
from opcodes import *
from space import *
from base import *
class Code:
'''Can generate, store and run sequence of ops representing js code'''
def __init__(self, is_strict=False):
self.tape = []
self.compiled = False
self.label_locs = None
self.is_strict = is_strict
self.contexts = []
self.current_ctx = None
self.return_locs = []
self._label_count = 0
self.label_locs = None
# useful references
self.GLOBAL_THIS = None
self.space = None
def get_new_label(self):
self._label_count += 1
return self._label_count
def emit(self, op_code, *args):
''' Adds op_code with specified args to tape '''
self.tape.append(OP_CODES[op_code](*args))
def compile(self, start_loc=0):
''' Records locations of labels and compiles the code '''
self.label_locs = {} if self.label_locs is None else self.label_locs
loc = start_loc
while loc < len(self.tape):
if type(self.tape[loc]) == LABEL:
self.label_locs[self.tape[loc].num] = loc
del self.tape[loc]
continue
loc += 1
self.compiled = True
def _call(self, func, this, args):
''' Calls a bytecode function func
NOTE: use !ONLY! when calling functions from native methods! '''
assert not func.is_native
# fake call - the the runner to return to the end of the file
old_contexts = self.contexts
old_return_locs = self.return_locs
old_curr_ctx = self.current_ctx
self.contexts = [FakeCtx()]
self.return_locs = [len(self.tape)] # target line after return
# prepare my ctx
my_ctx = func._generate_my_context(this, args)
self.current_ctx = my_ctx
# execute dunction
ret = self.run(my_ctx, starting_loc=self.label_locs[func.code])
# bring back old execution
self.current_ctx = old_curr_ctx
self.contexts = old_contexts
self.return_locs = old_return_locs
return ret
def execute_fragment_under_context(self, ctx, start_label, end_label):
''' just like run but returns if moved outside of the specified fragment
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, return_value/jump_loc/py_error)
# ctx.stack must be len 1 and its always empty after the call.
'''
old_curr_ctx = self.current_ctx
try:
self.current_ctx = ctx
return self._execute_fragment_under_context(
ctx, start_label, end_label)
except JsException as err:
# undo the things that were put on the stack (if any)
# don't worry, I know the recovery is possible through try statement and for this reason try statement
# has its own context and stack so it will not delete the contents of the outer stack
del ctx.stack[:]
return undefined, 3, err
finally:
self.current_ctx = old_curr_ctx
def _execute_fragment_under_context(self, ctx, start_label, end_label):
start, end = self.label_locs[start_label], self.label_locs[end_label]
initial_len = len(ctx.stack)
loc = start
entry_level = len(self.contexts)
# for e in self.tape[start:end]:
# print e
while loc < len(self.tape):
#print loc, self.tape[loc]
if len(self.contexts) == entry_level and loc >= end:
assert loc == end
assert len(ctx.stack) == (
1 + initial_len), 'Stack change must be equal to +1!'
return ctx.stack.pop(), 0, None # means normal return
# execute instruction
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
if len(self.contexts) == entry_level:
# check if jumped outside of the fragment and break if so
if not start <= loc < end:
assert len(ctx.stack) == (
1 + initial_len
), 'Stack change must be equal to +1!'
return ctx.stack.pop(), 2, status # jump outside
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
if len(self.contexts) == entry_level:
assert len(ctx.stack) == 1 + initial_len
return undefined, 1, ctx.stack.pop(
) # return signal
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
assert False, 'Remember to add NOP at the end!'
def run(self, ctx, starting_loc=0):
loc = starting_loc
self.current_ctx = ctx
while loc < len(self.tape):
# execute instruction
#print loc, self.tape[loc]
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
assert len(ctx.stack) == 1, ctx.stack
return ctx.stack.pop()
class FakeCtx(object):
def __init__(self):
self.stack = []

View file

@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'

View file

@ -0,0 +1,28 @@
from ..conversions import *
from ..func_utils import *
def Array(this, args):
return ArrayConstructor(args, args.space)
def ArrayConstructor(args, space):
if len(args) == 1:
l = get_arg(args, 0)
if type(l) == float:
if to_uint32(l) == l:
return space.NewArray(l)
else:
raise MakeError(
'RangeError',
'Invalid length specified for Array constructor (must be uint32)'
)
else:
return space.ConstructArray([l])
else:
return space.ConstructArray(list(args))
def isArray(this, args):
x = get_arg(args, 0)
return is_object(x) and x.Class == u'Array'

View file

@ -0,0 +1,14 @@
from ..conversions import *
from ..func_utils import *
def Boolean(this, args):
return to_boolean(get_arg(args, 0))
def BooleanConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.BooleanPrototype
temp.Class = 'Boolean'
temp.value = to_boolean(get_arg(args, 0))
return temp

View file

@ -0,0 +1,11 @@
from __future__ import unicode_literals
from js2py.internals.conversions import *
from js2py.internals.func_utils import *
class ConsoleMethods:
def log(this, args):
x = ' '.join(to_string(e) for e in args)
print(x)
return undefined

View file

@ -0,0 +1,405 @@
from ..base import *
from .time_helpers import *
TZ_OFFSET = (time.altzone // 3600)
ABS_OFFSET = abs(TZ_OFFSET)
TZ_NAME = time.tzname[1]
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
@Js
def Date(year, month, date, hours, minutes, seconds, ms):
return now().to_string()
Date.Class = 'Date'
def now():
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype)
@Js
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
args = arguments
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js
def parse(string):
return PyJsDate(
TimeClip(parse_date(string.to_string().value)),
prototype=DatePrototype)
Date.define_own_property('now', {
'value': Js(now),
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('parse', {
'value': parse,
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('UTC', {
'value': UTC,
'enumerable': False,
'writable': True,
'configurable': True
})
class PyJsDate(PyJs):
Class = 'Date'
extensible = True
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def parse_date(py_string): # todo support all date string formats
try:
try:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
except:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
return MakeDate(
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
MakeTime(
Js(dt.hour), Js(dt.minute), Js(dt.second),
Js(dt.microsecond // 1000)))
except:
raise MakeError(
'TypeError',
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
% py_string)
def date_constructor(*args):
if len(args) >= 2:
return date_constructor2(*args)
elif len(args) == 1:
return date_constructor1(args[0])
else:
return date_constructor0()
def date_constructor0():
return now()
def date_constructor1(value):
v = value.to_primitive()
if v._type() == 'String':
v = parse_date(v.value)
else:
v = v.to_int()
return PyJsDate(TimeClip(v), prototype=DatePrototype)
def date_constructor2(*args):
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
return PyJsDate(t, prototype=DatePrototype)
Date.create = date_constructor
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
def check_date(obj):
if obj.Class != 'Date':
raise MakeError('TypeError', 'this is not a Date object')
class DateProto:
def toString():
check_date(this)
if this.value is NaN:
return 'Invalid Date'
offset = (UTCToLocal(this.value) - this.value) // msPerHour
return this.local_strftime(
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
offset, 2, True), GetTimeZoneName(this.value))
def toDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def toLocaleString():
check_date(this)
return this.local_strftime('%d %B %Y %H:%M:%S')
def toLocaleDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toLocaleTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def valueOf():
check_date(this)
return this.value
def getTime():
check_date(this)
return this.value
def getFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(UTCToLocal(this.value))
def getUTCFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(this.value)
def getMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(UTCToLocal(this.value))
def getDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(UTCToLocal(this.value))
def getUTCMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(this.value)
def getUTCDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(this.value)
def getDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(UTCToLocal(this.value))
def getUTCDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(this.value)
def getHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(UTCToLocal(this.value))
def getUTCHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(this.value)
def getMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(UTCToLocal(this.value))
def getUTCMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(this.value)
def getSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(UTCToLocal(this.value))
def getUTCSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(this.value)
def getMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(UTCToLocal(this.value))
def getUTCMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(this.value)
def getTimezoneOffset():
check_date(this)
if this.value is NaN:
return NaN
return (this.value - UTCToLocal(this.value)) // 60000
def setTime(time):
check_date(this)
this.value = TimeClip(time.to_number().to_int())
return this.value
def setMilliseconds(ms):
check_date(this)
t = UTCToLocal(this.value)
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u
return u
def setUTCMilliseconds(ms):
check_date(this)
t = this.value
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(MakeDate(Day(t), tim))
this.value = u
return u
# todo Complete all setters!
def toUTCString():
check_date(this)
return this.utc_strftime('%d %B %Y %H:%M:%S')
def toISOString():
check_date(this)
t = this.value
year = YearFromTime(t)
month, day, hour, minute, second, milli = pad(
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
HourFromTime(t)), pad(MinFromTime(t)), pad(
SecFromTime(t)), pad(msFromTime(t))
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
year, 6, True), month, day, hour, minute, second, milli)
def toJSON(key):
o = this.to_object()
tv = o.to_primitive('Number')
if tv.Class == 'Number' and not tv.is_finite():
return this.null
toISO = o.get('toISOString')
if not toISO.is_callable():
raise this.MakeError('TypeError', 'toISOString is not callable')
return toISO.call(o, ())
def pad(num, n=2, sign=False):
'''returns n digit string representation of the num'''
s = unicode(abs(num))
if len(s) < n:
s = '0' * (n - len(s)) + s
if not sign:
return s
if num >= 0:
return '+' + s
else:
return '-' + s
fill_prototype(DatePrototype, DateProto, default_attrs)
Date.define_own_property(
'prototype', {
'value': DatePrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
DatePrototype.define_own_property('constructor', {
'value': Date,
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,75 @@
from ..base import *
from ..conversions import *
from ..func_utils import *
from pyjsparser import parse
from ..byte_trans import ByteCodeGenerator, Code
def Function(this, args):
# convert arguments to python list of strings
a = map(to_string, tuple(args))
_body = u';'
_args = ()
if len(a):
_body = u'%s;' % a[-1]
_args = a[:-1]
return executable_function(_body, _args, args.space, global_context=True)
def executable_function(_body, _args, space, global_context=True):
func_str = u'(function (%s) { ; %s ; });' % (u', '.join(_args), _body)
co = executable_code(
code_str=func_str, space=space, global_context=global_context)
return co()
# you can use this one lovely piece of function to compile and execute code on the fly! Watch out though as it may generate lots of code.
# todo tape cleanup? we dont know which pieces are needed and which are not so rather impossible without smarter machinery something like GC,
# a one solution would be to have a separate tape for functions
def executable_code(code_str, space, global_context=True):
# parse first to check if any SyntaxErrors
parsed = parse(code_str)
old_tape_len = len(space.byte_generator.exe.tape)
space.byte_generator.record_state()
start = space.byte_generator.exe.get_new_label()
skip = space.byte_generator.exe.get_new_label()
space.byte_generator.emit('JUMP', skip)
space.byte_generator.emit('LABEL', start)
space.byte_generator.emit(parsed)
space.byte_generator.emit('NOP')
space.byte_generator.emit('LABEL', skip)
space.byte_generator.emit('NOP')
space.byte_generator.restore_state()
space.byte_generator.exe.compile(
start_loc=old_tape_len
) # dont read the code from the beginning, dont be stupid!
ctx = space.GlobalObj if global_context else space.exe.current_ctx
def ex_code():
ret, status, token = space.byte_generator.exe.execute_fragment_under_context(
ctx, start, skip)
# todo Clean up the tape!
# this is NOT a way to do that because the fragment may contain the executable code! We dont want to remove it
#del space.byte_generator.exe.tape[old_tape_len:]
if status == 0:
return ret
elif status == 3:
raise token
else:
raise RuntimeError(
'Unexpected return status during JIT execution: %d' % status)
return ex_code
def _eval(this, args):
code_str = to_string(get_arg(args, 0))
return executable_code(code_str, args.space, global_context=True)()
def log(this, args):
print ' '.join(map(to_string, args))
return undefined

View file

@ -0,0 +1,157 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
import math
import random
CONSTANTS = {
'E': 2.7182818284590452354,
'LN10': 2.302585092994046,
'LN2': 0.6931471805599453,
'LOG2E': 1.4426950408889634,
'LOG10E': 0.4342944819032518,
'PI': 3.1415926535897932,
'SQRT1_2': 0.7071067811865476,
'SQRT2': 1.4142135623730951
}
class MathFunctions:
def abs(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return abs(a)
def acos(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.acos(a)
except:
return NaN
def asin(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.asin(a)
except:
return NaN
def atan(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return math.atan(a)
def atan2(this, args):
x = get_arg(args, 0)
y = get_arg(args, 1)
a = to_number(x)
b = to_number(y)
if a != a or b != b: # it must be a nan
return NaN
return math.atan2(a, b)
def ceil(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(math.ceil(a))
def floor(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(math.floor(a))
def round(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(round(a))
def sin(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.sin(a)
def cos(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.cos(a)
def tan(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.tan(a)
def log(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.log(a)
except:
return NaN
def exp(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return math.exp(a)
def pow(this, args):
x = get_arg(args, 0)
y = get_arg(args, 1)
a = to_number(x)
b = to_number(y)
if a != a or b != b: # it must be a nan
return NaN
try:
return a**b
except:
return NaN
def sqrt(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return a**0.5
except:
return NaN
def min(this, args):
if len(args) == 0:
return Infinity
return min(map(to_number, tuple(args)))
def max(this, args):
if len(args) == 0:
return -Infinity
return max(map(to_number, tuple(args)))
def random(this, args):
return random.random()

View file

@ -0,0 +1,27 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
def Number(this, args):
if len(args) == 0:
return 0.
return to_number(args[0])
def NumberConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.NumberPrototype
temp.Class = 'Number'
temp.value = float(to_number(get_arg(args, 0)) if len(args) > 0 else 0.)
return temp
CONSTS = {
'MAX_VALUE': 1.7976931348623157e308,
'MIN_VALUE': 5.0e-324,
'NaN': NaN,
'NEGATIVE_INFINITY': Infinity,
'POSITIVE_INFINITY': -Infinity
}

View file

@ -0,0 +1,204 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..base import is_data_descriptor
import six
def Object(this, args):
val = get_arg(args, 0)
if is_null(val) or is_undefined(val):
return args.space.NewObject()
return to_object(val, args.space)
def ObjectCreate(args, space):
if len(args):
val = get_arg(args, 0)
if is_object(val):
# Implementation dependent, but my will simply return :)
return val
elif type(val) in (NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE):
return to_object(val, space)
return space.NewObject()
class ObjectMethods:
def getPrototypeOf(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.getPrototypeOf called on non-object')
return null if obj.prototype is None else obj.prototype
def getOwnPropertyDescriptor(this, args):
obj = get_arg(args, 0)
prop = get_arg(args, 1)
if not is_object(obj):
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
desc = obj.own.get(to_string(prop))
return convert_to_js_type(desc, args.space)
def getOwnPropertyNames(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return args.space.ConstructArray(obj.own.keys())
def create(this, args):
obj = get_arg(args, 0)
if not (is_object(obj) or is_null(obj)):
raise MakeError('TypeError',
'Object prototype may only be an Object or null')
temp = args.space.NewObject()
temp.prototype = None if is_null(obj) else obj
if len(args) > 1 and not is_undefined(args[1]):
if six.PY2:
args.tup = (args[1], )
ObjectMethods.defineProperties.__func__(temp, args)
else:
args.tup = (args[1], )
ObjectMethods.defineProperties(temp, args)
return temp
def defineProperty(this, args):
obj = get_arg(args, 0)
prop = get_arg(args, 1)
attrs = get_arg(args, 2)
if not is_object(obj):
raise MakeError('TypeError',
'Object.defineProperty called on non-object')
name = to_string(prop)
if not obj.define_own_property(name, ToPropertyDescriptor(attrs),
False):
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
return obj
def defineProperties(this, args):
obj = get_arg(args, 0)
properties = get_arg(args, 1)
if not is_object(obj):
raise MakeError('TypeError',
'Object.defineProperties called on non-object')
props = to_object(properties, args.space)
for k, v in props.own.items():
if not v.get('enumerable'):
continue
desc = ToPropertyDescriptor(props.get(unicode(k)))
if not obj.define_own_property(unicode(k), desc, False):
raise MakeError('TypeError',
'Failed to define own property: %s' % k)
return obj
def seal(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.seal called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
obj.extensible = False
return obj
def freeze(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.freeze called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
if is_data_descriptor(desc):
desc['writable'] = False
obj.extensible = False
return obj
def preventExtensions(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.preventExtensions on non-object')
obj.extensible = False
return obj
def isSealed(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isSealed called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc.get('configurable'):
return False
return True
def isFrozen(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isFrozen called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc.get('configurable'):
return False
if is_data_descriptor(desc) and desc.get('writable'):
return False
return True
def isExtensible(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isExtensible called on non-object')
return obj.extensible
def keys(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.keys called on non-object')
return args.space.ConstructArray([
unicode(e) for e, d in six.iteritems(obj.own)
if d.get('enumerable')
])
# some utility functions:
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
if not is_object(obj):
raise MakeError('TypeError',
'Can\'t convert non-object to property descriptor')
desc = {}
if obj.has_property('enumerable'):
desc['enumerable'] = to_boolean(obj.get('enumerable'))
if obj.has_property('configurable'):
desc['configurable'] = to_boolean(obj.get('configurable'))
if obj.has_property('value'):
desc['value'] = obj.get('value')
if obj.has_property('writable'):
desc['writable'] = to_boolean(obj.get('writable'))
if obj.has_property('get'):
cand = obj.get('get')
if not (is_undefined(cand) or is_callable(cand)):
raise MakeError(
'TypeError',
'Invalid getter (it has to be a function or undefined)')
desc['get'] = cand
if obj.has_property('set'):
cand = obj.get('set')
if not (is_undefined(cand) or is_callable(cand)):
raise MakeError(
'TypeError',
'Invalid setter (it has to be a function or undefined)')
desc['set'] = cand
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise MakeError(
'TypeError',
'Invalid property. A property cannot both have accessors and be writable or have a value.'
)
return desc

View file

@ -0,0 +1,41 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..base import SpaceTuple
REG_EXP_FLAGS = ('g', 'i', 'm')
def RegExp(this, args):
pattern = get_arg(args, 0)
flags = get_arg(args, 1)
if GetClass(pattern) == 'RegExp':
if not is_undefined(flags):
raise MakeError(
'TypeError',
'Cannot supply flags when constructing one RegExp from another'
)
# return unchanged
return pattern
#pattern is not a regexp
if is_undefined(pattern):
pattern = u''
else:
pattern = to_string(pattern)
flags = to_string(flags) if not is_undefined(flags) else u''
for flag in flags:
if flag not in REG_EXP_FLAGS:
raise MakeError(
'SyntaxError',
'Invalid flags supplied to RegExp constructor "%s"' % flag)
if len(set(flags)) != len(flags):
raise MakeError(
'SyntaxError',
'Invalid flags supplied to RegExp constructor "%s"' % flags)
return args.space.NewRegExp(pattern, flags)
def RegExpCreate(args, space):
_args = SpaceTuple(args)
_args.space = space
return RegExp(undefined, _args)

View file

@ -0,0 +1,23 @@
from ..conversions import *
from ..func_utils import *
def fromCharCode(this, args):
res = u''
for e in args:
res += unichr(to_uint16(e))
return res
def String(this, args):
if len(args) == 0:
return u''
return to_string(args[0])
def StringConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.StringPrototype
temp.Class = 'String'
temp.value = to_string(get_arg(args, 0)) if len(args) > 0 else u''
return temp

View file

@ -0,0 +1,209 @@
from __future__ import unicode_literals
# NOTE: t must be INT!!!
import time
import datetime
import warnings
try:
from tzlocal import get_localzone
LOCAL_ZONE = get_localzone()
except: # except all problems...
warnings.warn(
'Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time'
)
class LOCAL_ZONE:
@staticmethod
def dst(*args):
return 1
from js2py.base import MakeError
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
msPerDay = 86400000
msPerYear = int(86400000 * 365.242)
msPerSecond = 1000
msPerMinute = 60000
msPerHour = 3600000
HoursPerDay = 24
MinutesPerHour = 60
SecondsPerMinute = 60
NaN = float('nan')
LocalTZA = -time.timezone * msPerSecond
def DaylightSavingTA(t):
if t is NaN:
return t
try:
return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
t // 1000)).seconds) * 1000
except:
warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...',
Warning)
return 1
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions')
def GetTimeZoneName(t):
return time.tzname[DaylightSavingTA(t) > 0]
def LocalToUTC(t):
return t - LocalTZA - DaylightSavingTA(t - LocalTZA)
def UTCToLocal(t):
return t + LocalTZA + DaylightSavingTA(t)
def Day(t):
return t // 86400000
def TimeWithinDay(t):
return t % 86400000
def DaysInYear(y):
if y % 4:
return 365
elif y % 100:
return 366
elif y % 400:
return 365
else:
return 366
def DayFromYear(y):
return 365 * (y - 1970) + (y - 1969) // 4 - (y - 1901) // 100 + (
y - 1601) // 400
def TimeFromYear(y):
return 86400000 * DayFromYear(y)
def YearFromTime(t):
guess = 1970 - t // 31556908800 # msPerYear
gt = TimeFromYear(guess)
if gt <= t:
while gt <= t:
guess += 1
gt = TimeFromYear(guess)
return guess - 1
else:
while gt > t:
guess -= 1
gt = TimeFromYear(guess)
return guess
def DayWithinYear(t):
return Day(t) - DayFromYear(YearFromTime(t))
def InLeapYear(t):
y = YearFromTime(t)
if y % 4:
return 0
elif y % 100:
return 1
elif y % 400:
return 0
else:
return 1
def MonthFromTime(t):
day = DayWithinYear(t)
leap = InLeapYear(t)
if day < 31:
return 0
day -= leap
if day < 59:
return 1
elif day < 90:
return 2
elif day < 120:
return 3
elif day < 151:
return 4
elif day < 181:
return 5
elif day < 212:
return 6
elif day < 243:
return 7
elif day < 273:
return 8
elif day < 304:
return 9
elif day < 334:
return 10
else:
return 11
def DateFromTime(t):
mon = MonthFromTime(t)
day = DayWithinYear(t)
return day - CUM[mon] - (1 if InLeapYear(t) and mon >= 2 else 0) + 1
def WeekDay(t):
# 0 == sunday
return (Day(t) + 4) % 7
def msFromTime(t):
return t % 1000
def SecFromTime(t):
return (t // 1000) % 60
def MinFromTime(t):
return (t // 60000) % 60
def HourFromTime(t):
return (t // 3600000) % 24
def MakeTime(hour, Min, sec, ms):
# takes PyJs objects and returns t
if not (hour.is_finite() and Min.is_finite() and sec.is_finite()
and ms.is_finite()):
return NaN
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int()
return h * 3600000 + m * 60000 + s * 1000 + milli
def MakeDay(year, month, date):
# takes PyJs objects and returns t
if not (year.is_finite() and month.is_finite() and date.is_finite()):
return NaN
y, m, dt = year.to_int(), month.to_int(), date.to_int()
y += m // 12
mn = m % 12
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y) == 366
and mn >= 2 else 0)
return d # ms per day
def MakeDate(day, time):
return 86400000 * day + time
def TimeClip(t):
if t != t or abs(t) == float('inf'):
return NaN
if abs(t) > 8.64 * 10**15:
return NaN
return int(t)

View file

@ -0,0 +1,148 @@
from __future__ import unicode_literals
# Type Conversions. to_type. All must return PyJs subclass instance
from simplex import *
def to_primitive(self, hint=None):
if is_primitive(self):
return self
if hint is None and (self.Class == 'Number' or self.Class == 'Boolean'):
# favour number for Class== Number or Boolean default = String
hint = 'Number'
return self.default_value(hint)
def to_boolean(self):
typ = Type(self)
if typ == 'Boolean': # no need to convert
return self
elif typ == 'Null' or typ == 'Undefined': # they are both always false
return False
elif typ == 'Number': # false only for 0, and NaN
return self and self == self # test for nan (nan -> flase)
elif typ == 'String':
return bool(self)
else: # object - always True
return True
def to_number(self):
typ = Type(self)
if typ == 'Number': # or self.Class=='Number': # no need to convert
return self
elif typ == 'Null': # null is 0
return 0.
elif typ == 'Undefined': # undefined is NaN
return NaN
elif typ == 'Boolean': # 1 for True 0 for false
return float(self)
elif typ == 'String':
s = self.strip() # Strip white space
if not s: # '' is simply 0
return 0.
if 'x' in s or 'X' in s[:3]: # hex (positive only)
try: # try to convert
num = int(s, 16)
except ValueError: # could not convert -> NaN
return NaN
return float(num)
sign = 1 # get sign
if s[0] in '+-':
if s[0] == '-':
sign = -1
s = s[1:]
if s == 'Infinity': # Check for infinity keyword. 'NaN' will be NaN anyway.
return sign * Infinity
try: # decimal try
num = sign * float(s) # Converted
except ValueError:
return NaN # could not convert to decimal > return NaN
return float(num)
else: # object - most likely it will be NaN.
return to_number(to_primitive(self, 'Number'))
def to_string(self):
typ = Type(self)
if typ == 'String':
return self
elif typ == 'Null':
return 'null'
elif typ == 'Undefined':
return 'undefined'
elif typ == 'Boolean':
return 'true' if self else 'false'
elif typ == 'Number': # or self.Class=='Number':
if is_nan(self):
return 'NaN'
elif is_infinity(self):
sign = '-' if self < 0 else ''
return sign + 'Infinity'
elif int(self) == self: # integer value!
return unicode(int(self))
return unicode(self) # todo make it print exactly like node.js
else: # object
return to_string(to_primitive(self, 'String'))
def to_object(self, space):
typ = Type(self)
if typ == 'Object':
return self
elif typ == 'Boolean': # Unsure ... todo check here
return space.Boolean.create((self, ), space)
elif typ == 'Number': # ?
return space.Number.create((self, ), space)
elif typ == 'String': # ?
return space.String.create((self, ), space)
elif typ == 'Null' or typ == 'Undefined':
raise MakeError('TypeError',
'undefined or null can\'t be converted to object')
else:
raise RuntimeError()
def to_int32(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
int32 = int(num) % 2**32
return int(int32 - 2**32 if int32 >= 2**31 else int32)
def to_int(self):
num = to_number(self)
if is_nan(num):
return 0
elif is_infinity(num):
return 10**20 if num > 0 else -10**20
return int(num)
def to_uint32(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
return int(num) % 2**32
def to_uint16(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
return int(num) % 2**16
def to_int16(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
int16 = int(num) % 2**16
return int(int16 - 2**16 if int16 >= 2**15 else int16)
def cok(self):
"""Check object coercible"""
if type(self) in (UNDEFINED_TYPE, NULL_TYPE):
raise MakeError('TypeError',
'undefined or null can\'t be converted to object')

View file

@ -0,0 +1,90 @@
# todo make sure what they mean by desc undefined? None or empty? Answer: None :) it can never be empty but None is sometimes returned.
# I am implementing everything as dicts to speed up property creation
# Warning: value, get, set props of dest are PyJs types. Rest is Py!
def is_data_descriptor(desc):
return desc and ('value' in desc or 'writable' in desc)
def is_accessor_descriptor(desc):
return desc and ('get' in desc or 'set' in desc)
def is_generic_descriptor(
desc
): # generic means not the data and not the setter - therefore it must be one that changes only enum and conf
return desc and not (is_data_descriptor(desc)
or is_accessor_descriptor(desc))
def from_property_descriptor(desc, space):
if not desc:
return {}
obj = space.NewObject()
if is_data_descriptor(desc):
obj.define_own_property(
'value', {
'value': desc['value'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'writable', {
'value': desc['writable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
else:
obj.define_own_property(
'get', {
'value': desc['get'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'set', {
'value': desc['set'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'writable', {
'value': desc['writable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'enumerable', {
'value': desc['enumerable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
return obj
def to_property_descriptor(obj):
if obj._type() != 'Object':
raise TypeError()
desc = {}
for e in ('enumerable', 'configurable', 'writable'):
if obj.has_property(e):
desc[e] = obj.get(e).to_boolean().value
if obj.has_property('value'):
desc['value'] = obj.get('value')
for e in ('get', 'set'):
if obj.has_property(e):
cand = obj.get(e)
if not (cand.is_callable() or cand.is_undefined()):
raise TypeError()
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise TypeError()

View file

@ -0,0 +1,284 @@
from __future__ import unicode_literals
from base import Scope
from func_utils import *
from conversions import *
import six
from prototypes.jsboolean import BooleanPrototype
from prototypes.jserror import ErrorPrototype
from prototypes.jsfunction import FunctionPrototype
from prototypes.jsnumber import NumberPrototype
from prototypes.jsobject import ObjectPrototype
from prototypes.jsregexp import RegExpPrototype
from prototypes.jsstring import StringPrototype
from prototypes.jsarray import ArrayPrototype
import prototypes.jsjson as jsjson
import prototypes.jsutils as jsutils
from constructors import jsnumber
from constructors import jsstring
from constructors import jsarray
from constructors import jsboolean
from constructors import jsregexp
from constructors import jsmath
from constructors import jsobject
from constructors import jsfunction
from constructors import jsconsole
def fill_proto(proto, proto_class, space):
for i in dir(proto_class):
e = getattr(proto_class, i)
if six.PY2:
if hasattr(e, '__func__'):
meth = e.__func__
else:
continue
else:
if hasattr(e, '__call__') and not i.startswith('__'):
meth = e
else:
continue
meth_name = meth.__name__.strip('_') # RexExp._exec -> RegExp.exec
js_meth = space.NewFunction(meth, space.ctx, (), meth_name, False, ())
set_non_enumerable(proto, meth_name, js_meth)
return proto
def easy_func(f, space):
return space.NewFunction(f, space.ctx, (), f.__name__, False, ())
def Empty(this, args):
return undefined
def set_non_enumerable(obj, name, prop):
obj.define_own_property(
unicode(name), {
'value': prop,
'writable': True,
'enumerable': False,
'configurable': True
}, True)
def set_protected(obj, name, prop):
obj.define_own_property(
unicode(name), {
'value': prop,
'writable': False,
'enumerable': False,
'configurable': False
}, True)
def fill_space(space, byte_generator):
# set global scope
global_scope = Scope({}, space, parent=None)
global_scope.THIS_BINDING = global_scope
global_scope.registers(byte_generator.declared_vars)
space.GlobalObj = global_scope
space.byte_generator = byte_generator
# first init all protos, later take care of constructors and details
# Function must be first obviously, we have to use a small trick to do that...
function_proto = space.NewFunction(Empty, space.ctx, (), 'Empty', False,
())
space.FunctionPrototype = function_proto # this will fill the prototypes of the methods!
fill_proto(function_proto, FunctionPrototype, space)
# Object next
object_proto = space.NewObject() # no proto
fill_proto(object_proto, ObjectPrototype, space)
space.ObjectPrototype = object_proto
function_proto.prototype = object_proto
# Number
number_proto = space.NewObject()
number_proto.prototype = object_proto
fill_proto(number_proto, NumberPrototype, space)
number_proto.value = 0.
number_proto.Class = 'Number'
space.NumberPrototype = number_proto
# String
string_proto = space.NewObject()
string_proto.prototype = object_proto
fill_proto(string_proto, StringPrototype, space)
string_proto.value = u''
string_proto.Class = 'String'
space.StringPrototype = string_proto
# Boolean
boolean_proto = space.NewObject()
boolean_proto.prototype = object_proto
fill_proto(boolean_proto, BooleanPrototype, space)
boolean_proto.value = False
boolean_proto.Class = 'Boolean'
space.BooleanPrototype = boolean_proto
# Array
array_proto = space.NewArray(0)
array_proto.prototype = object_proto
fill_proto(array_proto, ArrayPrototype, space)
space.ArrayPrototype = array_proto
# JSON
json = space.NewObject()
json.put(u'stringify', easy_func(jsjson.stringify, space))
json.put(u'parse', easy_func(jsjson.parse, space))
# Utils
parseFloat = easy_func(jsutils.parseFloat, space)
parseInt = easy_func(jsutils.parseInt, space)
isNaN = easy_func(jsutils.isNaN, space)
isFinite = easy_func(jsutils.isFinite, space)
# Error
error_proto = space.NewError(u'Error', u'')
error_proto.prototype = object_proto
error_proto.put(u'name', u'Error')
fill_proto(error_proto, ErrorPrototype, space)
space.ErrorPrototype = error_proto
def construct_constructor(typ):
def creator(this, args):
message = get_arg(args, 0)
if not is_undefined(message):
msg = to_string(message)
else:
msg = u''
return space.NewError(typ, msg)
j = easy_func(creator, space)
j.name = unicode(typ)
j.prototype = space.ERROR_TYPES[typ]
def new_create(args, space):
message = get_arg(args, 0)
if not is_undefined(message):
msg = to_string(message)
else:
msg = u''
return space.NewError(typ, msg)
j.create = new_create
return j
# fill remaining error types
error_constructors = {}
for err_type_name in (u'Error', u'EvalError', u'RangeError',
u'ReferenceError', u'SyntaxError', u'TypeError',
u'URIError'):
extra_err = space.NewError(u'Error', u'')
extra_err.put(u'name', err_type_name)
setattr(space, err_type_name + u'Prototype', extra_err)
error_constructors[err_type_name] = construct_constructor(
err_type_name)
assert space.TypeErrorPrototype is not None
# RegExp
regexp_proto = space.NewRegExp(u'(?:)', u'')
regexp_proto.prototype = object_proto
fill_proto(regexp_proto, RegExpPrototype, space)
space.RegExpPrototype = regexp_proto
# Json
# now all these boring constructors...
# Number
number = easy_func(jsnumber.Number, space)
space.Number = number
number.create = jsnumber.NumberConstructor
set_non_enumerable(number_proto, 'constructor', number)
set_protected(number, 'prototype', number_proto)
# number has some extra constants
for k, v in jsnumber.CONSTS.items():
set_protected(number, k, v)
# String
string = easy_func(jsstring.String, space)
space.String = string
string.create = jsstring.StringConstructor
set_non_enumerable(string_proto, 'constructor', string)
set_protected(string, 'prototype', string_proto)
# string has an extra function
set_non_enumerable(string, 'fromCharCode',
easy_func(jsstring.fromCharCode, space))
# Boolean
boolean = easy_func(jsboolean.Boolean, space)
space.Boolean = boolean
boolean.create = jsboolean.BooleanConstructor
set_non_enumerable(boolean_proto, 'constructor', boolean)
set_protected(boolean, 'prototype', boolean_proto)
# Array
array = easy_func(jsarray.Array, space)
space.Array = array
array.create = jsarray.ArrayConstructor
set_non_enumerable(array_proto, 'constructor', array)
set_protected(array, 'prototype', array_proto)
array.put(u'isArray', easy_func(jsarray.isArray, space))
# RegExp
regexp = easy_func(jsregexp.RegExp, space)
space.RegExp = regexp
regexp.create = jsregexp.RegExpCreate
set_non_enumerable(regexp_proto, 'constructor', regexp)
set_protected(regexp, 'prototype', regexp_proto)
# Object
_object = easy_func(jsobject.Object, space)
space.Object = _object
_object.create = jsobject.ObjectCreate
set_non_enumerable(object_proto, 'constructor', _object)
set_protected(_object, 'prototype', object_proto)
fill_proto(_object, jsobject.ObjectMethods, space)
# Function
function = easy_func(jsfunction.Function, space)
space.Function = function
# Math
math = space.NewObject()
math.Class = 'Math'
fill_proto(math, jsmath.MathFunctions, space)
for k, v in jsmath.CONSTANTS.items():
set_protected(math, k, v)
console = space.NewObject()
fill_proto(console, jsconsole.ConsoleMethods, space)
# set global object
builtins = {
'String': string,
'Number': number,
'Boolean': boolean,
'RegExp': regexp,
'exports': convert_to_js_type({}, space),
'Math': math,
#'Date',
'Object': _object,
'Function': function,
'JSON': json,
'Array': array,
'parseFloat': parseFloat,
'parseInt': parseInt,
'isFinite': isFinite,
'isNaN': isNaN,
'eval': easy_func(jsfunction._eval, space),
'console': console,
'log': console.get(u'log'),
}
builtins.update(error_constructors)
set_protected(global_scope, 'NaN', NaN)
set_protected(global_scope, 'Infinity', Infinity)
for k, v in builtins.items():
set_non_enumerable(global_scope, k, v)

View file

@ -0,0 +1,73 @@
from simplex import *
from conversions import *
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
def get_arg(arguments, n):
if len(arguments) <= n:
return undefined
return arguments[n]
def ensure_js_types(args, space=None):
return tuple(convert_to_js_type(e, space=space) for e in args)
def convert_to_js_type(e, space=None):
t = type(e)
if is_js_type(e):
return e
if t in (int, long, float):
return float(e)
elif isinstance(t, basestring):
return unicode(t)
elif t in (list, tuple):
if space is None:
raise MakeError(
'TypeError',
'Actually an internal error, could not convert to js type because space not specified'
)
return space.ConstructArray(ensure_js_types(e, space=space))
elif t == dict:
if space is None:
raise MakeError(
'TypeError',
'Actually an internal error, could not convert to js type because space not specified'
)
new = {}
for k, v in e.items():
new[to_string(convert_to_js_type(k, space))] = convert_to_js_type(
v, space)
return space.ConstructObject(new)
else:
raise MakeError('TypeError', 'Could not convert to js type!')
def is_js_type(e):
if type(e) in PRIMITIVES:
return True
elif hasattr(e, 'Class') and hasattr(e, 'value'): # not perfect but works
return True
else:
return False
# todo optimise these 2!
def js_array_to_tuple(arr):
length = to_uint32(arr.get(u'length'))
return tuple(arr.get(unicode(e)) for e in xrange(length))
def js_array_to_list(arr):
length = to_uint32(arr.get(u'length'))
return [arr.get(unicode(e)) for e in xrange(length)]
def js_arr_length(arr):
return to_uint32(arr.get(u'length'))

View file

View file

@ -0,0 +1,805 @@
from operations import *
from base import get_member, get_member_dot, PyJsFunction, Scope
class OP_CODE(object):
_params = []
# def eval(self, ctx):
# raise
def __repr__(self):
return self.__class__.__name__ + str(
tuple([getattr(self, e) for e in self._params]))
# --------------------- UNARY ----------------------
class UNARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
val = ctx.stack.pop()
ctx.stack.append(UNARY_OPERATIONS[self.operator](val))
# special unary operations
class TYPEOF(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
# typeof something_undefined does not throw reference error
val = ctx.get(self.identifier,
False) # <= this makes it slightly different!
ctx.stack.append(typeof_uop(val))
class POSTFIX(OP_CODE):
_params = ['cb', 'ca', 'identifier']
def __init__(self, post, incr, identifier):
self.identifier = identifier
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
target = to_number(ctx.get(self.identifier)) + self.cb
ctx.put(self.identifier, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER(OP_CODE):
_params = ['cb', 'ca']
def __init__(self, post, incr):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
name = ctx.stack.pop()
left = ctx.stack.pop()
target = to_number(get_member(left, name, ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put_member(name, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER_DOT(OP_CODE):
_params = ['cb', 'ca', 'prop']
def __init__(self, post, incr, prop):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
self.prop = prop
def eval(self, ctx):
left = ctx.stack.pop()
target = to_number(get_member_dot(left, self.prop,
ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put(self.prop, target)
ctx.stack.append(target + self.ca)
class DELETE(OP_CODE):
_params = ['name']
def __init__(self, name):
self.name = name
def eval(self, ctx):
ctx.stack.append(ctx.delete(self.name))
class DELETE_MEMBER(OP_CODE):
def eval(self, ctx):
prop = to_string(ctx.stack.pop())
obj = to_object(ctx.stack.pop(), ctx)
ctx.stack.append(obj.delete(prop, False))
# --------------------- BITWISE ----------------------
class BINARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
right = ctx.stack.pop()
left = ctx.stack.pop()
ctx.stack.append(BINARY_OPERATIONS[self.operator](left, right))
# &&, || and conditional are implemented in bytecode
# --------------------- JUMPS ----------------------
# simple label that will be removed from code after compilation. labels ID will be translated
# to source code position.
class LABEL(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
# I implemented interpreter in the way that when an integer is returned by eval operation the execution will jump
# to the location of the label (it is loc = label_locations[label])
class BASE_JUMP(OP_CODE):
_params = ['label']
def __init__(self, label):
self.label = label
class JUMP(BASE_JUMP):
def eval(self, ctx):
return self.label
class JUMP_IF_TRUE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if to_boolean(val):
return self.label
class JUMP_IF_EQ(BASE_JUMP):
# this one is used in switch statement - compares last 2 values using === operator and jumps popping both if true else pops last.
def eval(self, ctx):
cmp = ctx.stack.pop()
if strict_equality_op(ctx.stack[-1], cmp):
ctx.stack.pop()
return self.label
class JUMP_IF_TRUE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if to_boolean(val):
return self.label
class JUMP_IF_FALSE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if not to_boolean(val):
return self.label
class JUMP_IF_FALSE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if not to_boolean(val):
return self.label
class POP(OP_CODE):
def eval(self, ctx):
# todo remove this check later
assert len(ctx.stack), 'Popped from empty stack!'
del ctx.stack[-1]
# class REDUCE(OP_CODE):
# def eval(self, ctx):
# assert len(ctx.stack)==2
# ctx.stack[0] = ctx.stack[1]
# del ctx.stack[1]
# --------------- LOADING --------------
class LOAD_NONE(OP_CODE): # be careful with this :)
_params = []
def eval(self, ctx):
ctx.stack.append(None)
class LOAD_N_TUPLE(
OP_CODE
): # loads the tuple composed of n last elements on stack. elements are popped.
_params = ['n']
def __init__(self, n):
self.n = n
def eval(self, ctx):
tup = tuple(ctx.stack[-self.n:])
del ctx.stack[-self.n:]
ctx.stack.append(tup)
class LOAD_UNDEFINED(OP_CODE):
def eval(self, ctx):
ctx.stack.append(undefined)
class LOAD_NULL(OP_CODE):
def eval(self, ctx):
ctx.stack.append(null)
class LOAD_BOOLEAN(OP_CODE):
_params = ['val']
def __init__(self, val):
assert val in (0, 1)
self.val = bool(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_STRING(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, basestring)
self.val = unicode(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_NUMBER(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, (float, int, long))
self.val = float(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_REGEXP(OP_CODE):
_params = ['body', 'flags']
def __init__(self, body, flags):
self.body = body
self.flags = flags
def eval(self, ctx):
# we have to generate a new regexp - they are mutable
ctx.stack.append(ctx.space.NewRegExp(self.body, self.flags))
class LOAD_FUNCTION(OP_CODE):
_params = ['start', 'params', 'name', 'is_declaration', 'definitions']
def __init__(self, start, params, name, is_declaration, definitions):
assert type(start) == int
self.start = start # its an ID of label pointing to the beginning of the function bytecode
self.params = params
self.name = name
self.is_declaration = bool(is_declaration)
self.definitions = tuple(set(definitions + params))
def eval(self, ctx):
ctx.stack.append(
ctx.space.NewFunction(self.start, ctx, self.params, self.name,
self.is_declaration, self.definitions))
class LOAD_OBJECT(OP_CODE):
_params = [
'props'
] # props are py string pairs (prop_name, kind): kind can be either i, g or s. (init, get, set)
def __init__(self, props):
self.num = len(props)
self.props = props
def eval(self, ctx):
obj = ctx.space.NewObject()
if self.num:
obj._init(self.props, ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(obj)
class LOAD_ARRAY(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
def eval(self, ctx):
arr = ctx.space.NewArray(self.num)
if self.num:
arr._init(ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(arr)
class LOAD_THIS(OP_CODE):
def eval(self, ctx):
ctx.stack.append(ctx.THIS_BINDING)
class LOAD(OP_CODE): # todo check!
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
# 11.1.2
def eval(self, ctx):
ctx.stack.append(ctx.get(self.identifier, throw=True))
class LOAD_MEMBER(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
obj = ctx.stack.pop()
ctx.stack.append(get_member(obj, prop, ctx.space))
class LOAD_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
obj = ctx.stack.pop()
ctx.stack.append(get_member_dot(obj, self.prop, ctx.space))
# --------------- STORING --------------
class STORE(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
value = ctx.stack[-1] # don't pop
ctx.put(self.identifier, value)
class STORE_MEMBER(OP_CODE):
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
prop = to_string(name)
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % prop)
elif typ == UNDEFINED_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of undefined" % prop)
# just ignore...
else:
left.put_member(name, value)
ctx.stack.append(value)
class STORE_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
# just ignore...
else:
left.put(self.prop, value)
ctx.stack.append(value)
class STORE_OP(OP_CODE):
_params = ['identifier', 'op']
def __init__(self, identifier, op):
self.identifier = identifier
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
new_value = BINARY_OPERATIONS[self.op](ctx.get(self.identifier), value)
ctx.put(self.identifier, new_value)
ctx.stack.append(new_value)
class STORE_MEMBER_OP(OP_CODE):
_params = ['op']
def __init__(self, op):
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ is NULL_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of null" % to_string(name))
elif typ is UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % to_string(name))
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
left.put_member(name, ctx.stack[-1])
class STORE_MEMBER_DOT_OP(OP_CODE):
_params = ['prop', 'op']
def __init__(self, prop, op):
self.prop = prop
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
left.put(self.prop, ctx.stack[-1])
# --------------- CALLS --------------
def bytecode_call(ctx, func, this, args):
if type(func) is not PyJsFunction:
raise MakeError('TypeError', "%s is not a function" % Type(func))
if func.is_native: # call to built-in function or method
ctx.stack.append(func.call(this, args))
return None
# therefore not native. we have to return (new_context, function_label) to instruct interpreter to call
return func._generate_my_context(this, args), func.code
class CALL(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, args)
class CALL_METHOD(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_METHOD_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
args = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_NO_ARGS(OP_CODE):
def eval(self, ctx):
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, ())
class CALL_METHOD_NO_ARGS(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class CALL_METHOD_DOT_NO_ARGS(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class NOP(OP_CODE):
def eval(self, ctx):
pass
class RETURN(OP_CODE):
def eval(
self, ctx
): # remember to load the return value on stack before using RETURN op.
return (None, None)
class NEW(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create(args, space=ctx.space))
class NEW_NO_ARGS(OP_CODE):
def eval(self, ctx):
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create((), space=ctx.space))
# --------------- EXCEPTIONS --------------
class THROW(OP_CODE):
def eval(self, ctx):
raise MakeError(None, None, ctx.stack.pop())
class TRY_CATCH_FINALLY(OP_CODE):
_params = [
'try_label', 'catch_label', 'catch_variable', 'finally_label',
'finally_present', 'end_label'
]
def __init__(self, try_label, catch_label, catch_variable, finally_label,
finally_present, end_label):
self.try_label = try_label
self.catch_label = catch_label
self.catch_variable = catch_variable
self.finally_label = finally_label
self.finally_present = finally_present
self.end_label = end_label
def eval(self, ctx):
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, jump_loc/error)
ctx.stack.pop()
# execute try statement
try_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.try_label, self.catch_label)
errors = try_status[1] == 3
# catch
if errors and self.catch_variable is not None:
# generate catch block context...
catch_context = Scope({
self.catch_variable:
try_status[2].get_thrown_value(ctx.space)
}, ctx.space, ctx)
catch_context.THIS_BINDING = ctx.THIS_BINDING
catch_status = ctx.space.exe.execute_fragment_under_context(
catch_context, self.catch_label, self.finally_label)
else:
catch_status = None
# finally
if self.finally_present:
finally_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.finally_label, self.end_label)
else:
finally_status = None
# now return controls
other_status = catch_status or try_status
if finally_status is None or (finally_status[1] == 0
and other_status[1] != 0):
winning_status = other_status
else:
winning_status = finally_status
val, typ, spec = winning_status
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3:
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
# ------------ WITH + ITERATORS ----------
class WITH(OP_CODE):
_params = ['beg_label', 'end_label']
def __init__(self, beg_label, end_label):
self.beg_label = beg_label
self.end_label = end_label
def eval(self, ctx):
obj = to_object(ctx.stack.pop(), ctx.space)
with_context = Scope(
obj, ctx.space, ctx) # todo actually use the obj to modify the ctx
with_context.THIS_BINDING = ctx.THIS_BINDING
status = ctx.space.exe.execute_fragment_under_context(
with_context, self.beg_label, self.end_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
class FOR_IN(OP_CODE):
_params = ['name', 'body_start_label', 'continue_label', 'break_label']
def __init__(self, name, body_start_label, continue_label, break_label):
self.name = name
self.body_start_label = body_start_label
self.continue_label = continue_label
self.break_label = break_label
def eval(self, ctx):
iterable = ctx.stack.pop()
if is_null(iterable) or is_undefined(iterable):
ctx.stack.pop()
ctx.stack.append(undefined)
return self.break_label
obj = to_object(iterable, ctx.space)
for e in sorted(obj.own):
if not obj.own[e]['enumerable']:
continue
ctx.put(
self.name, e
) # JS would have been so much nicer if this was ctx.space.put(self.name, obj.get(e))
# evaluate the body
status = ctx.space.exe.execute_fragment_under_context(
ctx, self.body_start_label, self.break_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
continue
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
# now have to figure out whether this is a continue or something else...
ctx.stack.append(val)
if spec == self.continue_label:
# just a continue, perform next iteration as normal
continue
return spec # break or smth, go there and finish the iteration
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
return self.break_label
# all opcodes...
OP_CODES = {}
g = ''
for g in globals():
try:
if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE':
continue
except:
continue
OP_CODES[g] = globals()[g]

View file

@ -0,0 +1,314 @@
from __future__ import unicode_literals
from simplex import *
from conversions import *
# ------------------------------------------------------------------------------
# Unary operations
# -x
def minus_uop(self):
return -to_number(self)
# +x
def plus_uop(self): # +u
return to_number(self)
# !x
def logical_negation_uop(self): # !u cant do 'not u' :(
return not to_boolean(self)
# typeof x
def typeof_uop(self):
if is_callable(self):
return u'function'
typ = Type(self).lower()
if typ == u'null':
typ = u'object' # absolutely idiotic...
return typ
# ~u
def bit_invert_uop(self):
return float(to_int32(float(~to_int32(self))))
# void
def void_op(self):
return undefined
UNARY_OPERATIONS = {
'+': plus_uop,
'-': minus_uop,
'!': logical_negation_uop,
'~': bit_invert_uop,
'void': void_op,
'typeof':
typeof_uop, # this one only for member expressions! for identifiers its slightly different...
}
# ------------------------------------------------------------------------------
# ----- binary ops -------
# Bitwise operators
# <<, >>, &, ^, |, ~
# <<
def bit_lshift_op(self, other):
lnum = to_int32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_int32(float(lnum << shiftCount)))
# >>
def bit_rshift_op(self, other):
lnum = to_int32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_int32(float(lnum >> shiftCount)))
# >>>
def bit_bshift_op(self, other):
lnum = to_uint32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_uint32(float(lnum >> shiftCount)))
# &
def bit_and_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum & rnum)))
# ^
def bit_xor_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum ^ rnum)))
# |
def bit_or_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum | rnum)))
# Additive operators
# + and - are implemented here
# +
def add_op(self, other):
if type(self) is float and type(other) is float:
return self + other
if type(self) is unicode and type(other) is unicode:
return self + other
# standard way...
a = to_primitive(self)
b = to_primitive(other)
if type(a) is unicode or type(b) is unicode: # string wins hehe
return to_string(a) + to_string(b)
return to_number(a) + to_number(b)
# -
def sub_op(self, other):
return to_number(self) - to_number(other)
# Multiplicative operators
# *, / and % are implemented here
# *
def mul_op(self, other):
return to_number(self) * to_number(other)
# /
def div_op(self, other):
a = to_number(self)
b = to_number(other)
if b:
return a / float(b) # ensure at least one is a float.
if not a or a != a:
return NaN
return Infinity if a > 0 else -Infinity
# %
def mod_op(self, other):
a = to_number(self)
b = to_number(other)
if abs(a) == Infinity or not b:
return NaN
if abs(b) == Infinity:
return a
pyres = a % b # different signs in python and javascript
# python has the same sign as b and js has the same
# sign as a.
if a < 0 and pyres > 0:
pyres -= abs(b)
elif a > 0 and pyres < 0:
pyres += abs(b)
return float(pyres)
# Comparisons
# <, <=, !=, ==, >=, > are implemented here.
def abstract_relational_comparison(self, other,
self_first=True): # todo speed up!
''' self<other if self_first else other<self.
Returns the result of the question: is self smaller than other?
in case self_first is false it returns the answer of:
is other smaller than self.
result is PyJs type: bool or undefined'''
px = to_primitive(self, 'Number')
py = to_primitive(other, 'Number')
if not self_first: # reverse order
px, py = py, px
if not (Type(px) == 'String' and Type(py) == 'String'):
px, py = to_number(px), to_number(py)
if is_nan(px) or is_nan(py):
return None # watch out here!
return px < py # same cmp algorithm
else:
# I am pretty sure that python has the same
# string cmp algorithm but I have to confirm it
return px < py
# <
def less_op(self, other):
res = abstract_relational_comparison(self, other, True)
if res is None:
return False
return res
# <=
def less_eq_op(self, other):
res = abstract_relational_comparison(self, other, False)
if res is None:
return False
return not res
# >=
def greater_eq_op(self, other):
res = abstract_relational_comparison(self, other, True)
if res is None:
return False
return not res
# >
def greater_op(self, other):
res = abstract_relational_comparison(self, other, False)
if res is None:
return False
return res
# equality
def abstract_equality_op(self, other):
''' returns the result of JS == compare.
result is PyJs type: bool'''
tx, ty = Type(self), Type(other)
if tx == ty:
if tx == 'Undefined' or tx == 'Null':
return True
if tx == 'Number' or tx == 'String' or tx == 'Boolean':
return self == other
return self is other # Object
elif (tx == 'Undefined' and ty == 'Null') or (ty == 'Undefined'
and tx == 'Null'):
return True
elif tx == 'Number' and ty == 'String':
return abstract_equality_op(self, to_number(other))
elif tx == 'String' and ty == 'Number':
return abstract_equality_op(to_number(self), other)
elif tx == 'Boolean':
return abstract_equality_op(to_number(self), other)
elif ty == 'Boolean':
return abstract_equality_op(self, to_number(other))
elif (tx == 'String' or tx == 'Number') and is_object(other):
return abstract_equality_op(self, to_primitive(other))
elif (ty == 'String' or ty == 'Number') and is_object(self):
return abstract_equality_op(to_primitive(self), other)
else:
return False
def abstract_inequality_op(self, other):
return not abstract_equality_op(self, other)
def strict_equality_op(self, other):
typ = Type(self)
if typ != Type(other):
return False
if typ == 'Undefined' or typ == 'Null':
return True
if typ == 'Boolean' or typ == 'String' or typ == 'Number':
return self == other
else: # object
return self is other # Id compare.
def strict_inequality_op(self, other):
return not strict_equality_op(self, other)
def instanceof_op(self, other):
'''checks if self is instance of other'''
if not hasattr(other, 'has_instance'):
return False
return other.has_instance(self)
def in_op(self, other):
'''checks if self is in other'''
if not is_object(other):
raise MakeError(
'TypeError',
"You can\'t use 'in' operator to search in non-objects")
return other.has_property(to_string(self))
BINARY_OPERATIONS = {
'+': add_op,
'-': sub_op,
'*': mul_op,
'/': div_op,
'%': mod_op,
'<<': bit_lshift_op,
'>>': bit_rshift_op,
'>>>': bit_bshift_op,
'|': bit_or_op,
'&': bit_and_op,
'^': bit_xor_op,
'==': abstract_equality_op,
'!=': abstract_inequality_op,
'===': strict_equality_op,
'!==': strict_inequality_op,
'<': less_op,
'<=': less_eq_op,
'>': greater_op,
'>=': greater_eq_op,
'in': in_op,
'instanceof': instanceof_op,
}

View file

@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'

View file

@ -0,0 +1,489 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..operations import strict_equality_op
import six
if six.PY3:
xrange = range
import functools
ARR_STACK = set({})
class ArrayPrototype:
def toString(this, args):
arr = to_object(this, args.space)
func = arr.get('join')
if not is_callable(func):
return u'[object %s]' % GetClass(arr)
return func.call(this, ())
def toLocaleString(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
# separator is simply a comma ','
if not arr_len:
return ''
res = []
for i in xrange(arr_len):
element = array.get(unicode(i))
if is_undefined(element) or is_null(element):
res.append('')
else:
cand = to_object(element, args.space)
str_func = cand.get('toLocaleString')
if not is_callable(str_func):
raise MakeError(
'TypeError',
'toLocaleString method of item at index %d is not callable'
% i)
res.append(to_string(str_func.call(cand, ())))
return ','.join(res)
def concat(this, args):
array = to_object(this, args.space)
items = [array]
items.extend(tuple(args))
A = []
for E in items:
if GetClass(E) == 'Array':
k = 0
e_len = js_arr_length(E)
while k < e_len:
if E.has_property(unicode(k)):
A.append(E.get(unicode(k)))
k += 1
else:
A.append(E)
return args.space.ConstructArray(A)
def join(this, args):
ARR_STACK.add(this)
array = to_object(this, args.space)
separator = get_arg(args, 0)
arr_len = js_arr_length(array)
separator = ',' if is_undefined(separator) else to_string(separator)
elems = []
for e in xrange(arr_len):
elem = array.get(unicode(e))
if elem in ARR_STACK:
s = ''
else:
s = to_string(elem)
elems.append(
s if not (is_undefined(elem) or is_null(elem)) else '')
res = separator.join(elems)
ARR_STACK.remove(this)
return res
def pop(this, args): #todo check
array = to_object(this, args.space)
arr_len = js_arr_length(array)
if not arr_len:
array.put('length', float(arr_len))
return undefined
ind = unicode(arr_len - 1)
element = array.get(ind)
array.delete(ind)
array.put('length', float(arr_len - 1))
return element
def push(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
to_put = tuple(args)
i = arr_len
for i, e in enumerate(to_put, arr_len):
array.put(unicode(i), e, True)
array.put('length', float(arr_len + len(to_put)), True)
return float(i)
def reverse(this, args):
array = to_object(this, args.space)
vals = js_array_to_list(array)
has_props = [
array.has_property(unicode(e))
for e in xrange(js_arr_length(array))
]
vals.reverse()
has_props.reverse()
for i, val in enumerate(vals):
if has_props[i]:
array.put(unicode(i), val)
else:
array.delete(unicode(i))
return array
def shift(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
if not arr_len:
array.put('length', 0.)
return undefined
first = array.get('0')
for k in xrange(1, arr_len):
from_s, to_s = unicode(k), unicode(k - 1)
if array.has_property(from_s):
array.put(to_s, array.get(from_s))
else:
array.delete(to_s)
array.delete(unicode(arr_len - 1))
array.put('length', float(arr_len - 1))
return first
def slice(this, args): # todo check
array = to_object(this, args.space)
start = get_arg(args, 0)
end = get_arg(args, 1)
arr_len = js_arr_length(array)
relative_start = to_int(start)
k = max((arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
relative_end = arr_len if is_undefined(end) else to_int(end)
final = max((arr_len + relative_end), 0) if relative_end < 0 else min(
relative_end, arr_len)
res = []
n = 0
while k < final:
pk = unicode(k)
if array.has_property(pk):
res.append(array.get(pk))
k += 1
n += 1
return args.space.ConstructArray(res)
def sort(
this, args
): # todo: this assumes array continous (not sparse) - fix for sparse arrays
cmpfn = get_arg(args, 0)
if not GetClass(this) in ('Array', 'Arguments'):
return to_object(this, args.space) # do nothing
arr_len = js_arr_length(this)
if not arr_len:
return this
arr = [
(this.get(unicode(e)) if this.has_property(unicode(e)) else None)
for e in xrange(arr_len)
]
if not is_callable(cmpfn):
cmpfn = None
cmp = lambda a, b: sort_compare(a, b, cmpfn)
if six.PY3:
key = functools.cmp_to_key(cmp)
arr.sort(key=key)
else:
arr.sort(cmp=cmp)
for i in xrange(arr_len):
if arr[i] is None:
this.delete(unicode(i))
else:
this.put(unicode(i), arr[i])
return this
def splice(this, args):
# 1-8
array = to_object(this, args.space)
start = get_arg(args, 0)
deleteCount = get_arg(args, 1)
arr_len = js_arr_length(this)
relative_start = to_int(start)
actual_start = max(
(arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
actual_delete_count = min(
max(to_int(deleteCount), 0), arr_len - actual_start)
k = 0
A = args.space.NewArray(0)
# 9
while k < actual_delete_count:
if array.has_property(unicode(actual_start + k)):
A.put(unicode(k), array.get(unicode(actual_start + k)))
k += 1
# 10-11
items = list(args)[2:]
items_len = len(items)
# 12
if items_len < actual_delete_count:
k = actual_start
while k < (arr_len - actual_delete_count):
fr = unicode(k + actual_delete_count)
to = unicode(k + items_len)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k += 1
k = arr_len
while k > (arr_len - actual_delete_count + items_len):
array.delete(unicode(k - 1))
k -= 1
# 13
elif items_len > actual_delete_count:
k = arr_len - actual_delete_count
while k > actual_start:
fr = unicode(k + actual_delete_count - 1)
to = unicode(k + items_len - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
# 14-17
k = actual_start
while items:
E = items.pop(0)
array.put(unicode(k), E)
k += 1
array.put('length', float(arr_len - actual_delete_count + items_len))
return A
def unshift(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
argCount = len(args)
k = arr_len
while k > 0:
fr = unicode(k - 1)
to = unicode(k + argCount - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
items = tuple(args)
for j, e in enumerate(items):
array.put(unicode(j), e)
array.put('length', float(arr_len + argCount))
return float(arr_len + argCount)
def indexOf(this, args):
array = to_object(this, args.space)
searchElement = get_arg(args, 0)
arr_len = js_arr_length(array)
if arr_len == 0:
return -1.
if len(args) > 1:
n = to_int(args[1])
else:
n = 0
if n >= arr_len:
return -1.
if n >= 0:
k = n
else:
k = arr_len - abs(n)
if k < 0:
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
elementK = array.get(unicode(k))
if strict_equality_op(searchElement, elementK):
return float(k)
k += 1
return -1.
def lastIndexOf(this, args):
array = to_object(this, args.space)
searchElement = get_arg(args, 0)
arr_len = js_arr_length(array)
if arr_len == 0:
return -1.
if len(args) > 1:
n = to_int(args[1])
else:
n = arr_len - 1
if n >= 0:
k = min(n, arr_len - 1)
else:
k = arr_len - abs(n)
while k >= 0:
if array.has_property(unicode(k)):
elementK = array.get(unicode(k))
if strict_equality_op(searchElement, elementK):
return float(k)
k -= 1
return -1.
def every(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
T = get_arg(args, 1)
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if not to_boolean(
callbackfn.call(T, (kValue, float(k), array))):
return False
k += 1
return True
def some(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
T = get_arg(args, 1)
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if to_boolean(callbackfn.call(T, (kValue, float(k), array))):
return True
k += 1
return False
def forEach(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
while k < arr_len:
sk = unicode(k)
if array.has_property(sk):
kValue = array.get(sk)
callbackfn.call(_this, (kValue, float(k), array))
k += 1
return undefined
def map(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
A = args.space.NewArray(0)
while k < arr_len:
Pk = unicode(k)
if array.has_property(Pk):
kValue = array.get(Pk)
mappedValue = callbackfn.call(_this, (kValue, float(k), array))
A.define_own_property(
Pk, {
'value': mappedValue,
'writable': True,
'enumerable': True,
'configurable': True
}, False)
k += 1
return A
def filter(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
res = []
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if to_boolean(
callbackfn.call(_this, (kValue, float(k), array))):
res.append(kValue)
k += 1
return args.space.ConstructArray(res)
def reduce(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(args) < 2:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
k = 0
accumulator = undefined
if len(args) > 1: # initial value present
accumulator = args[1]
else:
kPresent = False
while not kPresent and k < arr_len:
kPresent = array.has_property(unicode(k))
if kPresent:
accumulator = array.get(unicode(k))
k += 1
if not kPresent:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
accumulator = callbackfn.call(
undefined, (accumulator, kValue, float(k), array))
k += 1
return accumulator
def reduceRight(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(args) < 2:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
k = arr_len - 1
accumulator = undefined
if len(args) > 1: # initial value present
accumulator = args[1]
else:
kPresent = False
while not kPresent and k >= 0:
kPresent = array.has_property(unicode(k))
if kPresent:
accumulator = array.get(unicode(k))
k -= 1
if not kPresent:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
while k >= 0:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
accumulator = callbackfn.call(
undefined, (accumulator, kValue, float(k), array))
k -= 1
return accumulator
def sort_compare(a, b, comp):
if a is None:
if b is None:
return 0
return 1
if b is None:
if a is None:
return 0
return -1
if is_undefined(a):
if is_undefined(b):
return 0
return 1
if is_undefined(b):
if is_undefined(a):
return 0
return -1
if comp is not None:
res = comp.call(undefined, (a, b))
return to_int(res)
x, y = to_string(a), to_string(b)
if x < y:
return -1
elif x > y:
return 1
return 0

View file

@ -0,0 +1,22 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class BooleanPrototype:
def toString(this, args):
if GetClass(this) != 'Boolean':
raise MakeError('TypeError',
'Boolean.prototype.toString is not generic')
if is_object(this):
this = this.value
return u'true' if this else u'false'
def valueOf(this, args):
if GetClass(this) != 'Boolean':
raise MakeError('TypeError',
'Boolean.prototype.valueOf is not generic')
if is_object(this):
this = this.value
return this

View file

@ -0,0 +1,15 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class ErrorPrototype:
def toString(this, args):
if Type(this) != 'Object':
raise MakeError('TypeError',
'Error.prototype.toString called on non-object')
name = this.get('name')
name = u'Error' if is_undefined(name) else to_string(name)
msg = this.get('message')
msg = '' if is_undefined(msg) else to_string(msg)
return name + (name and msg and ': ') + msg

View file

@ -0,0 +1,61 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
# todo fix apply and bind
class FunctionPrototype:
def toString(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.toString is not generic')
args = u', '.join(map(unicode, this.params))
return u'function %s(%s) { [native code] }' % (this.name if this.name
else u'', args)
def call(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.call is not generic')
_this = get_arg(args, 0)
_args = tuple(args)[1:]
return this.call(_this, _args)
def apply(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.apply is not generic')
_args = get_arg(args, 1)
if not is_object(_args):
raise MakeError(
'TypeError',
'argList argument to Function.prototype.apply must an Object')
_this = get_arg(args, 0)
return this.call(_this, js_array_to_tuple(_args))
def bind(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.bind is not generic')
bound_this = get_arg(args, 0)
bound_args = tuple(args)[1:]
def bound(dummy_this, extra_args):
return this.call(bound_this, bound_args + tuple(extra_args))
js_bound = args.space.NewFunction(bound, this.ctx, (), u'', False, ())
js_bound.put(u'length',
float(max(len(this.params) - len(bound_args), 0.)))
js_bound.put(u'name', u'boundFunc')
return js_bound

View file

@ -0,0 +1,205 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..operations import strict_equality_op
import json
indent = ''
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
def parse(this, args):
text, reviver = get_arg(args, 0), get_arg(args, 1)
s = to_string(text)
try:
unfiltered = json.loads(s)
except:
raise MakeError(
'SyntaxError',
'JSON.parse could not parse JSON string - Invalid syntax')
unfiltered = to_js(unfiltered, args.space)
if is_callable(reviver):
root = args.space.ConstructObject({'': unfiltered})
return walk(root, '', reviver)
else:
return unfiltered
def stringify(this, args):
global indent
value, replacer, space = get_arg(args, 0), get_arg(args, 1), get_arg(
args, 2)
stack = set([])
indent = ''
property_list = replacer_function = undefined
if is_object(replacer):
if is_callable(replacer):
replacer_function = replacer
elif replacer.Class == 'Array':
property_list = []
for e in replacer:
v = replacer[e]
item = undefined
typ = Type(v)
if typ == 'Number':
item = to_string(v)
elif typ == 'String':
item = v
elif typ == 'Object':
if GetClass(v) in ('String', 'Number'):
item = to_string(v)
if not is_undefined(item) and item not in property_list:
property_list.append(item)
if is_object(space):
if GetClass(space) == 'Number':
space = to_number(space)
elif GetClass(space) == 'String':
space = to_string(space)
if Type(space) == 'Number':
space = min(10, to_int(space))
gap = max(int(space), 0) * ' '
elif Type(space) == 'String':
gap = space[:10]
else:
gap = ''
return Str('', args.space.ConstructObject({
'': value
}), replacer_function, property_list, gap, stack, space)
def Str(key, holder, replacer_function, property_list, gap, stack, space):
value = holder.get(key)
if is_object(value):
to_json = value.get('toJSON')
if is_callable(to_json):
value = to_json.call(value, (key, ))
if not is_undefined(replacer_function):
value = replacer_function.call(holder, (key, value))
if is_object(value):
if value.Class == 'String':
value = to_string(value)
elif value.Class == 'Number':
value = to_number(value)
elif value.Class == 'Boolean':
value = to_boolean(value)
typ = Type(value)
if is_null(value):
return 'null'
elif typ == 'Boolean':
return 'true' if value else 'false'
elif typ == 'String':
return Quote(value)
elif typ == 'Number':
if not is_infinity(value):
return to_string(value)
return 'null'
if is_object(value) and not is_callable(value):
if value.Class == 'Array':
return ja(value, stack, gap, property_list, replacer_function,
space)
else:
return jo(value, stack, gap, property_list, replacer_function,
space)
return undefined
def jo(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise MakeError('TypeError', 'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
if not is_undefined(property_list):
k = property_list
else:
k = [unicode(e) for e, d in value.own.items() if d.get('enumerable')]
partial = []
for p in k:
str_p = Str(p, value, replacer_function, property_list, gap, stack,
space)
if not is_undefined(str_p):
member = json.dumps(p) + ':' + (
' ' if gap else
'') + str_p # todo not sure here - what space character?
partial.append(member)
if not partial:
final = '{}'
else:
if not gap:
final = '{%s}' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '{\n' + indent + properties + '\n' + stepback + '}'
stack.remove(value)
indent = stepback
return final
def ja(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise MakeError('TypeError', 'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
partial = []
length = js_arr_length(value)
for index in xrange(length):
index = unicode(index)
str_index = Str(index, value, replacer_function, property_list, gap,
stack, space)
if is_undefined(str_index):
partial.append('null')
else:
partial.append(str_index)
if not partial:
final = '[]'
else:
if not gap:
final = '[%s]' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '[\n' + indent + properties + '\n' + stepback + ']'
stack.remove(value)
indent = stepback
return final
def Quote(string):
return json.dumps(string)
def to_js(d, _args_space):
return convert_to_js_type(d, _args_space)
def walk(holder, name, reviver):
val = holder.get(name)
if GetClass(val) == 'Array':
for i in xrange(js_arr_length(val)):
i = unicode(i)
new_element = walk(val, i, reviver)
if is_undefined(new_element):
val.delete(i)
else:
new_element.put(i, new_element)
elif is_object(val):
for key in [
unicode(e) for e, d in val.own.items() if d.get('enumerable')
]:
new_element = walk(val, key, reviver)
if is_undefined(new_element):
val.delete(key)
else:
val.put(key, new_element)
return reviver.call(holder, (name, val))

View file

@ -0,0 +1,163 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
RADIX_SYMBOLS = {
0: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: 'a',
11: 'b',
12: 'c',
13: 'd',
14: 'e',
15: 'f',
16: 'g',
17: 'h',
18: 'i',
19: 'j',
20: 'k',
21: 'l',
22: 'm',
23: 'n',
24: 'o',
25: 'p',
26: 'q',
27: 'r',
28: 's',
29: 't',
30: 'u',
31: 'v',
32: 'w',
33: 'x',
34: 'y',
35: 'z'
}
def to_str_rep(num):
if is_nan(num):
return 'NaN'
elif is_infinity(num):
sign = '-' if num < 0 else ''
return sign + 'Infinity'
elif int(num) == num: # dont print .0
return unicode(int(num))
return unicode(num) # todo: Make it 100% consistent with Node
class NumberPrototype:
def toString(this, args):
if GetClass(this) != 'Number':
raise MakeError('TypeError',
'Number.prototype.valueOf is not generic')
if type(this) != float:
this = this.value
radix = get_arg(args, 0)
if is_undefined(radix):
return to_str_rep(this)
r = to_int(radix)
if r == 10:
return to_str_rep(this)
if r not in xrange(2, 37) or radix != r:
raise MakeError(
'RangeError',
'Number.prototype.toString() radix argument must be an integer between 2 and 36'
)
num = to_int(this)
if num < 0:
num = -num
sign = '-'
else:
sign = ''
res = ''
while num:
s = RADIX_SYMBOLS[num % r]
num = num // r
res = s + res
return sign + (res if res else '0')
def valueOf(this, args):
if GetClass(this) != 'Number':
raise MakeError('TypeError',
'Number.prototype.valueOf is not generic')
if type(this) != float:
this = this.value
return this
def toFixed(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toFixed called on incompatible receiver')
if type(this) != float:
this = this.value
fractionDigits = get_arg(args, 0)
digs = to_int(fractionDigits)
if digs < 0 or digs > 20:
raise MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
elif is_nan(this):
return 'NaN'
return format(this, '-.%df' % digs)
def toExponential(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toExponential called on incompatible receiver'
)
if type(this) != float:
this = this.value
fractionDigits = get_arg(args, 0)
digs = to_int(fractionDigits)
if digs < 0 or digs > 20:
raise MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
elif is_nan(this):
return 'NaN'
return format(this, '-.%de' % digs)
def toPrecision(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toPrecision called on incompatible receiver')
if type(this) != float:
this = this.value
precision = get_arg(args, 0)
if is_undefined(precision):
return to_string(this)
prec = to_int(precision)
if is_nan(this):
return 'NaN'
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
digs = prec - len(str(int(this)))
if digs >= 0:
return format(this, '-.%df' % digs)
else:
return format(this, '-.%df' % (prec - 1))
NumberPrototype.toLocaleString = NumberPrototype.toString

View file

@ -0,0 +1,48 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class ObjectPrototype:
def toString(this, args):
if type(this) == UNDEFINED_TYPE:
return u'[object Undefined]'
elif type(this) == NULL_TYPE:
return u'[object Null]'
return u'[object %s]' % GetClass(to_object(this, args.space))
def valueOf(this, args):
return to_object(this, args.space)
def toLocaleString(this, args):
o = to_object(this, args.space)
toString = o.get(u'toString')
if not is_callable(toString):
raise MakeError('TypeError', 'toString of this is not callcable')
else:
return toString.call(this, args)
def hasOwnProperty(this, args):
prop = get_arg(args, 0)
o = to_object(this, args.space)
return o.get_own_property(to_string(prop)) is not None
def isPrototypeOf(this, args):
# a bit stupid specification because of object conversion that will cause invalid values for primitives
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false
obj = get_arg(args, 0)
if not is_object(obj):
return False
o = to_object(this, args.space)
while 1:
obj = obj.prototype
if obj is None or is_null(obj):
return False
if obj is o:
return True
def propertyIsEnumerable(this, args):
prop = get_arg(args, 0)
o = to_object(this, args.space)
cand = o.own.get(to_string(prop))
return cand is not None and cand.get('enumerable')

View file

@ -0,0 +1,56 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class RegExpPrototype:
def toString(this, args):
flags = u''
try:
if this.glob:
flags += u'g'
if this.ignore_case:
flags += u'i'
if this.multiline:
flags += u'm'
except:
pass
try:
v = this.value if this.value else u'(?:)'
except:
v = u'(?:)'
return u'/%s/' % v + flags
def test(this, args):
string = get_arg(args, 0)
return RegExpExec(this, string, args.space) is not null
def _exec(
this, args
): # will be changed to exec in base.py. cant name it exec here...
string = get_arg(args, 0)
return RegExpExec(this, string, args.space)
def RegExpExec(this, string, space):
if GetClass(this) != 'RegExp':
raise MakeError('TypeError', 'RegExp.prototype.exec is not generic!')
string = to_string(string)
length = len(string)
i = to_int(this.get('lastIndex')) if this.glob else 0
matched = False
while not matched:
if i < 0 or i > length:
this.put('lastIndex', 0.)
return null
matched = this.match(string, i)
i += 1
start, end = matched.span() #[0]+i-1, matched.span()[1]+i-1
if this.glob:
this.put('lastIndex', float(end))
arr = convert_to_js_type(
[matched.group()] + list(matched.groups()), space=space)
arr.put('index', float(start))
arr.put('input', unicode(string))
return arr

View file

@ -0,0 +1,323 @@
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
import re
from ..conversions import *
from ..func_utils import *
from jsregexp import RegExpExec
DIGS = set(u'0123456789')
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
def replacement_template(rep, source, span, npar):
"""Takes the replacement template and some info about the match and returns filled template
"""
n = 0
res = ''
while n < len(rep) - 1:
char = rep[n]
if char == '$':
if rep[n + 1] == '$':
res += '$'
n += 2
continue
elif rep[n + 1] == '`':
# replace with string that is BEFORE match
res += source[:span[0]]
n += 2
continue
elif rep[n + 1] == '\'':
# replace with string that is AFTER match
res += source[span[1]:]
n += 2
continue
elif rep[n + 1] in DIGS:
dig = rep[n + 1]
if n + 2 < len(rep) and rep[n + 2] in DIGS:
dig += rep[n + 2]
num = int(dig)
# we will not do any replacements if we dont have this npar or dig is 0
if not num or num > len(npar):
res += '$' + dig
else:
# None - undefined has to be replaced with ''
res += npar[num - 1] if npar[num - 1] else ''
n += 1 + len(dig)
continue
res += char
n += 1
if n < len(rep):
res += rep[-1]
return res
###################################################
class StringPrototype:
def toString(this, args):
if GetClass(this) != 'String':
raise MakeError('TypeError',
'String.prototype.toString is not generic')
if type(this) == unicode:
return this
assert type(this.value) == unicode
return this.value
def valueOf(this, args):
if GetClass(this) != 'String':
raise MakeError('TypeError',
'String.prototype.valueOf is not generic')
if type(this) == unicode:
return this
assert type(this.value) == unicode
return this.value
def charAt(this, args):
cok(this)
pos = to_int(get_arg(args, 0))
s = to_string(this)
if 0 <= pos < len(s):
return s[pos]
return u''
def charCodeAt(this, args):
cok(this)
pos = to_int(get_arg(args, 0))
s = to_string(this)
if 0 <= pos < len(s):
return float(ord(s[pos]))
return NaN
def concat(this, args):
cok(this)
return to_string(this) + u''.join(map(to_string, args))
def indexOf(this, args):
cok(this)
search = to_string(get_arg(args, 0))
pos = to_int(get_arg(args, 1))
s = to_string(this)
return float(s.find(search, min(max(pos, 0), len(s))))
def lastIndexOf(this, args):
cok(this)
search = to_string(get_arg(args, 0))
pos = get_arg(args, 1)
s = to_string(this)
pos = 10**12 if is_nan(pos) else to_int(pos)
return float(s.rfind(search, 0, min(max(pos, 0) + 1, len(s))))
def localeCompare(this, args):
cok(this)
s = to_string(this)
that = to_string(get_arg(args, 0))
if s < that:
return -1.
elif s > that:
return 1.
return 0.
def match(this, args):
cok(this)
s = to_string(this)
regexp = get_arg(args, 0)
r = args.space.NewRegExp(
regexp, '') if GetClass(regexp) != 'RegExp' else regexp
if not r.glob:
return RegExpExec(r, s, space=args.space)
r.put('lastIndex', float(0))
found = []
previous_last_index = 0
last_match = True
while last_match:
result = RegExpExec(r, s, space=args.space)
if is_null(result):
last_match = False
else:
this_index = r.get('lastIndex')
if this_index == previous_last_index:
r.put('lastIndex', float(this_index + 1))
previous_last_index += 1
else:
previous_last_index = this_index
matchStr = result.get('0')
found.append(matchStr)
if not found:
return null
return args.space.ConstructArray(found)
def replace(this, args):
# VERY COMPLICATED. to check again.
cok(this)
s = to_string(this)
searchValue = get_arg(args, 0)
replaceValue = get_arg(args, 1)
res = ''
if not is_callable(replaceValue):
replaceValue = to_string(replaceValue)
func = False
else:
func = True
# Replace all ( global )
if GetClass(searchValue) == 'RegExp' and searchValue.glob:
last = 0
for e in re.finditer(searchValue.pat, s):
res += s[last:e.span()[0]]
if func:
# prepare arguments for custom func (replaceValue)
call_args = (e.group(), ) + e.groups() + (e.span()[1], s)
# convert all types to JS before Js bytecode call...
res += to_string(
replaceValue.call(
this, ensure_js_types(call_args,
space=args.space)))
else:
res += replacement_template(replaceValue, s, e.span(),
e.groups())
last = e.span()[1]
res += s[last:]
return res
elif GetClass(searchValue) == 'RegExp':
e = re.search(searchValue.pat, s)
if e is None:
return s
span = e.span()
pars = e.groups()
match = e.group()
else:
match = to_string(searchValue)
ind = s.find(match)
if ind == -1:
return s
span = ind, ind + len(match)
pars = ()
res = s[:span[0]]
if func:
call_args = (match, ) + pars + (span[1], s)
# convert all types to JS before Js bytecode call...
res += to_string(
replaceValue.call(this,
ensure_js_types(call_args,
space=args.space)))
else:
res += replacement_template(replaceValue, s, span, pars)
res += s[span[1]:]
return res
def search(this, args):
cok(this)
string = to_string(this)
regexp = get_arg(args, 0)
if GetClass(regexp) == 'RegExp':
rx = regexp
else:
rx = args.space.NewRegExp(regexp, '')
res = re.search(rx.pat, string)
if res is not None:
return float(res.span()[0])
return -1.
def slice(this, args):
cok(this)
s = to_string(this)
start = to_int(get_arg(args, 0))
length = len(s)
end = get_arg(args, 1)
end = length if is_undefined(end) else to_int(end)
#From = max(length+start, 0) if start<0 else min(length, start)
#To = max(length+end, 0) if end<0 else min(length, end)
return s[start:end]
def split(this, args):
# its a bit different from re.split!
cok(this)
s = to_string(this)
separator = get_arg(args, 0)
limit = get_arg(args, 1)
lim = 2**32 - 1 if is_undefined(limit) else to_uint32(limit)
if not lim:
return args.space.ConstructArray([])
if is_undefined(separator):
return args.space.ConstructArray([s])
len_s = len(s)
res = []
R = separator if GetClass(separator) == 'RegExp' else to_string(
separator)
if not len_s:
if SplitMatch(s, 0, R) is None:
return args.space.ConstructArray([s])
return args.space.ConstructArray([])
p = q = 0
while q != len_s:
e, cap = SplitMatch(s, q, R)
if e is None or e == p:
q += 1
continue
res.append(s[p:q])
p = q = e
if len(res) == lim:
return args.space.ConstructArray(res)
for element in cap:
res.append(element)
if len(res) == lim:
return args.space.ConstructArray(res)
res.append(s[p:])
return args.space.ConstructArray(res)
def substring(this, args):
cok(this)
s = to_string(this)
start = to_int(get_arg(args, 0))
length = len(s)
end = get_arg(args, 1)
end = length if is_undefined(end) else to_int(end)
fstart = min(max(start, 0), length)
fend = min(max(end, 0), length)
return s[min(fstart, fend):max(fstart, fend)]
def substr(this, args):
cok(this)
#I hate this function and its description in specification
r1 = to_string(this)
r2 = to_int(get_arg(args, 0))
length = get_arg(args, 1)
r3 = 10**20 if is_undefined(length) else to_int(length)
r4 = len(r1)
r5 = r2 if r2 >= 0 else max(0, r2 + r4)
r6 = min(max(r3, 0), r4 - r5)
if r6 <= 0:
return ''
return r1[r5:r5 + r6]
def toLowerCase(this, args):
cok(this)
return to_string(this).lower()
def toLocaleLowerCase(this, args):
cok(this)
return to_string(this).lower()
def toUpperCase(this, args):
cok(this)
return to_string(this).upper()
def toLocaleUpperCase(this, args):
cok(this)
return to_string(this).upper()
def trim(this, args):
cok(this)
return to_string(this).strip(WHITE)
def SplitMatch(s, q, R):
# s is Py String to match, q is the py int match start and R is Js RegExp or String.
if GetClass(R) == 'RegExp':
res = R.match(s, q)
return (None, ()) if res is None else (res.span()[1], res.groups())
# R is just a string
if s[q:].startswith(R):
return q + len(R), ()
return None, ()

View file

@ -0,0 +1,149 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
RADIX_CHARS = {
'1': 1,
'0': 0,
'3': 3,
'2': 2,
'5': 5,
'4': 4,
'7': 7,
'6': 6,
'9': 9,
'8': 8,
'a': 10,
'c': 12,
'b': 11,
'e': 14,
'd': 13,
'g': 16,
'f': 15,
'i': 18,
'h': 17,
'k': 20,
'j': 19,
'm': 22,
'l': 21,
'o': 24,
'n': 23,
'q': 26,
'p': 25,
's': 28,
'r': 27,
'u': 30,
't': 29,
'w': 32,
'v': 31,
'y': 34,
'x': 33,
'z': 35,
'A': 10,
'C': 12,
'B': 11,
'E': 14,
'D': 13,
'G': 16,
'F': 15,
'I': 18,
'H': 17,
'K': 20,
'J': 19,
'M': 22,
'L': 21,
'O': 24,
'N': 23,
'Q': 26,
'P': 25,
'S': 28,
'R': 27,
'U': 30,
'T': 29,
'W': 32,
'V': 31,
'Y': 34,
'X': 33,
'Z': 35
}
# parseFloat
# parseInt
# isFinite
# isNaN
def parseInt(this, args):
string, radix = get_arg(args, 0), get_arg(args, 1)
string = to_string(string).lstrip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
r = to_int32(radix)
strip_prefix = True
if r:
if r < 2 or r > 36:
return NaN
if r != 16:
strip_prefix = False
else:
r = 10
if strip_prefix:
if len(string) >= 2 and string[:2] in ('0x', '0X'):
string = string[2:]
r = 16
n = 0
num = 0
while n < len(string):
cand = RADIX_CHARS.get(string[n])
if cand is None or not cand < r:
break
num = cand + num * r
n += 1
if not n:
return NaN
return float(sign * num)
def parseFloat(this, args):
string = get_arg(args, 0)
string = to_string(string).strip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
num = None
length = 1
max_len = None
failed = 0
while length <= len(string):
try:
num = float(string[:length])
max_len = length
failed = 0
except:
failed += 1
if failed > 4: # cant be a number anymore
break
length += 1
if num is None:
return NaN
return sign * float(string[:max_len])
def isNaN(this, args):
number = get_arg(args, 0)
if is_nan(to_number(number)):
return True
return False
def isFinite(this, args):
number = get_arg(args, 0)
num = to_number(number)
if is_nan(num) or is_infinity(num):
return False
return True

View file

@ -0,0 +1,32 @@
import pyjsparser
from space import Space
import fill_space
from byte_trans import ByteCodeGenerator
from code import Code
from simplex import MakeError
import sys
sys.setrecursionlimit(100000)
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError(u'SyntaxError', unicode(msg))
def get_js_bytecode(js):
a = ByteCodeGenerator(Code())
d = pyjsparser.parse(js)
a.emit(d)
return a.exe.tape
def eval_js_vm(js):
a = ByteCodeGenerator(Code())
s = Space()
a.exe.space = s
s.exe = a.exe
d = pyjsparser.parse(js)
a.emit(d)
fill_space.fill_space(s, a)
# print a.exe.tape
a.exe.compile()
return a.exe.run(a.exe.space.GlobalObj)

View file

@ -0,0 +1,133 @@
from __future__ import unicode_literals
import six
#Undefined
class PyJsUndefined(object):
TYPE = 'Undefined'
Class = 'Undefined'
undefined = PyJsUndefined()
#Null
class PyJsNull(object):
TYPE = 'Null'
Class = 'Null'
null = PyJsNull()
Infinity = float('inf')
NaN = float('nan')
UNDEFINED_TYPE = PyJsUndefined
NULL_TYPE = PyJsNull
STRING_TYPE = unicode if six.PY2 else str
NUMBER_TYPE = float
BOOLEAN_TYPE = bool
# exactly 5 simplexes!
PRIMITIVES = frozenset(
[UNDEFINED_TYPE, NULL_TYPE, STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE])
TYPE_NAMES = {
UNDEFINED_TYPE: 'Undefined',
NULL_TYPE: 'Null',
STRING_TYPE: 'String',
NUMBER_TYPE: 'Number',
BOOLEAN_TYPE: 'Boolean',
}
def Type(x):
# Any -> Str
return TYPE_NAMES.get(type(x), 'Object')
def GetClass(x):
# Any -> Str
cand = TYPE_NAMES.get(type(x))
if cand is None:
return x.Class
return cand
def is_undefined(self):
return self is undefined
def is_null(self):
return self is null
def is_primitive(self):
return type(self) in PRIMITIVES
def is_object(self):
return not is_primitive(self)
def is_callable(self):
return hasattr(self, 'call')
def is_infinity(self):
return self == float('inf') or self == -float('inf')
def is_nan(self):
return self != self # nan!=nan evaluates to True
def is_finite(self):
return not (is_nan(self) or is_infinity(self))
class JsException(Exception):
def __init__(self, typ=None, message=None, throw=None):
if typ is None and message is None and throw is None:
# it means its the trasnlator based error (old format), do nothing
self._translator_based = True
else:
assert throw is None or (typ is None
and message is None), (throw, typ,
message)
self._translator_based = False
self.typ = typ
self.message = message
self.throw = throw
def get_thrown_value(self, space):
if self.throw is not None:
return self.throw
else:
return space.NewError(self.typ, self.message)
def __str__(self):
if self._translator_based:
if self.mes.Class == 'Error':
return self.mes.callprop('toString').value
else:
return self.mes.to_string().value
else:
if self.throw is not None:
from conversions import to_string
return to_string(self.throw)
else:
return self.typ + ': ' + self.message
def MakeError(typ, message=u'no info', throw=None):
return JsException(typ,
unicode(message) if message is not None else message,
throw)
def value_from_js_exception(js_exception, space):
if js_exception.throw is not None:
return js_exception.throw
else:
return space.NewError(js_exception.typ, js_exception.message)

View file

@ -0,0 +1,92 @@
from base import *
from simplex import *
class Space(object):
def __init__(self):
self.GlobalObj = None
self.ctx = None
self.byte_generator = None
self.Number = None
self.String = None
self.Boolean = None
self.RegExp = None
self.Object = None
self.Array = None
self.Function = None
self.BooleanPrototype = None
self.NumberPrototype = None
self.StringPrototype = None
self.FunctionPrototype = None
self.ArrayPrototype = None
self.RegExpPrototype = None
self.DatePrototype = None
self.ObjectPrototype = None
self.ErrorPrototype = None
self.EvalErrorPrototype = None
self.RangeErrorPrototype = None
self.ReferenceErrorPrototype = None
self.SyntaxErrorPrototype = None
self.TypeErrorPrototype = None
self.URIErrorPrototype = None
self.interpreter = None
@property
def ERROR_TYPES(self):
return {
'Error': self.ErrorPrototype,
'EvalError': self.EvalErrorPrototype,
'RangeError': self.RangeErrorPrototype,
'ReferenceError': self.ReferenceErrorPrototype,
'SyntaxError': self.SyntaxErrorPrototype,
'TypeError': self.TypeErrorPrototype,
'URIError': self.URIErrorPrototype,
}
def get_global_environment(self):
return self.GlobalCtx.variable_environment()
def NewObject(self):
return PyJsObject(self.ObjectPrototype)
def NewFunction(self, code, ctx, params, name, is_declaration,
definitions):
return PyJsFunction(
code,
ctx,
params,
name,
self,
is_declaration,
definitions,
prototype=self.FunctionPrototype)
def NewDate(self, value):
return PyJsDate(value, self.DatePrototype)
def NewArray(self, length=0):
return PyJsArray(length, self.ArrayPrototype)
def NewError(self, typ, message):
return PyJsError(message, self.ERROR_TYPES[typ])
def NewRegExp(self, body, flags):
return PyJsRegExp(body, flags, self.RegExpPrototype)
def ConstructArray(self, py_arr):
''' note py_arr elems are NOT converted to PyJs types!'''
arr = self.NewArray(len(py_arr))
arr._init(py_arr)
return arr
def ConstructObject(self, py_obj):
''' note py_obj items are NOT converted to PyJs types! '''
obj = self.NewObject()
for k, v in py_obj.items():
obj.put(unicode(k), v)
return obj

View file

@ -0,0 +1,62 @@
from timeit import timeit
from collections import namedtuple
from array import array
from itertools import izip
from collections import deque
class Y(object):
UUU = 88
def __init__(self, x):
self.x = x
def s(self, x):
return self.x + 1
class X(Y):
A = 10
B = 2
C = 4
D = 9
def __init__(self, x):
self.x = x
self.stack = []
self.par = super(X, self)
def s(self, x):
pass
def __add__(self, other):
return self.x + other.x
def another(self):
return Y.s(self, 1)
def yet_another(self):
return self.par.s(1)
def add(a, b):
return a.x + b.x
t = []
Type = None
try:
print timeit(
"""
t.append(4)
t.pop()
""",
"from __main__ import X,Y,namedtuple,array,t,add,Type, izip",
number=1000000)
except:
raise

View file

@ -0,0 +1,28 @@
def to_key(literal_or_identifier):
''' returns string representation of this object'''
if literal_or_identifier['type'] == 'Identifier':
return literal_or_identifier['name']
elif literal_or_identifier['type'] == 'Literal':
k = literal_or_identifier['value']
if isinstance(k, float):
return unicode(float_repr(k))
elif 'regex' in literal_or_identifier:
return compose_regex(k)
elif isinstance(k, bool):
return u'true' if k else u'false'
elif k is None:
return u'null'
else:
return unicode(k)
def compose_regex(val):
reg, flags = val
# reg = REGEXP_CONVERTER._unescape_string(reg)
return u'/%s/%s' % (reg, flags)
def float_repr(f):
if int(f) == f:
return unicode(repr(int(f)))
return unicode(repr(f))

View file

@ -0,0 +1 @@
__author__ = 'Piotrek'

View file

@ -0,0 +1,308 @@
from string import ascii_lowercase, digits
##################################
StringName = u'PyJsConstantString%d_'
NumberName = u'PyJsConstantNumber%d_'
RegExpName = u'PyJsConstantRegExp%d_'
##################################
ALPHAS = set(ascii_lowercase + ascii_lowercase.upper())
NUMS = set(digits)
IDENTIFIER_START = ALPHAS.union(NUMS)
ESCAPE_CHARS = {'n', '0', 'b', 'f', 'r', 't', 'v', '"', "'", '\\'}
OCTAL = {'0', '1', '2', '3', '4', '5', '6', '7'}
HEX = set('0123456789abcdefABCDEF')
from utils import *
IDENTIFIER_PART = IDENTIFIER_PART.union({'.'})
def _is_cancelled(source, n):
cancelled = False
k = 0
while True:
k += 1
if source[n - k] != '\\':
break
cancelled = not cancelled
return cancelled
def _ensure_regexp(source, n): #<- this function has to be improved
'''returns True if regexp starts at n else returns False
checks whether it is not a division '''
markers = '(+~"\'=[%:?!*^|&-,;/\\'
k = 0
while True:
k += 1
if n - k < 0:
return True
char = source[n - k]
if char in markers:
return True
if char != ' ' and char != '\n':
break
return False
def parse_num(source, start, charset):
"""Returns a first index>=start of chat not in charset"""
while start < len(source) and source[start] in charset:
start += 1
return start
def parse_exponent(source, start):
"""returns end of exponential, raises SyntaxError if failed"""
if not source[start] in {'e', 'E'}:
if source[start] in IDENTIFIER_PART:
raise SyntaxError('Invalid number literal!')
return start
start += 1
if source[start] in {'-', '+'}:
start += 1
FOUND = False
# we need at least one dig after exponent
while source[start] in NUMS:
FOUND = True
start += 1
if not FOUND or source[start] in IDENTIFIER_PART:
raise SyntaxError('Invalid number literal!')
return start
def remove_constants(source):
'''Replaces Strings and Regexp literals in the source code with
identifiers and *removes comments*. Identifier is of the format:
PyJsStringConst(String const number)_ - for Strings
PyJsRegExpConst(RegExp const number)_ - for RegExps
Returns dict which relates identifier and replaced constant.
Removes single line and multiline comments from JavaScript source code
Pseudo comments (inside strings) will not be removed.
For example this line:
var x = "/*PSEUDO COMMENT*/ TEXT //ANOTHER PSEUDO COMMENT"
will be unaltered'''
source = ' ' + source + '\n'
comments = []
inside_comment, single_comment = False, False
inside_single, inside_double = False, False
inside_regexp = False
regexp_class_count = 0
n = 0
while n < len(source):
char = source[n]
if char == '"' and not (inside_comment or inside_single
or inside_regexp):
if not _is_cancelled(source, n):
if inside_double:
inside_double[1] = n + 1
comments.append(inside_double)
inside_double = False
else:
inside_double = [n, None, 0]
elif char == "'" and not (inside_comment or inside_double
or inside_regexp):
if not _is_cancelled(source, n):
if inside_single:
inside_single[1] = n + 1
comments.append(inside_single)
inside_single = False
else:
inside_single = [n, None, 0]
elif (inside_single or inside_double):
if char in LINE_TERMINATOR:
if _is_cancelled(source, n):
if char == CR and source[n + 1] == LF:
n += 1
n += 1
continue
else:
raise SyntaxError(
'Invalid string literal. Line terminators must be escaped!'
)
else:
if inside_comment:
if single_comment:
if char in LINE_TERMINATOR:
inside_comment[1] = n
comments.append(inside_comment)
inside_comment = False
single_comment = False
else: # Multiline
if char == '/' and source[n - 1] == '*':
inside_comment[1] = n + 1
comments.append(inside_comment)
inside_comment = False
elif inside_regexp:
if not quiting_regexp:
if char in LINE_TERMINATOR:
raise SyntaxError(
'Invalid regexp literal. Line terminators cant appear!'
)
if _is_cancelled(source, n):
n += 1
continue
if char == '[':
regexp_class_count += 1
elif char == ']':
regexp_class_count = max(regexp_class_count - 1, 0)
elif char == '/' and not regexp_class_count:
quiting_regexp = True
else:
if char not in IDENTIFIER_START:
inside_regexp[1] = n
comments.append(inside_regexp)
inside_regexp = False
elif char == '/' and source[n - 1] == '/':
single_comment = True
inside_comment = [n - 1, None, 1]
elif char == '*' and source[n - 1] == '/':
inside_comment = [n - 1, None, 1]
elif char == '/' and source[n + 1] not in ('/', '*'):
if not _ensure_regexp(source, n): #<- improve this one
n += 1
continue #Probably just a division
quiting_regexp = False
inside_regexp = [n, None, 2]
elif not (inside_comment or inside_regexp):
if (char in NUMS and
source[n - 1] not in IDENTIFIER_PART) or char == '.':
if char == '.':
k = parse_num(source, n + 1, NUMS)
if k == n + 1: # just a stupid dot...
n += 1
continue
k = parse_exponent(source, k)
elif char == '0' and source[n + 1] in {
'x', 'X'
}: #Hex number probably
k = parse_num(source, n + 2, HEX)
if k == n + 2 or source[k] in IDENTIFIER_PART:
raise SyntaxError('Invalid hex literal!')
else: #int or exp or flot or exp flot
k = parse_num(source, n + 1, NUMS)
if source[k] == '.':
k = parse_num(source, k + 1, NUMS)
k = parse_exponent(source, k)
comments.append((n, k, 3))
n = k
continue
n += 1
res = ''
start = 0
count = 0
constants = {}
for end, next_start, typ in comments:
res += source[start:end]
start = next_start
if typ == 0: # String
name = StringName
elif typ == 1: # comment
continue
elif typ == 2: # regexp
name = RegExpName
elif typ == 3: # number
name = NumberName
else:
raise RuntimeError()
res += ' ' + name % count + ' '
constants[name % count] = source[end:next_start]
count += 1
res += source[start:]
# remove this stupid white space
for e in WHITE:
res = res.replace(e, ' ')
res = res.replace(CR + LF, '\n')
for e in LINE_TERMINATOR:
res = res.replace(e, '\n')
return res.strip(), constants
def recover_constants(py_source,
replacements): #now has n^2 complexity. improve to n
'''Converts identifiers representing Js constants to the PyJs constants
PyJsNumberConst_1_ which has the true value of 5 will be converted to PyJsNumber(5)'''
for identifier, value in replacements.iteritems():
if identifier.startswith('PyJsConstantRegExp'):
py_source = py_source.replace(identifier,
'JsRegExp(%s)' % repr(value))
elif identifier.startswith('PyJsConstantString'):
py_source = py_source.replace(
identifier, 'Js(u%s)' % unify_string_literals(value))
else:
py_source = py_source.replace(identifier, 'Js(%s)' % value)
return py_source
def unify_string_literals(js_string):
"""this function parses the string just like javascript
for example literal '\d' in JavaScript would be interpreted
as 'd' - backslash would be ignored and in Pyhon this
would be interpreted as '\\d' This function fixes this problem."""
n = 0
res = ''
limit = len(js_string)
while n < limit:
char = js_string[n]
if char == '\\':
new, n = do_escape(js_string, n)
res += new
else:
res += char
n += 1
return res
def unify_regexp_literals(js):
pass
def do_escape(source, n):
"""Its actually quite complicated to cover every case :)
http://www.javascriptkit.com/jsref/escapesequence.shtml"""
if not n + 1 < len(source):
return '' # not possible here but can be possible in general case.
if source[n + 1] in LINE_TERMINATOR:
if source[n + 1] == CR and n + 2 < len(source) and source[n + 2] == LF:
return source[n:n + 3], n + 3
return source[n:n + 2], n + 2
if source[n + 1] in ESCAPE_CHARS:
return source[n:n + 2], n + 2
if source[n + 1] in {'x', 'u'}:
char, length = ('u', 4) if source[n + 1] == 'u' else ('x', 2)
n += 2
end = parse_num(source, n, HEX)
if end - n < length:
raise SyntaxError('Invalid escape sequence!')
#if length==4:
# return unichr(int(source[n:n+4], 16)), n+4 # <- this was a very bad way of solving this problem :)
return source[n - 2:n + length], n + length
if source[n + 1] in OCTAL:
n += 1
end = parse_num(source, n, OCTAL)
end = min(end, n + 3) # cant be longer than 3
# now the max allowed is 377 ( in octal) and 255 in decimal
max_num = 255
num = 0
len_parsed = 0
for e in source[n:end]:
cand = 8 * num + int(e)
if cand > max_num:
break
num = cand
len_parsed += 1
# we have to return in a different form because python may want to parse more...
# for example '\777' will be parsed by python as a whole while js will use only \77
return '\\' + hex(num)[1:], n + len_parsed
return source[n + 1], n + 2
#####TEST######
if __name__ == '__main__':
test = ('''
''')
t, d = remove_constants(test)
print t, d

View file

@ -0,0 +1,83 @@
"""
exp_translate routine:
It takes a single line of JS code and returns a SINGLE line of Python code.
Note var is not present here because it was removed in previous stages. Also remove this useless void keyword
If case of parsing errors it must return a pos of error.
1. Convert all assignment operations to put operations, this may be hard :( DONE, wasn't that bad
2. Convert all gets and calls to get and callprop.
3. Convert unary operators like typeof, new, !, delete, ++, --
Delete can be handled by replacing last get method with delete.
4. Convert remaining operators that are not handled by python:
&&, || <= these should be easy simply replace && by and and || by or
=== and !==
comma operator , in, instanceof and finally :?
NOTES:
Strings and other literals are not present so each = means assignment
"""
from utils import *
from jsparser import *
def exps_translator(js):
#Check () {} and [] nums
ass = assignment_translator(js)
# Step 1
def assignment_translator(js):
sep = js.split(',')
res = sep[:]
for i, e in enumerate(sep):
if '=' not in e: # no need to convert
continue
res[i] = bass_translator(e)
return ','.join(res)
def bass_translator(s):
# I hope that I will not have to fix any bugs here because it will be terrible
if '(' in s or '[' in s:
converted = ''
for e in bracket_split(s, ['()', '[]'], strip=False):
if e[0] == '(':
converted += '(' + bass_translator(e[1:-1]) + ')'
elif e[0] == '[':
converted += '[' + bass_translator(e[1:-1]) + ']'
else:
converted += e
s = converted
if '=' not in s:
return s
ass = reversed(s.split('='))
last = ass.next()
res = last
for e in ass:
op = ''
if e[-1] in OP_METHODS: #increment assign like +=
op = ', "' + e[-1] + '"'
e = e[:-1]
cand = e.strip(
'() ') # (a) = 40 is valid so we need to transform '(a) ' to 'a'
if not is_property_accessor(cand): # it is not a property assignment
if not is_lval(cand) or is_internal(cand):
raise SyntaxError('Invalid left-hand side in assignment')
res = 'var.put(%s, %s%s)' % (cand.__repr__(), res, op)
elif cand[-1] == ']': # property assignment via []
c = list(bracket_split(cand, ['[]'], strip=False))
meth, prop = ''.join(c[:-1]).strip(), c[-1][1:-1].strip(
) #this does not have to be a string so dont remove
#() because it can be a call
res = '%s.put(%s, %s%s)' % (meth, prop, res, op)
else: # Prop set via '.'
c = cand.rfind('.')
meth, prop = cand[:c].strip(), cand[c + 1:].strip('() ')
if not is_lval(prop):
raise SyntaxError('Invalid left-hand side in assignment')
res = '%s.put(%s, %s%s)' % (meth, prop.__repr__(), res, op)
return res
if __name__ == '__main__':
print bass_translator('3.ddsd = 40')

View file

@ -0,0 +1,480 @@
"""This module translates JS flow into PY flow.
Translates:
IF ELSE
DO WHILE
WHILE
FOR 123
FOR iter
CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH
"""
from utils import *
from jsparser import *
from nodevisitor import exp_translator
import random
TO_REGISTER = []
CONTINUE_LABEL = 'JS_CONTINUE_LABEL_%s'
BREAK_LABEL = 'JS_BREAK_LABEL_%s'
PREPARE = '''HOLDER = var.own.get(NAME)\nvar.force_own_put(NAME, PyExceptionToJs(PyJsTempException))\n'''
RESTORE = '''if HOLDER is not None:\n var.own[NAME] = HOLDER\nelse:\n del var.own[NAME]\ndel HOLDER\n'''
TRY_CATCH = '''%stry:\nBLOCKfinally:\n%s''' % (PREPARE, indent(RESTORE))
def get_continue_label(label):
return CONTINUE_LABEL % label.encode('hex')
def get_break_label(label):
return BREAK_LABEL % label.encode('hex')
def pass_until(source, start, tokens=(';', )):
while start < len(source) and source[start] not in tokens:
start += 1
return start + 1
def do_bracket_exp(source, start, throw=True):
bra, cand = pass_bracket(source, start, '()')
if throw and not bra:
raise SyntaxError('Missing bracket expression')
bra = exp_translator(bra[1:-1])
if throw and not bra:
raise SyntaxError('Empty bracket condition')
return bra, cand if bra else start
def do_if(source, start):
start += 2 # pass this if
bra, start = do_bracket_exp(source, start, throw=True)
statement, start = do_statement(source, start)
if statement is None:
raise SyntaxError('Invalid if statement')
translated = 'if %s:\n' % bra + indent(statement)
elseif = except_keyword(source, start, 'else')
is_elseif = False
if elseif:
start = elseif
if except_keyword(source, start, 'if'):
is_elseif = True
elseif, start = do_statement(source, start)
if elseif is None:
raise SyntaxError('Invalid if statement)')
if is_elseif:
translated += 'el' + elseif
else:
translated += 'else:\n' + indent(elseif)
return translated, start
def do_statement(source, start):
"""returns none if not found other functions that begin with 'do_' raise
also this do_ type function passes white space"""
start = pass_white(source, start)
# start is the fist position after initial start that is not a white space or \n
if not start < len(source): #if finished parsing return None
return None, start
if any(startswith_keyword(source[start:], e) for e in {'case', 'default'}):
return None, start
rest = source[start:]
for key, meth in KEYWORD_METHODS.iteritems(
): # check for statements that are uniquely defined by their keywords
if rest.startswith(key):
# has to startwith this keyword and the next letter after keyword must be either EOF or not in IDENTIFIER_PART
if len(key) == len(rest) or rest[len(key)] not in IDENTIFIER_PART:
return meth(source, start)
if rest[0] == '{': #Block
return do_block(source, start)
# Now only label and expression left
cand = parse_identifier(source, start, False)
if cand is not None: # it can mean that its a label
label, cand_start = cand
cand_start = pass_white(source, cand_start)
if source[cand_start] == ':':
return do_label(source, start)
return do_expression(source, start)
def do_while(source, start):
start += 5 # pass while
bra, start = do_bracket_exp(source, start, throw=True)
statement, start = do_statement(source, start)
if statement is None:
raise SyntaxError('Missing statement to execute in while loop!')
return 'while %s:\n' % bra + indent(statement), start
def do_dowhile(source, start):
start += 2 # pass do
statement, start = do_statement(source, start)
if statement is None:
raise SyntaxError('Missing statement to execute in do while loop!')
start = except_keyword(source, start, 'while')
if not start:
raise SyntaxError('Missing while keyword in do-while loop')
bra, start = do_bracket_exp(source, start, throw=True)
statement += 'if not %s:\n' % bra + indent('break\n')
return 'while 1:\n' + indent(statement), start
def do_block(source, start):
bra, start = pass_bracket(source, start, '{}')
#print source[start:], bra
#return bra +'\n', start
if bra is None:
raise SyntaxError('Missing block ( {code} )')
code = ''
bra = bra[1:-1] + ';'
bra_pos = 0
while bra_pos < len(bra):
st, bra_pos = do_statement(bra, bra_pos)
if st is None:
break
code += st
bra_pos = pass_white(bra, bra_pos)
if bra_pos < len(bra):
raise SyntaxError('Block has more code that could not be parsed:\n' +
bra[bra_pos:])
return code, start
def do_empty(source, start):
return 'pass\n', start + 1
def do_expression(source, start):
start = pass_white(source, start)
end = pass_until(source, start, tokens=(';', ))
if end == start + 1: #empty statement
return 'pass\n', end
# AUTOMATIC SEMICOLON INSERTION FOLLOWS
# Without ASI this function would end with: return exp_translator(source[start:end].rstrip(';'))+'\n', end
# ASI makes things a bit more complicated:
# we will try to parse as much as possible, inserting ; in place of last new line in case of error
rev = False
rpos = 0
while True:
try:
code = source[start:end].rstrip(';')
cand = exp_translator(code) + '\n', end
just_to_test = compile(cand[0], '', 'exec')
return cand
except Exception as e:
if not rev:
rev = source[start:end][::-1]
lpos = rpos
while True:
rpos = pass_until(rev, rpos, LINE_TERMINATOR)
if rpos >= len(rev):
raise
if filter(lambda x: x not in SPACE, rev[lpos:rpos]):
break
end = start + len(rev) - rpos + 1
def do_var(source, start):
#todo auto ; insertion
start += 3 #pass var
end = pass_until(source, start, tokens=(';', ))
defs = argsplit(
source[start:end - 1]
) # defs is the list of defined vars with optional initializer
code = ''
for de in defs:
var, var_end = parse_identifier(de, 0, True)
TO_REGISTER.append(var)
var_end = pass_white(de, var_end)
if var_end < len(
de
): # we have something more to parse... It has to start with =
if de[var_end] != '=':
raise SyntaxError(
'Unexpected initializer in var statement. Expected "=", got "%s"'
% de[var_end])
code += exp_translator(de) + '\n'
if not code.strip():
code = 'pass\n'
return code, end
def do_label(source, start):
label, end = parse_identifier(source, start)
end = pass_white(source, end)
#now source[end] must be :
assert source[end] == ':'
end += 1
inside, end = do_statement(source, end)
if inside is None:
raise SyntaxError('Missing statement after label')
defs = ''
if inside.startswith('while ') or inside.startswith(
'for ') or inside.startswith('#for'):
# we have to add contine label as well...
# 3 or 1 since #for loop type has more lines before real for.
sep = 1 if not inside.startswith('#for') else 3
cont_label = get_continue_label(label)
temp = inside.split('\n')
injected = 'try:\n' + '\n'.join(temp[sep:])
injected += 'except %s:\n pass\n' % cont_label
inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
defs += 'class %s(Exception): pass\n' % cont_label
break_label = get_break_label(label)
inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label)
defs += 'class %s(Exception): pass\n' % break_label
return defs + inside, end
def do_for(source, start):
start += 3 # pass for
entered = start
bra, start = pass_bracket(source, start, '()')
inside, start = do_statement(source, start)
if inside is None:
raise SyntaxError('Missing statement after for')
bra = bra[1:-1]
if ';' in bra:
init = argsplit(bra, ';')
if len(init) != 3:
raise SyntaxError('Invalid for statement')
args = []
for i, item in enumerate(init):
end = pass_white(item, 0)
if end == len(item):
args.append('' if i != 1 else '1')
continue
if not i and except_keyword(item, end, 'var') is not None:
# var statement
args.append(do_var(item, end)[0])
continue
args.append(do_expression(item, end)[0])
return '#for JS loop\n%swhile %s:\n%s%s\n' % (
args[0], args[1].strip(), indent(inside), indent(args[2])), start
# iteration
end = pass_white(bra, 0)
register = False
if bra[end:].startswith('var '):
end += 3
end = pass_white(bra, end)
register = True
name, end = parse_identifier(bra, end)
if register:
TO_REGISTER.append(name)
end = pass_white(bra, end)
if bra[end:end + 2] != 'in' or bra[end + 2] in IDENTIFIER_PART:
#print source[entered-10:entered+50]
raise SyntaxError('Invalid "for x in y" statement')
end += 2 # pass in
exp = exp_translator(bra[end:])
res = 'for temp in %s:\n' % exp
res += indent('var.put(%s, temp)\n' % name.__repr__()) + indent(inside)
return res, start
# todo - IMPORTANT
def do_continue(source, start, name='continue'):
start += len(name) #pass continue
start = pass_white(source, start)
if start < len(source) and source[start] == ';':
return '%s\n' % name, start + 1
# labeled statement or error
label, start = parse_identifier(source, start)
start = pass_white(source, start)
if start < len(source) and source[start] != ';':
raise SyntaxError('Missing ; after label name in %s statement' % name)
return 'raise %s("%s")\n' % (get_continue_label(label)
if name == 'continue' else
get_break_label(label), name), start + 1
def do_break(source, start):
return do_continue(source, start, 'break')
def do_return(source, start):
start += 6 # pass return
end = source.find(';', start) + 1
if end == -1:
end = len(source)
trans = exp_translator(source[start:end].rstrip(';'))
return 'return %s\n' % (trans if trans else "var.get('undefined')"), end
# todo later?- Also important
def do_throw(source, start):
start += 5 # pass throw
end = source.find(';', start) + 1
if not end:
end = len(source)
trans = exp_translator(source[start:end].rstrip(';'))
if not trans:
raise SyntaxError('Invalid throw statement: nothing to throw')
res = 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans
return res, end
def do_try(source, start):
start += 3 # pass try
block, start = do_block(source, start)
result = 'try:\n%s' % indent(block)
catch = except_keyword(source, start, 'catch')
if catch:
bra, catch = pass_bracket(source, catch, '()')
bra = bra[1:-1]
identifier, bra_end = parse_identifier(bra, 0)
holder = 'PyJsHolder_%s_%d' % (identifier.encode('hex'),
random.randrange(1e8))
identifier = identifier.__repr__()
bra_end = pass_white(bra, bra_end)
if bra_end < len(bra):
raise SyntaxError('Invalid content of catch statement')
result += 'except PyJsException as PyJsTempException:\n'
block, catch = do_block(source, catch)
# fill in except ( catch ) block and remember to recover holder variable to its previous state
result += indent(
TRY_CATCH.replace('HOLDER', holder).replace('NAME',
identifier).replace(
'BLOCK',
indent(block)))
start = max(catch, start)
final = except_keyword(source, start, 'finally')
if not (final or catch):
raise SyntaxError(
'Try statement has to be followed by catch or finally')
if not final:
return result, start
# translate finally statement
block, start = do_block(source, final)
return result + 'finally:\n%s' % indent(block), start
def do_debugger(source, start):
start += 8 # pass debugger
end = pass_white(source, start)
if end < len(source) and source[end] == ';':
end += 1
return 'pass\n', end #ignore errors...
# todo automatic ; insertion. fuck this crappy feature
# Least important
def do_switch(source, start):
start += 6 # pass switch
code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n')
# parse value of check
val, start = pass_bracket(source, start, '()')
if val is None:
raise SyntaxError('Missing () after switch statement')
if not val.strip():
raise SyntaxError('Missing content inside () after switch statement')
code = code % exp_translator(val)
bra, start = pass_bracket(source, start, '{}')
if bra is None:
raise SyntaxError('Missing block {} after switch statement')
bra_pos = 0
bra = bra[1:-1] + ';'
while True:
case = except_keyword(bra, bra_pos, 'case')
default = except_keyword(bra, bra_pos, 'default')
assert not (case and default)
if case or default: # this ?: expression makes things much harder....
case_code = None
if case:
case_code = 'if SWITCHED or PyJsStrictEq(CONDITION, %s):\n'
# we are looking for a first : with count 1. ? gives -1 and : gives +1.
count = 0
for pos, e in enumerate(bra[case:], case):
if e == '?':
count -= 1
elif e == ':':
count += 1
if count == 1:
break
else:
raise SyntaxError(
'Missing : token after case in switch statement')
case_condition = exp_translator(
bra[case:pos]) # switch {case CONDITION: statements}
case_code = case_code % case_condition
case = pos + 1
if default:
case = except_token(bra, default, ':')
case_code = 'if True:\n'
# now parse case statements (things after ':' )
cand, case = do_statement(bra, case)
while cand:
case_code += indent(cand)
cand, case = do_statement(bra, case)
case_code += indent('SWITCHED = True\n')
code += indent(case_code)
bra_pos = case
else:
break
# prevent infinite loop :)
code += indent('break\n')
return code, start
def do_pyimport(source, start):
start += 8
lib, start = parse_identifier(source, start)
jlib = 'PyImport_%s' % lib
code = 'import %s as %s\n' % (lib, jlib)
#check whether valid lib name...
try:
compile(code, '', 'exec')
except:
raise SyntaxError(
'Invalid Python module name (%s) in pyimport statement' % lib)
# var.pyimport will handle module conversion to PyJs object
code += 'var.pyimport(%s, %s)\n' % (repr(lib), jlib)
return code, start
def do_with(source, start):
raise NotImplementedError('With statement is not implemented yet :(')
KEYWORD_METHODS = {
'do': do_dowhile,
'while': do_while,
'if': do_if,
'throw': do_throw,
'return': do_return,
'continue': do_continue,
'break': do_break,
'try': do_try,
'for': do_for,
'switch': do_switch,
'var': do_var,
'debugger': do_debugger, # this one does not do anything
'with': do_with,
'pyimport': do_pyimport
}
#Also not specific statements (harder to detect)
# Block {}
# Expression or Empty Statement
# Label
#
# Its easy to recognize block but harder to distinguish between label and expression statement
def translate_flow(source):
"""Source cant have arrays, object, constant or function literals.
Returns PySource and variables to register"""
global TO_REGISTER
TO_REGISTER = []
return do_block('{%s}' % source, 0)[0], TO_REGISTER
if __name__ == '__main__':
#print do_dowhile('do {} while(k+f)', 0)[0]
#print 'e: "%s"'%do_expression('++(c?g:h); mj', 0)[0]
print translate_flow('a; yimport test')[0]

View file

@ -0,0 +1,98 @@
"""This module removes JS functions from source code"""
from jsparser import *
from utils import *
INLINE_NAME = 'PyJsLvalInline%d_'
INLINE_COUNT = 0
PRE_EXP_STARTS = {
'return', 'new', 'void', 'throw', 'typeof', 'in', 'instanceof'
}
PRE_ALLOWED = IDENTIFIER_PART.union({';', '{', '}', ']', ')', ':'})
INCREMENTS = {'++', '--'}
def reset_inline_count():
global INLINE_COUNT
INLINE_COUNT = 0
def remove_functions(source, all_inline=False):
"""removes functions and returns new source, and 2 dicts.
first dict with removed hoisted(global) functions and second with replaced inline functions"""
global INLINE_COUNT
inline = {}
hoisted = {}
n = 0
limit = len(source) - 9 # 8 is length of 'function'
res = ''
last = 0
while n < limit:
if n and source[n - 1] in IDENTIFIER_PART:
n += 1
continue
if source[n:n + 8] == 'function' and source[n +
8] not in IDENTIFIER_PART:
if source[:n].rstrip().endswith(
'.'): # allow function as a property name :)
n += 1
continue
if source[n + 8:].lstrip().startswith(
':'): # allow functions inside objects...
n += 1
continue
entered = n
res += source[last:n]
name = ''
n = pass_white(source, n + 8)
if source[n] in IDENTIFIER_START: # hoisted function
name, n = parse_identifier(source, n)
args, n = pass_bracket(source, n, '()')
if not args:
raise SyntaxError('Function misses bracket with argnames ()')
args = args.strip('() \n')
args = tuple(parse_identifier(e, 0)[0]
for e in argsplit(args)) if args else ()
if len(args) - len(set(args)):
# I know its legal in JS but python does not allow duplicate argnames
# I will not work around it
raise SyntaxError(
'Function has duplicate argument names. Its not legal in this implementation. Sorry.'
)
block, n = pass_bracket(source, n, '{}')
if not block:
raise SyntaxError(
'Function does not have any code block to execute')
mixed = False # named function expression flag
if name and not all_inline:
# Here I will distinguish between named function expression (mixed) and a function statement
before = source[:entered].rstrip()
if any(endswith_keyword(before, e) for e in PRE_EXP_STARTS):
#print 'Ended ith keyword'
mixed = True
elif before and before[-1] not in PRE_ALLOWED and not before[
-2:] in INCREMENTS:
#print 'Ended with'+repr(before[-1]), before[-1]=='}'
mixed = True
else:
#print 'FUNCTION STATEMENT'
#its a function statement.
# todo remove fucking label if present!
hoisted[name] = block, args
if not name or mixed or all_inline: # its a function expression (can be both named and not named)
#print 'FUNCTION EXPRESSION'
INLINE_COUNT += 1
iname = INLINE_NAME % INLINE_COUNT # inline name
res += ' ' + iname
inline['%s@%s' % (
iname, name
)] = block, args #here added real name at the end because it has to be added to the func scope
last = n
else:
n += 1
res += source[last:]
return res, hoisted, inline
if __name__ == '__main__':
print remove_functions(
'5+5 function n (functiona ,functionaj) {dsd s, dsdd}')

View file

@ -0,0 +1,326 @@
"""
The process of translating JS will go like that: # TOP = 'imports and scope set'
1. Remove all the comments
2. Replace number, string and regexp literals with markers
4. Remove global Functions and move their translation to the TOP. Also add register code there.
5. Replace inline functions with lvals
6. Remove List and Object literals and replace them with lvals
7. Find and remove var declarations, generate python register code that would go on TOP.
Here we should be left with global code only where 1 line of js code = 1 line of python code.
Routine translating this code should be called glob_translate:
1. Search for outer structures and translate them using glob and inside using exps_translate
exps_translate routine:
1. Remove outer {}
2. Split lines at ;
3. Convert line by line using exp_translate
4. In case of error in 3 try to insert ; according to ECMA rules and repeat 3.
exp_translate routine:
It takes a single line of JS code and returns a SINGLE line of Python code.
Note var is not present here because it was removed in previous stages.
If case of parsing errors it must return a pos of error.
1. Convert all assignment operations to put operations, this may be hard :(
2. Convert all gets and calls to get and callprop.
3. Convert unary operators like typeof, new, !, delete.
Delete can be handled by replacing last get method with delete.
4. Convert remaining operators that are not handled by python eg: === and ,
lval format PyJsLvalNR
marker PyJs(TYPE_NAME)(NR)
TODO
1. Number literal replacement
2. Array literal replacement
3. Object literal replacement
5. Function replacement
4. Literal replacement translators
"""
from utils import *
OP_METHODS = {
'*': '__mul__',
'/': '__div__',
'%': '__mod__',
'+': '__add__',
'-': '__sub__',
'<<': '__lshift__',
'>>': '__rshift__',
'&': '__and__',
'^': '__xor__',
'|': '__or__'
}
def dbg(source):
try:
with open('C:\Users\Piotrek\Desktop\dbg.py', 'w') as f:
f.write(source)
except:
pass
def indent(lines, ind=4):
return ind * ' ' + lines.replace('\n', '\n' + ind * ' ').rstrip(' ')
def inject_before_lval(source, lval, code):
if source.count(lval) > 1:
dbg(source)
print
print lval
raise RuntimeError('To many lvals (%s)' % lval)
elif not source.count(lval):
dbg(source)
print
print lval
assert lval not in source
raise RuntimeError('No lval found "%s"' % lval)
end = source.index(lval)
inj = source.rfind('\n', 0, end)
ind = inj
while source[ind + 1] == ' ':
ind += 1
ind -= inj
return source[:inj + 1] + indent(code, ind) + source[inj + 1:]
def bracket_split(source, brackets=('()', '{}', '[]'), strip=False):
"""DOES NOT RETURN EMPTY STRINGS (can only return empty bracket content if strip=True)"""
starts = [e[0] for e in brackets]
in_bracket = 0
n = 0
last = 0
while n < len(source):
e = source[n]
if not in_bracket and e in starts:
in_bracket = 1
start = n
b_start, b_end = brackets[starts.index(e)]
elif in_bracket:
if e == b_start:
in_bracket += 1
elif e == b_end:
in_bracket -= 1
if not in_bracket:
if source[last:start]:
yield source[last:start]
last = n + 1
yield source[start + strip:n + 1 - strip]
n += 1
if source[last:]:
yield source[last:]
def pass_bracket(source, start, bracket='()'):
"""Returns content of brackets with brackets and first pos after brackets
if source[start] is followed by some optional white space and brackets.
Otherwise None"""
e = bracket_split(source[start:], [bracket], False)
try:
cand = e.next()
except StopIteration:
return None, None
if not cand.strip(): #white space...
try:
res = e.next()
return res, start + len(cand) + len(res)
except StopIteration:
return None, None
elif cand[-1] == bracket[1]:
return cand, start + len(cand)
else:
return None, None
def startswith_keyword(start, keyword):
start = start.lstrip()
if start.startswith(keyword):
if len(keyword) < len(start):
if start[len(keyword)] in IDENTIFIER_PART:
return False
return True
return False
def endswith_keyword(ending, keyword):
ending = ending.rstrip()
if ending.endswith(keyword):
if len(keyword) < len(ending):
if ending[len(ending) - len(keyword) - 1] in IDENTIFIER_PART:
return False
return True
return False
def pass_white(source, start):
n = start
while n < len(source):
if source[n] in SPACE:
n += 1
else:
break
return n
def except_token(source, start, token, throw=True):
"""Token can be only a single char. Returns position after token if found. Otherwise raises syntax error if throw
otherwise returns None"""
start = pass_white(source, start)
if start < len(source) and source[start] == token:
return start + 1
if throw:
raise SyntaxError('Missing token. Expected %s' % token)
return None
def except_keyword(source, start, keyword):
""" Returns position after keyword if found else None
Note: skips white space"""
start = pass_white(source, start)
kl = len(keyword) #keyword len
if kl + start > len(source):
return None
if source[start:start + kl] != keyword:
return None
if kl + start < len(source) and source[start + kl] in IDENTIFIER_PART:
return None
return start + kl
def parse_identifier(source, start, throw=True):
"""passes white space from start and returns first identifier,
if identifier invalid and throw raises SyntaxError otherwise returns None"""
start = pass_white(source, start)
end = start
if not end < len(source):
if throw:
raise SyntaxError('Missing identifier!')
return None
if source[end] not in IDENTIFIER_START:
if throw:
raise SyntaxError('Invalid identifier start: "%s"' % source[end])
return None
end += 1
while end < len(source) and source[end] in IDENTIFIER_PART:
end += 1
if not is_valid_lval(source[start:end]):
if throw:
raise SyntaxError(
'Invalid identifier name: "%s"' % source[start:end])
return None
return source[start:end], end
def argsplit(args, sep=','):
"""used to split JS args (it is not that simple as it seems because
sep can be inside brackets).
pass args *without* brackets!
Used also to parse array and object elements, and more"""
parsed_len = 0
last = 0
splits = []
for e in bracket_split(args, brackets=['()', '[]', '{}']):
if e[0] not in {'(', '[', '{'}:
for i, char in enumerate(e):
if char == sep:
splits.append(args[last:parsed_len + i])
last = parsed_len + i + 1
parsed_len += len(e)
splits.append(args[last:])
return splits
def split_add_ops(text):
"""Specialized function splitting text at add/sub operators.
Operands are *not* translated. Example result ['op1', '+', 'op2', '-', 'op3']"""
n = 0
text = text.replace('++', '##').replace(
'--', '@@') #text does not normally contain any of these
spotted = False # set to true if noticed anything other than +- or white space
last = 0
while n < len(text):
e = text[n]
if e == '+' or e == '-':
if spotted:
yield text[last:n].replace('##', '++').replace('@@', '--')
yield e
last = n + 1
spotted = False
elif e == '/' or e == '*' or e == '%':
spotted = False
elif e != ' ':
spotted = True
n += 1
yield text[last:n].replace('##', '++').replace('@@', '--')
def split_at_any(text,
lis,
translate=False,
not_before=[],
not_after=[],
validitate=None):
""" doc """
lis.sort(key=lambda x: len(x), reverse=True)
last = 0
n = 0
text_len = len(text)
while n < text_len:
if any(text[:n].endswith(e)
for e in not_before): #Cant end with end before
n += 1
continue
for e in lis:
s = len(e)
if s + n > text_len:
continue
if validitate and not validitate(e, text[:n], text[n + s:]):
continue
if any(text[n + s:].startswith(e)
for e in not_after): #Cant end with end before
n += 1
break
if e == text[n:n + s]:
yield text[last:n] if not translate else translate(
text[last:n])
yield e
n += s
last = n
break
else:
n += 1
yield text[last:n] if not translate else translate(text[last:n])
def split_at_single(text, sep, not_before=[], not_after=[]):
"""Works like text.split(sep) but separated fragments
cant end with not_before or start with not_after"""
n = 0
lt, s = len(text), len(sep)
last = 0
while n < lt:
if not s + n > lt:
if sep == text[n:n + s]:
if any(text[last:n].endswith(e) for e in not_before):
pass
elif any(text[n + s:].startswith(e) for e in not_after):
pass
else:
yield text[last:n]
last = n + s
n += s - 1
n += 1
yield text[last:]

View file

@ -0,0 +1,562 @@
from jsparser import *
from utils import *
import re
from utils import *
#Note all white space sent to this module must be ' ' so no '\n'
REPL = {}
#PROBLEMS
# <<=, >>=, >>>=
# they are unusual so I will not fix that now. a++ +b works fine and a+++++b (a++ + ++b) does not work even in V8
ASSIGNMENT_MATCH = '(?<!=|!|<|>)=(?!=)'
def unary_validitator(keyword, before, after):
if keyword[-1] in IDENTIFIER_PART:
if not after or after[0] in IDENTIFIER_PART:
return False
if before and before[-1] in IDENTIFIER_PART: # I am not sure here...
return False
return True
def comb_validitator(keyword, before, after):
if keyword == 'instanceof' or keyword == 'in':
if before and before[-1] in IDENTIFIER_PART:
return False
elif after and after[0] in IDENTIFIER_PART:
return False
return True
def bracket_replace(code):
new = ''
for e in bracket_split(code, ['()', '[]'], False):
if e[0] == '[':
name = '#PYJSREPL' + str(len(REPL)) + '{'
new += name
REPL[name] = e
elif e[0] == '(': # can be a function call
name = '@PYJSREPL' + str(len(REPL)) + '}'
new += name
REPL[name] = e
else:
new += e
return new
class NodeVisitor:
def __init__(self, code):
self.code = code
def rl(self, lis, op):
"""performs this operation on a list from *right to left*
op must take 2 args
a,b,c => op(a, op(b, c))"""
it = reversed(lis)
res = trans(it.next())
for e in it:
e = trans(e)
res = op(e, res)
return res
def lr(self, lis, op):
"""performs this operation on a list from *left to right*
op must take 2 args
a,b,c => op(op(a, b), c)"""
it = iter(lis)
res = trans(it.next())
for e in it:
e = trans(e)
res = op(res, e)
return res
def translate(self):
"""Translates outer operation and calls translate on inner operation.
Returns fully translated code."""
if not self.code:
return ''
new = bracket_replace(self.code)
#Check comma operator:
cand = new.split(',') #every comma in new must be an operator
if len(cand) > 1: #LR
return self.lr(cand, js_comma)
#Check = operator:
# dont split at != or !== or == or === or <= or >=
#note <<=, >>= or this >>> will NOT be supported
# maybe I will change my mind later
# Find this crappy ?:
if '?' in new:
cond_ind = new.find('?')
tenary_start = 0
for ass in re.finditer(ASSIGNMENT_MATCH, new):
cand = ass.span()[1]
if cand < cond_ind:
tenary_start = cand
else:
break
actual_tenary = new[tenary_start:]
spl = ''.join(split_at_any(new, [':', '?'], translate=trans))
tenary_translation = transform_crap(spl)
assignment = new[:tenary_start] + ' PyJsConstantTENARY'
return trans(assignment).replace('PyJsConstantTENARY',
tenary_translation)
cand = list(split_at_single(new, '=', ['!', '=', '<', '>'], ['=']))
if len(cand) > 1: # RL
it = reversed(cand)
res = trans(it.next())
for e in it:
e = e.strip()
if not e:
raise SyntaxError('Missing left-hand in assignment!')
op = ''
if e[-2:] in OP_METHODS:
op = ',' + e[-2:].__repr__()
e = e[:-2]
elif e[-1:] in OP_METHODS:
op = ',' + e[-1].__repr__()
e = e[:-1]
e = trans(e)
#Now replace last get method with put and change args
c = list(bracket_split(e, ['()']))
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip(
) #strips just to make sure... I will remove it later
if beg[-4:] != '.get':
raise SyntaxError('Invalid left-hand side in assignment')
beg = beg[0:-3] + 'put'
arglist = arglist[0:-1] + ', ' + res + op + ')'
res = beg + arglist
return res
#Now check remaining 2 arg operators that are not handled by python
#They all have Left to Right (LR) associativity
order = [OR, AND, BOR, BXOR, BAND, EQS, COMPS, BSHIFTS, ADDS, MULTS]
# actually we dont need OR and AND because they can be handled easier. But just for fun
dangerous = ['<', '>']
for typ in order:
#we have to use special method for ADDS since they can be also unary operation +/++ or -/-- FUCK
if '+' in typ:
cand = list(split_add_ops(new))
else:
#dont translate. cant start or end on dangerous op.
cand = list(
split_at_any(
new,
typ.keys(),
False,
dangerous,
dangerous,
validitate=comb_validitator))
if not len(cand) > 1:
continue
n = 1
res = trans(cand[0])
if not res:
raise SyntaxError("Missing operand!")
while n < len(cand):
e = cand[n]
if not e:
raise SyntaxError("Missing operand!")
if n % 2:
op = typ[e]
else:
res = op(res, trans(e))
n += 1
return res
#Now replace unary operators - only they are left
cand = list(
split_at_any(
new, UNARY.keys(), False, validitate=unary_validitator))
if len(cand) > 1: #contains unary operators
if '++' in cand or '--' in cand: #it cant contain both ++ and --
if '--' in cand:
op = '--'
meths = js_post_dec, js_pre_dec
else:
op = '++'
meths = js_post_inc, js_pre_inc
pos = cand.index(op)
if cand[pos - 1].strip(): # post increment
a = cand[pos - 1]
meth = meths[0]
elif cand[pos + 1].strip(): #pre increment
a = cand[pos + 1]
meth = meths[1]
else:
raise SyntaxError('Invalid use of ++ operator')
if cand[pos + 2:]:
raise SyntaxError('Too many operands')
operand = meth(trans(a))
cand = cand[:pos - 1]
# now last cand should be operand and every other odd element should be empty
else:
operand = trans(cand[-1])
del cand[-1]
for i, e in enumerate(reversed(cand)):
if i % 2:
if e.strip():
raise SyntaxError('Too many operands')
else:
operand = UNARY[e](operand)
return operand
#Replace brackets
if new[0] == '@' or new[0] == '#':
if len(
list(bracket_split(new, ('#{', '@}')))
) == 1: # we have only one bracket, otherwise pseudobracket like @@....
assert new in REPL
if new[0] == '#':
raise SyntaxError(
'[] cant be used as brackets! Use () instead.')
return '(' + trans(REPL[new][1:-1]) + ')'
#Replace function calls and prop getters
# 'now' must be a reference like: a or b.c.d but it can have also calls or getters ( for example a["b"](3))
#From here @@ means a function call and ## means get operation (note they dont have to present)
it = bracket_split(new, ('#{', '@}'))
res = []
for e in it:
if e[0] != '#' and e[0] != '@':
res += [x.strip() for x in e.split('.')]
else:
res += [e.strip()]
# res[0] can be inside @@ (name)...
res = filter(lambda x: x, res)
if is_internal(res[0]):
out = res[0]
elif res[0][0] in {'#', '@'}:
out = '(' + trans(REPL[res[0]][1:-1]) + ')'
elif is_valid_lval(
res[0]) or res[0] in {'this', 'false', 'true', 'null'}:
out = 'var.get(' + res[0].__repr__() + ')'
else:
if is_reserved(res[0]):
raise SyntaxError('Unexpected reserved word: "%s"' % res[0])
raise SyntaxError('Invalid identifier: "%s"' % res[0])
if len(res) == 1:
return out
n = 1
while n < len(res): #now every func call is a prop call
e = res[n]
if e[0] == '@': # direct call
out += trans_args(REPL[e])
n += 1
continue
args = False #assume not prop call
if n + 1 < len(res) and res[n + 1][0] == '@': #prop call
args = trans_args(REPL[res[n + 1]])[1:]
if args != ')':
args = ',' + args
if e[0] == '#':
prop = trans(REPL[e][1:-1])
else:
if not is_lval(e):
raise SyntaxError('Invalid identifier: "%s"' % e)
prop = e.__repr__()
if args: # prop call
n += 1
out += '.callprop(' + prop + args
else: #prop get
out += '.get(' + prop + ')'
n += 1
return out
def js_comma(a, b):
return 'PyJsComma(' + a + ',' + b + ')'
def js_or(a, b):
return '(' + a + ' or ' + b + ')'
def js_bor(a, b):
return '(' + a + '|' + b + ')'
def js_bxor(a, b):
return '(' + a + '^' + b + ')'
def js_band(a, b):
return '(' + a + '&' + b + ')'
def js_and(a, b):
return '(' + a + ' and ' + b + ')'
def js_strict_eq(a, b):
return 'PyJsStrictEq(' + a + ',' + b + ')'
def js_strict_neq(a, b):
return 'PyJsStrictNeq(' + a + ',' + b + ')'
#Not handled by python in the same way like JS. For example 2==2==True returns false.
# In JS above would return true so we need brackets.
def js_abstract_eq(a, b):
return '(' + a + '==' + b + ')'
#just like ==
def js_abstract_neq(a, b):
return '(' + a + '!=' + b + ')'
def js_lt(a, b):
return '(' + a + '<' + b + ')'
def js_le(a, b):
return '(' + a + '<=' + b + ')'
def js_ge(a, b):
return '(' + a + '>=' + b + ')'
def js_gt(a, b):
return '(' + a + '>' + b + ')'
def js_in(a, b):
return b + '.contains(' + a + ')'
def js_instanceof(a, b):
return a + '.instanceof(' + b + ')'
def js_lshift(a, b):
return '(' + a + '<<' + b + ')'
def js_rshift(a, b):
return '(' + a + '>>' + b + ')'
def js_shit(a, b):
return 'PyJsBshift(' + a + ',' + b + ')'
def js_add(
a,
b): # To simplify later process of converting unary operators + and ++
return '(%s+%s)' % (a, b)
def js_sub(a, b): # To simplify
return '(%s-%s)' % (a, b)
def js_mul(a, b):
return '(' + a + '*' + b + ')'
def js_div(a, b):
return '(' + a + '/' + b + ')'
def js_mod(a, b):
return '(' + a + '%' + b + ')'
def js_typeof(a):
cand = list(bracket_split(a, ('()', )))
if len(cand) == 2 and cand[0] == 'var.get':
return cand[0] + cand[1][:-1] + ',throw=False).typeof()'
return a + '.typeof()'
def js_void(a):
return '(' + a + ')'
def js_new(a):
cands = list(bracket_split(a, ('()', )))
lim = len(cands)
if lim < 2:
return a + '.create()'
n = 0
while n < lim:
c = cands[n]
if c[0] == '(':
if cands[n - 1].endswith(
'.get') and n + 1 >= lim: # last get operation.
return a + '.create()'
elif cands[n - 1][0] == '(':
return ''.join(cands[:n]) + '.create' + c + ''.join(
cands[n + 1:])
elif cands[n - 1] == '.callprop':
beg = ''.join(cands[:n - 1])
args = argsplit(c[1:-1], ',')
prop = args[0]
new_args = ','.join(args[1:])
create = '.get(%s).create(%s)' % (prop, new_args)
return beg + create + ''.join(cands[n + 1:])
n += 1
return a + '.create()'
def js_delete(a):
#replace last get with delete.
c = list(bracket_split(a, ['()']))
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip(
) #strips just to make sure... I will remove it later
if beg[-4:] != '.get':
raise SyntaxError('Invalid delete operation')
return beg[:-3] + 'delete' + arglist
def js_neg(a):
return '(-' + a + ')'
def js_pos(a):
return '(+' + a + ')'
def js_inv(a):
return '(~' + a + ')'
def js_not(a):
return a + '.neg()'
def postfix(a, inc, post):
bra = list(bracket_split(a, ('()', )))
meth = bra[-2]
if not meth.endswith('get'):
raise SyntaxError('Invalid ++ or -- operation.')
bra[-2] = bra[-2][:-3] + 'put'
bra[-1] = '(%s,%s%sJs(1))' % (bra[-1][1:-1], a, '+' if inc else '-')
res = ''.join(bra)
return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+')
def js_pre_inc(a):
return postfix(a, True, False)
def js_post_inc(a):
return postfix(a, True, True)
def js_pre_dec(a):
return postfix(a, False, False)
def js_post_dec(a):
return postfix(a, False, True)
OR = {'||': js_or}
AND = {'&&': js_and}
BOR = {'|': js_bor}
BXOR = {'^': js_bxor}
BAND = {'&': js_band}
EQS = {
'===': js_strict_eq,
'!==': js_strict_neq,
'==': js_abstract_eq, # we need == and != too. Read a note above method
'!=': js_abstract_neq
}
#Since JS does not have chained comparisons we need to implement all cmp methods.
COMPS = {
'<': js_lt,
'<=': js_le,
'>=': js_ge,
'>': js_gt,
'instanceof': js_instanceof, #todo change to validitate
'in': js_in
}
BSHIFTS = {'<<': js_lshift, '>>': js_rshift, '>>>': js_shit}
ADDS = {'+': js_add, '-': js_sub}
MULTS = {'*': js_mul, '/': js_div, '%': js_mod}
#Note they dont contain ++ and -- methods because they both have 2 different methods
# correct method will be found automatically in translate function
UNARY = {
'typeof': js_typeof,
'void': js_void,
'new': js_new,
'delete': js_delete,
'!': js_not,
'-': js_neg,
'+': js_pos,
'~': js_inv,
'++': None,
'--': None
}
def transform_crap(code): #needs some more tests
"""Transforms this ?: crap into if else python syntax"""
ind = code.rfind('?')
if ind == -1:
return code
sep = code.find(':', ind)
if sep == -1:
raise SyntaxError('Invalid ?: syntax (probably missing ":" )')
beg = max(code.rfind(':', 0, ind), code.find('?', 0, ind)) + 1
end = code.find(':', sep + 1)
end = len(code) if end == -1 else end
formula = '(' + code[ind + 1:sep] + ' if ' + code[
beg:ind] + ' else ' + code[sep + 1:end] + ')'
return transform_crap(code[:beg] + formula + code[end:])
from code import InteractiveConsole
#e = InteractiveConsole(globals()).interact()
import traceback
def trans(code):
return NodeVisitor(code.strip()).translate().strip()
#todo finish this trans args
def trans_args(code):
new = bracket_replace(code.strip()[1:-1])
args = ','.join(trans(e) for e in new.split(','))
return '(%s)' % args
EXP = 0
def exp_translator(code):
global REPL, EXP
EXP += 1
REPL = {}
#print EXP, code
code = code.replace('\n', ' ')
assert '@' not in code
assert ';' not in code
assert '#' not in code
#if not code.strip(): #?
# return 'var.get("undefined")'
try:
return trans(code)
except:
#print '\n\ntrans failed on \n\n' + code
#raw_input('\n\npress enter')
raise
if __name__ == '__main__':
#print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) .
# jiji (h , ji , i)(non )( )()()()')
for e in xrange(3):
print exp_translator('jk = kk.ik++')
#First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay!
print exp_translator('delete a.f')

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,300 @@
""" This module removes all objects/arrays from JS source code and replace them with LVALS.
Also it has s function translating removed object/array to python code.
Use this module just after removing constants. Later move on to removing functions"""
OBJECT_LVAL = 'PyJsLvalObject%d_'
ARRAY_LVAL = 'PyJsLvalArray%d_'
from utils import *
from jsparser import *
from nodevisitor import exp_translator
import functions
from flow import KEYWORD_METHODS
def FUNC_TRANSLATOR(*a): # stupid import system in python
raise RuntimeError('Remember to set func translator. Thank you.')
def set_func_translator(ftrans):
# stupid stupid Python or Peter
global FUNC_TRANSLATOR
FUNC_TRANSLATOR = ftrans
def is_empty_object(n, last):
"""n may be the inside of block or object"""
if n.strip():
return False
# seems to be but can be empty code
last = last.strip()
markers = {
')',
';',
}
if not last or last[-1] in markers:
return False
return True
# todo refine this function
def is_object(n, last):
"""n may be the inside of block or object.
last is the code before object"""
if is_empty_object(n, last):
return True
if not n.strip():
return False
#Object contains lines of code so it cant be an object
if len(argsplit(n, ';')) > 1:
return False
cands = argsplit(n, ',')
if not cands[-1].strip():
return True # {xxxx,} empty after last , it must be an object
for cand in cands:
cand = cand.strip()
# separate each candidate element at : in dict and check whether they are correct...
kv = argsplit(cand, ':')
if len(
kv
) > 2: # set the len of kv to 2 because of this stupid : expression
kv = kv[0], ':'.join(kv[1:])
if len(kv) == 2:
# key value pair, check whether not label or ?:
k, v = kv
if not is_lval(k.strip()):
return False
v = v.strip()
if v.startswith('function'):
continue
#will fail on label... {xxx: while {}}
if v[0] == '{': # value cant be a code block
return False
for e in KEYWORD_METHODS:
# if v starts with any statement then return false
if v.startswith(e) and len(e) < len(v) and v[len(
e)] not in IDENTIFIER_PART:
return False
elif not (cand.startswith('set ') or cand.startswith('get ')):
return False
return True
def is_array(last):
#it can be prop getter
last = last.strip()
if any(
endswith_keyword(last, e) for e in
{'return', 'new', 'void', 'throw', 'typeof', 'in', 'instanceof'}):
return True
markers = {')', ']'}
return not last or not (last[-1] in markers or last[-1] in IDENTIFIER_PART)
def remove_objects(code, count=1):
""" This function replaces objects with OBJECTS_LVALS, returns new code, replacement dict and count.
count arg is the number that should be added to the LVAL of the first replaced object
"""
replacements = {} #replacement dict
br = bracket_split(code, ['{}', '[]'])
res = ''
last = ''
for e in br:
#test whether e is an object
if e[0] == '{':
n, temp_rep, cand_count = remove_objects(e[1:-1], count)
# if e was not an object then n should not contain any :
if is_object(n, last):
#e was an object
res += ' ' + OBJECT_LVAL % count
replacements[OBJECT_LVAL % count] = e
count += 1
else:
# e was just a code block but could contain objects inside
res += '{%s}' % n
count = cand_count
replacements.update(temp_rep)
elif e[0] == '[':
if is_array(last):
res += e # will be translated later
else: # prop get
n, rep, count = remove_objects(e[1:-1], count)
res += '[%s]' % n
replacements.update(rep)
else: # e does not contain any objects
res += e
last = e #needed to test for this stipid empty object
return res, replacements, count
def remove_arrays(code, count=1):
"""removes arrays and replaces them with ARRAY_LVALS
returns new code and replacement dict
*NOTE* has to be called AFTER remove objects"""
res = ''
last = ''
replacements = {}
for e in bracket_split(code, ['[]']):
if e[0] == '[':
if is_array(last):
name = ARRAY_LVAL % count
res += ' ' + name
replacements[name] = e
count += 1
else: # pseudo array. But pseudo array can contain true array. for example a[['d'][3]] has 2 pseudo and 1 true array
cand, new_replacements, count = remove_arrays(e[1:-1], count)
res += '[%s]' % cand
replacements.update(new_replacements)
else:
res += e
last = e
return res, replacements, count
def translate_object(obj, lval, obj_count=1, arr_count=1):
obj = obj[1:-1] # remove {} from both ends
obj, obj_rep, obj_count = remove_objects(obj, obj_count)
obj, arr_rep, arr_count = remove_arrays(obj, arr_count)
# functions can be defined inside objects. exp translator cant translate them.
# we have to remove them and translate with func translator
# its better explained in translate_array function
obj, hoisted, inline = functions.remove_functions(obj, all_inline=True)
assert not hoisted
gsetters_after = ''
keys = argsplit(obj)
res = []
for i, e in enumerate(keys, 1):
e = e.strip()
if e.startswith('set '):
gsetters_after += translate_setter(lval, e)
elif e.startswith('get '):
gsetters_after += translate_getter(lval, e)
elif ':' not in e:
if i < len(keys
): # can happen legally only in the last element {3:2,}
raise SyntaxError('Unexpected "," in Object literal')
break
else: #Not getter, setter or elision
spl = argsplit(e, ':')
if len(spl) < 2:
raise SyntaxError('Invalid Object literal: ' + e)
try:
key, value = spl
except: #len(spl)> 2
print 'Unusual case ' + repr(e)
key = spl[0]
value = ':'.join(spl[1:])
key = key.strip()
if is_internal(key):
key = '%s.to_string().value' % key
else:
key = repr(key)
value = exp_translator(value)
if not value:
raise SyntaxError('Missing value in Object literal')
res.append('%s:%s' % (key, value))
res = '%s = Js({%s})\n' % (lval, ','.join(res)) + gsetters_after
# translate all the nested objects (including removed earlier functions)
for nested_name, nested_info in inline.iteritems(): # functions
nested_block, nested_args = nested_info
new_def = FUNC_TRANSLATOR(nested_name, nested_block, nested_args)
res = new_def + res
for lval, obj in obj_rep.iteritems(): #objects
new_def, obj_count, arr_count = translate_object(
obj, lval, obj_count, arr_count)
# add object definition BEFORE array definition
res = new_def + res
for lval, obj in arr_rep.iteritems(): # arrays
new_def, obj_count, arr_count = translate_array(
obj, lval, obj_count, arr_count)
# add object definition BEFORE array definition
res = new_def + res
return res, obj_count, arr_count
def translate_setter(lval, setter):
func = 'function' + setter[3:]
try:
_, data, _ = functions.remove_functions(func)
if not data or len(data) > 1:
raise Exception()
except:
raise SyntaxError('Could not parse setter: ' + setter)
prop = data.keys()[0]
body, args = data[prop]
if len(args) != 1: #setter must have exactly 1 argument
raise SyntaxError('Invalid setter. It must take exactly 1 argument.')
# now messy part
res = FUNC_TRANSLATOR('setter', body, args)
res += "%s.define_own_property(%s, {'set': setter})\n" % (lval, repr(prop))
return res
def translate_getter(lval, getter):
func = 'function' + getter[3:]
try:
_, data, _ = functions.remove_functions(func)
if not data or len(data) > 1:
raise Exception()
except:
raise SyntaxError('Could not parse getter: ' + getter)
prop = data.keys()[0]
body, args = data[prop]
if len(args) != 0: #setter must have exactly 0 argument
raise SyntaxError('Invalid getter. It must take exactly 0 argument.')
# now messy part
res = FUNC_TRANSLATOR('getter', body, args)
res += "%s.define_own_property(%s, {'get': setter})\n" % (lval, repr(prop))
return res
def translate_array(array, lval, obj_count=1, arr_count=1):
"""array has to be any js array for example [1,2,3]
lval has to be name of this array.
Returns python code that adds lval to the PY scope it should be put before lval"""
array = array[1:-1]
array, obj_rep, obj_count = remove_objects(array, obj_count)
array, arr_rep, arr_count = remove_arrays(array, arr_count)
#functions can be also defined in arrays, this caused many problems since in Python
# functions cant be defined inside literal
# remove functions (they dont contain arrays or objects so can be translated easily)
# hoisted functions are treated like inline
array, hoisted, inline = functions.remove_functions(array, all_inline=True)
assert not hoisted
arr = []
# separate elements in array
for e in argsplit(array, ','):
# translate expressions in array PyJsLvalInline will not be translated!
e = exp_translator(e.replace('\n', ''))
arr.append(e if e else 'None')
arr = '%s = Js([%s])\n' % (lval, ','.join(arr))
#But we can have more code to add to define arrays/objects/functions defined inside this array
# translate nested objects:
# functions:
for nested_name, nested_info in inline.iteritems():
nested_block, nested_args = nested_info
new_def = FUNC_TRANSLATOR(nested_name, nested_block, nested_args)
arr = new_def + arr
for lval, obj in obj_rep.iteritems():
new_def, obj_count, arr_count = translate_object(
obj, lval, obj_count, arr_count)
# add object definition BEFORE array definition
arr = new_def + arr
for lval, obj in arr_rep.iteritems():
new_def, obj_count, arr_count = translate_array(
obj, lval, obj_count, arr_count)
# add object definition BEFORE array definition
arr = new_def + arr
return arr, obj_count, arr_count
if __name__ == '__main__':
test = 'a = {404:{494:19}}; b = 303; if () {f={:}; { }}'
#print remove_objects(test)
#print list(bracket_split(' {}'))
print
print remove_arrays(
'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])'
)
print is_object('', ')')

View file

@ -0,0 +1,4 @@
from jsparser import *
from utils import *
# maybe I will try rewriting my parser in the future... Tokenizer makes things much easier and faster, unfortunately I
# did not know anything about parsers when I was starting this project so I invented my own.

View file

@ -0,0 +1,151 @@
from flow import translate_flow
from constants import remove_constants, recover_constants
from objects import remove_objects, remove_arrays, translate_object, translate_array, set_func_translator
from functions import remove_functions, reset_inline_count
from jsparser import inject_before_lval, indent, dbg
TOP_GLOBAL = '''from js2py.pyjs import *\nvar = Scope( JS_BUILTINS )\nset_global_object(var)\n'''
def translate_js(js, top=TOP_GLOBAL):
"""js has to be a javascript source code.
returns equivalent python code."""
# Remove constant literals
no_const, constants = remove_constants(js)
#print 'const count', len(constants)
# Remove object literals
no_obj, objects, obj_count = remove_objects(no_const)
#print 'obj count', len(objects)
# Remove arrays
no_arr, arrays, arr_count = remove_arrays(no_obj)
#print 'arr count', len(arrays)
# Here remove and replace functions
reset_inline_count()
no_func, hoisted, inline = remove_functions(no_arr)
#translate flow and expressions
py_seed, to_register = translate_flow(no_func)
# register variables and hoisted functions
#top += '# register variables\n'
top += 'var.registers(%s)\n' % str(to_register + hoisted.keys())
#Recover functions
# hoisted functions recovery
defs = ''
#defs += '# define hoisted functions\n'
#print len(hoisted) , 'HH'*40
for nested_name, nested_info in hoisted.iteritems():
nested_block, nested_args = nested_info
new_code = translate_func('PyJsLvalTempHoisted', nested_block,
nested_args)
new_code += 'PyJsLvalTempHoisted.func_name = %s\n' % repr(nested_name)
defs += new_code + '\nvar.put(%s, PyJsLvalTempHoisted)\n' % repr(
nested_name)
#defs += '# Everting ready!\n'
# inline functions recovery
for nested_name, nested_info in inline.iteritems():
nested_block, nested_args = nested_info
new_code = translate_func(nested_name, nested_block, nested_args)
py_seed = inject_before_lval(py_seed,
nested_name.split('@')[0], new_code)
# add hoisted definitiond - they have literals that have to be recovered
py_seed = defs + py_seed
#Recover arrays
for arr_lval, arr_code in arrays.iteritems():
translation, obj_count, arr_count = translate_array(
arr_code, arr_lval, obj_count, arr_count)
py_seed = inject_before_lval(py_seed, arr_lval, translation)
#Recover objects
for obj_lval, obj_code in objects.iteritems():
translation, obj_count, arr_count = translate_object(
obj_code, obj_lval, obj_count, arr_count)
py_seed = inject_before_lval(py_seed, obj_lval, translation)
#Recover constants
py_code = recover_constants(py_seed, constants)
return top + py_code
def translate_func(name, block, args):
"""Translates functions and all nested functions to Python code.
name - name of that function (global functions will be available under var while
inline will be available directly under this name )
block - code of the function (*with* brackets {} )
args - arguments that this function takes"""
inline = name.startswith('PyJsLvalInline')
real_name = ''
if inline:
name, real_name = name.split('@')
arglist = ', '.join(args) + ', ' if args else ''
code = '@Js\ndef %s(%sthis, arguments, var=var):\n' % (name, arglist)
# register local variables
scope = "'this':this, 'arguments':arguments" #it will be a simple dictionary
for arg in args:
scope += ', %s:%s' % (repr(arg), arg)
if real_name:
scope += ', %s:%s' % (repr(real_name), name)
code += indent('var = Scope({%s}, var)\n' % scope)
block, nested_hoisted, nested_inline = remove_functions(block)
py_code, to_register = translate_flow(block)
#register variables declared with var and names of hoisted functions.
to_register += nested_hoisted.keys()
if to_register:
code += indent('var.registers(%s)\n' % str(to_register))
for nested_name, info in nested_hoisted.iteritems():
nested_block, nested_args = info
new_code = translate_func('PyJsLvalTempHoisted', nested_block,
nested_args)
# Now put definition of hoisted function on the top
code += indent(new_code)
code += indent(
'PyJsLvalTempHoisted.func_name = %s\n' % repr(nested_name))
code += indent(
'var.put(%s, PyJsLvalTempHoisted)\n' % repr(nested_name))
for nested_name, info in nested_inline.iteritems():
nested_block, nested_args = info
new_code = translate_func(nested_name, nested_block, nested_args)
# Inject definitions of inline functions just before usage
# nested inline names have this format : LVAL_NAME@REAL_NAME
py_code = inject_before_lval(py_code,
nested_name.split('@')[0], new_code)
if py_code.strip():
code += indent(py_code)
return code
set_func_translator(translate_func)
#print inject_before_lval(' chuj\n moj\n lval\nelse\n', 'lval', 'siema\njestem piter\n')
import time
#print time.time()
#print translate_js('if (1) console.log("Hello, World!"); else if (5) console.log("Hello world?");')
#print time.time()
t = """
var x = [1,2,3,4,5,6];
for (var e in x) {console.log(e); delete x[3];}
console.log(5 in [1,2,3,4,5]);
"""
SANDBOX = '''
import traceback
try:
%s
except:
print traceback.format_exc()
print
raw_input('Press Enter to quit')
'''
if __name__ == '__main__':
# test with jq if works then it really works :)
#with open('jq.js', 'r') as f:
#jq = f.read()
#res = translate_js(jq)
res = translate_js(t)
dbg(SANDBOX % indent(res))
print 'Done'

View file

@ -0,0 +1,91 @@
import sys
import unicodedata
from collections import defaultdict
def is_lval(t):
"""Does not chceck whether t is not resticted or internal"""
if not t:
return False
i = iter(t)
if i.next() not in IDENTIFIER_START:
return False
return all(e in IDENTIFIER_PART for e in i)
def is_valid_lval(t):
"""Checks whether t is valid JS identifier name (no keyword like var, function, if etc)
Also returns false on internal"""
if not is_internal(t) and is_lval(t) and t not in RESERVED_NAMES:
return True
return False
def is_plval(t):
return t.startswith('PyJsLval')
def is_marker(t):
return t.startswith('PyJsMarker') or t.startswith('PyJsConstant')
def is_internal(t):
return is_plval(t) or is_marker(t) or t == 'var' # var is a scope var
def is_property_accessor(t):
return '[' in t or '.' in t
def is_reserved(t):
return t in RESERVED_NAMES
#http://stackoverflow.com/questions/14245893/efficiently-list-all-characters-in-a-given-unicode-category
BOM = u'\uFEFF'
ZWJ = u'\u200D'
ZWNJ = u'\u200C'
TAB = u'\u0009'
VT = u'\u000B'
FF = u'\u000C'
SP = u'\u0020'
NBSP = u'\u00A0'
LF = u'\u000A'
CR = u'\u000D'
LS = u'\u2028'
PS = u'\u2029'
U_CATEGORIES = defaultdict(list) # Thank you Martijn Pieters!
for c in map(unichr, range(sys.maxunicode + 1)):
U_CATEGORIES[unicodedata.category(c)].append(c)
UNICODE_LETTER = set(U_CATEGORIES['Lu'] + U_CATEGORIES['Ll'] +
U_CATEGORIES['Lt'] + U_CATEGORIES['Lm'] +
U_CATEGORIES['Lo'] + U_CATEGORIES['Nl'])
UNICODE_COMBINING_MARK = set(U_CATEGORIES['Mn'] + U_CATEGORIES['Mc'])
UNICODE_DIGIT = set(U_CATEGORIES['Nd'])
UNICODE_CONNECTOR_PUNCTUATION = set(U_CATEGORIES['Pc'])
IDENTIFIER_START = UNICODE_LETTER.union(
{'$', '_'}) # and some fucking unicode escape sequence
IDENTIFIER_PART = IDENTIFIER_START.union(UNICODE_COMBINING_MARK).union(
UNICODE_DIGIT).union(UNICODE_CONNECTOR_PUNCTUATION).union({ZWJ, ZWNJ})
USP = U_CATEGORIES['Zs']
KEYWORD = {
'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var',
'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete',
'in', 'try'
}
FUTURE_RESERVED_WORD = {
'class', 'enum', 'extends', 'super', 'const', 'export', 'import'
}
RESERVED_NAMES = KEYWORD.union(FUTURE_RESERVED_WORD).union(
{'null', 'false', 'true'})
WHITE = {TAB, VT, FF, SP, NBSP, BOM}.union(USP)
LINE_TERMINATOR = {LF, CR, LS, PS}
LLINE_TERMINATOR = list(LINE_TERMINATOR)
x = ''.join(WHITE) + ''.join(LINE_TERMINATOR)
SPACE = WHITE.union(LINE_TERMINATOR)
LINE_TERMINATOR_SEQUENCE = LINE_TERMINATOR.union({CR + LF})

113
libs/js2py/node_import.py Normal file
View file

@ -0,0 +1,113 @@
__all__ = ['require']
import subprocess, os, codecs, glob
from .evaljs import translate_js
import six
DID_INIT = False
DIRNAME = os.path.dirname(os.path.abspath(__file__))
PY_NODE_MODULES_PATH = os.path.join(DIRNAME, 'py_node_modules')
def _init():
global DID_INIT
if DID_INIT:
return
assert subprocess.call(
'node -v', shell=True, cwd=DIRNAME
) == 0, 'You must have node installed! run: brew install node'
assert subprocess.call(
'cd %s;npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify'
% repr(DIRNAME),
shell=True,
cwd=DIRNAME) == 0, 'Could not link required node_modules'
DID_INIT = True
ADD_TO_GLOBALS_FUNC = '''
;function addToGlobals(name, obj) {
if (!Object.prototype.hasOwnProperty('_fake_exports')) {
Object.prototype._fake_exports = {};
}
Object.prototype._fake_exports[name] = obj;
};
'''
# subprocess.call("""node -e 'require("browserify")'""", shell=True)
GET_FROM_GLOBALS_FUNC = '''
;function getFromGlobals(name) {
if (!Object.prototype.hasOwnProperty('_fake_exports')) {
throw Error("Could not find any value named "+name);
}
if (Object.prototype._fake_exports.hasOwnProperty(name)) {
return Object.prototype._fake_exports[name];
} else {
throw Error("Could not find any value named "+name);
}
};
'''
def require(module_name, include_polyfill=False, update=False):
assert isinstance(module_name, str), 'module_name must be a string!'
py_name = module_name.replace('-', '_')
module_filename = '%s.py' % py_name
var_name = py_name.rpartition('/')[-1]
if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH,
module_filename)) or update:
_init()
in_file_name = 'tmp0in439341018923js2py.js'
out_file_name = 'tmp0out439341018923js2py.js'
code = ADD_TO_GLOBALS_FUNC
if include_polyfill:
code += "\n;require('babel-polyfill');\n"
code += """
var module_temp_love_python = require(%s);
addToGlobals(%s, module_temp_love_python);
""" % (repr(module_name), repr(module_name))
with open(os.path.join(DIRNAME, in_file_name), 'wb') as f:
f.write(code.encode('utf-8') if six.PY3 else code)
pkg_name = module_name.partition('/')[0]
# make sure the module is installed
assert subprocess.call(
'cd %s;npm install %s' % (repr(DIRNAME), pkg_name),
shell=True,
cwd=DIRNAME
) == 0, 'Could not install the required module: ' + pkg_name
# convert the module
assert subprocess.call(
'''node -e "(require('browserify')('./%s').bundle(function (err,data) {fs.writeFile('%s', require('babel-core').transform(data, {'presets': require('babel-preset-es2015')}).code, ()=>{});}))"'''
% (in_file_name, out_file_name),
shell=True,
cwd=DIRNAME,
) == 0, 'Error when converting module to the js bundle'
os.remove(os.path.join(DIRNAME, in_file_name))
with codecs.open(os.path.join(DIRNAME, out_file_name), "r",
"utf-8") as f:
js_code = f.read()
os.remove(os.path.join(DIRNAME, out_file_name))
js_code += GET_FROM_GLOBALS_FUNC
js_code += ';var %s = getFromGlobals(%s);%s' % (
var_name, repr(module_name), var_name)
print('Please wait, translating...')
py_code = translate_js(js_code)
dirname = os.path.dirname(
os.path.join(PY_NODE_MODULES_PATH, module_filename))
if not os.path.isdir(dirname):
os.makedirs(dirname)
with open(os.path.join(PY_NODE_MODULES_PATH, module_filename),
'wb') as f:
f.write(py_code.encode('utf-8') if six.PY3 else py_code)
else:
with codecs.open(
os.path.join(PY_NODE_MODULES_PATH, module_filename), "r",
"utf-8") as f:
py_code = f.read()
context = {}
exec (py_code, context)
return context['var'][var_name].to_py()

View file

@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'

View file

@ -0,0 +1,476 @@
import six
if six.PY3:
xrange = range
import functools
def to_arr(this):
"""Returns Python array from Js array"""
return [this.get(str(e)) for e in xrange(len(this))]
ARR_STACK = set({})
class ArrayPrototype:
def toString():
# this function is wrong but I will leave it here fore debugging purposes.
func = this.get('join')
if not func.is_callable():
@this.Js
def func():
return '[object %s]' % this.Class
return func.call(this, ())
def toLocaleString():
array = this.to_object()
arr_len = array.get('length').to_uint32()
# separator is simply a comma ','
if not arr_len:
return ''
res = []
for i in xrange(arr_len):
element = array[str(i)]
if element.is_undefined() or element.is_null():
res.append('')
else:
cand = element.to_object()
str_func = element.get('toLocaleString')
if not str_func.is_callable():
raise this.MakeError(
'TypeError',
'toLocaleString method of item at index %d is not callable'
% i)
res.append(element.callprop('toLocaleString').value)
return ','.join(res)
def concat():
array = this.to_object()
A = this.Js([])
items = [array]
items.extend(to_arr(arguments))
n = 0
for E in items:
if E.Class == 'Array':
k = 0
e_len = len(E)
while k < e_len:
if E.has_property(str(k)):
A.put(str(n), E.get(str(k)))
n += 1
k += 1
else:
A.put(str(n), E)
n += 1
return A
def join(separator):
ARR_STACK.add(this)
array = this.to_object()
arr_len = array.get('length').to_uint32()
separator = ',' if separator.is_undefined() else separator.to_string(
).value
elems = []
for e in xrange(arr_len):
elem = array.get(str(e))
if elem in ARR_STACK:
s = ''
else:
s = elem.to_string().value
elems.append(
s if not (elem.is_undefined() or elem.is_null()) else '')
res = separator.join(elems)
ARR_STACK.remove(this)
return res
def pop(): #todo check
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not arr_len:
array.put('length', this.Js(arr_len))
return None
ind = str(arr_len - 1)
element = array.get(ind)
array.delete(ind)
array.put('length', this.Js(arr_len - 1))
return element
def push(item): # todo check
array = this.to_object()
arr_len = array.get('length').to_uint32()
to_put = arguments.to_list()
i = arr_len
for i, e in enumerate(to_put, arr_len):
array.put(str(i), e)
if to_put:
i += 1
array.put('length', this.Js(i))
return i
def reverse():
array = this.to_object() # my own algorithm
vals = to_arr(array)
has_props = [array.has_property(str(e)) for e in xrange(len(array))]
vals.reverse()
has_props.reverse()
for i, val in enumerate(vals):
if has_props[i]:
array.put(str(i), val)
else:
array.delete(str(i))
return array
def shift(): #todo check
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not arr_len:
array.put('length', this.Js(0))
return None
first = array.get('0')
for k in xrange(1, arr_len):
from_s, to_s = str(k), str(k - 1)
if array.has_property(from_s):
array.put(to_s, array.get(from_s))
else:
array.delete(to)
array.delete(str(arr_len - 1))
array.put('length', this.Js(str(arr_len - 1)))
return first
def slice(start, end): # todo check
array = this.to_object()
arr_len = array.get('length').to_uint32()
relative_start = start.to_int()
k = max((arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
relative_end = arr_len if end.is_undefined() else end.to_int()
final = max((arr_len + relative_end), 0) if relative_end < 0 else min(
relative_end, arr_len)
res = []
n = 0
while k < final:
pk = str(k)
if array.has_property(pk):
res.append(array.get(pk))
k += 1
n += 1
return res
def sort(cmpfn):
if not this.Class in ('Array', 'Arguments'):
return this.to_object() # do nothing
arr = []
for i in xrange(len(this)):
arr.append(this.get(six.text_type(i)))
if not arr:
return this
if not cmpfn.is_callable():
cmpfn = None
cmp = lambda a, b: sort_compare(a, b, cmpfn)
if six.PY3:
key = functools.cmp_to_key(cmp)
arr.sort(key=key)
else:
arr.sort(cmp=cmp)
for i in xrange(len(arr)):
this.put(six.text_type(i), arr[i])
return this
def splice(start, deleteCount):
# 1-8
array = this.to_object()
arr_len = array.get('length').to_uint32()
relative_start = start.to_int()
actual_start = max(
(arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
actual_delete_count = min(
max(deleteCount.to_int(), 0), arr_len - actual_start)
k = 0
A = this.Js([])
# 9
while k < actual_delete_count:
if array.has_property(str(actual_start + k)):
A.put(str(k), array.get(str(actual_start + k)))
k += 1
# 10-11
items = to_arr(arguments)[2:]
items_len = len(items)
# 12
if items_len < actual_delete_count:
k = actual_start
while k < (arr_len - actual_delete_count):
fr = str(k + actual_delete_count)
to = str(k + items_len)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k += 1
k = arr_len
while k > (arr_len - actual_delete_count + items_len):
array.delete(str(k - 1))
k -= 1
# 13
elif items_len > actual_delete_count:
k = arr_len - actual_delete_count
while k > actual_start:
fr = str(k + actual_delete_count - 1)
to = str(k + items_len - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
# 14-17
k = actual_start
while items:
E = items.pop(0)
array.put(str(k), E)
k += 1
array.put('length', this.Js(arr_len - actual_delete_count + items_len))
return A
def unshift():
array = this.to_object()
arr_len = array.get('length').to_uint32()
argCount = len(arguments)
k = arr_len
while k > 0:
fr = str(k - 1)
to = str(k + argCount - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
j = 0
items = to_arr(arguments)
while items:
E = items.pop(0)
array.put(str(j), E)
j += 1
array.put('length', this.Js(arr_len + argCount))
return arr_len + argCount
def indexOf(searchElement):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if arr_len == 0:
return -1
if len(arguments) > 1:
n = arguments[1].to_int()
else:
n = 0
if n >= arr_len:
return -1
if n >= 0:
k = n
else:
k = arr_len - abs(n)
if k < 0:
k = 0
while k < arr_len:
if array.has_property(str(k)):
elementK = array.get(str(k))
if searchElement.strict_equality_comparison(elementK):
return k
k += 1
return -1
def lastIndexOf(searchElement):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if arr_len == 0:
return -1
if len(arguments) > 1:
n = arguments[1].to_int()
else:
n = arr_len - 1
if n >= 0:
k = min(n, arr_len - 1)
else:
k = arr_len - abs(n)
while k >= 0:
if array.has_property(str(k)):
elementK = array.get(str(k))
if searchElement.strict_equality_comparison(elementK):
return k
k -= 1
return -1
def every(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if not callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
return False
k += 1
return True
def some(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
return True
k += 1
return False
def forEach(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
callbackfn.call(T, (kValue, this.Js(k), array))
k += 1
def map(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
A = this.Js([])
k = 0
while k < arr_len:
Pk = str(k)
if array.has_property(Pk):
kValue = array.get(Pk)
mappedValue = callbackfn.call(T, (kValue, this.Js(k), array))
A.define_own_property(
Pk, {
'value': mappedValue,
'writable': True,
'enumerable': True,
'configurable': True
})
k += 1
return A
def filter(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
res = []
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
res.append(kValue)
k += 1
return res # converted to js array automatically
def reduce(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(arguments) < 2:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
k = 0
if len(arguments) > 1: # initial value present
accumulator = arguments[1]
else:
kPresent = False
while not kPresent and k < arr_len:
kPresent = array.has_property(str(k))
if kPresent:
accumulator = array.get(str(k))
k += 1
if not kPresent:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
accumulator = callbackfn.call(
this.undefined, (accumulator, kValue, this.Js(k), array))
k += 1
return accumulator
def reduceRight(callbackfn):
array = this.to_object()
arr_len = array.get('length').to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(arguments) < 2:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
k = arr_len - 1
if len(arguments) > 1: # initial value present
accumulator = arguments[1]
else:
kPresent = False
while not kPresent and k >= 0:
kPresent = array.has_property(str(k))
if kPresent:
accumulator = array.get(str(k))
k -= 1
if not kPresent:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
while k >= 0:
if array.has_property(str(k)):
kValue = array.get(str(k))
accumulator = callbackfn.call(
this.undefined, (accumulator, kValue, this.Js(k), array))
k -= 1
return accumulator
def sort_compare(a, b, comp):
if a is None:
if b is None:
return 0
return 1
if b is None:
if a is None:
return 0
return -1
if a.is_undefined():
if b.is_undefined():
return 0
return 1
if b.is_undefined():
if a.is_undefined():
return 0
return -1
if comp is not None:
res = comp.call(a.undefined, (a, b))
return res.to_int()
x, y = a.to_string(), b.to_string()
if x < y:
return -1
elif x > y:
return 1
return 0

View file

@ -0,0 +1,19 @@
# this is based on jsarray.py
import six
if six.PY3:
xrange = range
import functools
def to_arr(this):
"""Returns Python array from Js array"""
return [this.get(str(e)) for e in xrange(len(this))]
ARR_STACK = set({})
class ArrayBufferPrototype:
pass

View file

@ -0,0 +1,10 @@
class BooleanPrototype:
def toString():
if this.Class != 'Boolean':
raise this.Js(TypeError)('this must be a boolean')
return 'true' if this.value else 'false'
def valueOf():
if this.Class != 'Boolean':
raise this.Js(TypeError)('this must be a boolean')
return this.value

View file

@ -0,0 +1,10 @@
class ErrorPrototype:
def toString():
if this.TYPE != 'Object':
raise this.MakeError(
'TypeError', 'Error.prototype.toString called on non-object')
name = this.get('name')
name = 'Error' if name.is_undefined() else name.to_string().value
msg = this.get('message')
msg = '' if msg.is_undefined() else msg.to_string().value
return name + (name and msg and ': ') + msg

View file

@ -0,0 +1,52 @@
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
# todo fix apply and bind
class FunctionPrototype:
def toString():
if not this.is_callable():
raise TypeError('toString is not generic!')
args = ', '.join(this.code.__code__.co_varnames[:this.argcount])
return 'function %s(%s) ' % (this.func_name, args) + this.source
def call():
arguments_ = arguments
if not len(arguments):
obj = this.Js(None)
else:
obj = arguments[0]
if len(arguments) <= 1:
args = ()
else:
args = tuple([arguments_[e] for e in xrange(1, len(arguments_))])
return this.call(obj, args)
def apply():
if not len(arguments):
obj = this.Js(None)
else:
obj = arguments[0]
if len(arguments) <= 1:
args = ()
else:
appl = arguments[1]
args = tuple([appl[e] for e in xrange(len(appl))])
return this.call(obj, args)
def bind(thisArg):
target = this
if not target.is_callable():
raise this.MakeError(
'Object must be callable in order to be used with bind method')
if len(arguments) <= 1:
args = ()
else:
args = tuple([arguments[e] for e in xrange(1, len(arguments))])
return this.PyJsBoundFunction(target, thisArg, args)

View file

@ -0,0 +1,219 @@
import json
from ..base import Js
indent = ''
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
def parse(text):
reviver = arguments[1]
s = text.to_string().value
try:
unfiltered = json.loads(s)
except:
raise this.MakeError('SyntaxError',
'Could not parse JSON string - Invalid syntax')
unfiltered = to_js(this, unfiltered)
if reviver.is_callable():
root = this.Js({'': unfiltered})
return walk(root, '', reviver)
else:
return unfiltered
def stringify(value, replacer, space):
global indent
stack = set([])
indent = ''
property_list = replacer_function = this.undefined
if replacer.is_object():
if replacer.is_callable():
replacer_function = replacer
elif replacer.Class == 'Array':
property_list = []
for e in replacer:
v = replacer[e]
item = this.undefined
if v._type() == 'Number':
item = v.to_string()
elif v._type() == 'String':
item = v
elif v.is_object():
if v.Class in ('String', 'Number'):
item = v.to_string()
if not item.is_undefined() and item.value not in property_list:
property_list.append(item.value)
if space.is_object():
if space.Class == 'Number':
space = space.to_number()
elif space.Class == 'String':
space = space.to_string()
if space._type() == 'Number':
space = this.Js(min(10, space.to_int()))
gap = max(int(space.value), 0) * ' '
elif space._type() == 'String':
gap = space.value[:10]
else:
gap = ''
return this.Js(
Str('', this.Js({
'': value
}), replacer_function, property_list, gap, stack, space))
def Str(key, holder, replacer_function, property_list, gap, stack, space):
value = holder[key]
if value.is_object():
to_json = value.get('toJSON')
if to_json.is_callable():
value = to_json.call(value, (key, ))
if not replacer_function.is_undefined():
value = replacer_function.call(holder, (key, value))
if value.is_object():
if value.Class == 'String':
value = value.to_string()
elif value.Class == 'Number':
value = value.to_number()
elif value.Class == 'Boolean':
value = value.to_boolean()
if value.is_null():
return 'null'
elif value.Class == 'Boolean':
return 'true' if value.value else 'false'
elif value._type() == 'String':
return Quote(value)
elif value._type() == 'Number':
if not value.is_infinity():
return value.to_string()
return 'null'
if value.is_object() and not value.is_callable():
if value.Class == 'Array':
return ja(value, stack, gap, property_list, replacer_function,
space)
else:
return jo(value, stack, gap, property_list, replacer_function,
space)
return None # undefined
def jo(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise value.MakeError('TypeError',
'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
if not property_list.is_undefined():
k = property_list
else:
k = [e.value for e in value]
partial = []
for p in k:
str_p = value.Js(
Str(p, value, replacer_function, property_list, gap, stack, space))
if not str_p.is_undefined():
member = json.dumps(p) + ':' + (
' ' if gap else
'') + str_p.value # todo not sure here - what space character?
partial.append(member)
if not partial:
final = '{}'
else:
if not gap:
final = '{%s}' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '{\n' + indent + properties + '\n' + stepback + '}'
stack.remove(value)
indent = stepback
return final
def ja(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise value.MakeError('TypeError',
'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
partial = []
length = len(value)
for index in xrange(length):
index = str(index)
str_index = value.Js(
Str(index, value, replacer_function, property_list, gap, stack,
space))
if str_index.is_undefined():
partial.append('null')
else:
partial.append(str_index.value)
if not partial:
final = '[]'
else:
if not gap:
final = '[%s]' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '[\n' + indent + properties + '\n' + stepback + ']'
stack.remove(value)
indent = stepback
return final
def Quote(string):
return string.Js(json.dumps(string.value))
def to_js(this, d):
if isinstance(d, dict):
return this.Js(dict((k, this.Js(v)) for k, v in six.iteritems(d)))
return this.Js(d)
def walk(holder, name, reviver):
val = holder.get(name)
if val.Class == 'Array':
for i in xrange(len(val)):
i = unicode(i)
new_element = walk(val, i, reviver)
if new_element.is_undefined():
val.delete(i)
else:
new_element.put(i, new_element)
elif val.is_object():
for key in val:
new_element = walk(val, key, reviver)
if new_element.is_undefined():
val.delete(key)
else:
val.put(key, new_element)
return reviver.call(holder, (name, val))
JSON = Js({})
JSON.define_own_property(
'parse', {
'value': Js(parse),
'enumerable': False,
'writable': True,
'configurable': True
})
JSON.define_own_property(
'stringify', {
'value': Js(stringify),
'enumerable': False,
'writable': True,
'configurable': True
})

View file

@ -0,0 +1,146 @@
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
RADIX_SYMBOLS = {
0: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: 'a',
11: 'b',
12: 'c',
13: 'd',
14: 'e',
15: 'f',
16: 'g',
17: 'h',
18: 'i',
19: 'j',
20: 'k',
21: 'l',
22: 'm',
23: 'n',
24: 'o',
25: 'p',
26: 'q',
27: 'r',
28: 's',
29: 't',
30: 'u',
31: 'v',
32: 'w',
33: 'x',
34: 'y',
35: 'z'
}
def to_str_rep(num):
if num.is_nan():
return num.Js('NaN')
elif num.is_infinity():
sign = '-' if num.value < 0 else ''
return num.Js(sign + 'Infinity')
elif isinstance(num.value,
(long, int)) or num.value.is_integer(): # dont print .0
return num.Js(unicode(int(num.value)))
return num.Js(unicode(num.value)) # accurate enough
class NumberPrototype:
def toString(radix):
if this.Class != 'Number':
raise this.MakeError('TypeError',
'Number.prototype.valueOf is not generic')
if radix.is_undefined():
return to_str_rep(this)
r = radix.to_int()
if r == 10:
return to_str_rep(this)
if r not in xrange(2, 37):
raise this.MakeError(
'RangeError',
'Number.prototype.toString() radix argument must be between 2 and 36'
)
num = this.to_int()
if num < 0:
num = -num
sign = '-'
else:
sign = ''
res = ''
while num:
s = RADIX_SYMBOLS[num % r]
num = num // r
res = s + res
return sign + (res if res else '0')
def valueOf():
if this.Class != 'Number':
raise this.MakeError('TypeError',
'Number.prototype.valueOf is not generic')
return this.value
def toLocaleString():
return this.to_string()
def toFixed(fractionDigits):
if this.Class != 'Number':
raise this.MakeError(
'TypeError',
'Number.prototype.toFixed called on incompatible receiver')
digs = fractionDigits.to_int()
if digs < 0 or digs > 20:
raise this.MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif this.is_infinity():
return 'Infinity' if this.value > 0 else '-Infinity'
elif this.is_nan():
return 'NaN'
return format(this.value, '-.%df' % digs)
def toExponential(fractionDigits):
if this.Class != 'Number':
raise this.MakeError(
'TypeError',
'Number.prototype.toExponential called on incompatible receiver'
)
digs = fractionDigits.to_int()
if digs < 0 or digs > 20:
raise this.MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif this.is_infinity():
return 'Infinity' if this.value > 0 else '-Infinity'
elif this.is_nan():
return 'NaN'
return format(this.value, '-.%de' % digs)
def toPrecision(precision):
if this.Class != 'Number':
raise this.MakeError(
'TypeError',
'Number.prototype.toPrecision called on incompatible receiver')
if precision.is_undefined():
return this.to_string()
prec = precision.to_int()
if this.is_nan():
return 'NaN'
elif this.is_infinity():
return 'Infinity' if this.value > 0 else '-Infinity'
digs = prec - len(str(int(this.value)))
if digs >= 0:
return format(this.value, '-.%df' % digs)
else:
return format(this.value, '-.%df' % (prec - 1))

View file

@ -0,0 +1,28 @@
class ObjectPrototype:
def toString():
return '[object %s]' % this.Class
def valueOf():
return this.to_object()
def toLocaleString():
return this.callprop('toString')
def hasOwnProperty(prop):
return this.get_own_property(prop.to_string().value) is not None
def isPrototypeOf(obj):
#a bit stupid specification but well
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false
if not obj.is_object():
return False
while 1:
obj = obj.prototype
if obj is None or obj.is_null():
return False
if obj is this:
return True
def propertyIsEnumerable(prop):
cand = this.own.get(prop.to_string().value)
return cand is not None and cand.get('enumerable')

View file

@ -0,0 +1,45 @@
class RegExpPrototype:
def toString():
flags = u''
try:
if this.glob:
flags += u'g'
if this.ignore_case:
flags += u'i'
if this.multiline:
flags += u'm'
except:
pass
v = this.value if this.value else '(?:)'
return u'/%s/' % v + flags
def test(string):
return Exec(this, string) is not this.null
def exec2(string
): # will be changed to exec in base.py. cant name it exec here
return Exec(this, string)
def Exec(this, string):
if this.Class != 'RegExp':
raise this.MakeError('TypeError',
'RegExp.prototype.exec is not generic!')
string = string.to_string()
length = len(string)
i = this.get('lastIndex').to_int() if this.glob else 0
matched = False
while not matched:
if i < 0 or i > length:
this.put('lastIndex', this.Js(0))
return this.null
matched = this.match(string.value, i)
i += 1
start, end = matched.span() #[0]+i-1, matched.span()[1]+i-1
if this.glob:
this.put('lastIndex', this.Js(end))
arr = this.Js(
[this.Js(e) for e in [matched.group()] + list(matched.groups())])
arr.put('index', this.Js(start))
arr.put('input', string)
return arr

View file

@ -0,0 +1,306 @@
# -*- coding: utf-8 -*-
from .jsregexp import Exec
import re
DIGS = set('0123456789')
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
def replacement_template(rep, source, span, npar):
"""Takes the replacement template and some info about the match and returns filled template
"""
n = 0
res = ''
while n < len(rep) - 1:
char = rep[n]
if char == '$':
if rep[n + 1] == '$':
res += '$'
n += 2
continue
elif rep[n + 1] == '`':
# replace with string that is BEFORE match
res += source[:span[0]]
n += 2
continue
elif rep[n + 1] == '\'':
# replace with string that is AFTER match
res += source[span[1]:]
n += 2
continue
elif rep[n + 1] in DIGS:
dig = rep[n + 1]
if n + 2 < len(rep) and rep[n + 2] in DIGS:
dig += rep[n + 2]
num = int(dig)
# we will not do any replacements if we dont have this npar or dig is 0
if not num or num > len(npar):
res += '$' + dig
else:
# None - undefined has to be replaced with ''
res += npar[num - 1] if npar[num - 1] else ''
n += 1 + len(dig)
continue
res += char
n += 1
if n < len(rep):
res += rep[-1]
return res
###################################################
class StringPrototype:
def toString():
if this.Class != 'String':
raise this.MakeError('TypeError',
'String.prototype.toString is not generic')
return this.value
def valueOf():
if this.Class != 'String':
raise this.MakeError('TypeError',
'String.prototype.valueOf is not generic')
return this.value
def charAt(pos):
this.cok()
pos = pos.to_int()
s = this.to_string()
if 0 <= pos < len(s.value):
char = s.value[pos]
if char not in s.CHAR_BANK:
s.Js(char) # add char to char bank
return s.CHAR_BANK[char]
return s.CHAR_BANK['']
def charCodeAt(pos):
this.cok()
pos = pos.to_int()
s = this.to_string()
if 0 <= pos < len(s.value):
return s.Js(ord(s.value[pos]))
return s.NaN
def concat():
this.cok()
s = this.to_string()
res = s.value
for e in arguments.to_list():
res += e.to_string().value
return res
def indexOf(searchString, position):
this.cok()
s = this.to_string().value
search = searchString.to_string().value
pos = position.to_int()
return this.Js(s.find(search, min(max(pos, 0), len(s))))
def lastIndexOf(searchString, position):
this.cok()
s = this.to_string().value
search = searchString.to_string().value
pos = position.to_number()
pos = 10**15 if pos.is_nan() else pos.to_int()
return s.rfind(search, 0, min(max(pos, 0) + 1, len(s)))
def localeCompare(that):
this.cok()
s = this.to_string()
that = that.to_string()
if s < that:
return this.Js(-1)
elif s > that:
return this.Js(1)
return this.Js(0)
def match(regexp):
this.cok()
s = this.to_string()
r = this.RegExp(regexp) if regexp.Class != 'RegExp' else regexp
if not r.glob:
return Exec(r, s)
r.put('lastIndex', this.Js(0))
found = []
previous_last_index = 0
last_match = True
while last_match:
result = Exec(r, s)
if result.is_null():
last_match = False
else:
this_index = r.get('lastIndex').value
if this_index == previous_last_index:
r.put('lastIndex', this.Js(this_index + 1))
previous_last_index += 1
else:
previous_last_index = this_index
matchStr = result.get('0')
found.append(matchStr)
if not found:
return this.null
return found
def replace(searchValue, replaceValue):
# VERY COMPLICATED. to check again.
this.cok()
string = this.to_string()
s = string.value
res = ''
if not replaceValue.is_callable():
replaceValue = replaceValue.to_string().value
func = False
else:
func = True
# Replace all ( global )
if searchValue.Class == 'RegExp' and searchValue.glob:
last = 0
for e in re.finditer(searchValue.pat, s):
res += s[last:e.span()[0]]
if func:
# prepare arguments for custom func (replaceValue)
args = (e.group(), ) + e.groups() + (e.span()[1], string)
# convert all types to JS
args = map(this.Js, args)
res += replaceValue(*args).to_string().value
else:
res += replacement_template(replaceValue, s, e.span(),
e.groups())
last = e.span()[1]
res += s[last:]
return this.Js(res)
elif searchValue.Class == 'RegExp':
e = re.search(searchValue.pat, s)
if e is None:
return string
span = e.span()
pars = e.groups()
match = e.group()
else:
match = searchValue.to_string().value
ind = s.find(match)
if ind == -1:
return string
span = ind, ind + len(match)
pars = ()
res = s[:span[0]]
if func:
args = (match, ) + pars + (span[1], string)
# convert all types to JS
this_ = this
args = tuple([this_.Js(x) for x in args])
res += replaceValue(*args).to_string().value
else:
res += replacement_template(replaceValue, s, span, pars)
res += s[span[1]:]
return res
def search(regexp):
this.cok()
string = this.to_string()
if regexp.Class == 'RegExp':
rx = regexp
else:
rx = this.RegExp(regexp)
res = re.search(rx.pat, string.value)
if res is not None:
return this.Js(res.span()[0])
return -1
def slice(start, end):
this.cok()
s = this.to_string()
start = start.to_int()
length = len(s.value)
end = length if end.is_undefined() else end.to_int()
#From = max(length+start, 0) if start<0 else min(length, start)
#To = max(length+end, 0) if end<0 else min(length, end)
return s.value[start:end]
def split(separator, limit):
# its a bit different that re.split!
this.cok()
S = this.to_string()
s = S.value
lim = 2**32 - 1 if limit.is_undefined() else limit.to_uint32()
if not lim:
return []
if separator.is_undefined():
return [s]
len_s = len(s)
res = []
R = separator if separator.Class == 'RegExp' else separator.to_string()
if not len_s:
if SplitMatch(s, 0, R) is None:
return [S]
return []
p = q = 0
while q != len_s:
e, cap = SplitMatch(s, q, R)
if e is None or e == p:
q += 1
continue
res.append(s[p:q])
p = q = e
if len(res) == lim:
return res
for element in cap:
res.append(this.Js(element))
if len(res) == lim:
return res
res.append(s[p:])
return res
def substring(start, end):
this.cok()
s = this.to_string().value
start = start.to_int()
length = len(s)
end = length if end.is_undefined() else end.to_int()
fstart = min(max(start, 0), length)
fend = min(max(end, 0), length)
return this.Js(s[min(fstart, fend):max(fstart, fend)])
def substr(start, length):
#I hate this function and its description in specification
r1 = this.to_string().value
r2 = start.to_int()
r3 = 10**20 if length.is_undefined() else length.to_int()
r4 = len(r1)
r5 = r2 if r2 >= 0 else max(0, r2 + r4)
r6 = min(max(r3, 0), r4 - r5)
if r6 <= 0:
return ''
return r1[r5:r5 + r6]
def toLowerCase():
this.cok()
return this.Js(this.to_string().value.lower())
def toLocaleLowerCase():
this.cok()
return this.Js(this.to_string().value.lower())
def toUpperCase():
this.cok()
return this.Js(this.to_string().value.upper())
def toLocaleUpperCase():
this.cok()
return this.Js(this.to_string().value.upper())
def trim():
this.cok()
return this.Js(this.to_string().value.strip(WHITE))
def SplitMatch(s, q, R):
# s is Py String to match, q is the py int match start and R is Js RegExp or String.
if R.Class == 'RegExp':
res = R.match(s, q)
return (None, ()) if res is None else (res.span()[1], res.groups())
# R is just a string
if s[q:].startswith(R.value):
return q + len(R.value), ()
return None, ()

View file

@ -0,0 +1,344 @@
# this is based on jsarray.py
import six
try:
import numpy
except:
pass
if six.PY3:
xrange = range
import functools
def to_arr(this):
"""Returns Python array from Js array"""
return [this.get(str(e)) for e in xrange(len(this))]
ARR_STACK = set({})
class TypedArrayPrototype:
def toString():
# this function is wrong
func = this.get('join')
if not func.is_callable():
@this.Js
def func():
return '[object %s]' % this.Class
return func.call(this, ())
def toLocaleString(locales=None, options=None):
array = this.to_object()
arr_len = array.get("length").to_uint32()
# separator is simply a comma ','
if not arr_len:
return ''
res = []
for i in xrange(arr_len):
element = array[str(i)]
if element.is_undefined() or element.is_null():
res.append('')
else:
cand = element.to_object()
str_func = element.get('toLocaleString')
if not str_func.is_callable():
raise this.MakeError(
'TypeError',
'toLocaleString method of item at index %d is not callable'
% i)
res.append(element.callprop('toLocaleString').value)
return ','.join(res)
def join(separator):
ARR_STACK.add(this)
array = this.to_object()
arr_len = array.get("length").to_uint32()
separator = ',' if separator.is_undefined() else separator.to_string(
).value
elems = []
for e in xrange(arr_len):
elem = array.get(str(e))
if elem in ARR_STACK:
s = ''
else:
s = elem.to_string().value
elems.append(
s if not (elem.is_undefined() or elem.is_null()) else '')
res = separator.join(elems)
ARR_STACK.remove(this)
return res
def reverse():
array = this.to_object() # my own algorithm
vals = to_arr(array)
has_props = [array.has_property(str(e)) for e in xrange(len(array))]
vals.reverse()
has_props.reverse()
for i, val in enumerate(vals):
if has_props[i]:
array.put(str(i), val)
else:
array.delete(str(i))
return array
def slice(start, end): # todo check
array = this.to_object()
arr_len = array.get("length").to_uint32()
relative_start = start.to_int()
k = max((arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
relative_end = arr_len if end.is_undefined() else end.to_int()
final = max((arr_len + relative_end), 0) if relative_end < 0 else min(
relative_end, arr_len)
res = []
n = 0
while k < final:
pk = str(k)
if array.has_property(pk):
res.append(array.get(pk))
k += 1
n += 1
return res
def sort(cmpfn):
if not this.Class in ('Array', 'Arguments'):
return this.to_object() # do nothing
arr = []
for i in xrange(len(this)):
arr.append(this.get(six.text_type(i)))
if not arr:
return this
if not cmpfn.is_callable():
cmpfn = None
cmp = lambda a, b: sort_compare(a, b, cmpfn)
if six.PY3:
key = functools.cmp_to_key(cmp)
arr.sort(key=key)
else:
arr.sort(cmp=cmp)
for i in xrange(len(arr)):
this.put(six.text_type(i), arr[i])
return this
def indexOf(searchElement):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if arr_len == 0:
return -1
if len(arguments) > 1:
n = arguments[1].to_int()
else:
n = 0
if n >= arr_len:
return -1
if n >= 0:
k = n
else:
k = arr_len - abs(n)
if k < 0:
k = 0
while k < arr_len:
if array.has_property(str(k)):
elementK = array.get(str(k))
if searchElement.strict_equality_comparison(elementK):
return k
k += 1
return -1
def lastIndexOf(searchElement):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if arr_len == 0:
return -1
if len(arguments) > 1:
n = arguments[1].to_int()
else:
n = arr_len - 1
if n >= 0:
k = min(n, arr_len - 1)
else:
k = arr_len - abs(n)
while k >= 0:
if array.has_property(str(k)):
elementK = array.get(str(k))
if searchElement.strict_equality_comparison(elementK):
return k
k -= 1
return -1
def every(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if not callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
return False
k += 1
return True
def some(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
return True
k += 1
return False
def forEach(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
callbackfn.call(T, (kValue, this.Js(k), array))
k += 1
def map(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
A = this.Js([])
k = 0
while k < arr_len:
Pk = str(k)
if array.has_property(Pk):
kValue = array.get(Pk)
mappedValue = callbackfn.call(T, (kValue, this.Js(k), array))
A.define_own_property(
Pk, {
'value': mappedValue,
'writable': True,
'enumerable': True,
'configurable': True
})
k += 1
return A
def filter(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
T = arguments[1]
res = []
k = 0
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
if callbackfn.call(
T, (kValue, this.Js(k), array)).to_boolean().value:
res.append(kValue)
k += 1
return res # converted to js array automatically
def reduce(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(arguments) < 2:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
k = 0
if len(arguments) > 1: # initial value present
accumulator = arguments[1]
else:
kPresent = False
while not kPresent and k < arr_len:
kPresent = array.has_property(str(k))
if kPresent:
accumulator = array.get(str(k))
k += 1
if not kPresent:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
while k < arr_len:
if array.has_property(str(k)):
kValue = array.get(str(k))
accumulator = callbackfn.call(
this.undefined, (accumulator, kValue, this.Js(k), array))
k += 1
return accumulator
def reduceRight(callbackfn):
array = this.to_object()
arr_len = array.get("length").to_uint32()
if not callbackfn.is_callable():
raise this.MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(arguments) < 2:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
k = arr_len - 1
if len(arguments) > 1: # initial value present
accumulator = arguments[1]
else:
kPresent = False
while not kPresent and k >= 0:
kPresent = array.has_property(str(k))
if kPresent:
accumulator = array.get(str(k))
k -= 1
if not kPresent:
raise this.MakeError(
'TypeError', 'Reduce of empty array with no initial value')
while k >= 0:
if array.has_property(str(k)):
kValue = array.get(str(k))
accumulator = callbackfn.call(
this.undefined, (accumulator, kValue, this.Js(k), array))
k -= 1
return accumulator
def sort_compare(a, b, comp):
if a is None:
if b is None:
return 0
return 1
if b is None:
if a is None:
return 0
return -1
if a.is_undefined():
if b.is_undefined():
return 0
return 1
if b.is_undefined():
if a.is_undefined():
return 0
return -1
if comp is not None:
res = comp.call(a.undefined, (a, b))
return res.to_int()
x, y = a.to_string(), b.to_string()
if x < y:
return -1
elif x > y:
return 1
return 0

Some files were not shown because too many files have changed in this diff Show more