先来说说parser
LEX、YACC的部分就不用提了,只用说说C语言相关的支持部分代码
位于include\parser\Expression.h 你会发现很多这个样子的东西:
struct Expression *idexp(char *var);
struct Expression *intexp(int var);
struct Expression *boolexp(unsigned char var);
struct Expression *strexp(char *var);
struct Expression *noneexp(void);
这个是为了语法分析器里的调用封装一层.具体的实现,在parser/Expression.c :
struct Expression *idexp(char *var) {
EXP_FUNC_HEADER(EXPTP_ID)
ret->exp.varName = lexer_scpy(var);
EXP_FUNC_RETURN
}
struct Expression *intexp(int var) {
EXP_FUNC_HEADER(EXPTP_INT)
ret->exp.varInt = var;
EXP_FUNC_RETURN
}
struct Expression *boolexp(unsigned char var) {
EXP_FUNC_HEADER(EXPTP_BOOL)
ret->exp.varBool = var;
EXP_FUNC_RETURN
}
可以看到大同小异:无非每个函数三个部分,头部调用一个宏,尾部调用一个宏,中间设置一个值,仅此而已.两个宏的定义位于文件首
#define EXP_FUNC_HEADER(_tp) struct Expression *ret = malloc(sizeof(struct Expression)); ret->tp = (_tp); ret->ln = ln;
#define EXP_FUNC_RETURN return ret;
分配内存,设定属性,设定行号,结尾再返回.很简单 频繁使用的结构体:
struct Expression {
enum ExpressionTP tp;
int ln;
union {
int varInt;
unsigned char varBool;
char *varString;
char *varName;
struct Expression *sgexp;
struct BinExpression *bin;
struct FunctionCallExpression *fcall;
struct IndexExpression *idx;
struct AttributeExpression *attr;
struct ExpressionList *array;
} exp;
};
emm把他叫做"类"有点过了… 注释写得很清楚,使用的时候,直接调用前文的函数.访问时,根据tp成员的枚举值确定访问exp联合体的哪个成员(其实这里就相当于做了个多态)
一些可能迷惑的地方
关于单向链表这个事,很多坑:
一个基本的链表结构定义
struct ExpressionList {
struct Expression *self;
struct ExpressionList *next;
};
添加元素
struct ExpressionList *creat_explst(struct Expression *exp) {
struct ExpressionList *ret = malloc(sizeof(struct ExpressionList));
ret->self = exp;
ret->next = NULL;
EXP_FUNC_RETURN
}
struct ExpressionList *link_explst(struct ExpressionList *explst, struct Expression *exp) {
struct ExpressionList *ret = creat_explst(exp);
struct ExpressionList *p;
for (p=explst; p->next; p = p->next) {;}
p->next = ret;
return explst;
}
函数1用来创建(分配内存)一个链表项,后者用来连接上去.
遍历链表
for (struct ExpressionList *p=lst; p; p=p->next) ;
注意的问题
明显的差异:添加元素时,for的第二部分写的是p->next ,而遍历的时候必须遍历到p 为空.这里利用的是for的性质:求值表达式1,求值表达式2,求值语句块,求值表达式3的流程 这里可以看出,当程序执行流到语句块的时候,就已经保证表达式2为真,此处即指针非空.添加元素的时候,当next指针为空的时候,直接转跳最后.也就是说我们得保证next指针为空在改next,不然如果覆盖了有所指向的next,就会漏掉最后一个元素,还会造成未知后果. 然而遍历的时候,只需要保证p非空,这样就能访问每一个p.
结果如何返回?
众所周知yyparse() 返回int ,那么我们如何获取程序解析成果呢?很简单,全局变量: _pr的声明位于parser/Parser.c L11 这里注意,L70的处理是经常用到的,如果不加这一句的话,如果脚本是空的,就会报错语法错误,如果有这一项归约条件,但是_pr未归0,调用者就无法得知到底有没有结果,从而冒失地访问_pr造成异常
pcd其实是一回事
来看看语法(直观)
<class_name>
{
mem1;
mem2;
...
memN;
}
{
method1(argc);
method2(argc);
...
methodN(argc);
}
...
空不空行无所谓.缩写都明白吧… 我们可以观察到,pcd的语法分析夹杂了一些奇怪的检查:
fl
: ID LS _INT RS SPL
{
if ($3 > 10) {
printf("warning: number of parameters of method '%s' is too large(%d)\n", $1, $3);
}
$$ = creat_fl($1, $3);
}
| fl ID LS _INT RS SPL
{
if ($4 > 10) {
printf("warning: number of parameters of method '%s' is too large(%d)\n", $1, $4);
}
for (struct MethodList *p=$1; p; p=p->next) {
if (!strcmp(p->name, $2)) {
// 有重定义
printf("error: redefinition of method '%s'.\n", $2);
exit(1);
}
}
$$ = link_fl($1, $2, $4);
}
;
第一个规则和第二个规则头部都有检查函数的参数个数(话说这个纯属无聊哈),考虑到参数一律压栈处理的话呢(x64另当别论),参数太拖确实不好 第二个规则检查是否重定义,遍历链表,然后比较有没有已出现的类名,如果有的话就报错,这个很简单
最后的一点说明
由于咱们特殊的结构呵,解析程序放在动态链接库里面,那么访问全局变量似乎很不合理,如何获取解析结果?函数:
struct ClassDeclareList *gr_pcd(void) {
return pcd_dest;
}
不知道内部机制哈(不是很了解),但是至少这个样子看起来安全些吧…
|