#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os, re
import logging
import collections
is_py3k = sys.version_info[0] > 2
if is_py3k:
import _thread as thread
from io import StringIO
str = str
raw_input = input
else:
import _thread
try:
from io import StringIO
except ImportError:
from io import StringIO
try:
import json
except ImportError:
import simplejson as json
import _PyV8
__author__ = 'Flier Lu <flier.lu@gmail.com>'
__version__ = '1.0'
__all__ = ["ReadOnly", "DontEnum", "DontDelete", "Internal",
"JSError", "JSObject", "JSArray", "JSFunction",
"JSClass", "JSEngine", "JSContext",
"JSObjectSpace", "JSAllocationAction",
"JSStackTrace", "JSStackFrame", "profiler",
"JSExtension", "JSLocker", "JSUnlocker", "AST"]
class JSAttribute(object):
def __init__(self, name):
self.name = name
def __call__(self, func):
setattr(func, "__%s__" % self.name, True)
return func
ReadOnly = JSAttribute(name='readonly')
DontEnum = JSAttribute(name='dontenum')
DontDelete = JSAttribute(name='dontdel')
Internal = JSAttribute(name='internal')
class JSError(Exception):
def __init__(self, impl):
Exception.__init__(self)
self._impl = impl
def __str__(self):
return str(self._impl)
def __unicode__(self, *args, **kwargs):
return str(self._impl)
def __getattribute__(self, attr):
impl = super(JSError, self).__getattribute__("_impl")
try:
return getattr(impl, attr)
except AttributeError:
return super(JSError, self).__getattribute__(attr)
RE_FRAME = re.compile(r"\s+at\s(?:new\s)?(?P<func>.+)\s\((?P<file>[^:]+):?(?P<row>\d+)?:?(?P<col>\d+)?\)")
RE_FUNC = re.compile(r"\s+at\s(?:new\s)?(?P<func>.+)\s\((?P<file>[^\)]+)\)")
RE_FILE = re.compile(r"\s+at\s(?P<file>[^:]+):?(?P<row>\d+)?:?(?P<col>\d+)?")
@staticmethod
def parse_stack(value):
stack = []
def int_or_nul(value):
return int(value) if value else None
for line in value.split('\n')[1:]:
m = JSError.RE_FRAME.match(line)
if m:
stack.append((m.group('func'), m.group('file'), int_or_nul(m.group('row')), int_or_nul(m.group('col'))))
continue
m = JSError.RE_FUNC.match(line)
if m:
stack.append((m.group('func'), m.group('file'), None, None))
continue
m = JSError.RE_FILE.match(line)
if m:
stack.append((None, m.group('file'), int_or_nul(m.group('row')), int_or_nul(m.group('col'))))
continue
assert line
return stack
@property
def frames(self):
return self.parse_stack(self.stackTrace)
_PyV8._JSError._jsclass = JSError
JSObject = _PyV8.JSObject
JSArray = _PyV8.JSArray
JSFunction = _PyV8.JSFunction
# contribute by e.generalov
JS_ESCAPABLE = re.compile(r'([^\x00-\x7f])')
HAS_UTF8 = re.compile(r'[\x80-\xff]')
def _js_escape_unicode_re_callack(match):
n = ord(match.group(0))
if n < 0x10000:
return '\\u%04x' % (n,)
else:
# surrogate pair
n -= 0x10000
s1 = 0xd800 | ((n >> 10) & 0x3ff)
s2 = 0xdc00 | (n & 0x3ff)
return '\\u%04x\\u%04x' % (s1, s2)
def js_escape_unicode(text):
"""Return an ASCII-only representation of a JavaScript string"""
if isinstance(text, str):
if HAS_UTF8.search(text) is None:
return text
text = text.decode('UTF-8')
return str(JS_ESCAPABLE.sub(_js_escape_unicode_re_callack, text))
class JSExtension(_PyV8.JSExtension):
def __init__(self, name, source, callback=None, dependencies=[], register=True):
_PyV8.JSExtension.__init__(self, js_escape_unicode(name), js_escape_unicode(source), callback, dependencies, register)
def func_apply(self, thisArg, argArray=[]):
if isinstance(thisArg, JSObject):
return self.invoke(thisArg, argArray)
this = JSContext.current.eval("(%s)" % json.dumps(thisArg))
return self.invoke(this, argArray)
JSFunction.apply = func_apply
class JSLocker(_PyV8.JSLocker):
def __enter__(self):
self.enter()
if JSContext.entered:
self.leave()
raise RuntimeError("Lock should be acquired before enter the context")
return self
def __exit__(self, exc_type, exc_value, traceback):
if JSContext.entered:
self.leave()
raise RuntimeError("Lock should be released after leave the context")
self.leave()
if is_py3k:
def __bool__(self):
return self.entered()
else:
def __nonzero__(self):
return self.entered()
class JSUnlocker(_PyV8.JSUnlocker):
def __enter__(self):
self.enter()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.leave()
if is_py3k:
def __bool__(self):
return self.entered()
else:
def __nonzero__(self):
return self.entered()
class JSClass(object):
__properties__ = {}
__watchpoints__ = {}
def __getattr__(self, name):
if name == 'constructor':
return JSClassConstructor(self.__class__)
if name == 'prototype':
return JSClassPrototype(self.__class__)
prop = self.__dict__.setdefault('__properties__', {}).get(name, None)
if prop and isinstance(prop[0], collections.Callable):
return prop[0]()
raise AttributeError(name)
def __setattr__(self, name, value):
prop = self.__dict__.setdefault('__properties__', {}).get(name, None)
if prop and isinstance(prop[1], collections.Callable):
return prop[1](value)
return object.__setattr__(self, name, value)
def toString(self):
"Returns a string representation of an object."
return "[object %s]" % self.__class__.__name__
def toLocaleString(self):
"Returns a value as a string value appropriate to the host environment's current locale."
return self.toString()
def valueOf(self):
"Returns the primitive value of the specified object."
return self
def hasOwnProperty(self, name):
"Returns a Boolean value indicating whether an object has a property with the specified name."
return hasattr(self, name)
def isPrototypeOf(self, obj):
"Returns a Boolean value indicating whether an object exists in the prototype chain of another object."
raise NotImplementedError()
def __defineGetter__(self, name, getter):
"Binds an object's property to a function to be called when that property is looked up."
self.__properties__[name] = (getter, self.__lookupSetter__(name))
def __lookupGetter__(self, name):
"Return the function bound as a getter to the specified property."
return self.__properties__.get(name, (None, None))[0]
def __defineSetter__(self, name, setter):
"Binds an object's property to a function to be called when an attempt is made to set that property."
self.__properties__[name] = (self.__lookupGetter__(name), setter)
def __lookupSetter__(self, name):
"Return the function bound as a setter to the specified property."
return self.__properties__.get(name, (None, None))[1]
def watch(self, prop, handler):
"Watches for a property to be assigned a value and runs a function when that occurs."
self.__watchpoints__[prop] = handler
def unwatch(self, prop):
"Removes a watchpoint set with the watch meth