`
RednaxelaFX
  • 浏览: 3016255 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系

阅读更多
对一个对象实例调用(string)转换时,可能会看到这样的结果: (object 0x01AAD840:0x01A9EEC4)
object到string的转换可以通过显式或隐式方式调用.这个转换在tjsVariant.cpp中实现.

\kirikiri2\src\core\tjs2\tjsVariant.cpp
void tTJSVariant::ToString()
{
    switch(vt)
    {
    case tvtVoid:
        String=NULL;
        vt=tvtString;
        break;

    case tvtObject:
      {
        tTJSVariantString * string = TJSObjectToString(*(tTJSVariantClosure*)&Object);
        ReleaseObject();
        String = string;
        vt=tvtString;
        break;
      }

    case tvtString:
        break;

    case tvtInteger:
        String=TJSIntegerToString(Integer);
        vt=tvtString;
        break;

    case tvtReal:
        String=TJSRealToString(Real);
        vt=tvtString;
        break;

    case tvtOctet:
        TJSThrowVariantConvertError(*this, tvtString);
        break;
    }
}

tTJSVariantString * TJSObjectToString(const tTJSVariantClosure &dsp)
{
    if(TJSObjectTypeInfoEnabled())
    {
        // retrieve object type information from debugging facility
        tjs_char tmp[256];
        TJS_sprintf(tmp, TJS_W("(object 0x%p"), dsp.Object);
        ttstr ret = tmp;
        ttstr type = TJSGetObjectTypeInfo(dsp.Object);
        if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");
        TJS_sprintf(tmp, TJS_W(":0x%p"), dsp.ObjThis);
        ret += tmp;
        type = TJSGetObjectTypeInfo(dsp.ObjThis);
        if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");
        ret += TJS_W(")");
        tTJSVariantString * str = ret.AsVariantStringNoAddRef();
        str->AddRef();
        return str;
    }
    else
    {
        tjs_char tmp[256];
        TJS_sprintf(tmp, TJS_W("(object 0x%p:0x%p)"), dsp.Object, dsp.ObjThis);
        return TJSAllocVariantString(tmp);
    }
}

注意到,上面例子(object 0x01AAD840:0x01A9EEC4)中的两个数字,前者是dsp.Object,后者是dsp.ObjThis. tTJSVariantClosure的这两个成员是由tTJSVariantClosure_S继承而来.

tTJSVariantClosure_S的定义如下:
struct tTJSVariantClosure_S
{
	iTJSDispatch2 *Object;
	iTJSDispatch2 *ObjThis;
};

由此得知,两个数字皆是地址(指针自身的内容都是地址).
我现在还不是太确定Object成员是不是在编译到中间代码的时候就已经构造出了实例,但从多个同类型的对象实例共享一个Object成员的实例来看,应该不是到脚本执行的时候才实例化的.现在虽然找到了几个关键点(T_NEW, VM_NEW, CreateNew, TJS_GET_VM_REG等),但面向对象程序的代码就是很难通过静态分析完全确定动态行为,我仍然理不出运行时行为的头绪.糟糕的是我暂时没办法以debug模式把TJS2的解释器正确编译出来,要调试也很麻烦.

Anyway,还不确定的就先放一边,来看看我确定的部分.上面提到了,每个tTJSVariantClosure实例都会有ObjectObjThis指针为成员.并且,tTJSVariantClosure拥有由iTJSDispatch2接口类型所拥有的各方法.在调用这些方法时,需要经由tTJSVariantClosure来访问,以确保执行时的上下文的正确性.

简单点说,TJS2中每个对象实例都有"两个指针",一个是Object,指向类中的程序代码(如初始化用的代码和函数等),由同类型的所有类型所共享,无状态;另一个是ObjThis,也就是TJS2中以"this"访问可以得到的对象,保存着实例自身的上下文(context,也可以理解为环境environment),不与其它实例共享,用于保持状态信息.在TJS2中,将这个上下文称为闭包(closure).可以参考KCDDP翻译的TJS2中文文档中"类(class)"一节.这里"闭包"可以理解为状态(主要是成员变量)与一个由"类"的语法作用域所形成的范围发生绑定的现象.这样,这个范围就对其中的成员变量形成了闭包.注意到函数也同样可以被赋值给变量,并且函数声名与将函数表达式赋值给一个变量近似等价,因此成员函数(作为变量)也是上下文的一部分.拥有一个专门指向上下文的指针,就意味着绑定可以在运行时改变.使用incontextof运算符就能做到"可调用对象"(Callable, 这里具体指类定义或函数)的上下文的显式指定.

下面的代码可以清晰的展示这一特性:
class A {

    var value = "A";
    
    function printField() {
        System.inform(value);
    }
    
    function printFunc() {
        print2();
    }
    
    function print2() {
        System.inform("A.print2");
    }
}

class B {

    var value = "B";
    
    function print2() {
        System.inform("B.print2");
    }
}

var a = new A();
var b = new B();
(a.printField incontextof b)(); // 使用incontextof运算符显式改变上下文
(a.printFunc incontextof b)();  // 这两行调用的上下文被替换为与b的相绑定
a.printField();  // 正常的方法调用
a.printFunc();   // 这两行调用的上下文都与a的绑定
// 运行结果: 依次显示B, B.print2, A, A.print2


要注意的是,TJS2里的"闭包"与一般支持嵌套定义函数的编程语言中所指的闭包并不相同;TJS2里"闭包"的用法相当独特,特别不应与JavaScript中所支持的闭包所混淆.
在一般的编程语言中,"闭包"的概念是如何会出现的呢? 下面简单解释一下.下面一段会混用"过程""函数""方法"等几个名词,请自行注意分辨区别.也可以查阅wikipedia上的相关条目.该条目原本有些描述不太对(缺乏对命令式语言中闭包的考虑),现在已经订正了一些.
根据《编译原理与实践》一书,可以将基于栈(stack)的运行时环境分为三类:
- 没有局部过程的基于栈的环境
- 带有局部过程的基于栈的环境
- 带有过程参数的基于栈的环境

假如一种语言不允许嵌套声明过程/函数/方法,则所有的函数如果不是局部的就一定是全局的.因而很容易为变量分配空间——全局变量可以放在一个全局区域,而所有函数内的局部变量则直接分配在栈上(或寄存器上).函数总是能访问到它的作用域内的所有变量,外加全局变量.一个函数在被调用的时候,会在栈上压入它的活动记录,在函数结束时销毁;因而所有局部变量将随着函数的退出而被销毁.

int global = 1;

void foo() {
    int local = 2;
    // 此处global与local都能被访问到
}

// 此处就不再能访问到local了

假如一种语言允许嵌套声明过程/函数/方法,但不允许将过程/函数/方法当作参数来传递,前一种运行时环境就无效了.试想下面的伪代码(以C的语义来考虑):
int global = 1;

void foo() {
    int local = 2;

    void goo(int local) {
        hoo();
    }

    void hoo() {
        // 此处能访问到global
        // 但是local呢?
        // 因为hoo()嵌套于foo()之中,我们希望hoo()也能访问到foo()的local
    }
    goo(local);
}

void main(void) {
    foo();
}

这段代码片段中,对hoo()而言global依然是全局变量因而可以正常访问,但local是非局部非全局的变量,不能再按照前面的方式考虑.按照标准的静态作用域则无法在任何活动记录中找到local的信息;如果接受动态作用域,那么可以通过控制链向上找到在goo()中的local.即使goo()中没有local,还可以继续向上找到foo()的local.但这样每次能找到"local"的偏移量都会随着调用的不同而不同,而我们想要的很可能不是这样的.
要解决这问题,仍然可以实现静态作用域.可以用一个访问链(access link)去记录一个函数的包围(enclosing)函数的活动记录,使内部的函数能访问到外部函数的局部变量.在上面的代码里,也就是说hoo()可以访问到foo()的(而不是goo()的)local变量.

前面两种情况都能通过灵活使用栈而得到顺利解决,但下面的这种情况就很难单独依靠栈来维持运行时环境了.假如一个语言允许将过程/函数/方法当作参数来传递,则编译程序无法再像前一种情况生成代码计算调用点上的访问链.为解决非局部引用的问题,函数应包含一个访问指针对,其中一个是代码指针一个是访问链或环境指针(注意到这里跟TJS2的实现的微妙相似与相异).它们通称为闭包(closure),因为访问链"闭合"了由非局部引用引起的"洞"."闭包"的概念来自微积分中λ算子,这里就不深入了.通过闭包,内部函数可以捕捉到外部函数所能访问到的所有引用,包括全局引用和外部函数的局部引用,外加内部函数自身的局部引用;也就是说,内部函数的作用域"捕捉"到了外部的.然而这个内部函数一旦被作为参数传递(例如说被外部函数作为返回值返回),它所能访问到的作用域不能马上被销毁,而要继续存在到没有任何引用继续指向该内部函数为止.这就与前面使用栈的方式相异,很难只用栈来放置活动记录就维持运行时环境.更一般的做法是把闭包相关的活动记录在堆(heap)上分配,然后由垃圾收集器处理销毁的工作.

参考下面代码(ActionScript3):
function add( lhs : int ) : Function {
    return function ( rhs : int ) : int {
        return lhs + rhs;
    }
}

var addFive = add( 5 );
var twelve = addFive( 7 ); // twelve == 12


总之,要特别引起注意的地方是这里引出"闭包"这改变的问题,来自以遵守静态作用域为前提,允许嵌套声明的函数对来自包围它的作用域的非局部非全局引用的访问.
在这个意义上,TJS2并没有实现一般的闭包.在TJS2中可以嵌套定义函数,但无论嵌套多少层,一个函数的上下文总是绑定于离它最近的"类"的作用域中;假如一个函数向嵌套的外层数去,数到头都没有类声明,则它的上下文绑定于全局对象(global).下面的代码将展示这点:
function foo() {
    function goo() {
        this.val = "a string";
    }
    goo();
}

foo();
System.inform(val); // 或者写为global.val都一样.
// 显示a string


幸好吉里吉里2的作者,W.Dee氏已经意识到了这点.在设计TJS2的接班者Risse时,他承诺能实现下面的代码:
function test()
{
    var i = 0;
    return function inc()
    {
        return ++i;
    }
}

var inc_func = test();
Log.message(inc_func()); // => 1
Log.message(inc_func()); // => 2


TJS2不但没实现一般意义的闭包,同时也没有遵循一般意义的静态作用域.TJS2中incontextof运算符可以在静态作用域的基础上将一个可调用对象的上下文重新绑定到任意对象级别上.TJS2解释器会在编译的时候检查是否可执行代码是否有可能调用了非局部(但是是成员的;"全局"在TJS2中是一个特殊的内建对象,全局变量可以认为是global的成员)的引用.如果有的话,则会通过"this proxy"(对应于%-2寄存器)来寻找那些引用.这么做可以说很灵活,但也很容易写出让人难以理解的代码(因为函数与上下文的绑定可能经常变化).建议慎用.
个人倾向于将这种作用域的做法成为"半自动动态作用域".总之关键就是不要以纯静态作用域的假设去阅读TJS2代码,否则一定会吃到苦头...

===========================================

十分有趣的一点是,TJS2中的==与===运算符,分别是由tTJSVariant::NormalCompare与tTJSVariant::DiscernCompare实现的;其中,前者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.Object;后者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.Object与Object.ObjThis.这解释了下面代码行为的原因:
class A {
    var someField; // member field
    
    function A() {} // ctor
    
    function foo() {} // member method
}
var a = new A();
System.inform((string)A.foo + (string)a.foo); // 显示两个相同的Object地址
System.inform(A.foo == a.foo); // 1, 即equality相等
System.inform(A.foo === a.foo); // 0, 即identity不相等
System.inform(A.foo === (a.foo incontextof null)); // 1, 即identity也相等
分享到:
评论

相关推荐

    patch.tjs

    patch.tjs

    跨数据库同步工具TJS

    跨数据库同步工具TJS相关文档及示范项目源代码

    content_1663916621294.tjs

    content_1663916621294.tjs

    tjs:TJS = tinyc编译器+ quickjs

    TJS = tinyc编译器+ quickjs 混合两个世界的力量,使您可以在js中(内联)调用c函数。 玩具项目,不保证安全,使用风险自负。例子// yes, it support es-moduleimport { Compiler } from "builtin:c" ;// initialize...

    club_tjs

    在项目目录中,可以运行: yarn start 在开发模式下运行应用程序。 打开在浏览器中查看它。 如果您进行编辑,则页面将重新加载。 您还将在控制台中看到任何棉绒错误。 yarn test 在交互式监视模式下启动测试运行...

    tjs.rar_game

    贪吃蛇游戏,直得对游戏开发的朋友学习或参考.

    gulp-converter-tjs:将旧的新类型的OpenCV HaarCascade xml数据转换为trackingJs的内部格式

    converter-tjs接受OpenCV训练数据(新的或旧的类型)的XML,将其解析为内部表示形式(JavaScript对象),然后推送以输出其trace.js表示形式。 例子 var gulp = require ( 'gulp' ) ; var converterTjs = require ( ...

    PATCH补丁工具 .rar

    简单的易语言程序,仅供学习交流,为初学者提供的补丁程序,方便写入文件,支持动态补丁,特征码补丁,支持寄存器hook功能全面

    krkr_archives:桐木的各种代码

    这是到目前为止我创建的与KiriKiri 2相关的脚本。 KiriKiri 2( ) KiriKiri Z( ) 轮廓如下所示。请参阅每个文件夹中的readme.txt以获取详细说明。 工具文件夹 包含在开发工作中有用的工具。 oggConverter将wav...

    TJS:以最少的代码实现ExJ

    TJS:以最少的代码实现ExJ

    thread-js:在浏览器中使用多线程的库

    并且同样适用于队列冲突优先级被最后一个传递的优先级覆盖,除非在对象实例化中设置了选项 no-queue-override 然后索引总和为 1 直到它到达一个空闲位置 obs:不推荐使用此选项由于它将在较大的队列上同步运行,...

    Ajax-jquery.initialize.zip

    Ajax-jquery.initialize.zip,用于动态创建元素初始化的jquery插件(几年前很好,在2019年 考虑react或其他东西而不是jquery),ajax代表异步javascript和xml。它是多种web技术的集合,包括html、css、json、xml和...

    2021-03-15-TJS:coursReact

    可用脚本在项目目录中,可以运行:yarn start 在开发模式下运行应用程序。 打开在浏览器中查看它。 如果您进行编辑,则页面将重新加载。 您还将在控制台中看到任何棉绒错误。yarn test 在交互式监视模式下启动测试...

    patch命令 修补文件

    倘若一次仅修改一个文件,可直接在指令列中下达指令依序执行。如果配合修补文件的方式则能一次修补大批文件,这也是Linux系统核心的升级方法之一 。 语法格式:patch [参数] 常用参数: -b 备份每一个原始文件 ...

    xp sp3补丁下载

    xp sp3补丁,sp2升为sp3补丁下载。如果使用360安全卫士的话,也可以直接下载安装最好,打开修复漏洞界面,在功能性补丁里可以找到这个补丁,选择下载即可,不过速度慢了点。对速度有要求的,可以直接下载xp sp3补丁...

    cm3d2-archive-replacer:CM3D2的补丁程序,可轻松注入资源

    要求与限制您的CM3D2版本必须为1.0.4或更高您不能将此模式与DEFLARC一起使用当前无法替换脚本文件(* .tjs / * .ks)安装将源文件下载并解压缩到您喜欢的文件夹中(而不是ReiPatcher / CM3D2文件夹) 编辑build.bat...

    patch安装包

    在移植WiFi时需要安装的工具,可以帮助你更好的移植WiFi

    平面埋入TJS激光器

    日本光技术共同研究所和三菱电机大规模集成电路研究所共同研制出具有平面结构的新型半导体激光器,实现了室温连续振荡。

    Castalia 4 Patch 通用补丁程序

    支持 Castalia 4 全系列的通用补丁没有对所有的版本都作测试理论上基本没有问题实际嘛~试试吧~!

Global site tag (gtag.js) - Google Analytics