From e36cb13e6a624902f062dcd3745df2af8a78a75a Mon Sep 17 00:00:00 2001 From: evilhero Date: Tue, 6 Sep 2016 11:06:07 -0400 Subject: [PATCH] FIX:(#1358) nzbname error when retrieving nzb and assigning filename from WWT tracker, FIX:(#1372)(#1369) Fix for creating series folder when adding a series when series title contains either double quotation marks, or an asterisk, FIX:(#1373) Filechecker would ignore filenames that had the extension captialized, FIX:(#1366) When Comic Publisher is not provided on CV, would error during add, FIX: Attempted fix for unicode characters when importing (series title, filenames), FIX: Removed str references that would cause an error on weekly pull in some instances, FIX: When checking for watched series, if series title being checked against had only one word, would cause a traceback error, FIX: When attempting to retrieve results/torrents from TPSE and was behind cloudflare, would error out, IMP: file-size check now works for 32p feeds, FIX: When pullist issue was marked as Wanted and issue was populated on series detail page, occassionaly would not have the same status of Wanted, FIX: Fixed incorrect placement of Comic Location title in GUI, IMP: Added short description for Search Delay option within GUI, FIX:(#1370) multiple selection from Manage Comics tab (Refresh/Delete/Pause) would only select one item --- data/interfaces/default/config.html | 6 +- lib/cfscrape/__init__.py | 153 + lib/js2py/LICENSE.md | 19 + lib/js2py/__init__.py | 69 + lib/js2py/base.py | 1937 +++++++++++++ lib/js2py/constructors/__init__.py | 1 + lib/js2py/constructors/jsarray.py | 38 + lib/js2py/constructors/jsboolean.py | 11 + lib/js2py/constructors/jsdate.py | 362 +++ lib/js2py/constructors/jsfunction.py | 49 + lib/js2py/constructors/jsmath.py | 151 + lib/js2py/constructors/jsnumber.py | 18 + lib/js2py/constructors/jsobject.py | 172 ++ lib/js2py/constructors/jsregexp.py | 11 + lib/js2py/constructors/jsstring.py | 30 + lib/js2py/constructors/time_helpers.py | 183 ++ lib/js2py/evaljs.py | 250 ++ lib/js2py/host/__init__.py | 0 lib/js2py/host/console.py | 11 + lib/js2py/host/dom/__init__.py | 0 lib/js2py/host/dom/constants.py | 47 + lib/js2py/host/dom/interface.py | 73 + lib/js2py/host/jseval.py | 50 + lib/js2py/host/jsfunctions.py | 85 + lib/js2py/legecy_translators/__init__.py | 1 + lib/js2py/legecy_translators/constants.py | 294 ++ lib/js2py/legecy_translators/exps.py | 79 + lib/js2py/legecy_translators/flow.py | 456 +++ lib/js2py/legecy_translators/functions.py | 84 + lib/js2py/legecy_translators/jsparser.py | 307 ++ lib/js2py/legecy_translators/nodevisitor.py | 500 ++++ lib/js2py/legecy_translators/nparser.py | 2891 +++++++++++++++++++ lib/js2py/legecy_translators/objects.py | 287 ++ lib/js2py/legecy_translators/tokenize.py | 4 + lib/js2py/legecy_translators/translator.py | 143 + lib/js2py/legecy_translators/utils.py | 80 + lib/js2py/prototypes/__init__.py | 1 + lib/js2py/prototypes/jsarray.py | 458 +++ lib/js2py/prototypes/jsboolean.py | 16 + lib/js2py/prototypes/jserror.py | 10 + lib/js2py/prototypes/jsfunction.py | 53 + lib/js2py/prototypes/jsjson.py | 210 ++ lib/js2py/prototypes/jsnumber.py | 100 + lib/js2py/prototypes/jsobject.py | 36 + lib/js2py/prototypes/jsregexp.py | 43 + lib/js2py/prototypes/jsstring.py | 307 ++ lib/js2py/pyjs.py | 51 + lib/js2py/todo | 18 + lib/js2py/translators/__init__.py | 38 + lib/js2py/translators/friendly_nodes.py | 327 +++ lib/js2py/translators/jsregexps.py | 219 ++ lib/js2py/translators/markdown.js | 1293 +++++++++ lib/js2py/translators/pyjsparser.py | 2888 ++++++++++++++++++ lib/js2py/translators/pyjsparserdata.py | 297 ++ lib/js2py/translators/std_nodes.py | 531 ++++ lib/js2py/translators/translating_nodes.py | 641 ++++ lib/js2py/translators/translator.py | 174 ++ lib/js2py/utils/__init__.py | 0 lib/js2py/utils/definitions.py | 80 + lib/js2py/utils/injector.py | 184 ++ mylar/cv.py | 1 + mylar/filechecker.py | 2 +- mylar/helpers.py | 4 +- mylar/librarysync.py | 21 +- mylar/rsscheck.py | 88 +- mylar/search.py | 8 +- mylar/webserve.py | 48 +- mylar/weeklypull.py | 30 +- 68 files changed, 16938 insertions(+), 91 deletions(-) create mode 100644 lib/cfscrape/__init__.py create mode 100644 lib/js2py/LICENSE.md create mode 100644 lib/js2py/__init__.py create mode 100644 lib/js2py/base.py create mode 100644 lib/js2py/constructors/__init__.py create mode 100644 lib/js2py/constructors/jsarray.py create mode 100644 lib/js2py/constructors/jsboolean.py create mode 100644 lib/js2py/constructors/jsdate.py create mode 100644 lib/js2py/constructors/jsfunction.py create mode 100644 lib/js2py/constructors/jsmath.py create mode 100644 lib/js2py/constructors/jsnumber.py create mode 100644 lib/js2py/constructors/jsobject.py create mode 100644 lib/js2py/constructors/jsregexp.py create mode 100644 lib/js2py/constructors/jsstring.py create mode 100644 lib/js2py/constructors/time_helpers.py create mode 100644 lib/js2py/evaljs.py create mode 100644 lib/js2py/host/__init__.py create mode 100644 lib/js2py/host/console.py create mode 100644 lib/js2py/host/dom/__init__.py create mode 100644 lib/js2py/host/dom/constants.py create mode 100644 lib/js2py/host/dom/interface.py create mode 100644 lib/js2py/host/jseval.py create mode 100644 lib/js2py/host/jsfunctions.py create mode 100644 lib/js2py/legecy_translators/__init__.py create mode 100644 lib/js2py/legecy_translators/constants.py create mode 100644 lib/js2py/legecy_translators/exps.py create mode 100644 lib/js2py/legecy_translators/flow.py create mode 100644 lib/js2py/legecy_translators/functions.py create mode 100644 lib/js2py/legecy_translators/jsparser.py create mode 100644 lib/js2py/legecy_translators/nodevisitor.py create mode 100644 lib/js2py/legecy_translators/nparser.py create mode 100644 lib/js2py/legecy_translators/objects.py create mode 100644 lib/js2py/legecy_translators/tokenize.py create mode 100644 lib/js2py/legecy_translators/translator.py create mode 100644 lib/js2py/legecy_translators/utils.py create mode 100644 lib/js2py/prototypes/__init__.py create mode 100644 lib/js2py/prototypes/jsarray.py create mode 100644 lib/js2py/prototypes/jsboolean.py create mode 100644 lib/js2py/prototypes/jserror.py create mode 100644 lib/js2py/prototypes/jsfunction.py create mode 100644 lib/js2py/prototypes/jsjson.py create mode 100644 lib/js2py/prototypes/jsnumber.py create mode 100644 lib/js2py/prototypes/jsobject.py create mode 100644 lib/js2py/prototypes/jsregexp.py create mode 100644 lib/js2py/prototypes/jsstring.py create mode 100644 lib/js2py/pyjs.py create mode 100644 lib/js2py/todo create mode 100644 lib/js2py/translators/__init__.py create mode 100644 lib/js2py/translators/friendly_nodes.py create mode 100644 lib/js2py/translators/jsregexps.py create mode 100644 lib/js2py/translators/markdown.js create mode 100644 lib/js2py/translators/pyjsparser.py create mode 100644 lib/js2py/translators/pyjsparserdata.py create mode 100644 lib/js2py/translators/std_nodes.py create mode 100644 lib/js2py/translators/translating_nodes.py create mode 100644 lib/js2py/translators/translator.py create mode 100644 lib/js2py/utils/__init__.py create mode 100644 lib/js2py/utils/definitions.py create mode 100644 lib/js2py/utils/injector.py diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index a757da50..1b4bacd1 100755 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -201,7 +201,7 @@ -
+
Interval
@@ -212,9 +212,11 @@
mins + The amount of time to wait between each search request (minimum is 1 min)
- +
+
Comic Location
Automatic folder creation happens BENEATH this path diff --git a/lib/cfscrape/__init__.py b/lib/cfscrape/__init__.py new file mode 100644 index 00000000..b47e8fb4 --- /dev/null +++ b/lib/cfscrape/__init__.py @@ -0,0 +1,153 @@ +from time import sleep +import logging +import random +import re +import os +from lib.requests.sessions import Session +import lib.js2py as js2py +from lib.js2py import eval_js as eval_js + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + +DEFAULT_USER_AGENTS = [ + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0" +] + +DEFAULT_USER_AGENT = random.choice(DEFAULT_USER_AGENTS) + + +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"] = DEFAULT_USER_AGENT + + 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 == 503 and resp.headers.get("Server") == "cloudflare-nginx": + return self.solve_cf_challenge(resp, **kwargs) + + # Otherwise, no Cloudflare anti-bot detected + return resp + + def solve_cf_challenge(self, resp, **kwargs): + sleep(5) # Cloudflare requires a delay before solving the challenge + + body = resp.text + parsed_url = urlparse(resp.url) + domain = urlparse(resp.url).netloc + submit_url = "%s://%s/cdn-cgi/l/chk_jschl" % (parsed_url.scheme, domain) + + params = kwargs.setdefault("params", {}) + headers = kwargs.setdefault("headers", {}) + headers["Referer"] = resp.url + + try: + params["jschl_vc"] = re.search(r'name="jschl_vc" value="(\w+)"', body).group(1) + params["pass"] = re.search(r'name="pass" value="(.+?)"', body).group(1) + + # Extract the arithmetic operation + js = self.extract_js(body) + + except Exception: + # 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("[!] 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.") + raise + + # Safely evaluate the Javascript expression + js = js.replace('return', '') + params["jschl_answer"] = str(int(eval_js(js)) + len(domain)) + + return self.get(submit_url, **kwargs) + + def extract_js(self, body): + 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) + js = re.sub(r"a\.value = (parseInt\(.+?\)).+", r"\1", js) + js = re.sub(r"\s{3,}[a-z](?: = |\.).+", "", js) + + # 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) + + return js.replace("parseInt", "return parseInt") + + @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) + 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 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"] + ) + + @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) + 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 diff --git a/lib/js2py/LICENSE.md b/lib/js2py/LICENSE.md new file mode 100644 index 00000000..059be8d6 --- /dev/null +++ b/lib/js2py/LICENSE.md @@ -0,0 +1,19 @@ +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 \ No newline at end of file diff --git a/lib/js2py/__init__.py b/lib/js2py/__init__.py new file mode 100644 index 00000000..5aec9aa0 --- /dev/null +++ b/lib/js2py/__init__.py @@ -0,0 +1,69 @@ +# 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'] +from .evaljs import * +from .translators import parse as parse_js + diff --git a/lib/js2py/base.py b/lib/js2py/base.py new file mode 100644 index 00000000..0f9bee5e --- /dev/null +++ b/lib/js2py/base.py @@ -0,0 +1,1937 @@ +'''Most important file in Js2Py implementation: PyJs class - father of all PyJs objects''' +from copy import copy +import re +#from translators import translator + +from .translators.friendly_nodes import REGEXP_CONVERTER +from .utils.injector import fix_js_args +from types import FunctionType, ModuleType, GeneratorType, BuiltinFunctionType, MethodType, BuiltinMethodType +import traceback + + + +# python 3 support +import six +if six.PY3: + basestring = str + long = int + xrange = range + unicode = str + + +def str_repr(s): + if six.PY2: + return repr(s.encode('utf-8')) + else: + return repr(s) + +def MakeError(name, message): + """Returns PyJsException with PyJsError inside""" + return JsToPyException(ERRORS[name](Js(message))) + + + + +def to_python(val): + if not isinstance(val, PyJs): + return val + if isinstance(val, PyJsUndefined) or isinstance(val, PyJsNull): + return None + elif isinstance(val, PyJsNumber): + # this can be either float or long/int better to assume its int/long when a whole number... + v = val.value + try: + i = int(v) if v==v else v # nan... + return v if i!=v else i + except: + return v + elif isinstance(val, (PyJsString, PyJsBoolean)): + return val.value + elif isinstance(val, PyObjectWrapper): + return val.__dict__['obj'] + return JsObjectWrapper(val) + +def to_dict(js_obj, known=None): # fixed recursion error in self referencing objects + res = {} + if known is None: + known = {} + if js_obj in known: + return known[js_obj] + known[js_obj] = res + for k in js_obj: + name = k.value + input = js_obj.get(name) + output = to_python(input) + if isinstance(output, JsObjectWrapper): + if output._obj.Class=='Object': + output = to_dict(output._obj, known) + known[input] = output + elif output._obj.Class=='Array': + output = to_list(output._obj) + known[input] = output + res[name] = output + return res + + +def to_list(js_obj, known=None): + res = len(js_obj)*[None] + if known is None: + known = {} + if js_obj in known: + return known[js_obj] + known[js_obj] = res + for k in js_obj: + try: + name = int(k.value) + except: + continue + input = js_obj.get(str(name)) + output = to_python(input) + if isinstance(output, JsObjectWrapper): + if output._obj.Class in ['Array', 'Arguments']: + output = to_list(output._obj, known) + known[input] = output + elif output._obj.Class in ['Object']: + output = to_dict(output._obj) + known[input] = output + res[name] = output + return res + + +def HJs(val): + if hasattr(val, '__call__'): # + @Js + def PyWrapper(this, arguments, var=None): + args = tuple(to_python(e) for e in arguments.to_list()) + try: + py_res = val.__call__(*args) + except Exception as e: + message = 'your Python function failed! ' + try: + message += e.message + except: + pass + raise MakeError('Error', message) + return py_wrap(py_res) + + try: + PyWrapper.func_name = val.__name__ + except: + pass + return PyWrapper + if isinstance(val, tuple): + val = list(val) + return Js(val) + +def Js(val): + '''Converts Py type to PyJs type''' + if isinstance(val, PyJs): + return val + elif val is None: + return undefined + elif isinstance(val, basestring): + return PyJsString(val, StringPrototype) + elif isinstance(val, bool): + return true if val else false + elif isinstance(val, float) or isinstance(val, int) or isinstance(val, long): + # This is supposed to speed things up. may not be the case + if val in NUM_BANK: + return NUM_BANK[val] + return PyJsNumber(float(val), NumberPrototype) + elif isinstance(val, FunctionType): + return PyJsFunction(val, FunctionPrototype) + #elif isinstance(val, ModuleType): + # mod = {} + # for name in dir(val): + # value = getattr(val, name) + # if isinstance(value, ModuleType): + # continue # prevent recursive module conversion + # try: + # jsval = HJs(value) + # except RuntimeError: + # print 'Could not convert %s to PyJs object!' % name + # continue + # mod[name] = jsval + # return Js(mod) + #elif isintance(val, ClassType): + + elif isinstance(val, dict): # convert to object + temp = PyJsObject({}, ObjectPrototype) + for k, v in six.iteritems(val): + temp.put(Js(k), Js(v)) + return temp + elif isinstance(val, (list, tuple)): #Convert to array + return PyJsArray(val, ArrayPrototype) + elif isinstance(val, JsObjectWrapper): + return val.__dict__['_obj'] + else: # try to convert to js object + return py_wrap(val) + #raise RuntimeError('Cant convert python type to js (%s)' % repr(val)) + #try: + # obj = {} + # for name in dir(val): + # if name.startswith('_'): #dont wrap attrs that start with _ + # continue + # value = getattr(val, name) + # import types + # if not isinstance(value, (FunctionType, BuiltinFunctionType, MethodType, BuiltinMethodType, + # dict, int, basestring, bool, float, long, list, tuple)): + # continue + # obj[name] = HJs(value) + # return Js(obj) + #except: + # raise RuntimeError('Cant convert python type to js (%s)' % repr(val)) + +def Type(val): + try: + return val.TYPE + except: + raise RuntimeError('Invalid type: '+str(val)) + +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): + return desc and not (is_data_descriptor(desc) or is_accessor_descriptor(desc)) + + + +############################################################################## + +class PyJs(object): + PRIMITIVES = {'String', 'Number', 'Boolean', 'Undefined', 'Null'} + TYPE = 'Object' + Class = None + extensible = True + prototype = None + own = {} + GlobalObject = None + IS_CHILD_SCOPE = False + value = None + + def __init__(self, value=None, prototype=None, extensible=False): + '''Constructor for Number String and Boolean''' + # I dont think this is needed anymore + # if self.Class=='String' and not isinstance(value, basestring): + # raise TypeError + # if self.Class=='Number': + # if not isinstance(value, float): + # if not (isinstance(value, int) or isinstance(value, long)): + # raise TypeError + # value = float(value) + # if self.Class=='Boolean' and not isinstance(value, bool): + # raise TypeError + self.value = value + self.extensible = extensible + self.prototype = prototype + self.own = {} + + def is_undefined(self): + return self.Class=='Undefined' + + def is_null(self): + return self.Class=='Null' + + def is_primitive(self): + return self.TYPE in self.PRIMITIVES + + def is_object(self): + return not self.is_primitive() + + def _type(self): + return Type(self) + + def is_callable(self): + return hasattr(self, 'call') + + def get_own_property(self, prop): + return self.own.get(prop) + + def get_property(self, prop): + cand = self.get_own_property(prop) + if cand: + return cand + if self.prototype is not None: + return self.prototype.get_property(prop) + + def get(self, prop): #external use! + #prop = prop.value + if self.Class=='Undefined' or self.Class=='Null': + raise MakeError('TypeError', 'Undefiend and null dont have properties!') + if not isinstance(prop, basestring): + prop = prop.to_string().value + if not isinstance(prop, basestring): raise RuntimeError('Bug') + cand = self.get_property(prop) + if cand is None: + return Js(None) + if is_data_descriptor(cand): + return cand['value'] + if cand['get'].is_undefined(): + return cand['get'] + return cand['get'].call(self) + + def can_put(self, prop): #to check + desc = self.get_own_property(prop) + if desc: #if we have this property + if is_accessor_descriptor(desc): + return desc['set'].is_callable() # Check if setter method is defined + else: #data desc + return desc['writable'] + if self.prototype is not None: + return self.extensible + inherited = self.get_property(prop) + if inherited is None: + return self.extensible + if is_accessor_descriptor(inherited): + return not inherited['set'].is_undefined() + elif self.extensible: + return inherited['writable'] + return False + + + def put(self, prop, val, op=None): #external use! + '''Just like in js: self.prop op= val + for example when op is '+' it will be self.prop+=val + op can be either None for simple assignment or one of: + * / % + - << >> & ^ |''' + if self.Class=='Undefined' or self.Class=='Null': + raise MakeError('TypeError', 'Undefiend and null dont have properties!') + if not isinstance(prop, basestring): + prop = prop.to_string().value + #we need to set the value to the incremented one + if op is not None: + val = getattr(self.get(prop), OP_METHODS[op])(val) + if not self.can_put(prop): + return val + own_desc = self.get_own_property(prop) + if is_data_descriptor(own_desc): + if self.Class=='Array': #only array has different define_own_prop + self.define_own_property(prop, {'value':val}) + else: + self.own[prop]['value'] = val + return val + desc = self.get_property(prop) + if is_accessor_descriptor(desc): + desc['set'].call(self, (val,)) + else: + new = {'value' : val, + 'writable' : True, + 'configurable' : True, + 'enumerable' : True} + if self.Class=='Array': + self.define_own_property(prop, new) + else: + self.own[prop] = new + return val + + def has_property(self, prop): + return self.get_property(prop) is not None + + def delete(self, prop): + if not isinstance(prop, basestring): + prop = prop.to_string().value + desc = self.get_own_property(prop) + if desc is None: + return Js(True) + if desc['configurable']: + del self.own[prop] + return Js(True) + return Js(False) + + def default_value(self, hint=None): # made a mistake at the very early stage and made it to prefer string... caused lots! of problems + 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 method.is_callable(): + cand = method.call(self) + if cand.is_primitive(): + return cand + raise MakeError('TypeError', 'Cannot convert object to primitive value') + + + def define_own_property(self, prop, desc): #Internal use only. External through Object + # 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 property + if not extensible: + return False + 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 + if not desc or desc==current: #We dont need to change anything. + return True + configurable = current['configurable'] + if not configurable: #Prevent changing configurable or enumerable + if desc.get('configurable'): + return False + if 'enumerable' in desc and desc['enumerable']!=current['enumerable']: + return False + if is_generic_descriptor(desc): + pass + elif is_data_descriptor(current)!=is_data_descriptor(desc): + if not configurable: + return False + if is_data_descriptor(current): + del current['value'] + del current['writable'] + current['set'] = undefined #undefined + current['get'] = undefined #undefined + else: + 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'): + return False + if not current['writable'] and 'value' in desc and current['value']!=desc['value']: + return False + elif is_accessor_descriptor(current) and is_accessor_descriptor(desc): + if not configurable: + if 'set' in desc and desc['set'] is not current['set']: + return False + if 'get' in desc and desc['get'] is not current['get']: + return False + current.update(desc) + return True + + #these methods will work only for Number class + def is_infinity(self): + assert self.Class=='Number' + return self.value==float('inf') or self.value==-float('inf') + + def is_nan(self): + assert self.Class=='Number' + return self.value!=self.value #nan!=nan evaluates to true + + def is_finite(self): + return not (self.is_nan() or self.is_infinity()) + #Type Conversions. to_type. All must return pyjs subclass instance + + def to_primitive(self, hint=None): + if self.is_primitive(): + 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' or typ=='String': #false only for 0, '' and NaN + return Js(bool(self.value and self.value==self.value)) # test for nan (nan -> flase) + else: #object - always true + return true + + def to_number(self): + typ = Type(self) + if typ=='Null': #null is 0 + return Js(0) + elif typ=='Undefined': # undefined is NaN + return NaN + elif typ=='Boolean': # 1 for true 0 for false + return Js(int(self.value)) + elif typ=='Number':# or self.Class=='Number': # no need to convert + return self + elif typ=='String': + s = self.value.strip() #Strip white space + if not s: # '' is simply 0 + return Js(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 Js(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 Js(sign*float('inf')) + try: #decimal try + num = sign*float(s) # Converted + except ValueError: + return NaN # could not convert to decimal > return NaN + return Js(num) + else: #object - most likely it will be NaN. + return self.to_primitive('Number').to_number() + + def to_string(self): + typ = Type(self) + if typ=='Null': + return Js('null') + elif typ=='Undefined': + return Js('undefined') + elif typ=='Boolean': + return Js('true') if self.value else Js('false') + elif typ=='Number': #or self.Class=='Number': + if self.is_nan(): + return Js('NaN') + elif self.is_infinity(): + sign = '-' if self.value<0 else '' + return Js(sign+'Infinity') + elif isinstance(self.value, long) or self.value.is_integer(): # dont print .0 + return Js(unicode(int(self.value))) + return Js(unicode(self.value)) # accurate enough + elif typ=='String': + return self + else: #object + return self.to_primitive('String').to_string() + + + def to_object(self): + typ = self.TYPE + if typ=='Null' or typ=='Undefined': + raise MakeError('TypeError', 'undefined or null can\'t be converted to object') + elif typ=='Boolean': # Unsure here... todo repair here + return Boolean.create(self) + elif typ=='Number': #? + return Number.create(self) + elif typ=='String': #? + return String.create(self) + else: #object + return self + + def to_int32(self): + num = self.to_number() + if num.is_nan() or num.is_infinity(): + return 0 + int32 = int(num.value) % 2**32 + return int(int32 - 2**32 if int32 >= 2**31 else int32) + + def strict_equality_comparison(self, other): + return PyJsStrictEq(self, other) + + def cok(self): + """Check object coercible""" + if self.Class in {'Undefined', 'Null'}: + raise MakeError('TypeError', 'undefined or null can\'t be converted to object') + + def to_int(self): + num = self.to_number() + if num.is_nan(): + return 0 + elif num.is_infinity(): + return 10**20 if num.value>0 else -10**20 + return int(num.value) + + def to_uint32(self): + num = self.to_number() + if num.is_nan() or num.is_infinity(): + return 0 + return int(num.value) % 2**32 + + def to_uint16(self): + num = self.to_number() + if num.is_nan() or num.is_infinity(): + return 0 + return int(num.value) % 2**16 + + def to_int16(self): + num = self.to_number() + if num.is_nan() or num.is_infinity(): + return 0 + int16 = int(num.value) % 2**16 + return int(int16 - 2**16 if int16 >= 2**15 else int16) + + + + def same_as(self, other): + typ = Type(self) + if typ!=other.Class: + return False + if typ=='Undefined' or typ=='Null': + return True + if typ=='Boolean' or typ=='Number' or typ=='String': + return self.value==other.value + else: #object + return self is other #Id compare. + + #Not to be used by translation (only internal use) + def __getitem__(self, item): + return self.get(str(item) if not isinstance(item, PyJs) else item.to_string().value) + + def __setitem__(self, item, value): + self.put(str(item) if not isinstance(item, PyJs) else item.to_string().value, Js(value)) + + def __len__(self): + try: + return self.get('length').to_uint32() + except: + raise TypeError('This object (%s) does not have length property'%self.Class) + #Oprators------------- + #Unary, other will be implemented as functions. Increments and decrements + # will be methods of Number class + def __neg__(self): #-u + return Js(-self.to_number().value) + + def __pos__(self): #+u + return self.to_number() + + def __invert__(self): #~u + return Js(Js(~self.to_int32()).to_int32()) + + def neg(self): # !u cant do 'not u' :( + return Js(not self.to_boolean().value) + + def __nonzero__(self): + return self.to_boolean().value + + def __bool__(self): + return self.to_boolean().value + + def typeof(self): + if self.is_callable(): + return Js('function') + typ = Type(self).lower() + if typ=='null': + typ = 'object' + return Js(typ) + + #Bitwise operators + # <<, >>, &, ^, | + + # << + def __lshift__(self, other): + lnum = self.to_int32() + rnum = other.to_uint32() + shiftCount = rnum & 0x1F + return Js(Js(lnum << shiftCount).to_int32()) + + # >> + def __rshift__(self, other): + lnum = self.to_int32() + rnum = other.to_uint32() + shiftCount = rnum & 0x1F + return Js(Js(lnum >> shiftCount).to_int32()) + + + # >>> + def pyjs_bshift(self, other): + lnum = self.to_uint32() + rnum = other.to_uint32() + shiftCount = rnum & 0x1F + return Js(Js(lnum >> shiftCount).to_uint32()) + + # & + def __and__(self, other): + lnum = self.to_int32() + rnum = other.to_int32() + return Js(Js(lnum & rnum).to_int32()) + + # ^ + def __xor__(self, other): + lnum = self.to_int32() + rnum = other.to_int32() + return Js(Js(lnum ^ rnum).to_int32()) + + # | + def __or__(self, other): + lnum = self.to_int32() + rnum = other.to_int32() + return Js(Js(lnum | rnum).to_int32()) + + # Additive operators + # + and - are implemented here + + # + + def __add__(self, other): + a = self.to_primitive() + b = other.to_primitive() + if a.TYPE=='String' or b.TYPE=='String': + return Js(a.to_string().value+b.to_string().value) + a = a.to_number() + b = b.to_number() + return Js(a.value+b.value) + + # - + def __sub__(self, other): + return Js(self.to_number().value-other.to_number().value) + + #Multiplicative operators + # *, / and % are implemented here + + # * + def __mul__(self, other): + return Js(self.to_number().value*other.to_number().value) + + # / + def __div__(self, other): + a = self.to_number().value + b = other.to_number().value + if b: + return Js(a/b) + if not a or a!=a: + return NaN + return Infinity if a>0 else -Infinity + + # % + def __mod__(self, other): + a = self.to_number().value + b = other.to_number().value + if abs(a)==float('inf') or not b: + return NaN + if abs(b)==float('inf'): + return Js(a) + pyres = Js(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.value>0: + pyres.value -= abs(b) + elif a>0 and pyres.value<0: + pyres.value += abs(b) + return Js(pyres) + + #Comparisons (I dont implement === and !== here, these + # will be implemented as external functions later) + # <, <=, !=, ==, >=, > are implemented here. + + def abstract_relational_comparison(self, other, self_first=True): + ''' self= + def __ge__(self, other): + res = self.abstract_relational_comparison(other, True) + if res.is_undefined(): + return false + return res.neg() + + #> + def __gt__(self, other): + res = self.abstract_relational_comparison(other, False) + if res.is_undefined(): + return false + return res + + def abstract_equality_comparison(self, other): + ''' returns the result of JS == compare. + result is PyJs type: bool''' + tx, ty = self.TYPE, other.TYPE + if tx==ty: + if tx=='Undefined' or tx=='Null': + return true + if tx=='Number' or tx=='String' or tx=='Boolean': + return Js(self.value==other.value) + return Js(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 self.abstract_equality_comparison(other.to_number()) + elif tx=='String' and ty=='Number': + return self.to_number().abstract_equality_comparison(other) + elif tx=='Boolean': + return self.to_number().abstract_equality_comparison(other) + elif ty=='Boolean': + return self.abstract_equality_comparison(other.to_number()) + elif (tx=='String' or tx=='Number') and other.is_object(): + return self.abstract_equality_comparison(other.to_primitive()) + elif (ty=='String' or ty=='Number') and self.is_object(): + return self.to_primitive().abstract_equality_comparison(other) + else: + return false + + #== + def __eq__(self, other): + return self.abstract_equality_comparison(other) + + #!= + def __ne__(self, other): + return self.abstract_equality_comparison(other).neg() + + #Other methods (instanceof) + + def instanceof(self, other): + '''checks if self is instance of other''' + if not hasattr(other, 'has_instance'): + return false + return other.has_instance(self) + + #iteration + def __iter__(self): + #Returns a generator of all own enumerable properties + # since the size od self.own can change we need to use different method of iteration. + # SLOW! New items will NOT show up. + returned = {} + if not self.IS_CHILD_SCOPE: + cands = sorted(name for name in self.own if self.own[name]['enumerable']) + else: + cands = sorted(name for name in self.own) + for cand in cands: + check = self.own.get(cand) + if check and check['enumerable']: + yield Js(cand) + + + def contains(self, other): + if not self.is_object(): + raise MakeError('TypeError',"You can\'t use 'in' operator to search in non-objects") + return Js(self.has_property(other.to_string().value)) + + #Other Special methods + def __call__(self, *args): + '''Call a property prop as a function (this will be global object). + + NOTE: dont pass this and arguments here, these will be added + automatically!''' + if not self.is_callable(): + raise MakeError('TypeError', '%s is not a function'%self.typeof()) + return self.call(self.GlobalObject, args) + + + def create(self, *args): + '''Generally not a constructor, raise an error''' + raise MakeError('TypeError', '%s is not a constructor'%self.Class) + + def __unicode__(self): + return self.to_string().value + + def __repr__(self): + if self.Class=='Object': + res = [] + for e in self: + res.append(str_repr(e.value)+': '+str_repr(self.get(e))) + return '{%s}'%', '.join(res) + elif self.Class=='String': + return str_repr(self.value) + elif self.Class=='Array': + res = [] + for e in self: + res.append(repr(self.get(e))) + return '[%s]'%', '.join(res) + else: + val = str_repr(self.to_string().value) + return val + + def _fuck_python3(self): # hack to make object hashable in python 3 (__eq__ causes problems) + return object.__hash__(self) + + def callprop(self, prop, *args): + '''Call a property prop as a method (this will be self). + + NOTE: dont pass this and arguments here, these will be added + automatically!''' + if not isinstance(prop, basestring): + prop = prop.to_string().value + cand = self.get(prop) + if not cand.is_callable(): + raise MakeError('TypeError','%s is not a function'%cand.typeof()) + return cand.call(self, args) + + def to_python(self): + """returns equivalent python object. + for example if this object is javascript array then this method will return equivalent python array""" + return to_python(self) + + def to_py(self): + """returns equivalent python object. + for example if this object is javascript array then this method will return equivalent python array""" + return self.to_python() + + +if six.PY3: + PyJs.__hash__ = PyJs._fuck_python3 + PyJs.__truediv__ = PyJs.__div__ +#Define some more classes representing operators: + +def PyJsStrictEq(a, b): + '''a===b''' + tx, ty = Type(a), Type(b) + if tx!=ty: + return false + if tx=='Undefined' or tx=='Null': + return true + if a.is_primitive(): #string bool and number case + return Js(a.value==b.value) + return Js(a is b) # object comparison + + +def PyJsStrictNeq(a, b): + ''' a!==b''' + return PyJsStrictEq(a, b).neg() + +def PyJsBshift(a, b): + """a>>>b""" + return a.pyjs_bshift(b) + + +def PyJsComma(a, b): + return b + +class PyJsException(Exception): + def __str__(self): + if self.mes.Class=='Error': + return self.mes.callprop('toString').value + else: + return unicode(self.mes) + +class PyJsSwitchException(Exception): pass + + +PyJs.MakeError = staticmethod(MakeError) + +def JsToPyException(js): + temp = PyJsException() + temp.mes = js + return temp + +def PyExceptionToJs(py): + return py.mes + +#Scope class it will hold all the variables accessible to user +class Scope(PyJs): + Class = 'global' + extensible = True + IS_CHILD_SCOPE = True + # 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, closure=None): + """Doc""" + self.prototype = closure + if closure 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}) + else: + # not global, less powerful but faster closure. + self.own = scope # simple dictionary which maps name directly to js object. + + def register(self, lval): + # registered keeps only global registered variables + if self.prototype is None: + # define in global scope + if lval in self.own: + self.own[lval]['configurable'] = False + else: + self.define_own_property(lval, {'value': undefined, 'configurable': False, + 'writable': True, 'enumerable': True}) + elif lval not in self.own: + # define in local scope since it has not been defined yet + self.own[lval] = undefined # default value + + def registers(self, lvals): + """register multiple variables""" + for lval in lvals: + self.register(lval) + + def put(self, lval, val, op=None): + if self.prototype is None: + # global scope put, simple + return PyJs.put(self, lval, val, op) + else: + # trying to put in local scope + # we dont know yet in which scope we should place this var + if lval in self.own: + if op: # increment operation + val = getattr(self.own[lval], OP_METHODS[op])(val) + self.own[lval] = 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(lval, val, op) + + def force_own_put(self, prop, val, configurable=False): + if self.prototype is None: # global scope + self.own[prop] = {'value': val, 'writable': True, 'enumerable':True, 'configurable':configurable} + else: + self.own[prop] = val + + def get(self, prop, throw=True): + #note prop is always a Py String + if not isinstance(prop, basestring): + prop = prop.to_string().value + if self.prototype is not None: + # fast local scope + cand = self.own.get(prop) + if cand is None: + return self.prototype.get(prop, throw) + return cand + # slow, global scope + if prop not in self.own: + if throw: + raise MakeError('ReferenceError', '%s is not defined' % prop) + return undefined + return PyJs.get(self, prop) + + def delete(self, lval): + if self.prototype is not None: + if lval in self.own: + return false + return self.prototype.delete(lval) + # we are in global scope here. Must exist and be configurable to delete + if lval not in self.own: + # this lval does not exist, why do you want to delete it??? + return true + if self.own[lval]['configurable']: + del self.own[lval] + return true + # not configurable, cant delete + return false + + def pyimport(self, name, module): + self.register(name) + self.put(name, py_wrap(module)) + + def __repr__(self): + return u'[Object Global]' + + def to_python(self): + return to_python(self) + +class This(Scope): + IS_CHILD_SCOPE = False + def get(self, prop, throw=False): + return Scope.get(self, prop, throw) + +class JsObjectWrapper(object): + def __init__(self, obj): + self.__dict__['_obj'] = obj + + def __call__(self, *args): + args = tuple(Js(e) for e in args) + if '_prop_of' in self.__dict__: + parent, meth = self.__dict__['_prop_of'] + return to_python(parent._obj.callprop(meth, *args)) + return to_python(self._obj(*args)) + + def __getattr__(self, item): + if item == 'new' and self._obj.is_callable(): + # return instance initializer + def PyJsInstanceInit(*args): + args = tuple(Js(e) for e in args) + return self._obj.create(*args).to_python() + return PyJsInstanceInit + cand = to_python(self._obj.get(str(item))) + # handling method calling... obj.meth(). Value of this in meth should be self + if isinstance(cand, self.__class__): + cand.__dict__['_prop_of'] = self, str(item) + return cand + + def __setattr__(self, item, value): + self._obj.put(str(item), Js(value)) + + def __getitem__(self, item): + cand = to_python(self._obj.get(str(item))) + if isinstance(cand, self.__class__): + cand.__dict__['_prop_of'] = self, str(item) + return cand + + def __setitem__(self, item, value): + self._obj.put(str(item), Js(value)) + + def __iter__(self): + if self._obj.Class=='Array': + return iter(self.to_list()) + elif self._obj.Class=='Object': + return iter(self.to_dict()) + else: + raise MakeError('TypeError', '%s is not iterable in Python' % self._obj.Class) + + def __repr__(self): + if self._obj.is_primitive() or self._obj.is_callable(): + return repr(self._obj) + elif self._obj.Class in {'Array', 'Arguments'}: + return repr(self.to_list()) + return repr(self.to_dict()) + + def __len__(self): + return len(self._obj) + + def __nonzero__(self): + return bool(self._obj) + + def __bool__(self): + return bool(self._obj) + + def to_dict(self): + return to_dict(self.__dict__['_obj']) + + def to_list(self): + return to_list(self.__dict__['_obj']) + + + +class PyObjectWrapper(PyJs): + Class = 'PyObjectWrapper' + def __init__(self, obj): + self.obj = obj + + def get(self, prop): + if not isinstance(prop, basestring): + prop = prop.to_string().value + try: + if prop.isdigit(): + return py_wrap(self.obj[int(prop)]) + return py_wrap(getattr(self.obj, prop)) + except: + return undefined + + def put(self, prop, val, throw=False): + if not isinstance(prop, basestring): + prop = prop.to_string().value + try: + setattr(self.obj, prop, to_python(val)) + except AttributeError: + raise MakeError('TypeError', 'Read only object probably...') + return val + + def __call__(self, *args): + py_args = tuple(to_python(e) for e in args) + try: + py_res = self.obj.__call__(*py_args) + except Exception as e: + message = 'your Python function failed! ' + try: + message += e.message + except: + pass + raise MakeError('Error', message) + return py_wrap(py_res) + + + def callprop(self, prop, *args): + py_args = tuple(to_python(e) for e in args) + if not isinstance(prop, basestring): + prop = prop.to_string().value + return self.get(prop)(*py_args) + + def delete(self, prop): + if not isinstance(prop, basestring): + prop = prop.to_string().value + try: + if prop.isdigit(): + del self.obj[int(prop)] + else: + delattr(self.obj, prop) + return true + except: + return false + + def __repr__(self): + return 'PyObjectWrapper(%s)' % str(self.obj) + + def to_python(self): + return self.obj + + def to_py(self): + return self.obj + + +def py_wrap(py): + if isinstance(py, (FunctionType, BuiltinFunctionType, MethodType, BuiltinMethodType, + dict, int, str, bool, float, list, tuple, long, basestring)) or py is None : + return HJs(py) + return PyObjectWrapper(py) + + + + +############################################################################## +#Define types + +#Object +class PyJsObject(PyJs): + Class = 'Object' + def __init__(self, prop_descs={}, prototype=None, extensible=True): + self.prototype = prototype + self.extensible = extensible + self.own = {} + for prop, desc in six.iteritems(prop_descs): + self.define_own_property(prop, desc) + + def __repr__(self): + return repr(self.to_python().to_dict()) + + + +ObjectPrototype = PyJsObject() + + +#Function +class PyJsFunction(PyJs): + Class = 'Function' + def __init__(self, func, prototype=None, extensible=True, source=None): + cand = fix_js_args(func) + has_scope = cand is func + func = cand + self.argcount = six.get_function_code(func).co_argcount - 2 - has_scope + self.code = func + self.source = source if source else '{ [python code] }' + self.func_name = func.__name__ if not func.__name__.startswith('PyJs_anonymous') else '' + self.extensible = extensible + self.prototype = prototype + self.own = {} + #set own property length to the number of arguments + self.define_own_property('length', {'value': Js(self.argcount), 'writable': False, + 'enumerable': False, 'configurable': False}) + + if self.func_name: + self.define_own_property('name', {'value': Js(self.func_name), 'writable': False, + 'enumerable': False, 'configurable': True}) + + # set own prototype + proto = Js({}) + # constructor points to this function + proto.define_own_property('constructor',{'value': self, 'writable': True, + 'enumerable': False, 'configurable': True}) + self.define_own_property('prototype', {'value': proto, 'writable': True, + 'enumerable': False, 'configurable': False}) + + def _set_name(self, name): + '''name is py type''' + if self.own.get('name'): + self.func_name = name + self.own['name']['value'] = Js(name) + + def construct(self, *args): + proto = self.get('prototype') + if not proto.is_object(): # set to standard prototype + proto = ObjectPrototype + obj = PyJsObject(prototype=proto) + cand = self.call(obj, *args) + return cand if cand.is_object() else obj + + def call(self, this, args=()): + '''Calls this function and returns a result + (converted to PyJs type so func can return python types) + + this must be a PyJs object and args must be a python tuple of PyJs objects. + + arguments object is passed automatically and will be equal to Js(args) + (tuple converted to arguments object).You dont need to worry about number + of arguments you provide if you supply less then missing ones will be set + to undefined (but not present in arguments object). + And if you supply too much then excess will not be passed + (but they will be present in arguments object). + ''' + if not hasattr(args, '__iter__'): #get rid of it later + args = (args,) + args = tuple(Js(e) for e in args) # this wont be needed later + + arguments = PyJsArguments(args, self) # tuple will be converted to arguments object. + arglen = self.argcount #function expects this number of args. + if len(args)>arglen: + args = args[0:arglen] + elif len(args)>': '__rshift__', + '&': '__and__', + '^': '__xor__', + '|': '__or__', + '>>>': 'pyjs_bshift'} + +def Empty(): + return Js(None) + +#Number +class PyJsNumber(PyJs): #Note i dont implement +0 and -0. Just 0. + TYPE = 'Number' + Class = 'Number' + + + +NumberPrototype = PyJsObject({}, ObjectPrototype) +NumberPrototype.Class = 'Number' +NumberPrototype.value = 0 + +Infinity = PyJsNumber(float('inf'), NumberPrototype) +NaN = PyJsNumber(float('nan'), NumberPrototype) +PyJs.NaN = NaN +PyJs.Infinity = Infinity + +# This dict aims to increase speed of string creation by storing character instances +CHAR_BANK = {} +NUM_BANK = {} +PyJs.CHAR_BANK = CHAR_BANK +#String +# Different than implementation design in order to improve performance +#for example I dont create separate property for each character in string, it would take ages. +class PyJsString(PyJs): + TYPE = 'String' + Class = 'String' + extensible = False + def __init__(self, value=None, prototype=None): + '''Constructor for Number String and Boolean''' + if not isinstance(value, basestring): + raise TypeError # this will be internal error + self.value = value + self.prototype = prototype + self.own = {} + # this should be optimized because its mych slower than python str creation (about 50 times!) + # Dont create separate properties for every index. Just + self.own['length'] = {'value': Js(len(value)), 'writable': False, + 'enumerable': False, 'configurable': False} + if len(value)==1: + CHAR_BANK[value] = self #, 'writable': False, + # 'enumerable': True, 'configurable': False} + + def get(self, prop): + if not isinstance(prop, basestring): + prop = prop.to_string().value + try: + index = int(prop) + if index<0: + return undefined + char = self.value[index] + if char not in CHAR_BANK: + Js(char) # this will add char to CHAR BANK + return CHAR_BANK[char] + except Exception: + pass + return PyJs.get(self, prop) + + def can_put(self, prop): + return False + + def __iter__(self): + for i in xrange(len(self.value)): + yield Js(i) # maybe create an int bank? + + +StringPrototype = PyJsObject({}, ObjectPrototype) +StringPrototype.Class = 'String' +StringPrototype.value = '' + +CHAR_BANK[''] = Js('') + +#Boolean +class PyJsBoolean(PyJs): + TYPE = 'Boolean' + Class = 'Boolean' + +BooleanPrototype = PyJsObject({}, ObjectPrototype) +BooleanPrototype.Class = 'Boolean' +BooleanPrototype.value = False + +true = PyJsBoolean(True, BooleanPrototype) +false = PyJsBoolean(False, BooleanPrototype) + + +#Undefined +class PyJsUndefined(PyJs): + TYPE = 'Undefined' + Class = 'Undefined' + def __init__(self): + pass + +undefined = PyJsUndefined() + +#Null +class PyJsNull(PyJs): + TYPE = 'Null' + Class = 'Null' + def __init__(self): + pass +null = PyJsNull() +PyJs.null = null + +class PyJsArray(PyJs): + Class = 'Array' + def __init__(self, arr=[], prototype=None): + self.extensible = True + self.prototype = prototype + self.own = {'length' : {'value': Js(0), 'writable': True, + 'enumerable': False, 'configurable': False}} + for i, e in enumerate(arr): + self.define_own_property(str(i), {'value': Js(e), 'writable': True, + 'enumerable': True, 'configurable': True}) + + def define_own_property(self, prop, desc): + old_len_desc = self.get_own_property('length') + old_len = old_len_desc['value'].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) + new_len = desc['value'].to_uint32() + if new_len!=desc['value'].to_number().value: + raise MakeError('RangeError', 'Invalid range!') + new_desc = {k:v for k,v in six.iteritems(desc)} + new_desc['value'] = Js(new_len) + if new_len>=old_len: + return PyJs.define_own_property(self, prop, new_desc) + 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): + return False + if new_len30*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'] = Js(old_len+1) + if not new_writable: + new_desc['writable'] = False + PyJs.define_own_property(self, prop, new_desc) + return False + old_len = new_len + else: # standard method + while new_len=old_len and not old_len_desc['writable']: + return False + if not PyJs.define_own_property(self, prop, desc): + return False + if index>=old_len: + old_len_desc['value'] = Js(index + 1) + return True + else: + return PyJs.define_own_property(self, prop, desc) + + def to_list(self): + return [self.get(str(e)) for e in xrange(self.get('length').to_uint32())] + + def __repr__(self): + return repr(self.to_python().to_list()) + + + + + +ArrayPrototype = PyJsArray([], ObjectPrototype) + +class PyJsArguments(PyJs): + Class = 'Arguments' + def __init__(self, args, callee): + self.own = {} + self.extensible = True + self.prototype = ObjectPrototype + self.define_own_property('length', {'value': Js(len(args)), 'writable': True, + 'enumerable': False, 'configurable': True}) + self.define_own_property('callee', {'value': callee, 'writable': True, + 'enumerable': False, 'configurable': True}) + for i, e in enumerate(args): + self.put(str(i), Js(e)) + + def to_list(self): + return [self.get(str(e)) for e in xrange(self.get('length').to_uint32())] + + +#We can define function proto after number proto because func uses number in its init +FunctionPrototype = PyJsFunction(Empty, ObjectPrototype) +FunctionPrototype.own['name']['value'] = Js('') + + +# I will not rewrite RegExp engine from scratch. I will use re because its much faster. +# I have to only make sure that I am handling all the differences correctly. +REGEXP_DB = {} + +class PyJsRegExp(PyJs): + Class = 'RegExp' + extensible = True + + def __init__(self, regexp, prototype=None): + + self.prototype = prototype + self.glob = False + self.ignore_case = 0 + self.multiline = 0 + # self._cache = {'str':'NoStringEmpty23093', + # 'iterator': None, + # 'lastpos': -1, + # 'matches': {}} + flags = '' + if not regexp[-1]=='/': + #contains some flags (allowed are i, g, m + spl = regexp.rfind('/') + flags = set(regexp[spl+1:]) + self.value = regexp[1:spl] + if 'g' in flags: + self.glob = True + if 'i' in flags: + self.ignore_case = re.IGNORECASE + if 'm' in flags: + self.multiline = re.MULTILINE + else: + self.value = regexp[1:-1] + + try: + if self.value in REGEXP_DB: + self.pat = REGEXP_DB[regexp] + else: + comp = 'None' + # we have to check whether pattern is valid. + # also this will speed up matching later + # todo critical fix patter conversion etc. ..!!!!! + # ugly hacks porting js reg exp to py reg exp works in 99% of cases ;) + possible_fixes = [ + (u'[]', u'[\0]'), + (u'[^]', u'[^\0]'), + (u'nofix1791', u'nofix1791') + ] + reg = self.value + for fix, rep in possible_fixes: + comp = REGEXP_CONVERTER._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 + REGEXP_DB[regexp] = self.pat + except: + #print 'Invalid pattern but fuck it', self.value, comp + raise MakeError('SyntaxError', 'Invalid RegExp pattern: %s -> %s'% (repr(self.value), repr(comp))) + # now set own properties: + self.own = {'source' : {'value': Js(self.value), 'enumerable': False, 'writable': False, 'configurable': False}, + 'global' : {'value': Js(self.glob), 'enumerable': False, 'writable': False, 'configurable': False}, + 'ignoreCase' : {'value': Js(bool(self.ignore_case)), 'enumerable': False, 'writable': False, 'configurable': False}, + 'multiline' : {'value': Js(bool(self.multiline)), 'enumerable': False, 'writable': False, 'configurable': False}, + 'lastIndex' : {'value': Js(0), 'enumerable': False, 'writable': True, 'configurable': False}} + + def match(self, string, pos): + '''string is of course py string''' + return self.pat.match(string, pos) # way easier :) + # assert 0<=pos <= len(string) + # if not pos: + # return re.match(self.pat, string) + # else: + # if self._cache['str']==string: + # if pos>self._cache['lastpos']: + # for m in self._cache['iterator']: + # start = m.start() + # self._cache['lastpos'] = start + # self._cache['matches'][start] = m + # if start==pos: + # return m + # elif start>pos: + # return None + # self._cache['lastpos'] = len(string) + # return None + # else: + # return self._cache['matches'].get(pos) + # else: + # self._cache['str'] = string + # self._cache['matches'] = {} + # self._cache['lastpos'] = -1 + # self._cache['iterator'] = re.finditer(self.pat, string) + # return self.match(string, pos) + + + +def JsRegExp(source): + # Takes regexp literal! + return PyJsRegExp(source, RegExpPrototype) + +RegExpPrototype = PyJsRegExp('/(?:)/', ObjectPrototype) + +####Exceptions: +default_attrs = {'writable':True, 'enumerable':False, 'configurable':True} + + +def fill_in_props(obj, props, default_desc): + for prop, value in props.items(): + default_desc['value'] = Js(value) + obj.define_own_property(prop, default_desc) + + + +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', Js(message).to_string()) + self.own['message']['enumerable'] = False + +ErrorPrototype = PyJsError(Js(''), ObjectPrototype) +@Js +def Error(message): + return PyJsError(None if message.is_undefined() else message, ErrorPrototype) +Error.create = Error +err = {'name': 'Error', + 'constructor': Error} +fill_in_props(ErrorPrototype, err, default_attrs) +Error.define_own_property('prototype', {'value': ErrorPrototype, + 'enumerable': False, + 'writable': False, + 'configurable': False}) + +def define_error_type(name): + TypeErrorPrototype = PyJsError(None, ErrorPrototype) + @Js + def TypeError(message): + return PyJsError(None if message.is_undefined() else message, TypeErrorPrototype) + err = {'name': name, + 'constructor': TypeError} + fill_in_props(TypeErrorPrototype, err, default_attrs) + TypeError.define_own_property('prototype', {'value': TypeErrorPrototype, + 'enumerable': False, + 'writable': False, + 'configurable': False}) + ERRORS[name] = TypeError + +ERRORS = {'Error': Error} +ERROR_NAMES = ['Eval', 'Type', 'Range', 'Reference', 'Syntax', 'URI'] + +for e in ERROR_NAMES: + define_error_type(e+'Error') + + +############################################################################## +# Import and fill prototypes here. + +#this works only for data properties +def fill_prototype(prototype, Class, attrs, constructor=False): + for i in dir(Class): + e = getattr(Class, i) + if six.PY2: + if hasattr(e, '__func__'): + temp = PyJsFunction(e.__func__, FunctionPrototype) + attrs = {k:v for k,v in attrs.iteritems()} + attrs['value'] = temp + prototype.define_own_property(i, attrs) + else: + if hasattr(e, '__call__') and not i.startswith('__'): + temp = PyJsFunction(e, FunctionPrototype) + attrs = {k:v for k,v in attrs.items()} + attrs['value'] = temp + prototype.define_own_property(i, attrs) + if constructor: + attrs['value'] = constructor + prototype.define_own_property('constructor', attrs) + + + + +PyJs.undefined = undefined +PyJs.Js = staticmethod(Js) + +from .prototypes import jsfunction, jsobject, jsnumber, jsstring, jsboolean, jsarray, jsregexp, jserror + + +#Object proto +fill_prototype(ObjectPrototype, jsobject.ObjectPrototype, default_attrs) +#Define __proto__ accessor (this cant be done by fill_prototype since) +@Js +def __proto__(): + return this.prototype if this.prototype is not None else null +getter = __proto__ +@Js +def __proto__(val): + if val.is_object(): + this.prototype = val +setter = __proto__ +ObjectPrototype.define_own_property('__proto__', {'set': setter, + 'get': getter, + 'enumerable': False, + 'configurable':True}) + + +#Function proto +fill_prototype(FunctionPrototype, jsfunction.FunctionPrototype, default_attrs) +#Number proto +fill_prototype(NumberPrototype, jsnumber.NumberPrototype, default_attrs) +#String proto +fill_prototype(StringPrototype, jsstring.StringPrototype, default_attrs) +#Boolean proto +fill_prototype(BooleanPrototype, jsboolean.BooleanPrototype, default_attrs) +#Array proto +fill_prototype(ArrayPrototype, jsarray.ArrayPrototype, default_attrs) +#Error proto +fill_prototype(ErrorPrototype, jserror.ErrorPrototype, default_attrs) +#RegExp proto +fill_prototype(RegExpPrototype, jsregexp.RegExpPrototype, default_attrs) +# add exec to regexpfunction (cant add it automatically because of its name :( +RegExpPrototype.own['exec'] = RegExpPrototype.own['exec2'] +del RegExpPrototype.own['exec2'] + +######################################################################### +# Constructors + +# String +@Js +def String(st): + if not len(arguments): + return Js('') + return arguments[0].to_string() + +@Js +def string_constructor(): + temp = PyJsObject(prototype=StringPrototype) + temp.Class = 'String' + #temp.TYPE = 'String' + if not len(arguments): + temp.value = '' + else: + temp.value = arguments[0].to_string().value + for i, ch in enumerate(temp.value): # this will make things long... + temp.own[str(i)] = {'value': Js(ch), 'writable': False, + 'enumerable': True, 'configurable': True} + temp.own['length'] = {'value': Js(len(temp.value)), 'writable': False, + 'enumerable': False, 'configurable': False} + return temp + +String.create = string_constructor + +# RegExp +REG_EXP_FLAGS = {'g', 'i', 'm'} +@Js +def RegExp(pattern, flags): + if pattern.Class=='RegExp': + if not flags.is_undefined(): + raise MakeError('TypeError', 'Cannot supply flags when constructing one RegExp from another') + # return unchanged + return pattern + #pattern is not a regexp + if pattern.is_undefined(): + pattern = '' + else: + pattern = pattern.to_string().value + # try: + # pattern = REGEXP_CONVERTER._unescape_string(pattern.to_string().value) + # except: + # raise MakeError('SyntaxError', 'Invalid regexp') + flags = flags.to_string().value if not flags.is_undefined() else '' + 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) + pattern = '/%s/'%(pattern if pattern else '(?:)') + flags + return JsRegExp(pattern) + +RegExp.create = RegExp +PyJs.RegExp = RegExp + +# Number + +@Js +def Number(): + if len(arguments): + return arguments[0].to_number() + else: + return Js(0) + +@Js +def number_constructor(): + temp = PyJsObject(prototype=NumberPrototype) + temp.Class = 'Number' + #temp.TYPE = 'Number' + if len(arguments): + temp.value = arguments[0].to_number().value + else: + temp.value = 0 + return temp + +Number.create = number_constructor + +# Boolean + +@Js +def Boolean(value): + return value.to_boolean() +@Js +def boolean_constructor(value): + temp = PyJsObject(prototype=BooleanPrototype) + temp.Class = 'Boolean' + #temp.TYPE = 'Boolean' + temp.value = value.to_boolean().value + return temp + +Boolean.create = boolean_constructor + + +############################################################################## + +def appengine(code): + try: + return translator.translate_js(code.decode('utf-8')) + except: + return traceback.format_exc() + + + +builtins = ('true','false','null','undefined','Infinity', + 'NaN') + +scope = dict(zip(builtins, [eval(e) for e in builtins])) + +JS_BUILTINS = {k:v for k,v in scope.items()} + + +# Fill in NUM_BANK +for e in xrange(-2**10,2**14): + NUM_BANK[e] = Js(e) + +if __name__=='__main__': + print(ObjectPrototype.get('toString').callprop('call')) + print(FunctionPrototype.own) + a= null-Js(49404) + x = a.put('ser', Js('der')) + print(Js(0) or Js('p') and Js(4.0000000000050000001)) + FunctionPrototype.put('Chuj', Js(409)) + for e in FunctionPrototype: + print('Obk', e.get('__proto__').get('__proto__').get('__proto__'), e) + import code + s = Js(4) + b = Js(6) + + s2 = Js(4) + o = ObjectPrototype + o.put('x', Js(100)) + var = Scope(scope) + e = code.InteractiveConsole(globals()) + #e.raw_input = interactor + e.interact() + + diff --git a/lib/js2py/constructors/__init__.py b/lib/js2py/constructors/__init__.py new file mode 100644 index 00000000..4bf95623 --- /dev/null +++ b/lib/js2py/constructors/__init__.py @@ -0,0 +1 @@ +__author__ = 'Piotr Dabkowski' \ No newline at end of file diff --git a/lib/js2py/constructors/jsarray.py b/lib/js2py/constructors/jsarray.py new file mode 100644 index 00000000..a6af010d --- /dev/null +++ b/lib/js2py/constructors/jsarray.py @@ -0,0 +1,38 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/constructors/jsboolean.py b/lib/js2py/constructors/jsboolean.py new file mode 100644 index 00000000..124e8fc6 --- /dev/null +++ b/lib/js2py/constructors/jsboolean.py @@ -0,0 +1,11 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/constructors/jsdate.py b/lib/js2py/constructors/jsdate.py new file mode 100644 index 00000000..8638fde5 --- /dev/null +++ b/lib/js2py/constructors/jsdate.py @@ -0,0 +1,362 @@ +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): + return NotImplementedError() + + +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 (UTCToLocal(this.value) - 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)=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}) \ No newline at end of file diff --git a/lib/js2py/constructors/jsfunction.py b/lib/js2py/constructors/jsfunction.py new file mode 100644 index 00000000..9a8c4b25 --- /dev/null +++ b/lib/js2py/constructors/jsfunction.py @@ -0,0 +1,49 @@ +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) + diff --git a/lib/js2py/constructors/jsmath.py b/lib/js2py/constructors/jsmath.py new file mode 100644 index 00000000..514bbc03 --- /dev/null +++ b/lib/js2py/constructors/jsmath.py @@ -0,0 +1,151 @@ +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) \ No newline at end of file diff --git a/lib/js2py/constructors/jsnumber.py b/lib/js2py/constructors/jsnumber.py new file mode 100644 index 00000000..9acb43a0 --- /dev/null +++ b/lib/js2py/constructors/jsnumber.py @@ -0,0 +1,18 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/constructors/jsobject.py b/lib/js2py/constructors/jsobject.py new file mode 100644 index 00000000..8a306237 --- /dev/null +++ b/lib/js2py/constructors/jsobject.py @@ -0,0 +1,172 @@ +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 + + + diff --git a/lib/js2py/constructors/jsregexp.py b/lib/js2py/constructors/jsregexp.py new file mode 100644 index 00000000..120c097d --- /dev/null +++ b/lib/js2py/constructors/jsregexp.py @@ -0,0 +1,11 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/constructors/jsstring.py b/lib/js2py/constructors/jsstring.py new file mode 100644 index 00000000..cf9100af --- /dev/null +++ b/lib/js2py/constructors/jsstring.py @@ -0,0 +1,30 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/constructors/time_helpers.py b/lib/js2py/constructors/time_helpers.py new file mode 100644 index 00000000..b9d9f937 --- /dev/null +++ b/lib/js2py/constructors/time_helpers.py @@ -0,0 +1,183 @@ +# 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', Warning) + class LOCAL_ZONE: + @staticmethod + def dst(*args): + return 1 + +from lib.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) + diff --git a/lib/js2py/evaljs.py b/lib/js2py/evaljs.py new file mode 100644 index 00000000..afe5dcb8 --- /dev/null +++ b/lib/js2py/evaljs.py @@ -0,0 +1,250 @@ +# coding=utf-8 +""" This module is still experimental! +""" +from .translators import translate_js, DEFAULT_HEADER +import sys +import time +import json +import six +import os +import hashlib +import codecs +import pyjs + + +__all__ = ['EvalJs', 'translate_js', 'import_js', 'eval_js', 'translate_file', 'run_file'] +DEBUG = 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) + + + +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, '', '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).hexdigest() + '.py' + try: + with open(filename, mode='w') as f: + f.write(code) + execfile(filename, 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() + diff --git a/lib/js2py/host/__init__.py b/lib/js2py/host/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/js2py/host/console.py b/lib/js2py/host/console.py new file mode 100644 index 00000000..55075ce2 --- /dev/null +++ b/lib/js2py/host/console.py @@ -0,0 +1,11 @@ +from lib.js2py.base import * + +@Js +def console(): + pass + +@Js +def log(): + print(arguments[0]) + +console.put('log', log) diff --git a/lib/js2py/host/dom/__init__.py b/lib/js2py/host/dom/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/js2py/host/dom/constants.py b/lib/js2py/host/dom/constants.py new file mode 100644 index 00000000..b4ed209b --- /dev/null +++ b/lib/js2py/host/dom/constants.py @@ -0,0 +1,47 @@ +from js2py.base import * + +def _get_conts(idl): + def is_valid(c): + try: + exec(c) + return 1 + except: + pass + return '\n'.join(filter(is_valid, (' '.join(e.strip(' ;').split()[-3:]) for e in idl.splitlines()))) + + +default_attrs = {'writable':True, 'enumerable':True, 'configurable':True} + + +def compose_prototype(Class, attrs=default_attrs): + prototype = Class() + for i in dir(Class): + e = getattr(Class, i) + if hasattr(e, '__func__'): + temp = PyJsFunction(e.__func__, FunctionPrototype) + attrs = {k:v for k,v in attrs.iteritems()} + attrs['value'] = temp + prototype.define_own_property(i, attrs) + return prototype + + +# Error codes + +INDEX_SIZE_ERR = 1 +DOMSTRING_SIZE_ERR = 2 +HIERARCHY_REQUEST_ERR = 3 +WRONG_DOCUMENT_ERR = 4 +INVALID_CHARACTER_ERR = 5 +NO_DATA_ALLOWED_ERR = 6 +NO_MODIFICATION_ALLOWED_ERR = 7 +NOT_FOUND_ERR = 8 +NOT_SUPPORTED_ERR = 9 +INUSE_ATTRIBUTE_ERR = 10 +INVALID_STATE_ERR = 11 +SYNTAX_ERR = 12 +INVALID_MODIFICATION_ERR = 13 +NAMESPACE_ERR = 14 +INVALID_ACCESS_ERR = 15 +VALIDATION_ERR = 16 +TYPE_MISMATCH_ERR = 17 + diff --git a/lib/js2py/host/dom/interface.py b/lib/js2py/host/dom/interface.py new file mode 100644 index 00000000..5e3e4a64 --- /dev/null +++ b/lib/js2py/host/dom/interface.py @@ -0,0 +1,73 @@ +from StringIO import StringIO +from constants import * +from bs4 import BeautifulSoup +from js2py.base import * +try: + import lxml + def parse(source): + return BeautifulSoup(source, 'lxml') +except: + def parse(source): + return BeautifulSoup(source) + + + + + + + +x = ''' + + + + + + + + + + +
Shady GroveAeolian
Over the River, CharlieDorian
''' + + + +class DOM(PyJs): + prototype = ObjectPrototype + def __init__(self): + self.own = {} + + def readonly(self, name, val): + self.define_own_property(name, {'writable':False, 'enumerable':False, 'configurable':False, 'value': Js(val)}) + + + +# DOMStringList + +class DOMStringListPrototype(DOM): + Class = 'DOMStringListPrototype' + + def contains(element): + return element.to_string().value in this._string_list + + def item(index): + return this._string_list[index.to_int()] if 0<=index.to_int()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 n4: # 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 URI handling! + + + diff --git a/lib/js2py/legecy_translators/__init__.py b/lib/js2py/legecy_translators/__init__.py new file mode 100644 index 00000000..d01e0f5f --- /dev/null +++ b/lib/js2py/legecy_translators/__init__.py @@ -0,0 +1 @@ +__author__ = 'Piotrek' diff --git a/lib/js2py/legecy_translators/constants.py b/lib/js2py/legecy_translators/constants.py new file mode 100644 index 00000000..96bb79fe --- /dev/null +++ b/lib/js2py/legecy_translators/constants.py @@ -0,0 +1,294 @@ +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 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 \ No newline at end of file diff --git a/lib/js2py/legecy_translators/exps.py b/lib/js2py/legecy_translators/exps.py new file mode 100644 index 00000000..5e72abca --- /dev/null +++ b/lib/js2py/legecy_translators/exps.py @@ -0,0 +1,79 @@ +""" +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') \ No newline at end of file diff --git a/lib/js2py/legecy_translators/flow.py b/lib/js2py/legecy_translators/flow.py new file mode 100644 index 00000000..523859cb --- /dev/null +++ b/lib/js2py/legecy_translators/flow.py @@ -0,0 +1,456 @@ +"""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(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>': '__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): + return None + if source[start:start+kl] != keyword: + return None + if kl+starttext_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 nlt: + 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:] \ No newline at end of file diff --git a/lib/js2py/legecy_translators/nodevisitor.py b/lib/js2py/legecy_translators/nodevisitor.py new file mode 100644 index 00000000..c642a2fb --- /dev/null +++ b/lib/js2py/legecy_translators/nodevisitor.py @@ -0,0 +1,500 @@ +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 n1: #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='+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') + diff --git a/lib/js2py/legecy_translators/nparser.py b/lib/js2py/legecy_translators/nparser.py new file mode 100644 index 00000000..8cd4d2bd --- /dev/null +++ b/lib/js2py/legecy_translators/nparser.py @@ -0,0 +1,2891 @@ +# JUST FOR NOW, later I will write my own - much faster and better. + +# Copyright (C) 2013 Ariya Hidayat +# Copyright (C) 2013 Thaddee Tyl +# Copyright (C) 2012 Ariya Hidayat +# Copyright (C) 2012 Mathias Bynens +# Copyright (C) 2012 Joost-Wim Boekesteijn +# Copyright (C) 2012 Kris Kowal +# Copyright (C) 2012 Yusuke Suzuki +# Copyright (C) 2012 Arpad Borsos +# Copyright (C) 2011 Ariya Hidayat +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# -*- coding: latin-1 -*- +from __future__ import print_function +import re +def typeof(t): + if t is None: return 'undefined' + elif isinstance(t, bool): return 'boolean' + elif isinstance(t, str): return 'string' + elif isinstance(t, int) or isinstance(t, float): return 'number' + elif hasattr(t, '__call__'): return 'function' + else: return 'object' + +def list_indexOf(l, v): + try: + return l.index(v) + except: + return -1 + +parseFloat = float +parseInt = int + +class jsdict(object): + def __init__(self, d): + self.__dict__.update(d) + def __getitem__(self, name): + if name in self.__dict__: + return self.__dict__[name] + else: + return None + def __setitem__(self, name, value): + self.__dict__[name] = value + return value + def __getattr__(self, name): + try: + return getattr(self, name) + except: + return None + def __setattr__(self, name, value): + self[name] = value + return value + def __contains__(self, name): + return name in self.__dict__ + def __repr__(self): + return str(self.__dict__) + +class RegExp(object): + def __init__(self, pattern, flags=''): + self.flags = flags + pyflags = 0 | re.M if 'm' in flags else 0 | re.I if 'i' in flags else 0 + self.source = pattern + self.pattern = re.compile(pattern, pyflags) + def test(self, s): + return self.pattern.search(s) is not None + +console = jsdict({"log":print}) + +def __temp__42(object=None, body=None): + return jsdict({ +"type": Syntax.WithStatement, +"object": object, +"body": body, +}) + +def __temp__41(test=None, body=None): + return jsdict({ +"type": Syntax.WhileStatement, +"test": test, +"body": body, +}) + +def __temp__40(id=None, init=None): + return jsdict({ +"type": Syntax.VariableDeclarator, +"id": id, +"init": init, +}) + +def __temp__39(declarations=None, kind=None): + return jsdict({ +"type": Syntax.VariableDeclaration, +"declarations": declarations, +"kind": kind, +}) + +def __temp__38(operator=None, argument=None): + if (operator == "++") or (operator == "--"): + return jsdict({ +"type": Syntax.UpdateExpression, +"operator": operator, +"argument": argument, +"prefix": True, +}) + return jsdict({ +"type": Syntax.UnaryExpression, +"operator": operator, +"argument": argument, +"prefix": True, +}) + +def __temp__37(block=None, guardedHandlers=None, handlers=None, finalizer=None): + return jsdict({ +"type": Syntax.TryStatement, +"block": block, +"guardedHandlers": guardedHandlers, +"handlers": handlers, +"finalizer": finalizer, +}) + +def __temp__36(argument=None): + return jsdict({ +"type": Syntax.ThrowStatement, +"argument": argument, +}) + +def __temp__35(): + return jsdict({ +"type": Syntax.ThisExpression, +}) + +def __temp__34(discriminant=None, cases=None): + return jsdict({ +"type": Syntax.SwitchStatement, +"discriminant": discriminant, +"cases": cases, +}) + +def __temp__33(test=None, consequent=None): + return jsdict({ +"type": Syntax.SwitchCase, +"test": test, +"consequent": consequent, +}) + +def __temp__32(expressions=None): + return jsdict({ +"type": Syntax.SequenceExpression, +"expressions": expressions, +}) + +def __temp__31(argument=None): + return jsdict({ +"type": Syntax.ReturnStatement, +"argument": argument, +}) + +def __temp__30(kind=None, key=None, value=None): + return jsdict({ +"type": Syntax.Property, +"key": key, +"value": value, +"kind": kind, +}) + +def __temp__29(body=None): + return jsdict({ +"type": Syntax.Program, +"body": body, +}) + +def __temp__28(operator=None, argument=None): + return jsdict({ +"type": Syntax.UpdateExpression, +"operator": operator, +"argument": argument, +"prefix": False, +}) + +def __temp__27(properties=None): + return jsdict({ +"type": Syntax.ObjectExpression, +"properties": properties, +}) + +def __temp__26(callee=None, args=None): + return jsdict({ +"type": Syntax.NewExpression, +"callee": callee, +"arguments": args, +}) + +def __temp__25(accessor=None, object=None, property=None): + return jsdict({ +"type": Syntax.MemberExpression, +"computed": accessor == "[", +"object": object, +"property": property, +}) + +def __temp__24(token=None): + return jsdict({ +"type": Syntax.Literal, +"value": token.value, +"raw": source[token.range[0]:token.range[1]], +}) + +def __temp__23(label=None, body=None): + return jsdict({ +"type": Syntax.LabeledStatement, +"label": label, +"body": body, +}) + +def __temp__22(test=None, consequent=None, alternate=None): + return jsdict({ +"type": Syntax.IfStatement, +"test": test, +"consequent": consequent, +"alternate": alternate, +}) + +def __temp__21(name=None): + return jsdict({ +"type": Syntax.Identifier, +"name": name, +}) + +def __temp__20(id=None, params=None, defaults=None, body=None): + return jsdict({ +"type": Syntax.FunctionExpression, +"id": id, +"params": params, +"defaults": defaults, +"body": body, +"rest": None, +"generator": False, +"expression": False, +}) + +def __temp__19(id=None, params=None, defaults=None, body=None): + return jsdict({ +"type": Syntax.FunctionDeclaration, +"id": id, +"params": params, +"defaults": defaults, +"body": body, +"rest": None, +"generator": False, +"expression": False, +}) + +def __temp__18(left=None, right=None, body=None): + return jsdict({ +"type": Syntax.ForInStatement, +"left": left, +"right": right, +"body": body, +"each": False, +}) + +def __temp__17(init=None, test=None, update=None, body=None): + return jsdict({ +"type": Syntax.ForStatement, +"init": init, +"test": test, +"update": update, +"body": body, +}) + +def __temp__16(expression=None): + return jsdict({ +"type": Syntax.ExpressionStatement, +"expression": expression, +}) + +def __temp__15(): + return jsdict({ +"type": Syntax.EmptyStatement, +}) + +def __temp__14(body=None, test=None): + return jsdict({ +"type": Syntax.DoWhileStatement, +"body": body, +"test": test, +}) + +def __temp__13(): + return jsdict({ +"type": Syntax.DebuggerStatement, +}) + +def __temp__12(label=None): + return jsdict({ +"type": Syntax.ContinueStatement, +"label": label, +}) + +def __temp__11(test=None, consequent=None, alternate=None): + return jsdict({ +"type": Syntax.ConditionalExpression, +"test": test, +"consequent": consequent, +"alternate": alternate, +}) + +def __temp__10(param=None, body=None): + return jsdict({ +"type": Syntax.CatchClause, +"param": param, +"body": body, +}) + +def __temp__9(callee=None, args=None): + return jsdict({ +"type": Syntax.CallExpression, +"callee": callee, +"arguments": args, +}) + +def __temp__8(label=None): + return jsdict({ +"type": Syntax.BreakStatement, +"label": label, +}) + +def __temp__7(body=None): + return jsdict({ +"type": Syntax.BlockStatement, +"body": body, +}) + +def __temp__6(operator=None, left=None, right=None): + type = (Syntax.LogicalExpression if (operator == "||") or (operator == "&&") else Syntax.BinaryExpression) + return jsdict({ +"type": type, +"operator": operator, +"left": left, +"right": right, +}) + +def __temp__5(operator=None, left=None, right=None): + return jsdict({ +"type": Syntax.AssignmentExpression, +"operator": operator, +"left": left, +"right": right, +}) + +def __temp__4(elements=None): + return jsdict({ +"type": Syntax.ArrayExpression, +"elements": elements, +}) + +def __temp__3(node=None): + if extra.source: + node.loc.source = extra.source + return node + +def __temp__2(node=None): + if node.range or node.loc: + if extra.loc: + state.markerStack.pop() + state.markerStack.pop() + if extra.range: + state.markerStack.pop() + else: + SyntaxTreeDelegate.markEnd(node) + return node + +def __temp__1(node=None): + if extra.range: + node.range = [state.markerStack.pop(), index] + if extra.loc: + node.loc = jsdict({ +"start": jsdict({ +"line": state.markerStack.pop(), +"column": state.markerStack.pop(), +}), +"end": jsdict({ +"line": lineNumber, +"column": index - lineStart, +}), +}) + SyntaxTreeDelegate.postProcess(node) + return node + +def __temp__0(): + if extra.loc: + state.markerStack.append(index - lineStart) + state.markerStack.append(lineNumber) + if extra.range: + state.markerStack.append(index) + +Token = None +TokenName = None +FnExprTokens = None +Syntax = None +PropertyKind = None +Messages = None +Regex = None +SyntaxTreeDelegate = None +source = None +strict = None +index = None +lineNumber = None +lineStart = None +length = None +delegate = None +lookahead = None +state = None +extra = None +Token = jsdict({ +"BooleanLiteral": 1, +"EOF": 2, +"Identifier": 3, +"Keyword": 4, +"NullLiteral": 5, +"NumericLiteral": 6, +"Punctuator": 7, +"StringLiteral": 8, +"RegularExpression": 9, +}) +TokenName = jsdict({ +}) +TokenName[Token.BooleanLiteral] = "Boolean" +TokenName[Token.EOF] = "" +TokenName[Token.Identifier] = "Identifier" +TokenName[Token.Keyword] = "Keyword" +TokenName[Token.NullLiteral] = "Null" +TokenName[Token.NumericLiteral] = "Numeric" +TokenName[Token.Punctuator] = "Punctuator" +TokenName[Token.StringLiteral] = "String" +TokenName[Token.RegularExpression] = "RegularExpression" +FnExprTokens = ["(", "{", "[", "in", "typeof", "instanceof", "new", "return", "case", "delete", "throw", "void", "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "|=", "^=", ",", "+", "-", "*", "/", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~", "&&", "||", "?", ":", "===", "==", ">=", "<=", "<", ">", "!=", "!=="] +Syntax = jsdict({ +"AssignmentExpression": "AssignmentExpression", +"ArrayExpression": "ArrayExpression", +"BlockStatement": "BlockStatement", +"BinaryExpression": "BinaryExpression", +"BreakStatement": "BreakStatement", +"CallExpression": "CallExpression", +"CatchClause": "CatchClause", +"ConditionalExpression": "ConditionalExpression", +"ContinueStatement": "ContinueStatement", +"DoWhileStatement": "DoWhileStatement", +"DebuggerStatement": "DebuggerStatement", +"EmptyStatement": "EmptyStatement", +"ExpressionStatement": "ExpressionStatement", +"ForStatement": "ForStatement", +"ForInStatement": "ForInStatement", +"FunctionDeclaration": "FunctionDeclaration", +"FunctionExpression": "FunctionExpression", +"Identifier": "Identifier", +"IfStatement": "IfStatement", +"Literal": "Literal", +"LabeledStatement": "LabeledStatement", +"LogicalExpression": "LogicalExpression", +"MemberExpression": "MemberExpression", +"NewExpression": "NewExpression", +"ObjectExpression": "ObjectExpression", +"Program": "Program", +"Property": "Property", +"ReturnStatement": "ReturnStatement", +"SequenceExpression": "SequenceExpression", +"SwitchStatement": "SwitchStatement", +"SwitchCase": "SwitchCase", +"ThisExpression": "ThisExpression", +"ThrowStatement": "ThrowStatement", +"TryStatement": "TryStatement", +"UnaryExpression": "UnaryExpression", +"UpdateExpression": "UpdateExpression", +"VariableDeclaration": "VariableDeclaration", +"VariableDeclarator": "VariableDeclarator", +"WhileStatement": "WhileStatement", +"WithStatement": "WithStatement", +}) +PropertyKind = jsdict({ +"Data": 1, +"Get": 2, +"Set": 4, +}) +Messages = jsdict({ +"UnexpectedToken": "Unexpected token %0", +"UnexpectedNumber": "Unexpected number", +"UnexpectedString": "Unexpected string", +"UnexpectedIdentifier": "Unexpected identifier", +"UnexpectedReserved": "Unexpected reserved word", +"UnexpectedEOS": "Unexpected end of input", +"NewlineAfterThrow": "Illegal newline after throw", +"InvalidRegExp": "Invalid regular expression", +"UnterminatedRegExp": "Invalid regular expression: missing /", +"InvalidLHSInAssignment": "Invalid left-hand side in assignment", +"InvalidLHSInForIn": "Invalid left-hand side in for-in", +"MultipleDefaultsInSwitch": "More than one default clause in switch statement", +"NoCatchOrFinally": "Missing catch or finally after try", +"UnknownLabel": "Undefined label '%0'", +"Redeclaration": "%0 '%1' has already been declared", +"IllegalContinue": "Illegal continue statement", +"IllegalBreak": "Illegal break statement", +"IllegalReturn": "Illegal return statement", +"StrictModeWith": "Strict mode code may not include a with statement", +"StrictCatchVariable": "Catch variable may not be eval or arguments in strict mode", +"StrictVarName": "Variable name may not be eval or arguments in strict mode", +"StrictParamName": "Parameter name eval or arguments is not allowed in strict mode", +"StrictParamDupe": "Strict mode function may not have duplicate parameter names", +"StrictFunctionName": "Function name may not be eval or arguments in strict mode", +"StrictOctalLiteral": "Octal literals are not allowed in strict mode.", +"StrictDelete": "Delete of an unqualified identifier in strict mode.", +"StrictDuplicateProperty": "Duplicate data property in object literal not allowed in strict mode", +"AccessorDataProperty": "Object literal may not have data and accessor property with the same name", +"AccessorGetSet": "Object literal may not have multiple get/set accessors with the same name", +"StrictLHSAssignment": "Assignment to eval or arguments is not allowed in strict mode", +"StrictLHSPostfix": "Postfix increment/decrement may not have eval or arguments operand in strict mode", +"StrictLHSPrefix": "Prefix increment/decrement may not have eval or arguments operand in strict mode", +"StrictReservedWord": "Use of future reserved word in strict mode", +}) +Regex = jsdict({ +"NonAsciiIdentifierStart": RegExp(u"[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]"), +"NonAsciiIdentifierPart": RegExp(u"[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u0800-\u082d\u0840-\u085b\u08a0\u08a2-\u08ac\u08e4-\u08fe\u0900-\u0963\u0966-\u096f\u0971-\u0977\u0979-\u097f\u0981-\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7\u09c8\u09cb-\u09ce\u09d7\u09dc\u09dd\u09df-\u09e3\u09e6-\u09f1\u0a01-\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a66-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5c\u0b5d\u0b5f-\u0b63\u0b66-\u0b6f\u0b71\u0b82\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c58\u0c59\u0c60-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0cde\u0ce0-\u0ce3\u0ce6-\u0cef\u0cf1\u0cf2\u0d02\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d57\u0d60-\u0d63\u0d66-\u0d6f\u0d7a-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e50-\u0e59\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0ed0-\u0ed9\u0edc-\u0edf\u0f00\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1049\u1050-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772\u1773\u1780-\u17d3\u17d7\u17dc\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1820-\u1877\u1880-\u18aa\u18b0-\u18f5\u1900-\u191c\u1920-\u192b\u1930-\u193b\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19d9\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1aa7\u1b00-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1bf3\u1c00-\u1c37\u1c40-\u1c49\u1c4d-\u1c7d\u1cd0-\u1cd2\u1cd4-\u1cf6\u1d00-\u1de6\u1dfc-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200c\u200d\u203f\u2040\u2054\u2071\u207f\u2090-\u209c\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u2e2f\u3005-\u3007\u3021-\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u3099\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua62b\ua640-\ua66f\ua674-\ua67d\ua67f-\ua697\ua69f-\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua827\ua840-\ua873\ua880-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f7\ua8fb\ua900-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf-\ua9d9\uaa00-\uaa36\uaa40-\uaa4d\uaa50-\uaa59\uaa60-\uaa76\uaa7a\uaa7b\uaa80-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabea\uabec\uabed\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\ufe70-\ufe74\ufe76-\ufefc\uff10-\uff19\uff21-\uff3a\uff3f\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]"), +}) +def assert__py__(condition=None, message=None): + if not condition: + raise RuntimeError("ASSERT: " + message) + +def isDecimalDigit(ch=None): + return (ch >= 48) and (ch <= 57) + +def isHexDigit(ch=None): + return "0123456789abcdefABCDEF".find(ch) >= 0 + +def isOctalDigit(ch=None): + return "01234567".find(ch) >= 0 + +def isWhiteSpace(ch=None): + return (((((ch == 32) or (ch == 9)) or (ch == 11)) or (ch == 12)) or (ch == 160)) or ((ch >= 5760) and (u"\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff".find(unichr(ch)) > 0)) + +def isLineTerminator(ch=None): + return (((ch == 10) or (ch == 13)) or (ch == 8232)) or (ch == 8233) + +def isIdentifierStart(ch=None): + return (((((ch == 36) or (ch == 95)) or ((ch >= 65) and (ch <= 90))) or ((ch >= 97) and (ch <= 122))) or (ch == 92)) or ((ch >= 128) and Regex.NonAsciiIdentifierStart.test(unichr(ch))) + +def isIdentifierPart(ch=None): + return ((((((ch == 36) or (ch == 95)) or ((ch >= 65) and (ch <= 90))) or ((ch >= 97) and (ch <= 122))) or ((ch >= 48) and (ch <= 57))) or (ch == 92)) or ((ch >= 128) and Regex.NonAsciiIdentifierPart.test(unichr(ch))) + +def isFutureReservedWord(id=None): + while 1: + if (id == "super") or ((id == "import") or ((id == "extends") or ((id == "export") or ((id == "enum") or (id == "class"))))): + return True + else: + return False + break + +def isStrictModeReservedWord(id=None): + while 1: + if (id == "let") or ((id == "yield") or ((id == "static") or ((id == "public") or ((id == "protected") or ((id == "private") or ((id == "package") or ((id == "interface") or (id == "implements")))))))): + return True + else: + return False + break + +def isRestrictedWord(id=None): + return (id == "eval") or (id == "arguments") + +def isKeyword(id=None): + if strict and isStrictModeReservedWord(id): + return True + while 1: + if len(id) == 2: + return ((id == "if") or (id == "in")) or (id == "do") + elif len(id) == 3: + return ((((id == "var") or (id == "for")) or (id == "new")) or (id == "try")) or (id == "let") + elif len(id) == 4: + return (((((id == "this") or (id == "else")) or (id == "case")) or (id == "void")) or (id == "with")) or (id == "enum") + elif len(id) == 5: + return (((((((id == "while") or (id == "break")) or (id == "catch")) or (id == "throw")) or (id == "const")) or (id == "yield")) or (id == "class")) or (id == "super") + elif len(id) == 6: + return (((((id == "return") or (id == "typeof")) or (id == "delete")) or (id == "switch")) or (id == "export")) or (id == "import") + elif len(id) == 7: + return ((id == "default") or (id == "finally")) or (id == "extends") + elif len(id) == 8: + return ((id == "function") or (id == "continue")) or (id == "debugger") + elif len(id) == 10: + return id == "instanceof" + else: + return False + break + +def addComment(type=None, value=None, start=None, end=None, loc=None): + comment = None + assert__py__(('undefined' if not 'start' in locals() else typeof(start)) == "number", "Comment must have valid position") + if state.lastCommentStart >= start: + return + state.lastCommentStart = start + comment = jsdict({ +"type": type, +"value": value, +}) + if extra.range: + comment.range = [start, end] + if extra.loc: + comment.loc = loc + extra.comments.append(comment) + +def skipSingleLineComment(): + global index, lineNumber, lineStart + start = None + loc = None + ch = None + comment = None + start = index - 2 + loc = jsdict({ +"start": jsdict({ +"line": lineNumber, +"column": (index - lineStart) - 2, +}), +}) + while index < length: + ch = (ord(source[index]) if index < len(source) else None) + index += 1 + index + if isLineTerminator(ch): + if extra.comments: + comment = source[(start + 2):(index - 1)] + loc.end = jsdict({ +"line": lineNumber, +"column": (index - lineStart) - 1, +}) + addComment("Line", comment, start, index - 1, loc) + if (ch == 13) and ((ord(source[index]) if index < len(source) else None) == 10): + index += 1 + index + lineNumber += 1 + lineNumber + lineStart = index + return + if extra.comments: + comment = source[(start + 2):index] + loc.end = jsdict({ +"line": lineNumber, +"column": index - lineStart, +}) + addComment("Line", comment, start, index, loc) + +def skipMultiLineComment(): + global index, lineNumber, lineStart + start = None + loc = None + ch = None + comment = None + if extra.comments: + start = index - 2 + loc = jsdict({ +"start": jsdict({ +"line": lineNumber, +"column": (index - lineStart) - 2, +}), +}) + while index < length: + ch = (ord(source[index]) if index < len(source) else None) + if isLineTerminator(ch): + if (ch == 13) and ((ord(source[index + 1]) if (index + 1) < len(source) else None) == 10): + index += 1 + index + lineNumber += 1 + lineNumber + index += 1 + index + lineStart = index + if index >= length: + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + elif ch == 42: + if (ord(source[index + 1]) if (index + 1) < len(source) else None) == 47: + index += 1 + index + index += 1 + index + if extra.comments: + comment = source[(start + 2):(index - 2)] + loc.end = jsdict({ +"line": lineNumber, +"column": index - lineStart, +}) + addComment("Block", comment, start, index, loc) + return + index += 1 + index + else: + index += 1 + index + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + +def skipComment(): + global index, lineNumber, lineStart + ch = None + while index < length: + ch = (ord(source[index]) if index < len(source) else None) + if isWhiteSpace(ch): + index += 1 + index + elif isLineTerminator(ch): + index += 1 + index + if (ch == 13) and ((ord(source[index]) if index < len(source) else None) == 10): + index += 1 + index + lineNumber += 1 + lineNumber + lineStart = index + elif ch == 47: + ch = (ord(source[index + 1]) if (index + 1) < len(source) else None) + if ch == 47: + index += 1 + index + index += 1 + index + skipSingleLineComment() + elif ch == 42: + index += 1 + index + index += 1 + index + skipMultiLineComment() + else: + break + else: + break + +def scanHexEscape(prefix=None): + global len__py__, index + i = None + len__py__ = None + ch = None + code = 0 + len__py__ = (4 if prefix == "u" else 2) + i = 0 + while 1: + if not (i < len__py__): + break + if (index < length) and isHexDigit(source[index]): + index += 1 + ch = source[index - 1] + code = (code * 16) + "0123456789abcdef".find(ch.lower()) + else: + return "" + i += 1 + return unichr(code) + +def getEscapedIdentifier(): + global index + ch = None + id = None + index += 1 + index += 1 + ch = (ord(source[index - 1]) if index - 1 < len(source) else None) + id = unichr(ch) + if ch == 92: + if (ord(source[index]) if index < len(source) else None) != 117: + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + index += 1 + index + ch = scanHexEscape("u") + if ((not ch) or (ch == "\\")) or (not isIdentifierStart((ord(ch[0]) if 0 < len(ch) else None))): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + id = ch + while index < length: + ch = (ord(source[index]) if index < len(source) else None) + if not isIdentifierPart(ch): + break + index += 1 + index + id += unichr(ch) + if ch == 92: + id = id[0:(0 + (len(id) - 1))] + if (ord(source[index]) if index < len(source) else None) != 117: + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + index += 1 + index + ch = scanHexEscape("u") + if ((not ch) or (ch == "\\")) or (not isIdentifierPart((ord(ch[0]) if 0 < len(ch) else None))): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + id += ch + return id + +def getIdentifier(): + global index + start = None + ch = None + index += 1 + start = index - 1 + while index < length: + ch = (ord(source[index]) if index < len(source) else None) + if ch == 92: + index = start + return getEscapedIdentifier() + if isIdentifierPart(ch): + index += 1 + index + else: + break + return source[start:index] + +def scanIdentifier(): + start = None + id = None + type = None + start = index + id = (getEscapedIdentifier() if (ord(source[index]) if index < len(source) else None) == 92 else getIdentifier()) + if len(id) == 1: + type = Token.Identifier + elif isKeyword(id): + type = Token.Keyword + elif id == "null": + type = Token.NullLiteral + elif (id == "true") or (id == "false"): + type = Token.BooleanLiteral + else: + type = Token.Identifier + return jsdict({ +"type": type, +"value": id, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + +def scanPunctuator(): + global index + start = index + code = (ord(source[index]) if index < len(source) else None) + code2 = None + ch1 = source[index] + ch2 = None + ch3 = None + ch4 = None + while 1: + if (code == 126) or ((code == 63) or ((code == 58) or ((code == 93) or ((code == 91) or ((code == 125) or ((code == 123) or ((code == 44) or ((code == 59) or ((code == 41) or ((code == 40) or (code == 46))))))))))): + index += 1 + index + if extra.tokenize: + if code == 40: + extra.openParenToken = len(extra.tokens) + elif code == 123: + extra.openCurlyToken = len(extra.tokens) + return jsdict({ +"type": Token.Punctuator, +"value": unichr(code), +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + else: + code2 = (ord(source[index + 1]) if (index + 1) < len(source) else None) + if code2 == 61: + while 1: + if (code == 124) or ((code == 94) or ((code == 62) or ((code == 60) or ((code == 47) or ((code == 45) or ((code == 43) or ((code == 42) or ((code == 38) or (code == 37))))))))): + index += 2 + return jsdict({ +"type": Token.Punctuator, +"value": unichr(code) + unichr(code2), +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + elif (code == 61) or (code == 33): + index += 2 + if (ord(source[index]) if index < len(source) else None) == 61: + index += 1 + index + return jsdict({ +"type": Token.Punctuator, +"value": source[start:index], +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + else: + break + break + break + break + ch2 = source[index + 1] if index + 1 < len(source) else None + ch3 = source[index + 2] if index + 2 < len(source) else None + ch4 = source[index + 3] if index + 3 < len(source) else None + if ((ch1 == ">") and (ch2 == ">")) and (ch3 == ">"): + if ch4 == "=": + index += 4 + return jsdict({ +"type": Token.Punctuator, +"value": ">>>=", +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + if ((ch1 == ">") and (ch2 == ">")) and (ch3 == ">"): + index += 3 + return jsdict({ +"type": Token.Punctuator, +"value": ">>>", +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + if ((ch1 == "<") and (ch2 == "<")) and (ch3 == "="): + index += 3 + return jsdict({ +"type": Token.Punctuator, +"value": "<<=", +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + if ((ch1 == ">") and (ch2 == ">")) and (ch3 == "="): + index += 3 + return jsdict({ +"type": Token.Punctuator, +"value": ">>=", +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + if (ch1 == ch2) and ("+-<>&|".find(ch1) >= 0): + index += 2 + return jsdict({ +"type": Token.Punctuator, +"value": ch1 + ch2, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + if "<>=!+-*%&|^/".find(ch1) >= 0: + index += 1 + index + return jsdict({ +"type": Token.Punctuator, +"value": ch1, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + +def scanHexLiteral(start=None): + global index + number = "" + while index < length: + if not isHexDigit(source[index]): + break + index += 1 + number += source[index - 1] + if len(number) == 0: + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + if isIdentifierStart((ord(source[index]) if index < len(source) else None)): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + return jsdict({ +"type": Token.NumericLiteral, +"value": parseInt("0x" + number, 16), +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + +def scanOctalLiteral(start=None): + global index + index += 1 + number = "0" + source[index - 1] + while index < length: + if not isOctalDigit(source[index]): + break + index += 1 + number += source[index - 1] + if isIdentifierStart((ord(source[index]) if index < len(source) else None)) or isDecimalDigit((ord(source[index]) if index < len(source) else None)): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + return jsdict({ +"type": Token.NumericLiteral, +"value": parseInt(number, 8), +"octal": True, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + +def scanNumericLiteral(): + global index + number = None + start = None + ch = None + ch = source[index] + assert__py__(isDecimalDigit((ord(ch[0]) if 0 < len(ch) else None)) or (ch == "."), "Numeric literal must start with a decimal digit or a decimal point") + start = index + number = "" + if ch != ".": + index += 1 + number = source[index - 1] + ch = source[index] if index < len(source) else None + if number == "0": + if (ch == "x") or (ch == "X"): + index += 1 + index + return scanHexLiteral(start) + if isOctalDigit(ch): + return scanOctalLiteral(start) + if ch and isDecimalDigit((ord(ch[0]) if 0 < len(ch) else None)): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + while isDecimalDigit((ord(source[index]) if index < len(source) else None)): + index += 1 + number += source[index - 1] + ch = source[index] if index < len(source) else None + if ch == ".": + index += 1 + number += source[index - 1] + while isDecimalDigit((ord(source[index]) if index < len(source) else None)): + index += 1 + number += source[index - 1] + ch = source[index] + if (ch == "e") or (ch == "E"): + index += 1 + number += source[index - 1] + ch = source[index] + if (ch == "+") or (ch == "-"): + index += 1 + number += source[index - 1] + if isDecimalDigit((ord(source[index]) if index < len(source) else None)): + while isDecimalDigit((ord(source[index]) if index < len(source) else None)): + index += 1 + number += source[index - 1] + else: + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + if isIdentifierStart((ord(source[index]) if index < len(source) else None)): + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + return jsdict({ +"type": Token.NumericLiteral, +"value": parseFloat(number), +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + +def scanStringLiteral(): + global index, lineNumber + str = "" + quote = None + start = None + ch = None + code = None + unescaped = None + restore = None + octal = False + quote = source[index] + assert__py__((quote == "'") or (quote == "\""), "String literal must starts with a quote") + start = index + index += 1 + index + while index < length: + index += 1 + ch = source[index - 1] + if ch == quote: + quote = "" + break + elif ch == "\\": + index += 1 + ch = source[index - 1] + if (not ch) or (not isLineTerminator((ord(ch[0]) if 0 < len(ch) else None))): + while 1: + if ch == "n": + str += u"\x0a" + break + elif ch == "r": + str += u"\x0d" + break + elif ch == "t": + str += u"\x09" + break + elif (ch == "x") or (ch == "u"): + restore = index + unescaped = scanHexEscape(ch) + if unescaped: + str += unescaped + else: + index = restore + str += ch + break + elif ch == "b": + str += u"\x08" + break + elif ch == "f": + str += u"\x0c" + break + elif ch == "v": + str += u"\x0b" + break + else: + if isOctalDigit(ch): + code = "01234567".find(ch) + if code != 0: + octal = True + if (index < length) and isOctalDigit(source[index]): + octal = True + index += 1 + code = (code * 8) + "01234567".find(source[index - 1]) + if (("0123".find(ch) >= 0) and (index < length)) and isOctalDigit(source[index]): + index += 1 + code = (code * 8) + "01234567".find(source[index - 1]) + str += unichr(code) + else: + str += ch + break + break + else: + lineNumber += 1 + lineNumber + if (ch == u"\x0d") and (source[index] == u"\x0a"): + index += 1 + index + elif isLineTerminator((ord(ch[0]) if 0 < len(ch) else None)): + break + else: + str += ch + if quote != "": + throwError(jsdict({ +}), Messages.UnexpectedToken, "ILLEGAL") + return jsdict({ +"type": Token.StringLiteral, +"value": str, +"octal": octal, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + +def scanRegExp(): + global lookahead, index + str = None + ch = None + start = None + pattern = None + flags = None + value = None + classMarker = False + restore = None + terminated = False + lookahead = None + skipComment() + start = index + ch = source[index] + assert__py__(ch == "/", "Regular expression literal must start with a slash") + index += 1 + str = source[index - 1] + while index < length: + index += 1 + ch = source[index - 1] + str += ch + if classMarker: + if ch == "]": + classMarker = False + else: + if ch == "\\": + index += 1 + ch = source[index - 1] + if isLineTerminator((ord(ch[0]) if 0 < len(ch) else None)): + throwError(jsdict({ +}), Messages.UnterminatedRegExp) + str += ch + elif ch == "/": + terminated = True + break + elif ch == "[": + classMarker = True + elif isLineTerminator((ord(ch[0]) if 0 < len(ch) else None)): + throwError(jsdict({ +}), Messages.UnterminatedRegExp) + if not terminated: + throwError(jsdict({ +}), Messages.UnterminatedRegExp) + pattern = str[1:(1 + (len(str) - 2))] + flags = "" + while index < length: + ch = source[index] + if not isIdentifierPart((ord(ch[0]) if 0 < len(ch) else None)): + break + index += 1 + index + if (ch == "\\") and (index < length): + ch = source[index] + if ch == "u": + index += 1 + index + restore = index + ch = scanHexEscape("u") + if ch: + flags += ch + str += "\\u" + while 1: + if not (restore < index): + break + str += source[restore] + restore += 1 + else: + index = restore + flags += "u" + str += "\\u" + else: + str += "\\" + else: + flags += ch + str += ch + try: + value = RegExp(pattern, flags) + except Exception as e: + throwError(jsdict({ +}), Messages.InvalidRegExp) + peek() + if extra.tokenize: + return jsdict({ +"type": Token.RegularExpression, +"value": value, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [start, index], +}) + return jsdict({ +"literal": str, +"value": value, +"range": [start, index], +}) + +def isIdentifierName(token=None): + return (((token.type == Token.Identifier) or (token.type == Token.Keyword)) or (token.type == Token.BooleanLiteral)) or (token.type == Token.NullLiteral) + +def advanceSlash(): + prevToken = None + checkToken = None + prevToken = extra.tokens[len(extra.tokens) - 1] + if not prevToken: + return scanRegExp() + if prevToken.type == "Punctuator": + if prevToken.value == ")": + checkToken = extra.tokens[extra.openParenToken - 1] + if (checkToken and (checkToken.type == "Keyword")) and ((((checkToken.value == "if") or (checkToken.value == "while")) or (checkToken.value == "for")) or (checkToken.value == "with")): + return scanRegExp() + return scanPunctuator() + if prevToken.value == "}": + if extra.tokens[extra.openCurlyToken - 3] and (extra.tokens[extra.openCurlyToken - 3].type == "Keyword"): + checkToken = extra.tokens[extra.openCurlyToken - 4] + if not checkToken: + return scanPunctuator() + elif extra.tokens[extra.openCurlyToken - 4] and (extra.tokens[extra.openCurlyToken - 4].type == "Keyword"): + checkToken = extra.tokens[extra.openCurlyToken - 5] + if not checkToken: + return scanRegExp() + else: + return scanPunctuator() + if FnExprTokens.indexOf(checkToken.value) >= 0: + return scanPunctuator() + return scanRegExp() + return scanRegExp() + if prevToken.type == "Keyword": + return scanRegExp() + return scanPunctuator() + +def advance(): + ch = None + skipComment() + if index >= length: + return jsdict({ +"type": Token.EOF, +"lineNumber": lineNumber, +"lineStart": lineStart, +"range": [index, index], +}) + ch = (ord(source[index]) if index < len(source) else None) + if ((ch == 40) or (ch == 41)) or (ch == 58): + return scanPunctuator() + if (ch == 39) or (ch == 34): + return scanStringLiteral() + if isIdentifierStart(ch): + return scanIdentifier() + if ch == 46: + if isDecimalDigit((ord(source[index + 1]) if (index + 1) < len(source) else None)): + return scanNumericLiteral() + return scanPunctuator() + if isDecimalDigit(ch): + return scanNumericLiteral() + if extra.tokenize and (ch == 47): + return advanceSlash() + return scanPunctuator() + +def lex(): + global index, lineNumber, lineStart, lookahead + token = None + token = lookahead + index = token.range[1] + lineNumber = token.lineNumber + lineStart = token.lineStart + lookahead = advance() + index = token.range[1] + lineNumber = token.lineNumber + lineStart = token.lineStart + return token + +def peek(): + global lookahead, index, lineNumber, lineStart + pos = None + line = None + start = None + pos = index + line = lineNumber + start = lineStart + lookahead = advance() + index = pos + lineNumber = line + lineStart = start + +SyntaxTreeDelegate = jsdict({ +"name": "SyntaxTree", +"markStart": __temp__0, +"markEnd": __temp__1, +"markEndIf": __temp__2, +"postProcess": __temp__3, +"createArrayExpression": __temp__4, +"createAssignmentExpression": __temp__5, +"createBinaryExpression": __temp__6, +"createBlockStatement": __temp__7, +"createBreakStatement": __temp__8, +"createCallExpression": __temp__9, +"createCatchClause": __temp__10, +"createConditionalExpression": __temp__11, +"createContinueStatement": __temp__12, +"createDebuggerStatement": __temp__13, +"createDoWhileStatement": __temp__14, +"createEmptyStatement": __temp__15, +"createExpressionStatement": __temp__16, +"createForStatement": __temp__17, +"createForInStatement": __temp__18, +"createFunctionDeclaration": __temp__19, +"createFunctionExpression": __temp__20, +"createIdentifier": __temp__21, +"createIfStatement": __temp__22, +"createLabeledStatement": __temp__23, +"createLiteral": __temp__24, +"createMemberExpression": __temp__25, +"createNewExpression": __temp__26, +"createObjectExpression": __temp__27, +"createPostfixExpression": __temp__28, +"createProgram": __temp__29, +"createProperty": __temp__30, +"createReturnStatement": __temp__31, +"createSequenceExpression": __temp__32, +"createSwitchCase": __temp__33, +"createSwitchStatement": __temp__34, +"createThisExpression": __temp__35, +"createThrowStatement": __temp__36, +"createTryStatement": __temp__37, +"createUnaryExpression": __temp__38, +"createVariableDeclaration": __temp__39, +"createVariableDeclarator": __temp__40, +"createWhileStatement": __temp__41, +"createWithStatement": __temp__42, +}) +def peekLineTerminator(): + global index, lineNumber, lineStart + pos = None + line = None + start = None + found = None + pos = index + line = lineNumber + start = lineStart + skipComment() + found = lineNumber != line + index = pos + lineNumber = line + lineStart = start + return found + +def throwError(token=None, messageFormat=None, a=None): + def __temp__43(whole=None, index=None): + assert__py__(index < len(args), "Message reference must be in range") + return args[index] + + error = None + args = Array.prototype.slice.call(arguments, 2) + msg = messageFormat.replace(RegExp(r'%(\d)'), __temp__43) + if ('undefined' if not ('lineNumber' in token) else typeof(token.lineNumber)) == "number": + error = RuntimeError((("Line " + token.lineNumber) + ": ") + msg) + error.index = token.range[0] + error.lineNumber = token.lineNumber + error.column = (token.range[0] - lineStart) + 1 + else: + error = RuntimeError((("Line " + lineNumber) + ": ") + msg) + error.index = index + error.lineNumber = lineNumber + error.column = (index - lineStart) + 1 + error.description = msg + raise error + +def throwErrorTolerant(): + try: + throwError.apply(None, arguments) + except Exception as e: + if extra.errors: + extra.errors.append(e) + else: + raise + +def throwUnexpected(token=None): + if token.type == Token.EOF: + throwError(token, Messages.UnexpectedEOS) + if token.type == Token.NumericLiteral: + throwError(token, Messages.UnexpectedNumber) + if token.type == Token.StringLiteral: + throwError(token, Messages.UnexpectedString) + if token.type == Token.Identifier: + throwError(token, Messages.UnexpectedIdentifier) + if token.type == Token.Keyword: + if isFutureReservedWord(token.value): + throwError(token, Messages.UnexpectedReserved) + elif strict and isStrictModeReservedWord(token.value): + throwErrorTolerant(token, Messages.StrictReservedWord) + return + throwError(token, Messages.UnexpectedToken, token.value) + throwError(token, Messages.UnexpectedToken, token.value) + +def expect(value=None): + token = lex() + if (token.type != Token.Punctuator) or (token.value != value): + throwUnexpected(token) + +def expectKeyword(keyword=None): + token = lex() + if (token.type != Token.Keyword) or (token.value != keyword): + throwUnexpected(token) + +def match(value=None): + return (lookahead.type == Token.Punctuator) and (lookahead.value == value) + +def matchKeyword(keyword=None): + return (lookahead.type == Token.Keyword) and (lookahead.value == keyword) + +def matchAssign(): + op = None + if lookahead.type != Token.Punctuator: + return False + op = lookahead.value + return (((((((((((op == "=") or (op == "*=")) or (op == "/=")) or (op == "%=")) or (op == "+=")) or (op == "-=")) or (op == "<<=")) or (op == ">>=")) or (op == ">>>=")) or (op == "&=")) or (op == "^=")) or (op == "|=") + +def consumeSemicolon(): + line = None + if (ord(source[index]) if index < len(source) else None) == 59: + lex() + return + line = lineNumber + skipComment() + if lineNumber != line: + return + if match(";"): + lex() + return + if (lookahead.type != Token.EOF) and (not match("}")): + throwUnexpected(lookahead) + +def isLeftHandSide(expr=None): + return (expr.type == Syntax.Identifier) or (expr.type == Syntax.MemberExpression) + +def parseArrayInitialiser(): + elements = [] + expect("[") + while not match("]"): + if match(","): + lex() + elements.append(None) + else: + elements.append(parseAssignmentExpression()) + if not match("]"): + expect(",") + expect("]") + return delegate.createArrayExpression(elements) + +def parsePropertyFunction(param=None, first=None): + global strict + previousStrict = None + body = None + previousStrict = strict + skipComment() + delegate.markStart() + body = parseFunctionSourceElements() + if (first and strict) and isRestrictedWord(param[0].name): + throwErrorTolerant(first, Messages.StrictParamName) + strict = previousStrict + return delegate.markEnd(delegate.createFunctionExpression(None, param, [], body)) + +def parseObjectPropertyKey(): + token = None + skipComment() + delegate.markStart() + token = lex() + if (token.type == Token.StringLiteral) or (token.type == Token.NumericLiteral): + if strict and token.octal: + throwErrorTolerant(token, Messages.StrictOctalLiteral) + return delegate.markEnd(delegate.createLiteral(token)) + return delegate.markEnd(delegate.createIdentifier(token.value)) + +def parseObjectProperty(): + token = None + key = None + id = None + value = None + param = None + token = lookahead + skipComment() + delegate.markStart() + if token.type == Token.Identifier: + id = parseObjectPropertyKey() + if (token.value == "get") and (not match(":")): + key = parseObjectPropertyKey() + expect("(") + expect(")") + value = parsePropertyFunction([]) + return delegate.markEnd(delegate.createProperty("get", key, value)) + if (token.value == "set") and (not match(":")): + key = parseObjectPropertyKey() + expect("(") + token = lookahead + if token.type != Token.Identifier: + expect(")") + throwErrorTolerant(token, Messages.UnexpectedToken, token.value) + value = parsePropertyFunction([]) + else: + param = [parseVariableIdentifier()] + expect(")") + value = parsePropertyFunction(param, token) + return delegate.markEnd(delegate.createProperty("set", key, value)) + expect(":") + value = parseAssignmentExpression() + return delegate.markEnd(delegate.createProperty("init", id, value)) + if (token.type == Token.EOF) or (token.type == Token.Punctuator): + throwUnexpected(token) + else: + key = parseObjectPropertyKey() + expect(":") + value = parseAssignmentExpression() + return delegate.markEnd(delegate.createProperty("init", key, value)) + +def parseObjectInitialiser(): + properties = [] + property = None + name = None + key = None + kind = None + map = jsdict({ +}) + toString = str + expect("{") + while not match("}"): + property = parseObjectProperty() + if property.key.type == Syntax.Identifier: + name = property.key.name + else: + name = toString(property.key.value) + kind = (PropertyKind.Data if property.kind == "init" else (PropertyKind.Get if property.kind == "get" else PropertyKind.Set)) + key = "$" + name + if key in map: + if map[key] == PropertyKind.Data: + if strict and (kind == PropertyKind.Data): + throwErrorTolerant(jsdict({ +}), Messages.StrictDuplicateProperty) + elif kind != PropertyKind.Data: + throwErrorTolerant(jsdict({ +}), Messages.AccessorDataProperty) + else: + if kind == PropertyKind.Data: + throwErrorTolerant(jsdict({ +}), Messages.AccessorDataProperty) + elif map[key] & kind: + throwErrorTolerant(jsdict({ +}), Messages.AccessorGetSet) + map[key] |= kind + else: + map[key] = kind + properties.append(property) + if not match("}"): + expect(",") + expect("}") + return delegate.createObjectExpression(properties) + +def parseGroupExpression(): + expr = None + expect("(") + expr = parseExpression() + expect(")") + return expr + +def parsePrimaryExpression(): + type = None + token = None + expr = None + if match("("): + return parseGroupExpression() + type = lookahead.type + delegate.markStart() + if type == Token.Identifier: + expr = delegate.createIdentifier(lex().value) + elif (type == Token.StringLiteral) or (type == Token.NumericLiteral): + if strict and lookahead.octal: + throwErrorTolerant(lookahead, Messages.StrictOctalLiteral) + expr = delegate.createLiteral(lex()) + elif type == Token.Keyword: + if matchKeyword("this"): + lex() + expr = delegate.createThisExpression() + elif matchKeyword("function"): + expr = parseFunctionExpression() + elif type == Token.BooleanLiteral: + token = lex() + token.value = token.value == "true" + expr = delegate.createLiteral(token) + elif type == Token.NullLiteral: + token = lex() + token.value = None + expr = delegate.createLiteral(token) + elif match("["): + expr = parseArrayInitialiser() + elif match("{"): + expr = parseObjectInitialiser() + elif match("/") or match("/="): + expr = delegate.createLiteral(scanRegExp()) + if expr: + return delegate.markEnd(expr) + throwUnexpected(lex()) + +def parseArguments(): + args = [] + expect("(") + if not match(")"): + while index < length: + args.append(parseAssignmentExpression()) + if match(")"): + break + expect(",") + expect(")") + return args + +def parseNonComputedProperty(): + token = None + delegate.markStart() + token = lex() + if not isIdentifierName(token): + throwUnexpected(token) + return delegate.markEnd(delegate.createIdentifier(token.value)) + +def parseNonComputedMember(): + expect(".") + return parseNonComputedProperty() + +def parseComputedMember(): + expr = None + expect("[") + expr = parseExpression() + expect("]") + return expr + +def parseNewExpression(): + callee = None + args = None + delegate.markStart() + expectKeyword("new") + callee = parseLeftHandSideExpression() + args = (parseArguments() if match("(") else []) + return delegate.markEnd(delegate.createNewExpression(callee, args)) + +def parseLeftHandSideExpressionAllowCall(): + marker = None + expr = None + args = None + property = None + marker = createLocationMarker() + expr = (parseNewExpression() if matchKeyword("new") else parsePrimaryExpression()) + while (match(".") or match("[")) or match("("): + if match("("): + args = parseArguments() + expr = delegate.createCallExpression(expr, args) + elif match("["): + property = parseComputedMember() + expr = delegate.createMemberExpression("[", expr, property) + else: + property = parseNonComputedMember() + expr = delegate.createMemberExpression(".", expr, property) + if marker: + marker.end() + marker.apply(expr) + return expr + +def parseLeftHandSideExpression(): + marker = None + expr = None + property = None + marker = createLocationMarker() + expr = (parseNewExpression() if matchKeyword("new") else parsePrimaryExpression()) + while match(".") or match("["): + if match("["): + property = parseComputedMember() + expr = delegate.createMemberExpression("[", expr, property) + else: + property = parseNonComputedMember() + expr = delegate.createMemberExpression(".", expr, property) + if marker: + marker.end() + marker.apply(expr) + return expr + +def parsePostfixExpression(): + expr = None + token = None + delegate.markStart() + expr = parseLeftHandSideExpressionAllowCall() + if lookahead.type == Token.Punctuator: + if (match("++") or match("--")) and (not peekLineTerminator()): + if (strict and (expr.type == Syntax.Identifier)) and isRestrictedWord(expr.name): + throwErrorTolerant(jsdict({ +}), Messages.StrictLHSPostfix) + if not isLeftHandSide(expr): + throwError(jsdict({ +}), Messages.InvalidLHSInAssignment) + token = lex() + expr = delegate.createPostfixExpression(token.value, expr) + return delegate.markEndIf(expr) + +def parseUnaryExpression(): + token = None + expr = None + delegate.markStart() + if (lookahead.type != Token.Punctuator) and (lookahead.type != Token.Keyword): + expr = parsePostfixExpression() + elif match("++") or match("--"): + token = lex() + expr = parseUnaryExpression() + if (strict and (expr.type == Syntax.Identifier)) and isRestrictedWord(expr.name): + throwErrorTolerant(jsdict({ +}), Messages.StrictLHSPrefix) + if not isLeftHandSide(expr): + throwError(jsdict({ +}), Messages.InvalidLHSInAssignment) + expr = delegate.createUnaryExpression(token.value, expr) + elif ((match("+") or match("-")) or match("~")) or match("!"): + token = lex() + expr = parseUnaryExpression() + expr = delegate.createUnaryExpression(token.value, expr) + elif (matchKeyword("delete") or matchKeyword("void")) or matchKeyword("typeof"): + token = lex() + expr = parseUnaryExpression() + expr = delegate.createUnaryExpression(token.value, expr) + if (strict and (expr.operator == "delete")) and (expr.argument.type == Syntax.Identifier): + throwErrorTolerant(jsdict({ +}), Messages.StrictDelete) + else: + expr = parsePostfixExpression() + return delegate.markEndIf(expr) + +def binaryPrecedence(token=None, allowIn=None): + prec = 0 + if (token.type != Token.Punctuator) and (token.type != Token.Keyword): + return 0 + while 1: + if token.value == "||": + prec = 1 + break + elif token.value == "&&": + prec = 2 + break + elif token.value == "|": + prec = 3 + break + elif token.value == "^": + prec = 4 + break + elif token.value == "&": + prec = 5 + break + elif (token.value == "!==") or ((token.value == "===") or ((token.value == "!=") or (token.value == "=="))): + prec = 6 + break + elif (token.value == "instanceof") or ((token.value == ">=") or ((token.value == "<=") or ((token.value == ">") or (token.value == "<")))): + prec = 7 + break + elif token.value == "in": + prec = (7 if allowIn else 0) + break + elif (token.value == ">>>") or ((token.value == ">>") or (token.value == "<<")): + prec = 8 + break + elif (token.value == "-") or (token.value == "+"): + prec = 9 + break + elif (token.value == "%") or ((token.value == "/") or (token.value == "*")): + prec = 11 + break + else: + break + break + return prec + +def parseBinaryExpression(): + marker = None + markers = None + expr = None + token = None + prec = None + previousAllowIn = None + stack = None + right = None + operator = None + left = None + i = None + previousAllowIn = state.allowIn + state.allowIn = True + marker = createLocationMarker() + left = parseUnaryExpression() + token = lookahead + prec = binaryPrecedence(token, previousAllowIn) + if prec == 0: + return left + token.prec = prec + lex() + markers = [marker, createLocationMarker()] + right = parseUnaryExpression() + stack = [left, token, right] + prec = binaryPrecedence(lookahead, previousAllowIn) + while prec > 0: + while (len(stack) > 2) and (prec <= stack[len(stack) - 2].prec): + right = stack.pop() + operator = stack.pop().value + left = stack.pop() + expr = delegate.createBinaryExpression(operator, left, right) + markers.pop() + marker = markers.pop() + if marker: + marker.end() + marker.apply(expr) + stack.append(expr) + markers.append(marker) + token = lex() + token.prec = prec + stack.append(token) + markers.append(createLocationMarker()) + expr = parseUnaryExpression() + stack.append(expr) + prec = binaryPrecedence(lookahead, previousAllowIn) + state.allowIn = previousAllowIn + i = len(stack) - 1 + expr = stack[i] + markers.pop() + while i > 1: + expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr) + i -= 2 + marker = markers.pop() + if marker: + marker.end() + marker.apply(expr) + return expr + +def parseConditionalExpression(): + expr = None + previousAllowIn = None + consequent = None + alternate = None + delegate.markStart() + expr = parseBinaryExpression() + if match("?"): + lex() + previousAllowIn = state.allowIn + state.allowIn = True + consequent = parseAssignmentExpression() + state.allowIn = previousAllowIn + expect(":") + alternate = parseAssignmentExpression() + expr = delegate.markEnd(delegate.createConditionalExpression(expr, consequent, alternate)) + else: + delegate.markEnd(jsdict({ +})) + return expr + +def parseAssignmentExpression(): + token = None + left = None + right = None + node = None + token = lookahead + delegate.markStart() + left = parseConditionalExpression() + node = left + if matchAssign(): + if not isLeftHandSide(left): + throwError(jsdict({ +}), Messages.InvalidLHSInAssignment) + if (strict and (left.type == Syntax.Identifier)) and isRestrictedWord(left.name): + throwErrorTolerant(token, Messages.StrictLHSAssignment) + token = lex() + right = parseAssignmentExpression() + node = delegate.createAssignmentExpression(token.value, left, right) + return delegate.markEndIf(node) + +def parseExpression(): + expr = None + delegate.markStart() + expr = parseAssignmentExpression() + if match(","): + expr = delegate.createSequenceExpression([expr]) + while index < length: + if not match(","): + break + lex() + expr.expressions.append(parseAssignmentExpression()) + return delegate.markEndIf(expr) + +def parseStatementList(): + list__py__ = [] + statement = None + while index < length: + if match("}"): + break + statement = parseSourceElement() + if ('undefined' if not 'statement' in locals() else typeof(statement)) == "undefined": + break + list__py__.append(statement) + return list__py__ + +def parseBlock(): + block = None + skipComment() + delegate.markStart() + expect("{") + block = parseStatementList() + expect("}") + return delegate.markEnd(delegate.createBlockStatement(block)) + +def parseVariableIdentifier(): + token = None + skipComment() + delegate.markStart() + token = lex() + if token.type != Token.Identifier: + throwUnexpected(token) + return delegate.markEnd(delegate.createIdentifier(token.value)) + +def parseVariableDeclaration(kind=None): + init = None + id = None + skipComment() + delegate.markStart() + id = parseVariableIdentifier() + if strict and isRestrictedWord(id.name): + throwErrorTolerant(jsdict({ +}), Messages.StrictVarName) + if kind == "const": + expect("=") + init = parseAssignmentExpression() + elif match("="): + lex() + init = parseAssignmentExpression() + return delegate.markEnd(delegate.createVariableDeclarator(id, init)) + +def parseVariableDeclarationList(kind=None): + list__py__ = [] + while 1: + list__py__.append(parseVariableDeclaration(kind)) + if not match(","): + break + lex() + if not (index < length): + break + return list__py__ + +def parseVariableStatement(): + declarations = None + expectKeyword("var") + declarations = parseVariableDeclarationList() + consumeSemicolon() + return delegate.createVariableDeclaration(declarations, "var") + +def parseConstLetDeclaration(kind=None): + declarations = None + skipComment() + delegate.markStart() + expectKeyword(kind) + declarations = parseVariableDeclarationList(kind) + consumeSemicolon() + return delegate.markEnd(delegate.createVariableDeclaration(declarations, kind)) + +def parseEmptyStatement(): + expect(";") + return delegate.createEmptyStatement() + +def parseExpressionStatement(): + expr = parseExpression() + consumeSemicolon() + return delegate.createExpressionStatement(expr) + +def parseIfStatement(): + test = None + consequent = None + alternate = None + expectKeyword("if") + expect("(") + test = parseExpression() + expect(")") + consequent = parseStatement() + if matchKeyword("else"): + lex() + alternate = parseStatement() + else: + alternate = None + return delegate.createIfStatement(test, consequent, alternate) + +def parseDoWhileStatement(): + body = None + test = None + oldInIteration = None + expectKeyword("do") + oldInIteration = state.inIteration + state.inIteration = True + body = parseStatement() + state.inIteration = oldInIteration + expectKeyword("while") + expect("(") + test = parseExpression() + expect(")") + if match(";"): + lex() + return delegate.createDoWhileStatement(body, test) + +def parseWhileStatement(): + test = None + body = None + oldInIteration = None + expectKeyword("while") + expect("(") + test = parseExpression() + expect(")") + oldInIteration = state.inIteration + state.inIteration = True + body = parseStatement() + state.inIteration = oldInIteration + return delegate.createWhileStatement(test, body) + +def parseForVariableDeclaration(): + token = None + declarations = None + delegate.markStart() + token = lex() + declarations = parseVariableDeclarationList() + return delegate.markEnd(delegate.createVariableDeclaration(declarations, token.value)) + +def parseForStatement(): + init = None + test = None + update = None + left = None + right = None + body = None + oldInIteration = None + update = None + test = update + init = test + expectKeyword("for") + expect("(") + if match(";"): + lex() + else: + if matchKeyword("var") or matchKeyword("let"): + state.allowIn = False + init = parseForVariableDeclaration() + state.allowIn = True + if (len(init.declarations) == 1) and matchKeyword("in"): + lex() + left = init + right = parseExpression() + init = None + else: + state.allowIn = False + init = parseExpression() + state.allowIn = True + if matchKeyword("in"): + if not isLeftHandSide(init): + throwError(jsdict({ +}), Messages.InvalidLHSInForIn) + lex() + left = init + right = parseExpression() + init = None + if ('undefined' if not 'left' in locals() else typeof(left)) == "undefined": + expect(";") + if ('undefined' if not 'left' in locals() else typeof(left)) == "undefined": + if not match(";"): + test = parseExpression() + expect(";") + if not match(")"): + update = parseExpression() + expect(")") + oldInIteration = state.inIteration + state.inIteration = True + body = parseStatement() + state.inIteration = oldInIteration + return (delegate.createForStatement(init, test, update, body) if ('undefined' if not 'left' in locals() else typeof(left)) == "undefined" else delegate.createForInStatement(left, right, body)) + +def parseContinueStatement(): + label = None + key = None + expectKeyword("continue") + if (ord(source[index]) if index < len(source) else None) == 59: + lex() + if not state.inIteration: + throwError(jsdict({ +}), Messages.IllegalContinue) + return delegate.createContinueStatement(None) + if peekLineTerminator(): + if not state.inIteration: + throwError(jsdict({ +}), Messages.IllegalContinue) + return delegate.createContinueStatement(None) + if lookahead.type == Token.Identifier: + label = parseVariableIdentifier() + key = "$" + label.name + if not (key in state.labelSet): + throwError(jsdict({ +}), Messages.UnknownLabel, label.name) + consumeSemicolon() + if (label == None) and (not state.inIteration): + throwError(jsdict({ +}), Messages.IllegalContinue) + return delegate.createContinueStatement(label) + +def parseBreakStatement(): + label = None + key = None + expectKeyword("break") + if (ord(source[index]) if index < len(source) else None) == 59: + lex() + if not (state.inIteration or state.inSwitch): + throwError(jsdict({ +}), Messages.IllegalBreak) + return delegate.createBreakStatement(None) + if peekLineTerminator(): + if not (state.inIteration or state.inSwitch): + throwError(jsdict({ +}), Messages.IllegalBreak) + return delegate.createBreakStatement(None) + if lookahead.type == Token.Identifier: + label = parseVariableIdentifier() + key = "$" + label.name + if not (key in state.labelSet): + throwError(jsdict({ +}), Messages.UnknownLabel, label.name) + consumeSemicolon() + if (label == None) and (not (state.inIteration or state.inSwitch)): + throwError(jsdict({ +}), Messages.IllegalBreak) + return delegate.createBreakStatement(label) + +def parseReturnStatement(): + argument = None + expectKeyword("return") + if not state.inFunctionBody: + throwErrorTolerant(jsdict({ +}), Messages.IllegalReturn) + if (ord(source[index]) if index < len(source) else None) == 32: + if isIdentifierStart((ord(source[index + 1]) if (index + 1) < len(source) else None)): + argument = parseExpression() + consumeSemicolon() + return delegate.createReturnStatement(argument) + if peekLineTerminator(): + return delegate.createReturnStatement(None) + if not match(";"): + if (not match("}")) and (lookahead.type != Token.EOF): + argument = parseExpression() + consumeSemicolon() + return delegate.createReturnStatement(argument) + +def parseWithStatement(): + object = None + body = None + if strict: + throwErrorTolerant(jsdict({ +}), Messages.StrictModeWith) + expectKeyword("with") + expect("(") + object = parseExpression() + expect(")") + body = parseStatement() + return delegate.createWithStatement(object, body) + +def parseSwitchCase(): + test = None + consequent = [] + statement = None + skipComment() + delegate.markStart() + if matchKeyword("default"): + lex() + test = None + else: + expectKeyword("case") + test = parseExpression() + expect(":") + while index < length: + if (match("}") or matchKeyword("default")) or matchKeyword("case"): + break + statement = parseStatement() + consequent.append(statement) + return delegate.markEnd(delegate.createSwitchCase(test, consequent)) + +def parseSwitchStatement(): + discriminant = None + cases = None + clause = None + oldInSwitch = None + defaultFound = None + expectKeyword("switch") + expect("(") + discriminant = parseExpression() + expect(")") + expect("{") + if match("}"): + lex() + return delegate.createSwitchStatement(discriminant) + cases = [] + oldInSwitch = state.inSwitch + state.inSwitch = True + defaultFound = False + while index < length: + if match("}"): + break + clause = parseSwitchCase() + if clause.test == None: + if defaultFound: + throwError(jsdict({ +}), Messages.MultipleDefaultsInSwitch) + defaultFound = True + cases.append(clause) + state.inSwitch = oldInSwitch + expect("}") + return delegate.createSwitchStatement(discriminant, cases) + +def parseThrowStatement(): + argument = None + expectKeyword("throw") + if peekLineTerminator(): + throwError(jsdict({ +}), Messages.NewlineAfterThrow) + argument = parseExpression() + consumeSemicolon() + return delegate.createThrowStatement(argument) + +def parseCatchClause(): + param = None + body = None + skipComment() + delegate.markStart() + expectKeyword("catch") + expect("(") + if match(")"): + throwUnexpected(lookahead) + param = parseVariableIdentifier() + if strict and isRestrictedWord(param.name): + throwErrorTolerant(jsdict({ +}), Messages.StrictCatchVariable) + expect(")") + body = parseBlock() + return delegate.markEnd(delegate.createCatchClause(param, body)) + +def parseTryStatement(): + block = None + handlers = [] + finalizer = None + expectKeyword("try") + block = parseBlock() + if matchKeyword("catch"): + handlers.append(parseCatchClause()) + if matchKeyword("finally"): + lex() + finalizer = parseBlock() + if (len(handlers) == 0) and (not finalizer): + throwError(jsdict({ +}), Messages.NoCatchOrFinally) + return delegate.createTryStatement(block, [], handlers, finalizer) + +def parseDebuggerStatement(): + expectKeyword("debugger") + consumeSemicolon() + return delegate.createDebuggerStatement() + +def parseStatement(): + type = lookahead.type + expr = None + labeledBody = None + key = None + if type == Token.EOF: + throwUnexpected(lookahead) + skipComment() + delegate.markStart() + if type == Token.Punctuator: + while 1: + if lookahead.value == ";": + return delegate.markEnd(parseEmptyStatement()) + elif lookahead.value == "{": + return delegate.markEnd(parseBlock()) + elif lookahead.value == "(": + return delegate.markEnd(parseExpressionStatement()) + else: + break + break + if type == Token.Keyword: + while 1: + if lookahead.value == "break": + return delegate.markEnd(parseBreakStatement()) + elif lookahead.value == "continue": + return delegate.markEnd(parseContinueStatement()) + elif lookahead.value == "debugger": + return delegate.markEnd(parseDebuggerStatement()) + elif lookahead.value == "do": + return delegate.markEnd(parseDoWhileStatement()) + elif lookahead.value == "for": + return delegate.markEnd(parseForStatement()) + elif lookahead.value == "function": + return delegate.markEnd(parseFunctionDeclaration()) + elif lookahead.value == "if": + return delegate.markEnd(parseIfStatement()) + elif lookahead.value == "return": + return delegate.markEnd(parseReturnStatement()) + elif lookahead.value == "switch": + return delegate.markEnd(parseSwitchStatement()) + elif lookahead.value == "throw": + return delegate.markEnd(parseThrowStatement()) + elif lookahead.value == "try": + return delegate.markEnd(parseTryStatement()) + elif lookahead.value == "var": + return delegate.markEnd(parseVariableStatement()) + elif lookahead.value == "while": + return delegate.markEnd(parseWhileStatement()) + elif lookahead.value == "with": + return delegate.markEnd(parseWithStatement()) + else: + break + break + expr = parseExpression() + if (expr.type == Syntax.Identifier) and match(":"): + lex() + key = "$" + expr.name + if key in state.labelSet: + throwError(jsdict({ +}), Messages.Redeclaration, "Label", expr.name) + state.labelSet[key] = True + labeledBody = parseStatement() + del state.labelSet[key] + return delegate.markEnd(delegate.createLabeledStatement(expr, labeledBody)) + consumeSemicolon() + return delegate.markEnd(delegate.createExpressionStatement(expr)) + +def parseFunctionSourceElements(): + global strict + sourceElement = None + sourceElements = [] + token = None + directive = None + firstRestricted = None + oldLabelSet = None + oldInIteration = None + oldInSwitch = None + oldInFunctionBody = None + skipComment() + delegate.markStart() + expect("{") + while index < length: + if lookahead.type != Token.StringLiteral: + break + token = lookahead + sourceElement = parseSourceElement() + sourceElements.append(sourceElement) + if sourceElement.expression.type != Syntax.Literal: + break + directive = source[(token.range[0] + 1):(token.range[1] - 1)] + if directive == "use strict": + strict = True + if firstRestricted: + throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral) + else: + if (not firstRestricted) and token.octal: + firstRestricted = token + oldLabelSet = state.labelSet + oldInIteration = state.inIteration + oldInSwitch = state.inSwitch + oldInFunctionBody = state.inFunctionBody + state.labelSet = jsdict({ +}) + state.inIteration = False + state.inSwitch = False + state.inFunctionBody = True + while index < length: + if match("}"): + break + sourceElement = parseSourceElement() + if ('undefined' if not 'sourceElement' in locals() else typeof(sourceElement)) == "undefined": + break + sourceElements.append(sourceElement) + expect("}") + state.labelSet = oldLabelSet + state.inIteration = oldInIteration + state.inSwitch = oldInSwitch + state.inFunctionBody = oldInFunctionBody + return delegate.markEnd(delegate.createBlockStatement(sourceElements)) + +def parseParams(firstRestricted=None): + param = None + params = [] + token = None + stricted = None + paramSet = None + key = None + message = None + expect("(") + if not match(")"): + paramSet = jsdict({ +}) + while index < length: + token = lookahead + param = parseVariableIdentifier() + key = "$" + token.value + if strict: + if isRestrictedWord(token.value): + stricted = token + message = Messages.StrictParamName + if key in paramSet: + stricted = token + message = Messages.StrictParamDupe + elif not firstRestricted: + if isRestrictedWord(token.value): + firstRestricted = token + message = Messages.StrictParamName + elif isStrictModeReservedWord(token.value): + firstRestricted = token + message = Messages.StrictReservedWord + elif key in paramSet: + firstRestricted = token + message = Messages.StrictParamDupe + params.append(param) + paramSet[key] = True + if match(")"): + break + expect(",") + expect(")") + return jsdict({ +"params": params, +"stricted": stricted, +"firstRestricted": firstRestricted, +"message": message, +}) + +def parseFunctionDeclaration(): + global strict + id = None + params = [] + body = None + token = None + stricted = None + tmp = None + firstRestricted = None + message = None + previousStrict = None + skipComment() + delegate.markStart() + expectKeyword("function") + token = lookahead + id = parseVariableIdentifier() + if strict: + if isRestrictedWord(token.value): + throwErrorTolerant(token, Messages.StrictFunctionName) + else: + if isRestrictedWord(token.value): + firstRestricted = token + message = Messages.StrictFunctionName + elif isStrictModeReservedWord(token.value): + firstRestricted = token + message = Messages.StrictReservedWord + tmp = parseParams(firstRestricted) + params = tmp.params + stricted = tmp.stricted + firstRestricted = tmp.firstRestricted + if tmp.message: + message = tmp.message + previousStrict = strict + body = parseFunctionSourceElements() + if strict and firstRestricted: + throwError(firstRestricted, message) + if strict and stricted: + throwErrorTolerant(stricted, message) + strict = previousStrict + return delegate.markEnd(delegate.createFunctionDeclaration(id, params, [], body)) + +def parseFunctionExpression(): + global strict + token = None + id = None + stricted = None + firstRestricted = None + message = None + tmp = None + params = [] + body = None + previousStrict = None + delegate.markStart() + expectKeyword("function") + if not match("("): + token = lookahead + id = parseVariableIdentifier() + if strict: + if isRestrictedWord(token.value): + throwErrorTolerant(token, Messages.StrictFunctionName) + else: + if isRestrictedWord(token.value): + firstRestricted = token + message = Messages.StrictFunctionName + elif isStrictModeReservedWord(token.value): + firstRestricted = token + message = Messages.StrictReservedWord + tmp = parseParams(firstRestricted) + params = tmp.params + stricted = tmp.stricted + firstRestricted = tmp.firstRestricted + if tmp.message: + message = tmp.message + previousStrict = strict + body = parseFunctionSourceElements() + if strict and firstRestricted: + throwError(firstRestricted, message) + if strict and stricted: + throwErrorTolerant(stricted, message) + strict = previousStrict + return delegate.markEnd(delegate.createFunctionExpression(id, params, [], body)) + +def parseSourceElement(): + if lookahead.type == Token.Keyword: + while 1: + if (lookahead.value == "let") or (lookahead.value == "const"): + return parseConstLetDeclaration(lookahead.value) + elif lookahead.value == "function": + return parseFunctionDeclaration() + else: + return parseStatement() + break + if lookahead.type != Token.EOF: + return parseStatement() + +def parseSourceElements(): + global strict + sourceElement = None + sourceElements = [] + token = None + directive = None + firstRestricted = None + while index < length: + token = lookahead + if token.type != Token.StringLiteral: + break + sourceElement = parseSourceElement() + sourceElements.append(sourceElement) + if sourceElement.expression.type != Syntax.Literal: + break + directive = source[(token.range[0] + 1):(token.range[1] - 1)] + if directive == "use strict": + strict = True + if firstRestricted: + throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral) + else: + if (not firstRestricted) and token.octal: + firstRestricted = token + while index < length: + sourceElement = parseSourceElement() + if ('undefined' if not 'sourceElement' in locals() else typeof(sourceElement)) == "undefined": + break + sourceElements.append(sourceElement) + return sourceElements + +def parseProgram(): + global strict + body = None + skipComment() + delegate.markStart() + strict = False + peek() + body = parseSourceElements() + return delegate.markEnd(delegate.createProgram(body)) + +def collectToken(): + start = None + loc = None + token = None + range = None + value = None + skipComment() + start = index + loc = jsdict({ +"start": jsdict({ +"line": lineNumber, +"column": index - lineStart, +}), +}) + token = extra.advance() + loc.end = jsdict({ +"line": lineNumber, +"column": index - lineStart, +}) + if token.type != Token.EOF: + range = [token.range[0], token.range[1]] + value = source[token.range[0]:token.range[1]] + extra.tokens.append(jsdict({ +"type": TokenName[token.type], +"value": value, +"range": range, +"loc": loc, +})) + return token + +def collectRegex(): + pos = None + loc = None + regex = None + token = None + skipComment() + pos = index + loc = jsdict({ +"start": jsdict({ +"line": lineNumber, +"column": index - lineStart, +}), +}) + regex = extra.scanRegExp() + loc.end = jsdict({ +"line": lineNumber, +"column": index - lineStart, +}) + if not extra.tokenize: + if len(extra.tokens) > 0: + token = extra.tokens[len(extra.tokens) - 1] + if (token.range[0] == pos) and (token.type == "Punctuator"): + if (token.value == "/") or (token.value == "/="): + extra.tokens.pop() + extra.tokens.append(jsdict({ +"type": "RegularExpression", +"value": regex.literal, +"range": [pos, index], +"loc": loc, +})) + return regex + +def filterTokenLocation(): + i = None + entry = None + token = None + tokens = [] + i = 0 + while 1: + if not (i < len(extra.tokens)): + break + entry = extra.tokens[i] + token = jsdict({ +"type": entry.type, +"value": entry.value, +}) + if extra.range: + token.range = entry.range + if extra.loc: + token.loc = entry.loc + tokens.append(token) + i += 1 + extra.tokens = tokens + +class LocationMarker(object): + def __init__(self=None): + self.marker = [index, lineNumber, index - lineStart, 0, 0, 0] + + def end(self=None): + self.marker[3] = index + self.marker[4] = lineNumber + self.marker[5] = index - lineStart + + def apply(self=None, node=None): + if extra.range: + node.range = [self.marker[0], self.marker[3]] + if extra.loc: + node.loc = jsdict({ +"start": jsdict({ +"line": self.marker[1], +"column": self.marker[2], +}), +"end": jsdict({ +"line": self.marker[4], +"column": self.marker[5], +}), +}) + node = delegate.postProcess(node) + +def createLocationMarker(): + if (not extra.loc) and (not extra.range): + return None + skipComment() + return LocationMarker() + +def patch(): + global advance, scanRegExp + if ('undefined' if not ('tokens' in extra) else typeof(extra.tokens)) != "undefined": + extra.advance = advance + extra.scanRegExp = scanRegExp + advance = collectToken + scanRegExp = collectRegex + +def unpatch(): + global advance, scanRegExp + if ('undefined' if not ('scanRegExp' in extra) else typeof(extra.scanRegExp)) == "function": + advance = extra.advance + scanRegExp = extra.scanRegExp + +def tokenize(code, **options): + global delegate, source, index, lineNumber, lineStart, length, lookahead, state, extra + options = jsdict(options) + toString = None + token = None + tokens = None + toString = str + if (('undefined' if not 'code' in locals() else typeof(code)) != "string") and (not isinstance(code, str)): + code = toString(code) + delegate = SyntaxTreeDelegate + source = code + index = 0 + lineNumber = (1 if len(source) > 0 else 0) + lineStart = 0 + length = len(source) + lookahead = None + state = jsdict({ +"allowIn": True, +"labelSet": jsdict({ +}), +"inFunctionBody": False, +"inIteration": False, +"inSwitch": False, +"lastCommentStart": -1, +}) + extra = jsdict({ +}) + options = options or jsdict({ +}) + options.tokens = True + extra.tokens = [] + extra.tokenize = True + extra.openParenToken = -1 + extra.openCurlyToken = -1 + extra.range = (('undefined' if not ('range' in options) else typeof(options.range)) == "boolean") and options.range + extra.loc = (('undefined' if not ('loc' in options) else typeof(options.loc)) == "boolean") and options.loc + if (('undefined' if not ('comment' in options) else typeof(options.comment)) == "boolean") and options.comment: + extra.comments = [] + if (('undefined' if not ('tolerant' in options) else typeof(options.tolerant)) == "boolean") and options.tolerant: + extra.errors = [] + if length > 0: + if (typeof(source[0])) == "undefined": + if isinstance(code, str): + source = code.valueOf() + patch() + try: + peek() + if lookahead.type == Token.EOF: + return extra.tokens + token = lex() + while lookahead.type != Token.EOF: + try: + token = lex() + except Exception as lexError: + token = lookahead + if extra.errors: + extra.errors.append(lexError) + break + else: + raise + filterTokenLocation() + tokens = extra.tokens + if ('undefined' if not ('comments' in extra) else typeof(extra.comments)) != "undefined": + tokens.comments = extra.comments + if ('undefined' if not ('errors' in extra) else typeof(extra.errors)) != "undefined": + tokens.errors = extra.errors + except Exception as e: + raise + finally: + unpatch() + extra = jsdict({ +}) + return tokens + +def parse(code, **options): + global delegate, source, index, lineNumber, lineStart, length, lookahead, state, extra + options = jsdict(options) + program = None + toString = None + toString = str + if (('undefined' if not 'code' in locals() else typeof(code)) != "string") and (not isinstance(code, str)): + code = toString(code) + delegate = SyntaxTreeDelegate + source = code + index = 0 + lineNumber = (1 if len(source) > 0 else 0) + lineStart = 0 + length = len(source) + lookahead = None + state = jsdict({ +"allowIn": True, +"labelSet": jsdict({ +}), +"inFunctionBody": False, +"inIteration": False, +"inSwitch": False, +"lastCommentStart": -1, +"markerStack": [], +}) + extra = jsdict({ +}) + if ('undefined' if not 'options' in locals() else typeof(options)) != "undefined": + extra.range = (('undefined' if not ('range' in options) else typeof(options.range)) == "boolean") and options.range + extra.loc = (('undefined' if not ('loc' in options) else typeof(options.loc)) == "boolean") and options.loc + if (extra.loc and (options.source != None)) and (options.source != undefined): + extra.source = toString(options.source) + if (('undefined' if not ('tokens' in options) else typeof(options.tokens)) == "boolean") and options.tokens: + extra.tokens = [] + if (('undefined' if not ('comment' in options) else typeof(options.comment)) == "boolean") and options.comment: + extra.comments = [] + if (('undefined' if not ('tolerant' in options) else typeof(options.tolerant)) == "boolean") and options.tolerant: + extra.errors = [] + if length > 0: + if (typeof(source[0])) == "undefined": + if isinstance(code, str): + source = code.valueOf() + patch() + try: + program = parseProgram() + if ('undefined' if not ('comments' in extra) else typeof(extra.comments)) != "undefined": + program.comments = extra.comments + if ('undefined' if not ('tokens' in extra) else typeof(extra.tokens)) != "undefined": + filterTokenLocation() + program.tokens = extra.tokens + if ('undefined' if not ('errors' in extra) else typeof(extra.errors)) != "undefined": + program.errors = extra.errors + except Exception as e: + raise + finally: + unpatch() + extra = jsdict({ +}) + return program + + +parse('var = 490 \n a=4;') \ No newline at end of file diff --git a/lib/js2py/legecy_translators/objects.py b/lib/js2py/legecy_translators/objects.py new file mode 100644 index 00000000..a05f77bf --- /dev/null +++ b/lib/js2py/legecy_translators/objects.py @@ -0,0 +1,287 @@ +""" 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) 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('', ')') + + diff --git a/lib/js2py/legecy_translators/tokenize.py b/lib/js2py/legecy_translators/tokenize.py new file mode 100644 index 00000000..d6af0195 --- /dev/null +++ b/lib/js2py/legecy_translators/tokenize.py @@ -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. diff --git a/lib/js2py/legecy_translators/translator.py b/lib/js2py/legecy_translators/translator.py new file mode 100644 index 00000000..1994ab6e --- /dev/null +++ b/lib/js2py/legecy_translators/translator.py @@ -0,0 +1,143 @@ +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' \ No newline at end of file diff --git a/lib/js2py/legecy_translators/utils.py b/lib/js2py/legecy_translators/utils.py new file mode 100644 index 00000000..f5bd7890 --- /dev/null +++ b/lib/js2py/legecy_translators/utils.py @@ -0,0 +1,80 @@ +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}) \ No newline at end of file diff --git a/lib/js2py/prototypes/__init__.py b/lib/js2py/prototypes/__init__.py new file mode 100644 index 00000000..8de79cb9 --- /dev/null +++ b/lib/js2py/prototypes/__init__.py @@ -0,0 +1 @@ +__author__ = 'Piotr Dabkowski' diff --git a/lib/js2py/prototypes/jsarray.py b/lib/js2py/prototypes/jsarray.py new file mode 100644 index 00000000..5605c600 --- /dev/null +++ b/lib/js2py/prototypes/jsarray.py @@ -0,0 +1,458 @@ +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 (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 k1: # initial value present + accumulator = arguments[1] + else: + kPresent = False + while not kPresent and k1: # 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 xy: + return 1 + return 0 + + + + diff --git a/lib/js2py/prototypes/jsboolean.py b/lib/js2py/prototypes/jsboolean.py new file mode 100644 index 00000000..af048953 --- /dev/null +++ b/lib/js2py/prototypes/jsboolean.py @@ -0,0 +1,16 @@ + + +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 + + + + diff --git a/lib/js2py/prototypes/jserror.py b/lib/js2py/prototypes/jserror.py new file mode 100644 index 00000000..934e0fc9 --- /dev/null +++ b/lib/js2py/prototypes/jserror.py @@ -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 \ No newline at end of file diff --git a/lib/js2py/prototypes/jsfunction.py b/lib/js2py/prototypes/jsfunction.py new file mode 100644 index 00000000..1b67db24 --- /dev/null +++ b/lib/js2py/prototypes/jsfunction.py @@ -0,0 +1,53 @@ +# 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) + + diff --git a/lib/js2py/prototypes/jsjson.py b/lib/js2py/prototypes/jsjson.py new file mode 100644 index 00000000..e2008618 --- /dev/null +++ b/lib/js2py/prototypes/jsjson.py @@ -0,0 +1,210 @@ +import json +from lib.js2py.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}) + 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({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}) diff --git a/lib/js2py/prototypes/jsnumber.py b/lib/js2py/prototypes/jsnumber.py new file mode 100644 index 00000000..42f0f0b9 --- /dev/null +++ b/lib/js2py/prototypes/jsnumber.py @@ -0,0 +1,100 @@ +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)) + + + diff --git a/lib/js2py/prototypes/jsobject.py b/lib/js2py/prototypes/jsobject.py new file mode 100644 index 00000000..793869af --- /dev/null +++ b/lib/js2py/prototypes/jsobject.py @@ -0,0 +1,36 @@ + +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') + + + + + + diff --git a/lib/js2py/prototypes/jsregexp.py b/lib/js2py/prototypes/jsregexp.py new file mode 100644 index 00000000..97f13ba5 --- /dev/null +++ b/lib/js2py/prototypes/jsregexp.py @@ -0,0 +1,43 @@ + +class RegExpPrototype: + def toString(): + flags = u'' + if this.glob: + flags += u'g' + if this.ignore_case: + flags += u'i' + if this.multiline: + flags += u'm' + 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 + diff --git a/lib/js2py/prototypes/jsstring.py b/lib/js2py/prototypes/jsstring.py new file mode 100644 index 00000000..ebeed399 --- /dev/null +++ b/lib/js2py/prototypes/jsstring.py @@ -0,0 +1,307 @@ +# -*- 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+2len(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 nthat: + 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, () + diff --git a/lib/js2py/pyjs.py b/lib/js2py/pyjs.py new file mode 100644 index 00000000..25cf1279 --- /dev/null +++ b/lib/js2py/pyjs.py @@ -0,0 +1,51 @@ +from .base import * +from .constructors.jsmath import Math +from .constructors.jsdate import Date +from .constructors.jsobject import Object +from .constructors.jsfunction import Function +from .constructors.jsstring import String +from .constructors.jsnumber import Number +from .constructors.jsboolean import Boolean +from .constructors.jsregexp import RegExp +from .constructors.jsarray import Array +from .prototypes.jsjson import JSON +from .host.console import console +from .host.jseval import Eval +from .host.jsfunctions import parseFloat, parseInt, isFinite, isNaN + +# Now we have all the necessary items to create global environment for script +__all__ = ['Js', 'PyJsComma', 'PyJsStrictEq', 'PyJsStrictNeq', + 'PyJsException', 'PyJsBshift', 'Scope', 'PyExceptionToJs', + 'JsToPyException', 'JS_BUILTINS', 'appengine', 'set_global_object', + 'JsRegExp', 'PyJsException', 'PyExceptionToJs', 'JsToPyException', 'PyJsSwitchException'] + + +# these were defined in base.py +builtins = ('true','false','null','undefined','Infinity', + 'NaN', 'console', 'String', 'Number', 'Boolean', 'RegExp', + 'Math', 'Date', 'Object', 'Function', 'Array', + 'parseFloat', 'parseInt', 'isFinite', 'isNaN') + #Array, Function, JSON, Error is done later :) + # also some built in functions like eval... + +def set_global_object(obj): + obj.IS_CHILD_SCOPE = False + this = This({}) + this.own = obj.own + this.prototype = obj.prototype + PyJs.GlobalObject = this + # make this available + obj.register('this') + obj.put('this', this) + + + +scope = dict(zip(builtins, [globals()[e] for e in builtins])) +# Now add errors: +for name, error in ERRORS.items(): + scope[name] = error +#add eval +scope['eval'] = Eval +scope['JSON'] = JSON +JS_BUILTINS = {k:v for k,v in scope.items()} + diff --git a/lib/js2py/todo b/lib/js2py/todo new file mode 100644 index 00000000..22d839ae --- /dev/null +++ b/lib/js2py/todo @@ -0,0 +1,18 @@ +# TODO +Check Object Constructor +Complete list prototype +Fix function bind... +Fix regexp compile ??? +Check prototypes: + String: replace, split +fix recursion error in special case in to_dict and to_list + +Array constructor + + +escape, URL... etc + +Check primitive.to_object() +var obj = new Number(0); var x = new Array(obj); x + +Smarter import... diff --git a/lib/js2py/translators/__init__.py b/lib/js2py/translators/__init__.py new file mode 100644 index 00000000..9aa6281f --- /dev/null +++ b/lib/js2py/translators/__init__.py @@ -0,0 +1,38 @@ +# 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 + +__all__ = ['PyJsParser', 'Node', 'WrappingNode', 'node_to_dict', 'parse', 'translate_js', 'translate', 'syntax_tree_translate', + 'DEFAULT_HEADER'] +__author__ = 'Piotr Dabkowski' +__version__ = '2.2.0' +from .pyjsparser import PyJsParser, Node, WrappingNode, node_to_dict +from .translator import translate_js, trasnlate, syntax_tree_translate, DEFAULT_HEADER + + +def parse(javascript_code): + """Returns syntax tree of javascript_code. + + Syntax tree has the same structure as syntax tree produced by esprima.js + + Same as PyJsParser().parse For your convenience :) """ + p = PyJsParser() + return p.parse(javascript_code) + + diff --git a/lib/js2py/translators/friendly_nodes.py b/lib/js2py/translators/friendly_nodes.py new file mode 100644 index 00000000..1fc1886d --- /dev/null +++ b/lib/js2py/translators/friendly_nodes.py @@ -0,0 +1,327 @@ +import binascii + +from .pyjsparser import PyJsParser +import six +if six.PY3: + basestring = str + long = int + xrange = range + unicode = str + +REGEXP_CONVERTER = PyJsParser() + +def to_hex(s): + return binascii.hexlify(s.encode('utf8')).decode('utf8') # fucking python 3, I hate it so much + # wtf was wrong with s.encode('hex') ??? +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: + print() + print(lval) + raise RuntimeError('To many lvals (%s)' % lval) + elif not source.count(lval): + 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 get_continue_label(label): + return CONTINUE_LABEL%to_hex(label) + +def get_break_label(label): + return BREAK_LABEL%to_hex(label) + + +def is_valid_py_name(name): + try: + compile(name+' = 11', 'a','exec') + except: + return False + return True + +def indent(lines, ind=4): + return ind*' '+lines.replace('\n', '\n'+ind*' ').rstrip(' ') + +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 repr(int(f)) + return repr(f) + +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 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='+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): + # eval and return undefined + return 'PyJsComma(%s, Js(None))' % 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': + print(a) + 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 js_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,Js(%s.to_number())%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 js_postfix(a, True, False) + +def js_post_inc(a): + return js_postfix(a, True, True) + +def js_pre_dec(a): + return js_postfix(a, False, False) + +def js_post_dec(a): + return js_postfix(a, False, True) + + + +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)) + + + +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} +BINARY = {} +BINARY.update(ADDS) +BINARY.update(MULTS) +BINARY.update(BSHIFTS) +BINARY.update(COMPS) +BINARY.update(EQS) +BINARY.update(BAND) +BINARY.update(BXOR) +BINARY.update(BOR) +BINARY.update(AND) +BINARY.update(OR) +#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 + } \ No newline at end of file diff --git a/lib/js2py/translators/jsregexps.py b/lib/js2py/translators/jsregexps.py new file mode 100644 index 00000000..c6e56c93 --- /dev/null +++ b/lib/js2py/translators/jsregexps.py @@ -0,0 +1,219 @@ +from pyjsparserdata import * + +REGEXP_SPECIAL_SINGLE = {'\\', '^', '$', '*', '+', '?', '.'} + +NOT_PATTERN_CHARS = {'^', '$', '\\', '.', '*', '+', '?', '(', ')', '[', ']', '|'} # what about '{', '}', ??? + +CHAR_CLASS_ESCAPE = {'d', 'D', 's', 'S', 'w', 'W'} +CONTROL_ESCAPE_CHARS = {'f', 'n', 'r', 't', 'v'} +CONTROL_LETTERS = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} + +def SpecialChar(char): + return {'type': 'SpecialChar', + 'content': char} + + +def isPatternCharacter(char): + return char not in NOT_PATTERN_CHARS + +class JsRegExpParser: + def __init__(self, source, flags): + self.source = source + self.flags = flags + self.index = 0 + self.length = len(source) + self.lineNumber = 0 + self.lineStart = 0 + + + def parsePattern(self): + '''Perform sctring escape - for regexp literals''' + return {'type': 'Pattern', + 'contents': self.parseDisjunction()} + + def parseDisjunction(self): + alternatives = [] + while True: + alternatives.append(self.parseAlternative()) + if not self.isEOF(): + self.expect_character('|') + else: + break + return {'type': 'Disjunction', + 'contents': alternatives} + + def isEOF(self): + if self.index>=self.length: + return True + return False + + def expect_character(self, character): + if self.source[self.index]!=character: + self.throwUnexpected(character) + self.index += 1 + + def parseAlternative(self): + contents = [] + while not self.isEOF() and self.source[self.index]!='|': + contents.append(self.parseTerm()) + return {'type': 'Alternative', + 'contents': contents} + + def follows(self, chars): + for i, c in enumerate(chars): + if self.index+i>=self.length or self.source[self.index+i] != c: + return False + return True + + def parseTerm(self): + assertion = self.parseAssertion() + if assertion: + return assertion + else: + return {'type': 'Term', + 'contents': self.parseAtom()} # quantifier will go inside atom! + + + def parseAssertion(self): + if self.follows('$'): + content = SpecialChar('$') + self.index += 1 + elif self.follows('^'): + content = SpecialChar('^') + self.index += 1 + elif self.follows('\\b'): + content = SpecialChar('\\b') + self.index += 2 + elif self.follows('\\B'): + content = SpecialChar('\\B') + self.index += 2 + elif self.follows('(?='): + self.index += 3 + dis = self.parseDisjunction() + self.expect_character(')') + content = {'type': 'Lookached', + 'contents': dis, + 'negated': False} + elif self.follows('(?!'): + self.index += 3 + dis = self.parseDisjunction() + self.expect_character(')') + content = {'type': 'Lookached', + 'contents': dis, + 'negated': True} + else: + return None + return {'type': 'Assertion', + 'content': content} + + def parseAtom(self): + if self.follows('.'): + content = SpecialChar('.') + self.index += 1 + elif self.follows('\\'): + self.index += 1 + content = self.parseAtomEscape() + elif self.follows('['): + content = self.parseCharacterClass() + elif self.follows('(?:'): + self.index += 3 + dis = self.parseDisjunction() + self.expect_character(')') + content = 'idk' + elif self.follows('('): + self.index += 1 + dis = self.parseDisjunction() + self.expect_character(')') + content = 'idk' + elif isPatternCharacter(self.source[self.index]): + content = self.source[self.index] + self.index += 1 + else: + return None + quantifier = self.parseQuantifier() + return {'type': 'Atom', + 'content': content, + 'quantifier': quantifier} + + def parseQuantifier(self): + prefix = self.parseQuantifierPrefix() + if not prefix: + return None + greedy = True + if self.follows('?'): + self.index += 1 + greedy = False + return {'type': 'Quantifier', + 'contents': prefix, + 'greedy': greedy} + + def parseQuantifierPrefix(self): + if self.isEOF(): + return None + if self.follows('+'): + content = '+' + self.index += 1 + elif self.follows('?'): + content = '?' + self.index += 1 + elif self.follows('*'): + content = '*' + self.index += 1 + elif self.follows('{'): # try matching otherwise return None and restore the state + i = self.index + self.index += 1 + digs1 = self.scanDecimalDigs() + # if no minimal number of digs provided then return no quantifier + if not digs1: + self.index = i + return None + # scan char limit if provided + if self.follows(','): + self.index += 1 + digs2 = self.scanDecimalDigs() + else: + digs2 = '' + # must be valid! + if not self.follows('}'): + self.index = i + return None + else: + self.expect_character('}') + content = int(digs1), int(digs2) if digs2 else None + else: + return None + return content + + + def parseAtomEscape(self): + ch = self.source[self.index] + if isDecimalDigit(ch) and ch!=0: + digs = self.scanDecimalDigs() + elif ch in CHAR_CLASS_ESCAPE: + self.index += 1 + return SpecialChar('\\' + ch) + else: + return self.parseCharacterEscape() + + def parseCharacterEscape(self): + ch = self.source[self.index] + if ch in CONTROL_ESCAPE_CHARS: + return SpecialChar('\\' + ch) + if ch=='c': + 'ok, fuck this shit.' + + + def scanDecimalDigs(self): + s = self.index + while not self.isEOF() and isDecimalDigit(self.source[self.index]): + self.index += 1 + return self.source[s:self.index] + + + + + +a = JsRegExpParser('a(?=x)', '') +print(a.parsePattern()) \ No newline at end of file diff --git a/lib/js2py/translators/markdown.js b/lib/js2py/translators/markdown.js new file mode 100644 index 00000000..89d8893a --- /dev/null +++ b/lib/js2py/translators/markdown.js @@ -0,0 +1,1293 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/chjj/marked + */ + +;(function() { + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^( *[-*_]){3,} *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + nptable: noop, + lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, + blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, + list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, + def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + table: noop, + paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, + text: /^[^\n]+/ +}; + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = replace(block.item, 'gm') + (/bull/g, block.bullet) + (); + +block.list = replace(block.list) + (/bull/g, block.bullet) + ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') + ('def', '\\n+(?=' + block.def.source + ')') + (); + +block.blockquote = replace(block.blockquote) + ('def', block.def) + (); + +block._tag = '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; + +block.html = replace(block.html) + ('comment', //) + ('closed', /<(tag)[\s\S]+?<\/\1>/) + ('closing', /])*?>/) + (/tag/g, block._tag) + (); + +block.paragraph = replace(block.paragraph) + ('hr', block.hr) + ('heading', block.heading) + ('lheading', block.lheading) + ('blockquote', block.blockquote) + ('tag', '<' + block._tag) + ('def', block.def) + (); + +/** + * Normal Block Grammar + */ + +block.normal = merge({}, block); + +/** + * GFM Block Grammar + */ + +block.gfm = merge({}, block.normal, { + fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, + paragraph: /^/, + heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ +}); + +block.gfm.paragraph = replace(block.paragraph) + ('(?!', '(?!' + + block.gfm.fences.source.replace('\\1', '\\2') + '|' + + block.list.source.replace('\\1', '\\3') + '|') + (); + +/** + * GFM + Tables Block Grammar + */ + +block.tables = merge({}, block.gfm, { + nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, + table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ +}); + +/** + * Block Lexer + */ + +function Lexer(options) { + this.tokens = []; + this.tokens.links = {}; + this.options = options || marked.defaults; + this.rules = block.normal; + + if (this.options.gfm) { + if (this.options.tables) { + this.rules = block.tables; + } else { + this.rules = block.gfm; + } + } +} + +/** + * Expose Block Rules + */ + +Lexer.rules = block; + +/** + * Static Lex Method + */ + +Lexer.lex = function(src, options) { + var lexer = new Lexer(options); + return lexer.lex(src); +}; + +/** + * Preprocessing + */ + +Lexer.prototype.lex = function(src) { + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' ') + .replace(/\u00a0/g, ' ') + .replace(/\u2424/g, '\n'); + + return this.token(src, true); +}; + +/** + * Lexing + */ + +Lexer.prototype.token = function(src, top, bq) { + var src = src.replace(/^ +$/gm, '') + , next + , loose + , cap + , bull + , b + , item + , space + , i + , l; + + while (src) { + // newline + if (cap = this.rules.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + this.tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + this.tokens.push({ + type: 'code', + text: !this.options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = this.rules.fences.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] || '' + }); + continue; + } + + // heading + if (cap = this.rules.heading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // table no leading pipe (gfm) + if (top && (cap = this.rules.nptable.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = item.cells[i].split(/ *\| */); + } + + this.tokens.push(item); + + continue; + } + + // lheading + if (cap = this.rules.lheading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // hr + if (cap = this.rules.hr.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = this.rules.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + this.tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + this.token(cap, top, true); + + this.tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = this.rules.list.exec(src)) { + src = src.substring(cap[0].length); + bull = cap[2]; + + this.tokens.push({ + type: 'list_start', + ordered: bull.length > 1 + }); + + // Get each top-level item. + cap = cap[0].match(this.rules.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !this.options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether the next list item belongs here. + // Backpedal if it does not belong in this list. + if (this.options.smartLists && i !== l - 1) { + b = block.bullet.exec(cap[i + 1])[0]; + if (bull !== b && !(bull.length > 1 && b.length > 1)) { + src = cap.slice(i + 1).join('\n') + src; + i = l - 1; + } + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item.charAt(item.length - 1) === '\n'; + if (!loose) loose = next; + } + + this.tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + this.token(item, false, bq); + + this.tokens.push({ + type: 'list_item_end' + }); + } + + this.tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = this.rules.html.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: this.options.sanitize + ? 'paragraph' + : 'html', + pre: !this.options.sanitizer + && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), + text: cap[0] + }); + continue; + } + + // def + if ((!bq && top) && (cap = this.rules.def.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.links[cap[1].toLowerCase()] = { + href: cap[2], + title: cap[3] + }; + continue; + } + + // table (gfm) + if (top && (cap = this.rules.table.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = item.cells[i] + .replace(/^ *\| *| *\| *$/g, '') + .split(/ *\| */); + } + + this.tokens.push(item); + + continue; + } + + // top-level paragraph + if (top && (cap = this.rules.paragraph.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'paragraph', + text: cap[1].charAt(cap[1].length - 1) === '\n' + ? cap[1].slice(0, -1) + : cap[1] + }); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + + if (src) { + throw new + Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return this.tokens; +}; + +/** + * Inline-Level Grammar + */ + +var inline = { + escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + url: noop, + tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + link: /^!?\[(inside)\]\(href\)/, + reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, + em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, + code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + del: noop, + text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; + +inline.link = replace(inline.link) + ('inside', inline._inside) + ('href', inline._href) + (); + +inline.reflink = replace(inline.reflink) + ('inside', inline._inside) + (); + +/** + * Normal Inline Grammar + */ + +inline.normal = merge({}, inline); + +/** + * Pedantic Inline Grammar + */ + +inline.pedantic = merge({}, inline.normal, { + strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ +}); + +/** + * GFM Inline Grammar + */ + +inline.gfm = merge({}, inline.normal, { + escape: replace(inline.escape)('])', '~|])')(), + url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, + del: /^~~(?=\S)([\s\S]*?\S)~~/, + text: replace(inline.text) + (']|', '~]|') + ('|', '|https?://|') + () +}); + +/** + * GFM + Line Breaks Inline Grammar + */ + +inline.breaks = merge({}, inline.gfm, { + br: replace(inline.br)('{2,}', '*')(), + text: replace(inline.gfm.text)('{2,}', '*')() +}); + +/** + * Inline Lexer & Compiler + */ + +function InlineLexer(links, options) { + this.options = options || marked.defaults; + this.links = links; + this.rules = inline.normal; + this.renderer = this.options.renderer || new Renderer; + this.renderer.options = this.options; + + if (!this.links) { + throw new + Error('Tokens array requires a `links` property.'); + } + + if (this.options.gfm) { + if (this.options.breaks) { + this.rules = inline.breaks; + } else { + this.rules = inline.gfm; + } + } else if (this.options.pedantic) { + this.rules = inline.pedantic; + } +} + +/** + * Expose Inline Rules + */ + +InlineLexer.rules = inline; + +/** + * Static Lexing/Compiling Method + */ + +InlineLexer.output = function(src, links, options) { + var inline = new InlineLexer(links, options); + return inline.output(src); +}; + +/** + * Lexing/Compiling + */ + +InlineLexer.prototype.output = function(src) { + var out = '' + , link + , text + , href + , cap; + + while (src) { + // escape + if (cap = this.rules.escape.exec(src)) { + src = src.substring(cap[0].length); + out += cap[1]; + continue; + } + + // autolink + if (cap = this.rules.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = cap[1].charAt(6) === ':' + ? this.mangle(cap[1].substring(7)) + : this.mangle(cap[1]); + href = this.mangle('mailto:') + text; + } else { + text = escape(cap[1]); + href = text; + } + out += this.renderer.link(href, null, text); + continue; + } + + // url (gfm) + if (!this.inLink && (cap = this.rules.url.exec(src))) { + src = src.substring(cap[0].length); + text = escape(cap[1]); + href = text; + out += this.renderer.link(href, null, text); + continue; + } + + // tag + if (cap = this.rules.tag.exec(src)) { + if (!this.inLink && /^/i.test(cap[0])) { + this.inLink = false; + } + src = src.substring(cap[0].length); + out += this.options.sanitize + ? this.options.sanitizer + ? this.options.sanitizer(cap[0]) + : escape(cap[0]) + : cap[0] + continue; + } + + // link + if (cap = this.rules.link.exec(src)) { + src = src.substring(cap[0].length); + this.inLink = true; + out += this.outputLink(cap, { + href: cap[2], + title: cap[3] + }); + this.inLink = false; + continue; + } + + // reflink, nolink + if ((cap = this.rules.reflink.exec(src)) + || (cap = this.rules.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = this.links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0].charAt(0); + src = cap[0].substring(1) + src; + continue; + } + this.inLink = true; + out += this.outputLink(cap, link); + this.inLink = false; + continue; + } + + // strong + if (cap = this.rules.strong.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.strong(this.output(cap[2] || cap[1])); + continue; + } + + // em + if (cap = this.rules.em.exec(src)) { + console.log('s2 ' + src) + src = src.substring(cap[0].length); + out += this.renderer.em(this.output(cap[2] || cap[1])); + continue; + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.codespan(escape(cap[2], true)); + continue; + } + + // br + if (cap = this.rules.br.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.br(); + continue; + } + + // del (gfm) + if (cap = this.rules.del.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.del(this.output(cap[1])); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + console.log(src) + src = src.substring(cap[0].length); + console.log('here '+src) + out += this.renderer.text(escape(this.smartypants(cap[0]))); + continue; + } + + if (src) { + throw new + Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return out; +}; + +/** + * Compile Link + */ + +InlineLexer.prototype.outputLink = function(cap, link) { + var href = escape(link.href) + , title = link.title ? escape(link.title) : null; + + return cap[0].charAt(0) !== '!' + ? this.renderer.link(href, title, this.output(cap[1])) + : this.renderer.image(href, title, escape(cap[1])); +}; + +/** + * Smartypants Transformations + */ + +InlineLexer.prototype.smartypants = function(text) { + if (!this.options.smartypants) return text; + return text + // em-dashes + .replace(/---/g, '\u2014') + // en-dashes + .replace(/--/g, '\u2013') + // opening singles + .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') + // closing singles & apostrophes + .replace(/'/g, '\u2019') + // opening doubles + .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') + // closing doubles + .replace(/"/g, '\u201d') + // ellipses + .replace(/\.{3}/g, '\u2026'); +}; + +/** + * Mangle Links + */ + +InlineLexer.prototype.mangle = function(text) { + if (!this.options.mangle) return text; + var out = '' + , l = text.length + , i = 0 + , ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +}; + +/** + * Renderer + */ + +function Renderer(options) { + this.options = options || {}; +} + +Renderer.prototype.code = function(code, lang, escaped) { + if (this.options.highlight) { + var out = this.options.highlight(code, lang); + if (out != null && out !== code) { + escaped = true; + code = out; + } + } + + if (!lang) { + return '
'
+      + (escaped ? code : escape(code, true))
+      + '\n
'; + } + + return '
'
+    + (escaped ? code : escape(code, true))
+    + '\n
\n'; +}; + +Renderer.prototype.blockquote = function(quote) { + return '
\n' + quote + '
\n'; +}; + +Renderer.prototype.html = function(html) { + return html; +}; + +Renderer.prototype.heading = function(text, level, raw) { + return '' + + text + + '\n'; +}; + +Renderer.prototype.hr = function() { + return this.options.xhtml ? '
\n' : '
\n'; +}; + +Renderer.prototype.list = function(body, ordered) { + var type = ordered ? 'ol' : 'ul'; + return '<' + type + '>\n' + body + '\n'; +}; + +Renderer.prototype.listitem = function(text) { + return '
  • ' + text + '
  • \n'; +}; + +Renderer.prototype.paragraph = function(text) { + return '

    ' + text + '

    \n'; +}; + +Renderer.prototype.table = function(header, body) { + return '\n' + + '\n' + + header + + '\n' + + '\n' + + body + + '\n' + + '
    \n'; +}; + +Renderer.prototype.tablerow = function(content) { + return '\n' + content + '\n'; +}; + +Renderer.prototype.tablecell = function(content, flags) { + var type = flags.header ? 'th' : 'td'; + var tag = flags.align + ? '<' + type + ' style="text-align:' + flags.align + '">' + : '<' + type + '>'; + return tag + content + '\n'; +}; + +// span level renderer +Renderer.prototype.strong = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.em = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.codespan = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.br = function() { + return this.options.xhtml ? '
    ' : '
    '; +}; + +Renderer.prototype.del = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.link = function(href, title, text) { + if (this.options.sanitize) { + try { + var prot = decodeURIComponent(unescape(href)) + .replace(/[^\w:]/g, '') + .toLowerCase(); + } catch (e) { + return ''; + } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { + return ''; + } + } + var out = '
    '; + return out; +}; + +Renderer.prototype.image = function(href, title, text) { + var out = '' + text + '' : '>'; + return out; +}; + +Renderer.prototype.text = function(text) { + return text; +}; + +/** + * Parsing & Compiling + */ + +function Parser(options) { + this.tokens = []; + this.token = null; + this.options = options || marked.defaults; + this.options.renderer = this.options.renderer || new Renderer; + this.renderer = this.options.renderer; + this.renderer.options = this.options; +} + +/** + * Static Parse Method + */ + +Parser.parse = function(src, options, renderer) { + var parser = new Parser(options, renderer); + return parser.parse(src); +}; + +/** + * Parse Loop + */ + +Parser.prototype.parse = function(src) { + this.inline = new InlineLexer(src.links, this.options, this.renderer); + this.tokens = src.reverse(); + + var out = ''; + while (this.next()) { + out += this.tok(); + console.log(out) + } + + return out; +}; + +/** + * Next Token + */ + +Parser.prototype.next = function() { + return this.token = this.tokens.pop(); +}; + +/** + * Preview Next Token + */ + +Parser.prototype.peek = function() { + return this.tokens[this.tokens.length - 1] || 0; +}; + +/** + * Parse Text Tokens + */ + +Parser.prototype.parseText = function() { + var body = this.token.text; + + while (this.peek().type === 'text') { + body += '\n' + this.next().text; + } + + return this.inline.output(body); +}; + +/** + * Parse Current Token + */ + +Parser.prototype.tok = function() { + console.log( this.token) + switch (this.token.type) { + case 'space': { + return ''; + } + case 'hr': { + return this.renderer.hr(); + } + case 'heading': { + return this.renderer.heading( + this.inline.output(this.token.text), + this.token.depth, + this.token.text); + } + case 'code': { + return this.renderer.code(this.token.text, + this.token.lang, + this.token.escaped); + } + case 'table': { + var header = '' + , body = '' + , i + , row + , cell + , flags + , j; + + // header + cell = ''; + for (i = 0; i < this.token.header.length; i++) { + flags = { header: true, align: this.token.align[i] }; + cell += this.renderer.tablecell( + this.inline.output(this.token.header[i]), + { header: true, align: this.token.align[i] } + ); + } + header += this.renderer.tablerow(cell); + + for (i = 0; i < this.token.cells.length; i++) { + row = this.token.cells[i]; + + cell = ''; + for (j = 0; j < row.length; j++) { + cell += this.renderer.tablecell( + this.inline.output(row[j]), + { header: false, align: this.token.align[j] } + ); + } + + body += this.renderer.tablerow(cell); + } + return this.renderer.table(header, body); + } + case 'blockquote_start': { + var body = ''; + + while (this.next().type !== 'blockquote_end') { + body += this.tok(); + } + + return this.renderer.blockquote(body); + } + case 'list_start': { + var body = '' + , ordered = this.token.ordered; + + while (this.next().type !== 'list_end') { + body += this.tok(); + } + + return this.renderer.list(body, ordered); + } + case 'list_item_start': { + var body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.token.type === 'text' + ? this.parseText() + : this.tok(); + } + + return this.renderer.listitem(body); + } + case 'loose_item_start': { + var body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.tok(); + } + + return this.renderer.listitem(body); + } + case 'html': { + var html = !this.token.pre && !this.options.pedantic + ? this.inline.output(this.token.text) + : this.token.text; + return this.renderer.html(html); + } + case 'paragraph': { + xxx = this.inline.output(this.token.text) + console.log(xxx) + return this.renderer.paragraph(xxx); + } + case 'text': { + return this.renderer.paragraph(this.parseText()); + } + } +}; + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function unescape(html) { + return html.replace(/&([#\w]+);/g, function(_, n) { + n = n.toLowerCase(); + if (n === 'colon') return ':'; + if (n.charAt(0) === '#') { + return n.charAt(1) === 'x' + ? String.fromCharCode(parseInt(n.substring(2), 16)) + : String.fromCharCode(+n.substring(1)); + } + return ''; + }); +} + +function replace(regex, opt) { + regex = regex.source; + opt = opt || ''; + return function self(name, val) { + if (!name) return new RegExp(regex, opt); + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return self; + }; +} + +function noop() {} +noop.exec = noop; + +function merge(obj) { + var i = 1 + , target + , key; + + for (; i < arguments.length; i++) { + target = arguments[i]; + for (key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + obj[key] = target[key]; + } + } + } + + return obj; +} + + +/** + * Marked + */ + +function marked(src, opt, callback) { + if (callback || typeof opt === 'function') { + if (!callback) { + callback = opt; + opt = null; + } + + opt = merge({}, marked.defaults, opt || {}); + + var highlight = opt.highlight + , tokens + , pending + , i = 0; + + try { + tokens = Lexer.lex(src, opt) + } catch (e) { + return callback(e); + } + + pending = tokens.length; + + var done = function(err) { + if (err) { + opt.highlight = highlight; + return callback(err); + } + + var out; + + try { + out = Parser.parse(tokens, opt); + } catch (e) { + err = e; + } + + opt.highlight = highlight; + + return err + ? callback(err) + : callback(null, out); + }; + + if (!highlight || highlight.length < 3) { + return done(); + } + + delete opt.highlight; + + if (!pending) return done(); + + for (; i < tokens.length; i++) { + (function(token) { + if (token.type !== 'code') { + return --pending || done(); + } + return highlight(token.text, token.lang, function(err, code) { + if (err) return done(err); + if (code == null || code === token.text) { + return --pending || done(); + } + token.text = code; + token.escaped = true; + --pending || done(); + }); + })(tokens[i]); + } + + return; + } + try { + if (opt) opt = merge({}, marked.defaults, opt); + return Parser.parse(Lexer.lex(src, opt), opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/chjj/marked.'; + if ((opt || marked.defaults).silent) { + return '

    An error occured:

    '
    +        + escape(e.message + '', true)
    +        + '
    '; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.defaults = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: false, + sanitizer: null, + mangle: true, + smartLists: false, + silent: false, + highlight: null, + langPrefix: 'lang-', + smartypants: false, + headerPrefix: '', + renderer: new Renderer, + xhtml: false +}; + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Renderer = Renderer; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; + +if (typeof module !== 'undefined' && typeof exports === 'object') { + module.exports = marked; +} else if (typeof define === 'function' && define.amd) { + define(function() { return marked; }); +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); +console.log(marked('nbchhhhhhhhd _xxxxxxxxxx_').links) \ No newline at end of file diff --git a/lib/js2py/translators/pyjsparser.py b/lib/js2py/translators/pyjsparser.py new file mode 100644 index 00000000..9d07cedf --- /dev/null +++ b/lib/js2py/translators/pyjsparser.py @@ -0,0 +1,2888 @@ +# 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 +from __future__ import unicode_literals +from .pyjsparserdata import * +from .std_nodes import * +from pprint import pprint + +REGEXP_SPECIAL_SINGLE = {'\\', '^', '$', '*', '+', '?', '.', '[', ']', '(', ')', '{', '{', '|', '-'} + + +import six +if six.PY3: + basestring = str + long = int + xrange = range + unicode = str + +ESPRIMA_VERSION = '2.2.0' +DEBUG = False +# Small naming convention changes +# len -> leng +# id -> d +# type -> typ +# str -> st +true = True +false = False +null = None + +class PyJsParser: + """ Usage: + parser = PyJsParser() + parser.parse('var JavaScriptCode = 5.1') + """ + def __init__(self): + self.clean() + + def test(self, code): + pprint(self.parse(code)) + + + def clean(self): + self.strict = None + self.sourceType = None + self.index = 0 + self.lineNumber = 1 + self.lineStart = 0 + self.hasLineTerminator = None + self.lastIndex = None + self.lastLineNumber = None + self.lastLineStart = None + self.startIndex = None + self.startLineNumber = None + self.startLineStart = None + self.scanning = None + self.lookahead = None + self.state = None + self.extra = None + self.isBindingElement = None + self.isAssignmentTarget = None + self.firstCoverInitializedNameError = None + + # 7.4 Comments + + def skipSingleLineComment(self, offset): + start = self.index - offset; + while self.index < self.length: + ch = self.source[self.index]; + self.index += 1 + if isLineTerminator(ch): + if (ord(ch) == 13 and ord(self.source[self.index]) == 10): + self.index += 1 + self.lineNumber += 1 + self.hasLineTerminator = True + self.lineStart = self.index + return + + def skipMultiLineComment(self): + while self.index < self.length: + ch = ord(self.source[self.index]) + if isLineTerminator(ch): + if (ch == 0x0D and ord(self.source[self.index+1]) == 0x0A): + self.index += 1 + self.lineNumber += 1 + self.index += 1 + self.hasLineTerminator = True + self.lineStart = self.index + elif ch == 0x2A: + # Block comment ends with '*/'. + if ord(self.source[self.index+1]) == 0x2F: + self.index += 2 + return + self.index += 1 + else: + self.index += 1 + self.tolerateUnexpectedToken() + + def skipComment(self): + self.hasLineTerminator = False + start = (self.index==0) + while self.index < self.length: + ch = ord(self.source[self.index]) + if isWhiteSpace(ch): + self.index += 1 + elif isLineTerminator(ch): + self.hasLineTerminator = True + self.index += 1 + if (ch == 0x0D and ord(self.source[self.index]) == 0x0A): + self.index += 1 + self.lineNumber += 1 + self.lineStart = self.index + start = True + elif (ch == 0x2F): # U+002F is '/' + ch = ord(self.source[self.index+1]) + if (ch == 0x2F): + self.index += 2 + self.skipSingleLineComment(2) + start = True + elif (ch == 0x2A): # U+002A is '*' + self.index += 2 + self.skipMultiLineComment() + else: + break + elif (start and ch == 0x2D): # U+002D is '-' + # U+003E is '>' + if (ord(self.source[self.index+1]) == 0x2D) and (ord(self.source[self.index+2]) == 0x3E): + # '-->' is a single-line comment + self.index += 3 + self.skipSingleLineComment(3) + else: + break + elif (ch == 0x3C): # U+003C is '<' + if self.source[self.index+1: self.index+4]=='!--': + #