v1.9.0的版本可以在class类结构中使用self来表示当前所在类的类名。此外,该版本还增加了zenglApi_SetDefLookupHandle和zenglApi_SetDefLookupResult接口

    页面导航:

项目下载地址:

    zengl language v1.9.0的源代码的相关地址:https://github.com/zenglong/zengl_language  当前版本对应的tag标签为:v1.9.0

zengl 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特殊类名:

    为了演示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属于哪个类了。

自定义def宏值查询函数:

    当前版本在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相关的文章。

    我不管这个世上的人怎么说我,我只想依照我的信念做事,绝不后悔,不管现在将来都一样。

—— 海贼王

 

上下篇

下一篇: zengl v1.9.1 函数参数可以使用负数作为默认值

上一篇: zengl v1.8.3 修复ReUse接口缺陷及段错误,完善语法检测,使用Android Studio开发android项目

相关文章

zengl编程语言v0.0.7

开发自己的编程语言-zengl编程语言v0.0.2初始化抽象语法树

zengl编程语言v0.0.8第二代语法解析函数

zengl v1.2.2 正式发布版及智能采集器源代码

zengl编程语言v0.0.16数组,21点扑克小游戏

zengl v1.2.5 RC4加密,64位系统及Mac系统编译,Api调用位置规范,内建模块函数