天天看點

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

不知道内部機制哈(不是很了解),但是至少這個樣子看起來安全些吧…

繼續閱讀