小话游戏脚本(三)

小话游戏脚本(三)

三.heScript的一种简单实现

在此就heSript实现过程中的一些解决方案和自己的想法陈列一番,由于自己编程水平实在拙劣,又没什么实际经验,所以导致相关的代码非常糟糕,所以竭诚欢迎大家批评指正,在下先拜谢了:D ( PS:以下代码均使用MinGW3.4.2进行编译,使用IDE为Dev-C++4.9.9.2,由于代码中使用了一些C++的新近标准,所以在VC6中不能正确编译,而在VC7.1及VC8中的编译问题则未有试验 )

.一开始我先定义了一个简单的错误处理模块,用于处理程序运行过程中的各种异常,并且据此定义了一个为方便使用的宏 THROW,其中代码相当简单,有意者可参见示例程序中的 heException.h文件 :)

.接着我编写了一个相当简单的词法分析器,用以将读入的文本字符转换成词法单元(Token),目的是为接下来的编译操作打下基础,相应的头文件粗列如下:

//预定义的词法属性枚举值

enum heToken

{

TOKEN_INCLUDE = 0,

TOKEN_VAR,

TOKEN_CONST,

TOKEN_BLOCK,

TOKEN_ENDBLOCK,

TOKEN_INT,

TOKEN_STRING,

TOKEN_IDENTIFY,

TOKEN_END,

TOKEN_FORCE32 = 0xFFFFFFFF

};

class heLexParser

{

public:

heLexParser( const char* filename );

~heLexParser();

heToken GetNextToken();

void ReadToken( heToken token );

int GetIntVal() const;

const string& GetStrVal() const;

private:

bool isNum( char lexChar );

bool isIdentify( char lexChar );

fstream m_fs;//文件流

static const int MAX_LINE_COUNT = 512;

static const int MAX_STRING_COUNT = 256;

static const int MAX_NAME_COUNT = 32;//最长的 函数及参数 名字

char m_buffer[MAX_LINE_COUNT];//用以暂存脚本内容

char m_value[MAX_NAME_COUNT];//存储真实的 Token 数据

int m_intVal;//用以记录返回的整数值

string m_strVal;//用以记录返回的字符串值

};

其中的代码实现相当简单,有意者可以参见示例程序中的heLexParser.h/cpp文件,其中对于词法属性的解析( GetNextToken() )比较杂乱,正统并且更具扩展性的做法是使用有限状态机 :)

.接着便该是编译模块了,由于heScript的设计相对简单,所以我编写了heScript这个类来执行编译工作以及运行编译后的脚本代码,当然,在编写编译执行模块之前,我必须首先定义好脚本的编译码格式,经过几番的修改,现在的情况如下(有意者请参看heScriptType.h文件):

const int BAD_PARAM_VALUE = -1;

//参数

union Param

{

int iValue;

const char* cString;

};

//参数链表

struct ParamList

{

Param param;

ParamList* pNext;

ParamList():pNext(NULL) { param.iValue = BAD_PARAM_VALUE; }

};

//指令

struct Operation

{

#ifdef HEDEBUG

friend ostream& operator << ( ostream& o, const Operation& op );

#endif

OpCode opCode;

int paramCount;

ParamList paramList;

~Operation();//处理内存的管理

};

//指令流

typedef std::list<Operation> OpStream;

接着为了便于管理脚本中出现的各类数据,我分别编写了很多表类,相关的头文件都比较简单,现分列如下(实现代码可以参见相关的cpp文件):

//为了处理Include重复文件的问题,编写了以下的类

class heIncludeTable

{

public:

void Add( const string& str );

bool Get( const string& str ) const;

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

set<string> m_table;

};

//常量表

typedef Pair< int, bool > CstValRet;//此处的Pair为自定义类型,参见这里,下同

class heConstValueTable

{

public:

void Add( const string& str, int value );

CstValRet Get( const string& str ) const;

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

map<string, int> m_table;

};

//字符串表

class heStringTable

{

public:

const char* Add( const string& str );

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

vector<string> m_table;

};

//变量表

const int BAD_VAR_INDEX = -1;

typedef Pair<int,bool> VarValRet;

class heVarTable

{

public:

void Add( const string& var, int val );

VarValRet Get( int index ) const;

int GetDir( int index ) const;

VarValRet Get( const string& var ) const;

int GetIndex( const string& var ) const;

void SetVar( int varIndex, int val );//用以设定变量的值

void AddVar( int varIndex, int delta );//用以改变变量的值

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

map<int,int> m_table;

map<string,int> m_indexTable;

static int c_varCount;

};

//模块表

const int BAD_BLOCK_INDEX = -1;

typedef Pair< OpStream*, bool > BlkRet;

class heBlockTable

{

public:

void Add( const string& blockName, const OpStream& blockOp );

BlkRet Get( int index );

const OpStream& GetDir( int index ) const;

BlkRet Get( const string& blockName );

int GetIndex( const string& blockName ) const;

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

map<int,OpStream> m_table;//用以存储block指令流

map<string,int> m_indexTable;

static int c_blockCount;

};

//以下的函数管理,感觉在数据表示上有些冗余:)

typedef void (*PFunc)( const ParamList& paramList );

class heFuncManager

{

public:

//注册自定义函数

void Register( const string& funcName, int paramCount, PFunc pFunc );

PFunc GetFunc( int funcIndex ) const;

int GetIndex( const string& funcName ) const;

int GetParamCount( int funcIndex ) const;

#ifdef HEDEBUG

void Show( ostream& o ) const;

#endif

private:

std::map<int,PFunc> m_funcs;//整数 函数指针 一一映射

std::map<string,int> m_funcsName;//函数名 整数 一一映射

std::map<int,int> m_funcsParam;//整数 参数个数 一一映射

static int c_funcCount;

};

关于Pair类型的定义说明:

template<typename T1,typename T2>

struct Pair

{

Pair( const T1& t1, const T2& t2 );

Pair():first(T1()),second(T2()) {};

T1 first;

T2 second;

};

template<typename T1,typename T2>

Pair<T1,T2>::Pair<T1,T2>( const T1& t1, const T2& t2 ):first(t1),second(t2) {};

好了,简绍完这些辅助模块,该是重头戏编译登场了:)由于相关的代码比较繁琐,这里便暂列出几个核心的函数用以说明:

void parseCode( heLexParser& lexParser, OpStream& opStream );

void parseInclude( heLexParser& lexParser, OpStream& opStream );

void parseVar( heLexParser& lexParser, OpStream& opStream );

void parseConst( heLexParser& lexParser, OpStream& opStream );

void parseBlock( heLexParser& lexParser, OpStream& opStream );

void parseCommand( heLexParser& lexParser, OpStream& opStream );

解析过程中,我使用了递归下降的方法,因为感觉这很符合人类的思维习惯,当然,更好的做法可能是使用自动化工具,如Yacc,另外进一步的信息可以点这里

接着便只剩下运行了,经过一番摸索,还是使用堆栈最为稳定,所以我定义了如下的两个运行时堆栈:

stack<const OpStream*> m_bkStack;//当前执行的指令流

stack<OpStream::const_iterator> m_opStack;//当前执行的指令

而用于执行指令的函数总相对简单:

void Run( int begOp = 0 );//对外接口

void run();//运行所有指令

void runSingle();//运行单条指令

一切搞定之后,我们就可以简单的编写一个测试程序用以执行上面的实例脚本代码了:)

四.小小的一番总结

也算是花了不少的时间,我胡侃了一番游戏脚本,其中的内容着实一般,希望大家不要耻笑,高手直接无视便可,写这些东西的初因也是为了自己更好的学习,也没有任何传道授业解惑的意思,至于编写上面的那点程序也仅仅是完成自己的一个喜好,顺便也练练自己那双拙笨的双手,就实用性角度而言,我绝不认为白手起家重新构建一门脚本语言是一种明智之举,毕竟几经完备、备受考验的脚本语言并不匮乏,如Lua、Python、Ruby等等都是一流的脚本语言,自己实现脚本,除了纯粹用以提高自己的水平或是工程所迫以外,没有什么其他好处,所以需要实事求是的看待,总之还是那句老话:除非深思熟虑,不要重造车轮 :)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

velocity模板引擎学习(1)

velocity与freemaker、jstl并称为java web开发三大标签技术,而且velocity在codeplex上还有.net的移植版本NVeloc...

22850
来自专栏芋道源码1024

Dubbo 源码解析 —— 简单原理、与spring融合

前言 结束了集群容错和服务发布原理这两个小专题之后,有朋友问我服务引用什么时候开始,本篇为服务引用的启蒙篇.之前是一直和大家一起看源码,鉴于Talk is ch...

37440
来自专栏web编程技术分享

【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第五节)

482110
来自专栏JackieZheng

Java豆瓣电影爬虫——减少与数据库交互实现批量插入

  节前一个误操作把mysql中record表和movie表都清空了,显然我是没有做什么mysql备份的。所以,索性我把所有的表数据都清空的,一夜回到解放前……...

36370
来自专栏腾讯Bugly的专栏

小萝莉说Crash(一):Unrecognized selector sent to instance xxxx

大家好,我是来自Bugly Crash实验室的小萝莉(害羞ing),很高兴能和大家一起讨论关于移动终端App的Crash问题及解决方法。 在上次的“精神哥讲Cr...

89840
来自专栏决胜机器学习

设计模式专题(二十) ——职责链模式

设计模式专题(二十)——职责链模式 (原创内容,转载请注明来源,谢谢) 一、概述 职责链模式(Chainof Responsibility),是使多个对象都有...

35690
来自专栏挖掘大数据

整合Kafka到spark-streaming实例

在这篇文章里,我们模拟了一个场景,实时分析订单数据,统计实时收益。

2.4K90
来自专栏AhDung

【VBS】vbs指定编码保存文本文件(含xml、ini什么的)

- 让用户填写一些信息,待安装完成后把这些信息写入软件安装目录中的指定ini、xml文件中

12610
来自专栏互联网杂技

重新认识javascript的settimeout和异步

今晚看到QLeelulu的一道JavaScript面试题(setTimeout),稍微想了一下,好不容易连猜带蒙,凑巧说对了答案。但是原因到底是什么呢?自己一时...

34690
来自专栏章鱼的慢慢技术路

浅谈单片机中C语言与汇编语言的转换

47330

扫码关注云+社区

领取腾讯云代金券