下面有一段代码,我们的目标是把这段代码翻译成机器码,首先把它分解成记号:
int ig;
void Fun(int ix,int iy)
{
if(ix>iy)
{
if(10==iy)
{
iy=ix+ig;
}
ix=iy;
}
}
void Fun2()
{
}
分解成的记号如下:
接下来我们要分析这些记号了.
看上面的源码,我用一块灰布把函数体挡住了,也就是说看不到函数体了,那就假装它不存在.发现只有变量定义和函数头,如果想支持更多的话可能还有”define”,”include”,结构体定义...但是决不可能有表达式,if之类的.接下来就开始分析可见部分:
BOOL Parse()
{
_TOKEN *pEnd=NULL;
for(_TOKEN *p=”记号”表头指针;p!=NULL;p=p->pNext){
if(p->iTTypex==TTEX_DT){// 数据类型
if(VarDefine(p,&pEnd)==TRUE){// 是变量定义吗?
p=pEnd; continue;
}
if(FunDeclare(p,&pEnd)==TRUE){// 是函数申明吗?
p=pEnd; continue;
}
if(FunDefine(p,&pEnd)==TRUE){// 是函数定义吗?
p=pEnd; continue;
}
...
return FALSE;
}
else if(...){...}
else return FALSE;
}
return TRUE;
}
VarDefine,FunDeclare,FunDefine三个函数都没具体实现,但是它们的意图是很明显的,就拿VarDefine来说,从p指向的记号节点开始分析,如果确实是变量定义就将变量定义的结束记号也就是”;”的指针填写在pEnd中,并且返回TRUE.如果不是变量定义返回FALSE,把机会让给FunDeclare函数...
注:
1.我总结了某些书看的累的原因:战线拉的太长.读者遇到一个问题可能要看十几页甚至几十页才能找到答案,是跳过去找答案还是继续往下看?如果跳过去,找到了答案,但是往往又有新的问题或者其他原因导致你找答案也不应看懂,只好来来回回只到把该熟悉的都熟悉,该记住的都记住了才能看懂.在没看懂的时候还有可能种种原因不能继续看下去,第2天再看的时候发现前面有东西忘记了,这种书你必须长期看下去了.看到熟透了,基本上才有看懂的希望.
在实现VarDefine等函数之前要明确一下目标:把”记号”翻译成中间代码,取名为”记号码”,用链表存放.下面是”记号码”表的节点定义:
struct _TCODE
{
int iTCType; // 记号码类型
union // 可选成员
{
char *pFunName; // 函数名(翻译成机器码的时候需要)
_EXP *pExp; // 表达式链表头指针(先不管它)
}Option;
_TCODE *pNext; // 下一个记号码
};
“记号码”的种类也是有限的,所以也用宏来表示它的类型.
#define TCT_FUN_ENTRY 1 // 函数入口
#define TCT_FUN_EXIT 2 // 函数出口
#define TCT_IF_CONDITION 3 // if条件
#define TCT_ELSE_IF_CONDITION 4 // else if条件
#define TCT_ELSE 5 // else
#define TCT_IF_END 6 // if结束
#define TCT_ELSE_IF_END 7 // else if结束
#define TCT_ELSE_END 8 // else 结束
#define TCT_LOOP_ENTRY 9 // 循环入口
#define TCT_LOOP_CONDITION 10 // 循环条件
#define TCT_LOOP_EXTI 11 // 循环出口
#define TCT_BREAK 12 // break
#define TCT_CONTINUE 13 // continue
#define TCT_JMP_LOOP_ENTRY 14 // 跳转到循环入口
#define TCT_JMP_LOOP_EXIT 15 // 跳转到循环出口
#define TCT_JMP_ELSE_END 16 // 跳转到else结束
现在就来实现VarDefine等函数,挑选FunDefine函数来实现:
BOOL FunDefine(_TOKEN *pSt,_TOKEN **pEnd)
{
_TOKEN *pEnd=NULL;
_TCODE *pTcode=NULL;
int iRetype=pSt->iTType; // 函数返回值类型
int iRetgrade=0; // 函数返回值指针层数,比如这种函数:int ******Fun()...
int iCall=TT_STDCALL; // 函数调用约定,让他默认为_stdcall
char *pName=NULL;
for(_TOKEN *p=pSt;p->iTType==TT_PS;p=p->pNext) iRetgrade++;//TT_PS:”*”
if(p->iTTypex==TTEX_CALL){iCall=p->iTType; p=p->pNext;}//如果有调用约定,取之
if(p->iTType==TT_NAME){
pName=new char[strlen(p->pName)+1];
strcpy(pName,p->pName);
p=p->pNext;
}else return FLASE;
if(p->iTType==TT_LPAR) p=p->pNext; else {delete []pName;return FALSE;}
pTcode=AddTcode(&记号码头指针,TCT_FUN_ENTRY,pName); // 添加记号码
if(GetParam(p,pTcode,&pEnd)==FALSE){
delete[]pName;returnFALSE;}else p=p->pNext;
if(Match(p,pName,&pEnd)==FALSE){delete []pName; return FALSE;}
AddTcode(&记号码头指针,TCT_FUN_EXIT,pName); // 添加记号码
delete []pName;
return TRUE;
}
注:
1. 这种分析方法很山寨,不过容易理解,先通过这种分析法来了解总体思路,正则表达式方法后面再说
2. 代码写的太挤可读性差,只是是希望能在一页之内写完,遇到问题查找也快
3. 看上对错误检查不够仔细,某些错误是可以留到后面的,最主要的是指针是否为NULL没有检查,没关系,山寨版将会用异常处理中报错然后修改指令指针
上传的附件: