四、解析器的部分實作細節
- 先來說說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
,那麼我們如何擷取程式解析成果呢?很簡單,全局變量:
_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;
}
不知道内部機制哈(不是很了解),但是至少這個樣子看起來安全些吧…