編譯原理解釋器(二)C語言語法分析器的實現


在詞法分析器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文件)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM