类是面向对象编程里的一个很常见的概念,所谓类就是一组数据成员和方法(即函数)的集合。类的出现很大程度上提高了代码的重利用率。本节v0.0.23版 本实现了一个简单的类,类里面只有数据成员,...

    类是面向对象编程里的一个很常见的概念,所谓类就是一组数据成员和方法(即函数)的集合。类的出现很大程度上提高了代码的重利用率。本节v0.0.23版 本实现了一个简单的类,类里面只有数据成员,还没有类方法,相当于C语言的结构体。另外还引入了SDL游戏引擎模块,最后使用SDL模块里的函数创建了一 个小的图形界面的游戏。

    本节v0.0.23版本的源代码的下载地址为:http://pan.baidu.com/share/link?shareid=218459&uk=940392313  (此为百度云盘的共享链接地址) ,访问该地址可以看到三个文件:zengl_lang_v0.0.23_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.23_forLinux.tar.gz  (Linux系统下的源代码和makefile) ,v0.0.23-v0.0.22-diffs.txt  (v0.0.23和v0.0.22两个版本之间的代码变化情况)。

    SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/   从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.23_forXP.rar zengl_language_v0.0.23_forLinux.tar.gzv0.0.23-v0.0.22-diffs.txt

   
先来看下本版本的描述(在linux代码包里的usage.txt里有这段描述,在目前几个带有git版本的....-diffs.txt和git log中也有这段描述):

    v0.0.23版本,该版本实现了SDL(2D游戏引擎)的库函数,添加了负数,八进制,十六进制,类的定义,类的声明及类的使用。
   
    sdlLib.c和sdlLib.h是SDL库函数的实现过程。结构和builtin.c,builtin.h的结构差不多,里面有SDL库的初始化,窗 口创建,加载位图,显示位图等函数。具体使用方法可以参考test_sdl_class.zl(该程式使用了class类来代替以前的数组的写法)。
    以前的版本无法表示负数,现在可以在一个数字前添加单一的减号来表示负数,不过,当负数和其他数在一起运算时,要将数字和数字前的减号用括号括起来,以防 止出现优先级抢夺的问题,例如:-10*5此时没加括号,则会先乘运算再减法运算,所以最好是(-10)*5,这样先减运算得到负数,再乘运算,虽然这里 的结果应该是一样的,但当表达式复杂时,不加括号结果就可能不对了。
    另外在main.c的getToken里添加了八进制和十六进制的token的解析,以0e开头的是八进制,e是eight英文8的首字母,0x开头的是 十六进制,十六进制的写法跟c语言和php中的是一样的,只有八进制的写法需要注意。main.c中当获取到八进制的token时会调用 convertOctalToDec函数将八进制转为十进制的token,当获取到十六进制的token时,会调用convertHexToDec将十六 进制转为十进制的token。在十进制整数解析时会调用convertStrToDec将超过2147483647的数以负数的形式保留下来,如 4294967295以-1的形式写入汇编代码中,之所以这么做是因为在run.c运行的时候,会调用atoi函数将超过整数最大值的数转为 2147483647,这样结果就不对,而以负数的补码形式保存下来的话,在计算机内存里的值不会发生变化,可以通过GetUIntStr builtin的库函数来将负数以uint无符号整形的形式还原显示出来。
    最后本版本实现了一个最基本的类的定义,声明和使用,目前的类只有成员,没有方法即类的函数。所以本质上相当于c语言的结构体。
    类定义格式是class 类名 类成员;类成员;... endcls这样的格式,在使用前要用类名 变量名;的格式来声明。目前类的声明要单独一条语句,且只能是类名 变量名加分号的格式,以后如有需要会将声明的格式进行扩展。
    类的使用方法以点来访问类的成员如user.poker.point。也可以和数组连用,如:user.pokerlist[i].point的形式等。关于类的使用方法可以参考test_sdl_class.zl。
    类的本质其实就是数组,例如:user.name,如果name是user类的第一个成员则相当于user[0],所以可以用user[0]来代替user.name 。
    类的实现原理:在类的定义时将类的名称,类ID(编译器生成的类的唯一标识),类成员信息加入到hash表中,在类的声明时,在变量的全局或局部hash 表中为变量添加类id信息,当通过点运算符访问某成员时,就在类成员的hash表中搜索到该成员的索引值,最后以数组的汇编形式写入汇编代码中。
   
    其他的详情请用git log -p 或 gitk等软件来查看。
   
    作者:zenglong
    时间:2012年5月29日
    官方网站:www.zengl.com

    本节的测试脚本test_sdl_class.zl里使用了新加入的SDL游戏引擎模块,创建了一个可以控制人物精灵四处移动的小游戏。windows下的游戏截图如下

    test_sdl_class.zl在Linux下运行时的截图如下:

    test_sdl_class.zl脚本代码如下:

use builtin,sdl; //加载内建模块和SDL游戏引擎模块

def TRUE 1;
def FALSE 0;

//sdl event def SDL游戏引擎事件相关的宏定义
def NoEvent 0;
def EvKeyDown 2;
def EvKeyUp 3;
def KeyEsc 27;
def KeyUp 273;
def KeyDown 274;
def KeyLeft 276;
def KeyRight 275;
def EvQuit 12;
//sdl event def end

def WinWidth 640; //整个游戏窗口的宽
def WinHeight 480; //游戏窗口的高
def spWidth 32; //人物精灵位图的宽
def spHeight 64; //人物精灵位图的高
def Speed 7; //人物精灵的移动速度

class clsPosition //和绘制矩形有关的类定义
    x; //左上角x坐标
    y; //左上角y坐标
    w; //矩形宽
    h; //矩形高
endcls //类定义结束

class clsSpirit //和人物精灵相关的类定义
    image; //人物精灵的位图指针成员
    clsPosition src; //将人物精灵绘制到游戏表面时要用到的绘制源矩形
    clsPosition dest; //绘制目标矩形
endcls

class clsFont //和字体相关的类定义
    obj; //SDL打开的字体对象指针
    clsPosition src; //绘制字体到游戏表面时要用到的绘制源矩形
    clsPosition dest; //绘制目标矩形
endcls

class clsEvent //和用户事件相关的类定义,如按键事件,消息循环事件,关闭按钮事件,鼠标事件等。
    type; //事件类型
    keytype; //按键类型
endcls

clsSpirit spirit; //声明一个clsSpirit类型的全局变量spirit代表游戏中的人物精灵
clsFont font; //声明一个clsFont类型的全局变量font代表字体
clsEvent event; //声明事件全局变量event

sdlInit(); //SDL游戏初始化

screen = sdlCreateWin(WinWidth,WinHeight,32,0); //创建一个游戏窗口,返回游戏的屏幕指针或者叫表面指针
sdlSetCaption('ZENGL SDL 测试 按上下左右键移动人物 ,按ESC键退出 官网:www.zengl.com'); //设置游戏的窗口标题
bkimg = sdlLoadBmp('bkimg.bmp'); //加载bkimg.bmp背景位图
spirit = array(sdlLoadBmp('foo2.bmp',0,0xff,0xff),
            array(0,0,spWidth,spHeight),array(0,0,spWidth,spHeight)); //加载foo2.bmp人物精灵位图
sdlFontInit(); //字体全局初始化
font = array(sdlOpenFont('lazy.ttf',28),
            array(0,0,0,0),array(100,100,0,0)); //加载lazy.ttf字体文件
fontcolor = array(0xff,0,0); //设置字体的前景色为红色。
bgcolor = array(0,0,0); //设置字体的背景色为黑色。

event = array(); //将事件通过array函数初始化
gameover = FALSE; //判断游戏是否结束的全局变量
for(;!gameover;)
    time = sdlGetTicks(); //获取游戏的滴答数,用于设置帧数的。
    for(;sdlPollEvent(event)!=NoEvent;)
        switch(event.type)
        case EvQuit: //如果点了游戏窗口的关闭按钮时,设置gameover
            gameover = TRUE;
            break;
        case EvKeyDown: //如果按下了ESC键时,也gameover
            switch(event.keytype)
            case KeyEsc:
                gameover = TRUE;
                break;
            endswt
            break;
        endswt
    endfor
    sdlGetKeyState(event); //获取用户的按键数组
    if(event[KeyUp] && spirit.dest.y > 0) //如果按下了上键,则根据精灵当前的位置的y坐标减去速度值,得到新的y坐标。当然y值不能小于0,小于0就到窗口外了。
        spirit.dest.y -=Speed;
    endif
    if(event[KeyDown] && spirit.dest.y < WinHeight-spHeight) //按下键时则人物向下移动
        spirit.dest.y +=Speed;
    endif
    if(event[KeyLeft] && spirit.dest.x >0) //按下左键则左移
        spirit.dest.x -=Speed;
    endif
    if(event[KeyRight] && spirit.dest.x <WinWidth-spWidth) //按右键则右移
        spirit.dest.x +=Speed;
    endif
    sdlDelay(10); //设置10毫秒延时。
    sdlSetBkImg(bkimg); //设置游戏背景图片
    sdlBlitImg(spirit.image,spirit.src,screen,spirit.dest); //将人物精灵绘制到游戏表面
    time = sdlGetTicks() - time; //将当前滴答数减去上一次统计的滴答数,得到一帧经过的毫秒数
    text = sdlDrawText(font.obj,'FPS:'+1000/time + 'f/s',fontcolor,bgcolor); //绘制帧数的字符串位图
    sdlBlitImg(text,0,screen,font.dest); //将帧数的字符串位图绘制到游戏表面
    sdlFreeImage(text); //释放掉字符串位图
    sdlShowScreen(); //将绘制的游戏表面输出到显卡显示出来。
endfor

sdlCloseFont(font.obj); //关闭前面打开的字体
sdlFontQuit(); //释放字体系统资源
sdlQuit(); //释放SDL系统资源并退出

    上面的注释在原文件中都有。在linux代码包的makefile文件里有个地方需要注意,下面是makefile文件的代码片段:

#makefile for zengl language

CC = gcc

CFLAGS = -g3 -ggdb -O0

SDL_LIB_CFLAGS_H = -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT

SDL_LIB_CFLAGS_LIB = -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lSDL_ttf -lpthread
.......................................   //省略N行代码

    从上面可以看出,makefile中增加了和SDL游戏引擎相关的头文件和链接库文件相关的配置(由红色标注的部分),这部分就是告诉编译器SDL游戏引擎的头文件和链接库文件所在位置。这些信息在linux下可以使用sdl-config命令得到。
下面是我的slackware系统打印出来的SDL配置信息:

    至于SDL在linux下的安装方法,可以参考lazyfoo.net ,这个网站有SDL开发的详细入门教程,他的第一课就是教大家如何在windows系统下和linux系统下安装sdl ,在linux下安装完SDL后,正常情况下就可以调用上图中的sdl-config命令打印配置信息,请根据自己的配置信息对应修改makefile文 件里的SDL_LIB_CFLAGS_HSDL_LIB_CFLAGS_LIB
    windows下的代码包里已经将SDL的VC开发包加入到压缩包里了:

    上图中的SDL-1.2.15就是SDL 1.2.15版本的VC开发目录,里面包含了所需的头文件和链接库文件,并且在VS2008的zenglrun的项目属性里也使用相对路径的方式将附加包 含目录指向了SDL-1.2.15里的include目录,将附加库目录指向了SDL-1.2.15里的lib下的x86目录,还设置了链接附加依赖性为 SDL.lib SDLmain.lib SDL_ttf.lib SDL_image.lib ,所以只要有VS2008的开发环境,直接解压运行项目即可。

    因为zengl_lang_v0.0.23项目里加入了SDL所需的DLL动态链接库如SDL.dll , SDL_image.dll , SDL_ttf.dll等,所以本节的XP的压缩包有2M多,比之前的版本要大。

    最后还是老生常谈的话题:
    windowsXP压缩包中的代码
都是采用GBK的编码,Linux压缩包中的代码以及git里的信息都是UTF8的编码,
还有本节的测试脚本test_sdl_class.zl是采用UTF8的编码的,XP和linux下都是一样,因为SDL游戏引擎只支持UTF8的汉字显示,所以如果哪些地方出现了乱码,请自行调整。

    对于windows用户,请确保在项目属性的配置里,命令行参数配置的是test_sdl_class.zl(对于zengl_lang_v0.0.23的项目)或
test_sdl_class.zlc(对于zenglrun的项目)。另外工作目录设置为../zengl_lang_v0.0.23  ,这样在zengl_lang_v0.0.23下修改了.zl脚本并重新生成.zlc后,就无需再将.zlc拷贝到zenglrun项目下了。

    另外对于vs2008的用户,我在项目属性里:[配置属性>>>>C/C++ >>>> 高级] 部分设置了禁用特定警告:4013,4715,4996 ,这几个警告会显示一些某某函数是非安全的函数,或者函数没有返回值等,这里禁用掉,防止出现过多的警告。另外还有个警告是显示某某变量没被使用过的, 这个警告我没禁用,可以不用管它。我最开始是在Linux系统中使用eclipse+CDT插件以及gcc等开发的zengl ,在我的GCC下面并没有显示过这些讨厌的警告,所以就没处理,不过还好这些警告都无关痛痒,无需理会。
    还有一个地方:VS2008项目中,在[配置属性>>>> C/C++ >>>> 预处理器] 部分都设置了预处理器定义的宏:OS_IN_WINDOWS,另外在zenglrun开启命令行调试配置中还增加了__ZENGL_DEBUG的宏,主要针对zenglrun的项目 ,之所以需要OS_IN_WINDOWS,是因为源代码既要在WINDOWS下编译,又要在LINUX下编译,所以需要通过这个宏来告诉编译器当前的环境 是windows还是linux,在 windows下面,在程序结束时会执行system ("pause");这条语句(vs2008下为了能看到结果,需要暂停,否则就一闪而过,什么都看不到咯。) 而linux系统主要在bash终端下执行,不需要这条语句,而__ZENGL_DEBUG宏主要用来判断是否需要在zenglrun的目标程序中加入调 试代码。

    linux系统下的用户请结合usage.txt的说明,先运行make clean 将原来生成的zengl zenglrun zengldebug和 main.o parser.o assemble.o ld.o func.o run.o debug.o symbol.o builtin.o sdlLib.o文件
删除。

    再运行make all (单纯的make只能生成zengl,所以需要make all来生成所有的目标)

    生成zengl zenglrun
zengldebug 和 main.o parser.o assemble.o ld.o func.o run.o debug.o symbol.o  builtin.o sdlLib.o。(在生成过程中如果出现一些警告,暂不管他)

    最后运行 ./zengl test_sdl_class.zl
查看printASTnodes函数打印抽象语法树节点的结果,以及符号表输出的变量信息以及函数信息还有
类定义和类成员信息等。(例如变量的内存地址,以及在源文件的行列号,函数的唯一标识ID等)。
    接着运行./zenglrun test_sdl_class.zlc

    如果要调试脚本则使用./zengldebug test_sdl_class.zlc(注意是.zlc结尾的文件名,因为zenglrun虚拟机和zengldebug调试器只能运行.zlc里的汇编代码)。

    zengl语言涉及到的很多高级的编译原理都可以在《龙书》中找到。

    最后的最后,如果转载请注明来源 http://www.zengl.com   , OK , 先到这里,休息,休息一下 O(∩_∩)O~

上下篇

下一篇: zengl编程语言v0.0.24,SDL捕获鼠标事件,BUG修复

上一篇: zengl编程语言v0.0.22实现switch...case

相关文章

zengl v1.2.0 嵌入式编程语言

zengl编程语言v0.0.17单行多行注释

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

zengl v1.8.2 修复内存泄漏

zengl编程语言v0.0.18初始化数组函数

zengl v1.7.3 获取数组之类的内存块中非NONE成员的数量