KJS的一些简略笔记

今天原本是在cnBeta上看新闻的,看到KDE 4.0.1的发布消息时留意到其中提到Konquerer的JavaScript引擎(KJS)的更新,突然就心痒起来,到KDE的SVN去抓了KJS的代码下来看。粗略浏览了几个文件,做点记录。
KDE的svn trunk地址: svn://anonsvn.kde.org/home/kde/trunk/KDE
其中KJS的部分是: svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs/kjs
今天抓到的是revision 772368。话说代码里不是考虑到了MSVC的兼容性了么,为什么没见到MSVC的project/workspace/solution文件的?难道是在KDE的大包里么……回头得再找找。或者回头试试在MinGW里build一下看行不行。

KJS是由Harri Porten为KDE的浏览器Konquerer而编写的JavaScript引擎,目前支持的是ECMAScript 3。在一些实现细节上,KJS似乎更倾向于与IE的JScript的行为相兼容(相比于倾向Mozilla/FF)。未实现E4X。实现了Mozilla扩展的__getter__和__setter__以及相关设施。
Apple的WebKit中的JavaScript引擎 JavaScriptCore基于KJS。Safari自然也是使用JavaScriptCore的咯。在JavaScriptCore的介绍页面上的todo-list有点意思。
对了,不要忘记看看John Resig制作的 ECMAScript族谱哦: http://ejohn.org/files/ecma-cloud.png

KJS的代码看起来很干净。阅读的时候感觉比较轻松。在我读过的JavaScript引擎的源代码中,KJS可能是第二容易阅读的。第一是 Rhino,再怎么说用Java来写代码便是限制住了些恶心的东西(例如我很讨厌的宏……),而且也不用自己写GC了,交给下面的JVM就行。而Rhino的兄长,Mozilla SpiderMonkey则有不少恶心代码;嗯,不过应该说还算好的了。想来我读过的JavaScript-like语言的实现中最恶心的可能还是吉里吉里2里的TJS……以上纯粹个人喜好意义上的评论。

KJS中,解释器前端的lexer是手写的(lexer.h/cpp),没什么特别,就是一个ad hoc的DFA。没有用到正则表达式的库。关键字匹配是委托给Lookup类完成的,其中用到了hash table。lexer.h里有个奇怪的地方,明明声明了int lookupKeyword(const char *);却没有任何地方实现了这个方法,是refactor的时候漏了改么。lexer.cpp, line 578的有这么行注释:
// Hack for "f = function somename() { ... }", too hard to get into the grammar

想起之前在es4-discuss上讨论关于FunDeclInSmt的问题,记个链接: Function declarations in statements。上面那行注释对应的是FunExpr的状况。嗯回头留意下别的JavaScript引擎是怎么处理这个语法的。
Parser则是bison生成的(grammar.y/h/cpp,Parser.h/cpp)。中间是一棵抽象语法树。后端是一个tree-walker形式的解释器(主要在interpreter.h/cpp)。
解释器状态在ExecState中保存(ExecState.h/cpp)。每个解释器实例应使用一个独特的JSObject作为globalObejct。解释器允许在多线程环境下使用,为了保证线程安全,引擎实现了锁(JSLock.h/cpp)。

基本的类型层次从JSValue类开始。
JSValue <- primitive type及Object的实现类的基类。但只有primitive type直接继承自该类
  └JSCell <- 为GC需要而设立的一个基类
     └JSObject <- Object的实现类,也是其它non-primitive type的基类。


GC则是使用mark-and-sweep算法。没有分代。mark用bitmap来记录。GC功能主要在collector.h/cpp中实现。

KJS中注册native function的方式真独特。以前还没见过这么做的,唉唉,孤陋寡闻了。
举个例子,看看KJS中Boolean.prototype的实现部分。
bool_object.h, line 52:
/**
 * @internal
 *
 * Class to implement all methods that are properties of the
 * Boolean.prototype object
 */
class BooleanProtoFunc : public InternalFunctionImp {
public:
  BooleanProtoFunc(ExecState*, FunctionPrototype*, int i, int len, const Identifier&);

  virtual JSValue *callAsFunction(ExecState *exec, JSObject *thisObj, const List &args);

  enum { ToString, ValueOf };
private:
  int id;
};


