天天看点

DOG(4):解析器的部分实现细节先来说说parser一些可能迷惑的地方结果如何返回?pcd其实是一回事最后的一点说明

四、解析器的部分实现细节

  • 先来说说parser
  • 一些可能迷惑的地方
    • 一个基本的链表结构定义
    • 添加元素
    • 遍历链表
    • 注意的问题
  • 结果如何返回?
  • pcd其实是一回事
  • 最后的一点说明

先来说说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;					// ID
		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的第二部分写的是

p->next

,而遍历的时候必须遍历到

p

为空.这里利用的是for的性质:求值表达式1,求值表达式2,求值语句块,求值表达式3的流程

这里可以看出,当程序执行流到语句块的时候,就已经保证表达式2为真,此处即指针非空.添加元素的时候,当next指针为空的时候,直接转跳最后.也就是说我们得保证next指针为空在改next,不然如果覆盖了有所指向的next,就会漏掉最后一个元素,还会造成未知后果.

然而遍历的时候,只需要保证p非空,这样就能访问每一个p.

结果如何返回?

众所周知

yyparse()

返回

int

,那么我们如何获取程序解析成果呢?很简单,全局变量:

DOG(4):解析器的部分实现细节先来说说parser一些可能迷惑的地方结果如何返回?pcd其实是一回事最后的一点说明

_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;
}
           

不知道内部机制哈(不是很了解),但是至少这个样子看起来安全些吧…

继续阅读