在詞法分析器scanner.h和scanner.c都正確且存在的情況下,加入parser.h和parser.c就可以完成語法分析器!
“parser”是語法分析器。輸入流是“字典”,輸出流是語法樹。
step2
編寫parser.h 代碼如下:
#ifndef PARSER_H #define PARSER_H #include"scanner.h" typedef double (*FuncPtr)(double); struct ExprNode //語法樹節點類型 { enum Token_Type OpCode;//PLUS,MINUS,MUL,DIV,POWER,FUNC,CONST_ID,T union { struct{ExprNode *Left,*Right;}CaseOperater;//二元運算:只有左右孩子的內部節點 struct{ExprNode *Child;FuncPtr MathFuncPtr;}CaseFunc;//函數調用:只有一個孩子的內部節點,還有一個指向對應函數名的指針 MathFuncPtr double CaseConst;//常數:葉子節點 右值 double *CaseParmPtr;//參數T 左值:存放T的值得地址 }Content; }; extern void Parser(char *SrcFilePtr); //語法分析器對外接口 #endif // PARSER_H
step1
插入parser.c
#include"parser.h" #ifndef PARSER_DEBUG #include"semantic.h" #endif // PARSER_DEBUG double Parameter = 0, Origin_x = 0, Origin_y = 0, Scale_x = 1, Scale_y = 1, Rot_angle = 0; static Token token;//記號 static void FetchToken();//調用詞法分析器的GetToken,把得到的當前記錄保存起來。如果得到的記號是非法輸入errtoken,則指出一個語法錯誤 static void MatchToken(enum Token_Type AToken);//匹配當前記號 static void SyntaxError(int case_of);//處理語法錯誤的子程序。根據錯誤的性質打印相關信息並且終止程序運行。錯誤性質可以根據傳參不同來區分:SyntaxError(1)詞法錯 SyntaxError(2)語法錯 static void ErrMsg(unsigned LineNo, char *descrip, char *string);//打印錯誤信息 static void PrintSyntaxTree(struct ExprNode * root, int indent);//前序遍歷打印樹 //非終結符遞歸子程序聲明 有2類 //第1類 //語法分析,不構造語法樹,因此語句的子程序均設計為過程->void類型的函數 static void Program();//遞歸下降分析 static void Statement(); static void OriginStatement(); static void RotStatement(); static void ScaleStatement(); static void ForStatement(); //第2類 //語法分析+構造語法樹,因此表達式均設計為返回值為指向語法樹節點的指針的函數。 static struct ExprNode *Expression();//二元加減 static struct ExprNode *Term();//乘除 static struct ExprNode *Factor();//一元正負 //把項和因子獨立開處理解決了加減號與乘除號的優先級問題。在這幾個過程的反復調用中,始終傳遞fsys變量的值,保證可以在出錯的情況下跳過出錯的符號,使分析過程得以進行下去。 static struct ExprNode *Component();//冪 static struct ExprNode *Atom();//參數T,函數,括號->一個整體的 void enter(char *x) { printf("enter in "); printf(x); printf("\n"); } void back(char *x) { printf("exit from "); printf(x); printf("\n"); } void call_match(char *x) { printf("matchtoken "); printf(x); printf("\n"); } void Tree_trace(ExprNode *x) { PrintSyntaxTree(x, 1); } //外部接口與語法樹構造函數聲明 extern void Parser(char* SrcFilePtr); static struct ExprNode * MakeExprNode(enum Token_Type opcode, ...);//生成語法樹的一個節點 static void FetchToken()//調用詞法分析器的GetToken,把得到的當前記錄保存起來。 { token = GetToken(); if (token.type == ERRTOKEN) SyntaxError(1); //如果得到的記號是非法輸入errtoken,則指出一個語法錯誤 } //匹配當前的記號, static void MatchToken(enum Token_Type The_Token) { if (token.type != The_Token) SyntaxError(2);//若失敗,則指出語法錯誤 FetchToken();//若成功,則獲取下一個 } //語法錯誤處理 static void SyntaxError(int case_of) { switch (case_of) { case 1: ErrMsg(LineNo, " 錯誤記號 ", token.lexeme); break; case 2: ErrMsg(LineNo, " 不是預期記號 ", token.lexeme); break; } } //打印錯誤信息 void ErrMsg(unsigned LineNo, char *descrip, char *string) { printf("Line No %5d:%s %s !\n", LineNo, descrip, string); CloseScanner(); exit(1); } //先序遍歷並打印表達式的語法樹 根!左!右! void PrintSyntaxTree(struct ExprNode *root, int indent) { int temp; for (temp = 1; temp <= indent; temp++) printf("\t");//縮進 switch (root->OpCode)//打印根節點 { case PLUS: printf("%s\n", "+"); break; case MINUS: printf("%s\n", "-"); break; case MUL: printf("%s\n", "*"); break; case DIV: printf("%s\n", "/"); break; case POWER: printf("%s\n", "**"); break; case FUNC: printf("%x\n", root->Content.CaseFunc.MathFuncPtr); break; case CONST_ID: printf("%f\n", root->Content.CaseConst); break; case T: printf("%s\n", "T"); break; default: printf("Error Tree Node !\n"); exit(0); } if (root->OpCode == CONST_ID || root->OpCode == T)//葉子節點返回 return;//常數和參數只有葉子節點 常數:右值;參數:左值地址 if (root->OpCode == FUNC)//遞歸打印一個孩子節點 PrintSyntaxTree(root->Content.CaseFunc.Child, indent + 1);//函數有孩子節點和指向函數名的指針 else//遞歸打印兩個孩子節點 {//二元運算:左右孩子的內部節點 PrintSyntaxTree(root->Content.CaseOperater.Left, indent + 1); PrintSyntaxTree(root->Content.CaseOperater.Right, indent + 1); } } //繪圖語言解釋器入口(與主程序的外部接口) void Parser(char *SrcFilePtr)//語法分析器的入口 { enter("Parser"); if (!InitScanner(SrcFilePtr))//初始化詞法分析器 { printf("Open Source File Error !\n"); return; } FetchToken();//獲取第一個記號 Program();//遞歸下降分析 CloseScanner();//關閉詞法分析器 back("Parser"); return; } //Program的遞歸子程序 static void Program() { enter("Program"); while (token.type != NONTOKEN)//記號類型不是空 { Statement();//轉到每一種文法 MatchToken(SEMICO);//匹配到分隔符 } back("Program"); } //----------Statement的遞歸子程序 static void Statement()//轉到每一種文法 { enter("Statement"); switch (token.type) { case ORIGIN: OriginStatement(); break; case SCALE: ScaleStatement(); break; case ROT: RotStatement(); break; case FOR: ForStatement(); break; default: SyntaxError(2); } back("Statement"); } //----------OriginStatement的遞歸子程序 //eg:origin is (20, 200); static void OriginStatement(void) { struct ExprNode *tmp;//語法樹節點的類型 enter("OriginStatement"); MatchToken(ORIGIN); MatchToken(IS); MatchToken(L_BRACKET);//eg:origin is ( tmp = Expression(); Origin_x = GetExprValue(tmp);//獲取橫坐標點平移距離 DelExprTree(tmp);//刪除一棵樹 MatchToken(COMMA);//eg:, tmp = Expression(); Origin_y = GetExprValue(tmp); //獲取縱坐標的平移距離 DelExprTree(tmp); MatchToken(R_BRACKET);//eg:) back("OriginStatement"); } //----------ScaleStatement的遞歸子程序 //eg:scale is (40, 40); static void ScaleStatement(void) { struct ExprNode *tmp; enter("ScaleStatement"); MatchToken(SCALE); MatchToken(IS); MatchToken(L_BRACKET);//eg:scale is ( tmp = Expression(); Scale_x = GetExprValue(tmp);//獲取橫坐標的比例因子 DelExprTree(tmp); MatchToken(COMMA);//eg:, tmp = Expression(); Scale_y = GetExprValue(tmp);//獲取縱坐標的比例因子 DelExprTree(tmp); MatchToken(R_BRACKET);//eg:) back("ScaleStatement"); } //----------RotStatement的遞歸子程序 //eg:rot is 0; static void RotStatement(void) { struct ExprNode *tmp; enter("RotStatement"); MatchToken(ROT); MatchToken(IS);//eg:rot is tmp = Expression(); Rot_angle = GetExprValue(tmp);//獲取旋轉角度 DelExprTree(tmp); back("RotStatement"); } //----------ForStatement的遞歸子程序 //對右部文法符號的展開->遇到終結符號直接匹配,遇到非終結符就調用相應子程序 //ForStatement中唯一的非終結符是Expression,他出現在5個不同位置,分別代表循環的起始、終止、步長、橫坐標、縱坐標,需要5個樹節點指針保存這5棵語法樹 static void ForStatement() { //eg:for T from 0 to 2*pi step pi/50 draw (t, -sin(t)); double Start, End, Step;//繪圖起點、終點、步長 struct ExprNode *start_ptr, *end_ptr, *step_ptr, *x_ptr, *y_ptr;//指向各表達式語法樹根節點 enter("ForStatement"); //遇到非終結符就調用相應子程序 MatchToken(FOR); call_match("FOR"); MatchToken(T); call_match("T"); MatchToken(FROM); call_match("FROM");// eg:for T from start_ptr = Expression();//獲得參數起點表達式的語法樹 Start = GetExprValue(start_ptr);//計算參數起點表達式的值 DelExprTree(start_ptr);//釋放參數起點語法樹所占空間 eg:0 MatchToken(TO); call_match("TO");// eg:to end_ptr = Expression();//構造參數終點表達式語法樹 End = GetExprValue(end_ptr);//計算參數終點表達式的值 eg:2*pi DelExprTree(end_ptr);//釋放參數終點語法樹所占空間 MatchToken(STEP); call_match("STEP");// eg:step step_ptr = Expression();//構造參數步長表達式語法樹 Step = GetExprValue(step_ptr);//計算參數步長表達式的值 eg:pi/50 DelExprTree(step_ptr);//釋放參數步長語法樹所占空間 MatchToken(DRAW); call_match("DRAW"); MatchToken(L_BRACKET); call_match("(");// eg:draw( x_ptr = Expression();//跟節點 eg:t MatchToken(COMMA); call_match(",");// eg:, y_ptr = Expression();//根節點 MatchToken(R_BRACKET); call_match(")"); DrawLoop(Start, End, Step, x_ptr, y_ptr); //繪制圖形 DelExprTree(x_ptr); //釋放橫坐標語法樹所占空間 DelExprTree(y_ptr); //釋放縱坐標語法樹所占空間 back("ForStatement"); } //----------Expression的遞歸子程序 //把函數設計為語法樹節點的指針,在函數內引進2個語法樹節點的指針變量,分別作為Expression左右操作數(Term)的語法樹節點指針 //表達式應該是由正負號或無符號開頭、由若干個項以加減號連接而成。 static struct ExprNode* Expression()//展開右部,並且構造語法樹 { struct ExprNode *left, *right;//左右子樹節點的指針 Token_Type token_tmp;//當前記號 enter("Expression"); left = Term();//分析左操作數且得到其語法樹 while (token.type == PLUS || token.type == MINUS) { token_tmp = token.type; MatchToken(token_tmp); right = Term();//分析右操作數且得到其語法樹 left = MakeExprNode(token_tmp, left, right);//構造運算的語法樹,結果為左子樹 } Tree_trace(left);//打印表達式的語法樹 back("Expression"); return left;//返回最終表達式的語法樹 } //----------Term的遞歸子程序 //項是由若干個因子以乘除號連接而成 static struct ExprNode* Term() { struct ExprNode *left, *right; Token_Type token_tmp; left = Factor(); while (token.type == MUL || token.type == DIV) { token_tmp = token.type; MatchToken(token_tmp); right = Factor(); left = MakeExprNode(token_tmp, left, right); } return left; } //----------Factor的遞歸子程序 //因子則可能是一個標識符或一個數字,或是一個以括號括起來的子表達式 static struct ExprNode *Factor() { struct ExprNode *left, *right; if (token.type == PLUS) //匹配一元加運算 { MatchToken(PLUS); right = Factor(); //表達式退化為僅有右操作數的表達式 } else if (token.type == MINUS) { MatchToken(MINUS); right = Factor(); left = new ExprNode; left->OpCode = CONST_ID; left->Content.CaseConst = 0.0; right = MakeExprNode(MINUS, left, right); } else right = Component(); //匹配非終結符Component return right; } //----------Component的遞歸子程序 //冪 static struct ExprNode* Component()//右結合 { struct ExprNode *left, *right; left = Atom(); if (token.type == POWER) { MatchToken(POWER); right = Component(); //遞歸調用Component以實現POWER的右結合 left = MakeExprNode(POWER, left, right); } return left; } //----------Atom的遞歸子程序 //包括分隔符 函數 常數 參數 static struct ExprNode* Atom() { struct Token t = token; struct ExprNode *address = nullptr, *tmp; switch (token.type) { case CONST_ID: MatchToken(CONST_ID); address = MakeExprNode(CONST_ID, t.value); break; case T: MatchToken(T); address = MakeExprNode(T); break; case FUNC: MatchToken(FUNC); MatchToken(L_BRACKET); tmp = Expression(); address = MakeExprNode(FUNC, t.FuncPtr, tmp); MatchToken(R_BRACKET); break; case L_BRACKET: MatchToken(L_BRACKET); address = Expression(); MatchToken(R_BRACKET); break; default: SyntaxError(2); } return address; } //----------生成語法樹的一個節點 static struct ExprNode *MakeExprNode(enum Token_Type opcode, ...) { struct ExprNode *ExprPtr = new(struct ExprNode); ExprPtr->OpCode = opcode;//接收記號的類別 va_list ArgPtr;//聲明一個轉換參數的變量 va_start(ArgPtr, opcode); //初始化變量 switch (opcode)//根據記號的類別構造不同的節點 { case CONST_ID://常數 ExprPtr->Content.CaseConst = (double)va_arg(ArgPtr, double);//右值 break; case T://參數 ExprPtr->Content.CaseParmPtr = &Parameter;//左值:地址 break; case FUNC://函數調用 ExprPtr->Content.CaseFunc.MathFuncPtr = (FuncPtr)va_arg(ArgPtr, FuncPtr);//指向對應函數名的指針 MathFuncPtr ExprPtr->Content.CaseFunc.Child = (struct ExprNode*)va_arg(ArgPtr, struct ExprNode *);//孩子的內部節點 break; default://二元運算節點 ExprPtr->Content.CaseOperater.Left = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *); ExprPtr->Content.CaseOperater.Right = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *); break; } va_end(ArgPtr);//結束變量列表,和va_start成對使用 return ExprPtr; }
step3
插入parsermain代碼
#include<stdio.h> #include<stdlib.h> extern void Parser(char *SrcFilePtr);//測試主程序 void main() { Parser("test0.txt");//調用parser進行語法分析,其中被測試的語句在test.txt中 system("pause"); } //通過更改的Parser()函數參數來改變要讀取的文件(在yufa>parsertest目錄下可看到0-5 6個txt文件)