v1.9.0的版本可以在class类结构中使用self来表示当前所在类的类名。此外,该版本还增加了zenglApi_SetDefLookupHandle和zenglApi_SetDefLookupResult接口
页面导航:
zengl language v1.9.0的源代码的相关地址:https://github.com/zenglong/zengl_language 当前版本对应的tag标签为:v1.9.0
v1.9.0的版本可以在class类结构中使用self来表示当前所在类的类名。
此外,该版本还增加了zenglApi_SetDefLookupHandle和zenglApi_SetDefLookupResult接口,通过这两个接口用户就可以自定义def宏值查询函数,该查询函数会根据查询名称返回对应的宏值。
例如:def TRUE ___TRUE___; 这个语句,就会调用用户自定义的查询函数,并将___TRUE___作为查询名称传递给查询函数,经过查询后,就会设置整数1(假设用户自定义的值是1)作为TRUE的宏值,因此这个语句等效于 def TRUE 1,如果用户自定义的___TRUE___这个查询名称的查询结果是2,则等效于 def TRUE 2; 从而实现用户程序向脚本中导入宏值。
为了演示self这种特殊类名的用法,当前版本在test_scripts/v1.9.0/的目录中增加了test_self.zl的测试脚本:
use builtin; class Test act; arg; fun Do(act, arg, obj) self obj; obj.act = act; obj.arg = arg; if(act == 'play') self.play(arg); else self.other(arg); endif self.Print(obj); endfun fun play(game) print 'play: ' + game; endfun fun other(str) print 'other: ' + str; endfun fun Print(obj) self obj; print 'obj.act: ' + obj.act; print 'obj.arg: ' + obj.arg; endfun endclass Test test; test = array(); Test.Do('play', 'football', test); Test.Do('hello', 'worlds', test); Test.Print(test);
上面脚本在Test类结构中,就使用了self来表示Test类名。之前的版本需要使用Test.play这样的语法来调用类方法,当前版本则可以使用self.play这样的语法来调用当前类中的方法。此外,之前版本只能使用Test obj;这样的语法来声明类变量,当前版本则可以使用self obj这样的语法来用当前类声明变量。
使用self特殊类名的好处在于,无需关心当前类的类名的变化,即使类名发生了变化,类结构中使用了self的代码也无需修改。此外,通过self来调用方法,可以知道哪些代码是调用的当前类中的方法,哪些是调用的外部类的方法,调用外部加载的类中的方法,还是需要使用完整的类名。
上面的test_self.zl脚本在命令行中的执行结果类似如下:
[root@anonymous linux]# ./zengl test_scripts/v1.9.0/test_self.zl run(编译执行中)... stat cache file: "caches/1_9_0_8_dv01_67d713ee2e783f5d0e5f77512af67d8d" failed, maybe no cache file [recompile] play: football obj.act: play obj.arg: football other: worlds obj.act: hello obj.arg: worlds obj.act: hello obj.arg: worlds write zengl cache to file "caches/1_9_0_8_dv01_67d713ee2e783f5d0e5f77512af67d8d" success run finished(编译执行结束) [root@anonymous linux]#
在zengl_global.h的C头文件中增加了和实现self特殊类名相关的结构:
/******************************************************************************** 下面是和zengl_symbol.c符号表处理相关的结构体和枚举等定义 ********************************************************************************/ #define ZL_SYM_SELF_TOKEN_STR "self" // 使用self这种特殊类名来表示当前所在类的类名 ............................................................................................ #define ZL_SYM_SELF_CLASS_TABLE_SIZE 20 // self节点对应的类信息的动态数组的初始化和动态扩容的大小 ............................................................................................ typedef struct _ZENGL_SYM_SELF_CLASS_TABLE_MEMBER{ ZL_BOOL isvalid; // 判断当前成员是否有效 ZL_INT self_nodenum; // self节点的AST节点号 ZL_INT class_nodenum; // self节点所在的类的class节点的AST节点号 ZL_INT classid; // self节点对应的类ID }ZENGL_SYM_SELF_CLASS_TABLE_MEMBER; // self节点对应的类信息的动态数组的成员的结构定义 typedef struct _ZENGL_SYM_SELF_CLASS_TABLE{ ZL_BOOL isInit; // 判断self节点对应的类信息的动态数组是否初始化 ZL_INT size; // 动态数组小大,当前最大可容纳的成员数,当count有效成员数等于size时,会对动态数组进行动态扩容,同时增加size的值 ZL_INT count; // 动态数组中的有效成员数 ZENGL_SYM_SELF_CLASS_TABLE_MEMBER * members; // 指向动态数组的指针,动态数组中的每个成员的结构都是ZENGL_SYM_SELF_CLASS_TABLE_MEMBER类型 ZL_INT cur_class_nodenum; // 编译器在将类的class节点加入AST抽象语法树时,会将该节点的AST节点号记录到cur_class_nodenum字段,这样类里面的self节点就可以和该class节点建立关联了,从而可以知道self节点属于哪个类了 }ZENGL_SYM_SELF_CLASS_TABLE; // 存储self节点对应的类信息的动态数组的结构定义 /******************************************************************************** 上面是和zengl_symbol.c符号表处理相关的结构体和枚举等定义 ********************************************************************************/
在编译zengl脚本的时候,会使用上面定义的ZENGL_SYM_SELF_CLASS_TABLE结构来创建一个和self节点相关的动态数组,在该动态数组中会将每个self节点和对应的类ID信息建立一个关联。从而可以知道脚本中的这些self属于哪个类了。
当前版本在zenglApi.c文件中新增了zenglApi_SetDefLookupHandle和zenglApi_SetDefLookupResult接口:
/* API接口,设置用户自定义的def宏值查询函数,通过该查询函数可以根据自定义的查询名称来返回相应的宏值(从而实现用户程序向脚本中导入自定义的宏值) */ ZL_EXPORT ZL_EXP_INT zenglApi_SetDefLookupHandle(ZL_EXP_VOID * VM_ARG, ZL_EXP_VOID * argDefLookupHandle) { ZENGL_VM_TYPE * VM = (ZENGL_VM_TYPE *)VM_ARG; ZENGL_COMPILE_TYPE * compile; ZL_VM_API_DEF_LOOKUP_HANDLE defLookupHandle = (ZL_VM_API_DEF_LOOKUP_HANDLE)argDefLookupHandle; if(VM->signer != ZL_VM_SIGNER) //通过虚拟机签名判断是否是有效的虚拟机 return -1; compile = &VM->compile; compile->def_lookup.lookupHandle = defLookupHandle; return 0; } /** * API接口,在用户自定义的def宏值查询函数中,可以使用该接口来设置查询到的宏值 * valType表示宏值的类型,可以是整数,浮点数或者是字符串类型,valStr表示宏值的字符串形式, * 宏值在编译时都是以字符串的形式进行存储的,因此,即便是浮点数(例如:3.1415926),也需要将浮点数的字符串形式传递过来, * 整数类型也是一样的,也需要将整数的字符串形式传递过来 */ ZL_EXPORT ZL_EXP_INT zenglApi_SetDefLookupResult(ZL_EXP_VOID * VM_ARG, ZENGL_EXPORT_MOD_FUN_ARG_TYPE valType, ZL_EXP_CHAR * valStr) { ZENGL_VM_TYPE * VM = (ZENGL_VM_TYPE *)VM_ARG; ZENGL_COMPILE_TYPE * compile; if(VM->signer != ZL_VM_SIGNER) //通过虚拟机签名判断是否是有效的虚拟机 return -1; compile = &VM->compile; if(!compile->def_lookup.isInLookupHandle) return -1; if(compile->def_lookup.hasFound) return -2; switch(valType) { case ZL_EXP_FAT_INT: compile->def_lookup.token = ZL_TK_NUM; break; case ZL_EXP_FAT_FLOAT: compile->def_lookup.token = ZL_TK_FLOAT; break; case ZL_EXP_FAT_STR: compile->def_lookup.token = ZL_TK_STR; break; default: return -1; break; } compile->def_lookup.valIndex = compile->DefPoolAddString(VM_ARG, valStr); compile->def_lookup.hasFound = ZL_TRUE; return 0; }
为了测试这两个接口,在main.c文件中就定义了main_def_lookup_handle的用户自定义的def宏值查询函数:
/** * 用户自定义的def宏值查询函数,该查询函数会根据查询名称返回对应的宏值 * 例如:def TRUE ___TRUE___; 这个语句,就会调用下面这个函数,并将___TRUE___作为查询名称传递给该函数, * 函数在经过查询后,就会设置整数1作为TRUE的宏值,因此这个语句等效于 def TRUE 1; */ ZL_EXP_VOID main_def_lookup_handle(ZL_EXP_VOID * VM_ARG, ZL_EXP_CHAR * defValName) { if(strcmp(defValName, "___TRUE___") == 0) { zenglApi_SetDefLookupResult(VM_ARG, ZL_EXP_FAT_INT, "1"); } else if(strcmp(defValName, "___FALSE___") == 0) { zenglApi_SetDefLookupResult(VM_ARG, ZL_EXP_FAT_INT, "0"); } else if(strcmp(defValName, "___PI___") == 0) { zenglApi_SetDefLookupResult(VM_ARG, ZL_EXP_FAT_FLOAT, "3.141592653"); } else if(strcmp(defValName, "___TEST_STR___") == 0) { zenglApi_SetDefLookupResult(VM_ARG, ZL_EXP_FAT_STR, "this is a test string by user defined"); } } .............................................................................. /** 用户程序执行入口。 */ int main(int argc,char * argv[]) { .............................................................................. // 设置用户自定义的def宏值查询函数 zenglApi_SetDefLookupHandle(VM, main_def_lookup_handle); .............................................................................. if(zenglApi_Run(VM,argv[1]) == -1) { //编译执行zengl脚本 .............................................................................. } .............................................................................. }
通过main_def_lookup_handle这个查询函数,就可以在zengl脚本的def宏定义中,使用___TRUE___之类的查询名称来表示用户自定义的宏值了。在test_scripts/v1.9.0/目录中还增加了test_def.zl的测试脚本:
use builtin; def TRU ___TRUE___; def FAL ___FALSE___; def PI ___PI___; def STR ___TEST_STR___; print 'TRU: ' + TRU; print 'FAL: ' + FAL; print 'PI: ' + PI; print '2 * PI: ' + (2 * PI); print 'STR: ' + STR;
上面脚本在编译时,会将___TRUE___,___FALSE___,___PI___等作为宏值查询名称,传递给用户自定义的main_def_lookup_handle查询函数,并从查询函数中获取到自定义的宏值。
由于用户自定义的___TRUE___对应的宏值为整数1,因此,本例中,def TRUE ___TRUE___; 就相当于 def TRUE 1; 此外,由于用户自定义的___PI___对应的宏值为3.141592653,因此,本例中,def PI ___PI___; 就相当于 def PI 3.141592653; 等等,以此类推。
上面的test_def.zl脚本的执行结果类似如下:
[root@anonymous linux]# ./zengl test_scripts/v1.9.0/test_def.zl run(编译执行中)... stat cache file: "caches/1_9_0_8_dv01_65ab9795f56d35587529c5f0bdee42c8" failed, maybe no cache file [recompile] TRU: 1 FAL: 0 PI: 3.141592653 2 * PI: 6.283185306 STR: this is a test string by user defined write zengl cache to file "caches/1_9_0_8_dv01_65ab9795f56d35587529c5f0bdee42c8" success run finished(编译执行结束) [root@anonymous linux]#
需要注意的地方是,由于将查询名称转为自定义的宏值的过程是在编译时完成的,因此,一旦zengl脚本完成编译并生成了对应的编译缓存,那么缓存中的宏值就会被固定下来,即便用户自定义的宏值发生了改变,缓存中的宏值也不会变化,也还是原来的值。
可以通过清理缓存的方式来解决这个问题,也可以通过定义一个宏值相关的版本号,然后将该版本号放置到生成的缓存文件名或缓存路径中,这样当用户自定义的宏值发生变化时,只要调整版本号,就可以迫使脚本重新编译生成新的缓存了(因为缓存文件名或缓存路径因版本号而改变了)。
例如,在main.c文件中,就定义了一个和def宏值相关的版本号:
#define DEF_VERSION "dv01" // 针对用户自定义的宏值设置的版本号,该版本号会写入缓存路径中,这样当用户的def查询函数查询到的宏值发生变化时,只要调整版本号,就可以及时更新脚本的缓存了 ............................................................................. /** * 根据full_path脚本路径,得到最终要生成的缓存文件的路径信息 */ static void main_get_zengl_cache_path(char * cache_path, int cache_path_size, char * full_path) { char fullpath_md5[33]; char cache_prefix[30] = {0}; const char * cache_path_prefix = "caches/"; // 缓存文件都放在caches目录中 int append_length; main_compute_md5(fullpath_md5, full_path, ZL_EXP_TRUE, ZL_EXP_TRUE); // 将full_path进行md5编码 // 在缓存路径前面加上zengl版本号和指针长度,不同的zengl版本生成的缓存有可能会不一样,另外,32位和64位环境下生成的内存缓存数据也是不一样的 // 32位系统中生成的缓存数据放到64位中运行,或者反过来,都会报内存相关的错误 sprintf(cache_prefix, "%d_%d_%d_%ld_%s_", ZL_EXP_MAJOR_VERSION, ZL_EXP_MINOR_VERSION, ZL_EXP_REVISION, sizeof(char *), DEF_VERSION); append_length = main_full_path_append(cache_path, 0, cache_path_size, (char *)cache_path_prefix); append_length += main_full_path_append(cache_path, append_length, cache_path_size, cache_prefix); append_length += main_full_path_append(cache_path, append_length, cache_path_size, fullpath_md5); cache_path[append_length] = '\0'; }
上面代码就在zengl的缓存文件名中加入了DEF_VERSION这个和宏值相关的版本号(当前定义的版本号为dv01),因此,生成的会是类似 1_9_0_8_dv01_65ab....... 这样的缓存文件名。如果以后版本号变为dv02时,生成的就会是类似 1_9_0_8_dv02_....... 的缓存文件名了,从而迫使程序去重新编译脚本以生成dv02版本的缓存文件了。
android和zenglOX中和当前版本相关的测试脚本文件名为testdef.zl和testself.zl,也就是少了中间的下划线。这两个系统的测试方法(在Android Studio打开项目的方式,以及在zenglOX 3.2.0中的编译测试方式)请参考zengl v1.8.3相关的文章。
我不管这个世上的人怎么说我,我只想依照我的信念做事,绝不后悔,不管现在将来都一样。
—— 海贼王