commit
6deaa6c384
@ -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
|
@ -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
|
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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)
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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)
|
@ -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
|
||||
})
|
@ -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
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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
|
||||
})
|
@ -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)
|
@ -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;'))
|
@ -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;
|
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
@ -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()
|
@ -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)
|
@ -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())
|
@ -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)
|
@ -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)
|
@ -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()
|
@ -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 = []
|
@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
@ -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'
|
@ -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
|
@ -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
|
@ -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
|
||||
})
|
@ -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
|
@ -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()
|
@ -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
|
||||
}
|
@ -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
|
@ -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)
|
@ -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
|
@ -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)
|
@ -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')
|
@ -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()
|
@ -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)
|
@ -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'))
|
@ -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]
|
@ -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,
|
||||
}
|
@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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))
|
@ -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
|
@ -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')
|
@ -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
|
@ -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, ()
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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
|
@ -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))
|
@ -0,0 +1 @@
|
||||
__author__ = 'Piotrek'
|
@ -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
|
@ -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')
|
@ -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]
|
@ -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}')
|
@ -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:]
|
@ -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
@ -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('', ')')
|
@ -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.
|
@ -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'
|
@ -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})
|
@ -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()
|
@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -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
|
||||
})
|
@ -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))
|
@ -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')
|
@ -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
|
@ -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, ()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue