有时候希望在lua中加入自定义风格的代码,例如将某个经常用的函数简化为一个符号,在开发某个GAL引擎时,因为需要大量的用到回显文字的命令,所以希望将该命令简化,可以极大地提升写脚本的效率.例如:
//原有脚本实现
echo("这真是一个糟糕的事情","人物1");
echo("这真是一个糟糕的事情","人物2");
echo("这真是一个糟糕的事情","人物3");
echo("这真是一个糟糕的事情","人物4");
...
//修改后的实现
@人物1:"这真是一个不错的事情"
@人物2:"这真是一个不错的事情"
@人物3:"这真是一个不错的事情"
@人物4:"这真是一个不错的事情"
...
这看起来是一件不错的事,^_^想象一下一个文字游戏,基本上都是文字回显的命令,简化后的效率提升不言而喻.
当然,这就要拿lua(遵循GPL协议)的源码开刀,lua的脚本解析主要在llex.c文件中,通过分析每个字符,提取出关键字,符号,变量等传递给lparser.c检查语法.
而在llex.c中最主要的函数是static int llex (LexState *ls, SemInfo *seminfo),该函数负责分割并解析每个WORD,然后返回给语法检查器,我们需要在这里动手将lua中没有定义的符号@解析为echo命令并将两个参数返回.
//翻译并传递echo命令
char *str_echo=0,*str_rolename=0;
int len_echo=0,len_rolename=0;
int trans_echo_command_step = -1;
static int llex (LexState *ls, SemInfo *seminfo) {
luaZ_resetbuffer(ls->buff);
if(trans_echo_command_step!=-1)
{
switch(trans_echo_command_step)
{
case 1: //返回左括号
trans_echo_command_step++;
return 40;
case 2: //返回echo字符串
trans_echo_command_step++;
seminfo->ts = luaX_newstring(ls, str_echo, len_echo);
return TK_STRING;
case 3: //返回逗号
trans_echo_command_step++;
return 44;
case 4: //返回rolename字符串
trans_echo_command_step++;
seminfo->ts = luaX_newstring(ls, str_rolename, len_rolename);
return TK_STRING;
case 5: //返回右括号
trans_echo_command_step=-1;
return 41;
}
}
for (;;) {
switch (ls->current) {
case '\n': case '\r': { /* line breaks */
inclinenumber(ls);
break;
}
case ' ': case '\f': case '\t': case '\v': { /* spaces */
next(ls);
break;
}
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fscript_echo_process */
case '@':
{
TString *ts;
str_echo=0,str_rolename=0;
len_echo=0,len_rolename=0;
next(ls);
if(ls->current != 34) // 如果不是 " 则读取rolename
{
// 读取人名
do
{
save_and_next(ls);
}
while (ls->current != ':');
// 复制人名
str_rolename = luaZ_buffer(ls->buff);
len_rolename = ls->buff->n;
next(ls); // 跳过 :
}
fs_get_string(ls, ls->current);
str_echo = luaZ_buffer(ls->buff) + len_rolename + 1;
len_echo = ls->buff->n - len_rolename - 2;
//第一步返回命令
trans_echo_command_step = 1;
ts = luaX_newstring(ls, "doecho",6);
seminfo->ts = ts;
if (isreserved(ts)) /* reserved word? */
return ts->tsv.extra - 1 + FIRST_RESERVED;
else
return TK_NAME;
}
case '-': { /* '-' or '--' (comment) */
next(ls);
if (ls->current != '-') return '-';
/* else is a comment */
next(ls);
if (ls->current == '[') { /* long comment? */
int sep = skip_sep(ls);
luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */
if (sep >= 0) {
read_long_string(ls, NULL, sep); /* skip long comment */
luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */
break;
}
}
/* else short comment */
while (!currIsNewline(ls) && ls->current != EOZ)
next(ls); /* skip until end of line (or end of file) */
break;
}
case '[': { /* long string or simply '[' */
int sep = skip_sep(ls);
if (sep >= 0) {
read_long_string(ls, seminfo, sep);
return TK_STRING;
}
else if (sep == -1) return '[';
else lexerror(ls, "invalid long string delimiter", TK_STRING);
}
case '=': {
next(ls);
if (ls->current != '=') return '=';
else { next(ls); return TK_EQ; }
}
case '<': {
next(ls);
if (ls->current != '=') return '<';
else { next(ls); return TK_LE; }
}
case '>': {
next(ls);
if (ls->current != '=') return '>';
else { next(ls); return TK_GE; }
}
case '~': {
next(ls);
if (ls->current != '=') return '~';
else { next(ls); return TK_NE; }
}
case ':': {
next(ls);
if (ls->current != ':') return ':';
else { next(ls); return TK_DBCOLON; }
}
case '"': case '\'': { /* short literal strings */
read_string(ls, ls->current, seminfo);
return TK_STRING;
}
case '.': { /* '.', '..', '...', or number */
save_and_next(ls);
if (check_next(ls, ".")) {
if (check_next(ls, "."))
return TK_DOTS; /* '...' */
else return TK_CONCAT; /* '..' */
}
else if (!lisdigit(ls->current)) return '.';
/* else go through */
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
read_numeral(ls, seminfo);
return TK_NUMBER;
}
case EOZ: {
return TK_EOS;
}
default: {
if (lislalpha(ls->current)|| ls->current > 0x80) { /* identifier or reserved word? */
TString *ts;
do {
if(ls->current > 0x80)
{
save_and_next(ls);
save_and_next(ls);
}
else
save_and_next(ls);
}
while (lislalnum(ls->current) || ls->current > 0x80);
ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
luaZ_bufflen(ls->buff));
seminfo->ts = ts;
if (isreserved(ts)) /* reserved word? */
return ts->tsv.extra - 1 + FIRST_RESERVED;
else {
return TK_NAME;
}
}
else { /* single-char tokens (+ - / ...) */
int c = ls->current;
next(ls);
return c;
}
}
}
}
}
加感叹号的地方为解析@符号的地方,要注意的是这里要完全按照lua的流程依次将命令,左右括号,逗号和两个参数都按照顺序传递回去,不然的话语法检查器会认为语法错误!
另外,这里已经添加了中文的支持,想得到中文支持的朋友可以参考第166到177行的代码^_^