bool_object.cpp, line 65:
// ECMA 15.6.4.2 + 15.6.4.3
JSValue *BooleanProtoFunc::callAsFunction(ExecState* exec, JSObject *thisObj, const List &/*args*/)
{
  // no generic function. "this" has to be a Boolean object
  if (!thisObj->inherits(&BooleanInstance::info))
    return throwError(exec, TypeError);

  // execute "toString()" or "valueOf()", respectively

  JSValue *v = static_cast<BooleanInstance*>(thisObj)->internalValue();
  assert(v);

  if (id == ToString)
    return jsString(v->toString(exec));
  return jsBoolean(v->toBoolean(exec)); /* TODO: optimize for bool case */
}


像这样,Boolean.prototype.ToString()和Boolean.prototype.ValueOf()就被实现为一个JSObject::callAsFunction()了,在callAsFunction()里通过id来区别具体要调用的是哪个函数。id所用到的值得范围则是由一个enum提供的。
天啊。在Boolean里还好只有2个方法,但在Math和Date里,原生提供的方法就多多了,Object.prototype也是……结果看到的是这种代码:
math_object.h, line 37:
enum { Euler, Ln2, Ln10, Log2E, Log10E, Pi, Sqrt1_2, Sqrt2,
       Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Pow,
       Exp, Floor, Log, Max, Min, Random, Round, Sin, Sqrt, Tan };


math_object.cpp, line 85:
JSValue *MathObjectImp::getValueProperty(ExecState *, int token) const
{
  double d = -42; // ;)
  switch (token) {
  case Euler:
    d = exp(1.0);
    break;
  case Ln2:
    d = log(2.0);
    break;
  //... other cases omitted
  }
  
  return jsNumber(d);
}


math_object.cpp, line 131:
JSValue *MathFuncImp::callAsFunction(ExecState *exec, JSObject* /*thisObj*/, const List &args)
{
  double arg = args[0]->toNumber(exec);
  double arg2 = args[1]->toNumber(exec);
  double result;

  switch (id) {
  case MathObjectImp::Abs:
    result = ( arg < 0 || arg == -0) ? (-arg) : arg;
    break;
  case MathObjectImp::ACos:
    result = ::acos(arg);
    break;
  case MathObjectImp::ASin:
    result = ::asin(arg);
    break;
  //... other cases omitted
  }
  
  return jsNumber(result);
}


这里可是26个方法给switch(id)了……

KDE提供了一个方便将KJS嵌入别的KDE/Qt应用的库 KJSEmbed。不知道KJSEmbed里是怎么将native function绑定到JavaScript端的,不过想来跟KJS的做法应该一样(因为被KJS的JSObject固定了)?

(更新:顺便就把KJSEmbed也抓了下来。svn trunk: svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs/kjsembed
其中qobject_binding.h(197)/qobject_binding.cpp(604),static_binding.h(49,93)/static_binding.cpp(41)确实也出现了callAsFunction()。不过却没有前述的enum+switch(id);SlotBinding类里有个switch(returnTypeId),不过跟前面的那种switch比较不一样了。
取而代之,恶心的宏出现了 T T
qobject_binding.h, line 38:
/**
* A simple pointer syle method.
* This will extract the pointer, cast it to the native type and place it in "value".
* Any data that should be returned from this method should be placed into "result";
*
*/
#define START_QOBJECT_METHOD( METHODNAME, TYPE) \
KJS::JSValue *METHODNAME( KJS::ExecState *exec, KJS::JSObject *self, const KJS::List &args ) \
{ \
        Q_UNUSED( args ); \
        KJS::JSValue *result = KJS::jsNull(); \
        KJSEmbed::QObjectBinding *imp = KJSEmbed::extractBindingImp<KJSEmbed::QObjectBinding>(exec, self ); \
        if( imp ) \
        { \
            TYPE *object = imp->qobject<TYPE>(); \
            if( object ) \
            {

/**
* End a variant method started by START_QOBJECT_METHOD
*/
#define END_QOBJECT_METHOD \
            } \
            else \
                KJS::throwError(exec, KJS::ReferenceError, QString("QO: The internal object died %1:%2.").arg(__FILE__).arg(__LINE__));\
        } \
        else \
           KJS::throwError(exec, KJS::ReferenceError, QString("QObject died."));\
        return result; \
}

这这这……我讨厌这样的宏 T T
但我也确实没见过什么别的好办法。总不能每次都重复的手写那些代码吧。


以后想起来了再慢慢更新……

P.S. 祝大家新年快乐噢~

你可能感兴趣的:(JavaScript,SVN,prototype,webkit,qt)