#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
import sys, os, re
import thread
import logging
try:
from cStringIO import StringIO
except ImportError:
from StringIO 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 unicode(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
JSExtension = _PyV8.JSExtension
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()
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()
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 callable(prop[0]):
return prop[0]()
raise AttributeError(name)
def __setattr__(self, name, value):
prop = self.__dict__.setdefault('__properties__', {}).get(name, None)
if prop and callable(prop[1]):
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 method."
del self.__watchpoints__[prop]
class JSClassConstructor(JSClass):
def __init__(self, cls):
self.cls = cls
@property
def name(self):
return self.cls.__name__
def toString(self):
return "function %s() {\n [native code]\n}" % self.name
def __call__(self, *args, **kwds):
return self.cls(*args, **kwds)
class JSClassPrototype(JSClass):
def __init__(self, cls):
self.cls = cls
@property
def constructor(self):
return JSClassConstructor(self.cls)
@property
def name(self):
return self.cls.__name__
class JSDebugProtocol(object):
"""
Support the V8 debugger JSON based protocol.
<http://code.google.com/p/v8/wiki/DebuggerProtocol>
"""
class Packet(object):
REQUEST = 'request'
RESPONSE = 'response'
EVENT = 'event'
def __init__(self, payload):
self.data = json.loads(payload) if type(payload) in [str, unicode] else payload
@property
def seq(self):
return self.data['seq']
@property
def type(self):
return self.data['type']
class Request(Packet):
@property
def cmd(self):
return self.data['command']
@property
def args(self):
return self.data['args']
class Response(Packet):
@property
def request_seq(self):
return self.data['request_seq']
@property
def cmd(self):
return self.data['command']