zoukankan      html  css  js  c++  java
  • 编译原理解释器(二)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("
    ");
    }
    void back(char *x)
    {
        printf("exit from   "); printf(x); printf("
    ");
    }
    void call_match(char *x)
    {
        printf("matchtoken    "); printf(x); printf("
    ");
    }
    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 !
    ", LineNo, descrip, string);
        CloseScanner();
        exit(1);
    }
    
    //先序遍历并打印表达式的语法树  根!左!右!
    void PrintSyntaxTree(struct ExprNode *root, int indent)
    {
        int temp;
        for (temp = 1; temp <= indent; temp++)
            printf("	");//缩进
        switch (root->OpCode)//打印根节点
        {
        case PLUS:  printf("%s
    ", "+"); break;
        case MINUS: printf("%s
    ", "-"); break;
        case MUL:   printf("%s
    ", "*"); break;
        case DIV:   printf("%s
    ", "/"); break;
        case POWER: printf("%s
    ", "**"); break;
        case FUNC:  printf("%x
    ", root->Content.CaseFunc.MathFuncPtr); break;
        case CONST_ID:   printf("%f
    ", root->Content.CaseConst); break;
        case T:     printf("%s
    ", "T"); break;
        default:    printf("Error Tree Node !
    "); 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 !
    ");
            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文件)
  • 相关阅读:
    [课程设计]Scrum 1.6 多鱼点餐系统开发进度
    [课程设计]Scrum 1.7 多鱼点餐系统开发进度
    [课程设计]Scrum 1.5 多鱼点餐系统开发进度
    [课程设计]Scrum 1.4 多鱼点餐系统开发进度
    [课程设计]Scrum 1.3 多鱼点餐系统开发进度
    [课程设计]Scrum 1.2 Spring 计划&系统流程&DayOne燃尽图
    [课程设计]Scrum 1.1 NABCD模型&产品Backlog
    [课程设计]Scrum团队分工及明确任务1.0 ----多鱼点餐
    学习进度条
    [课程设计]多鱼点餐系统个人总结
  • 原文地址:https://www.cnblogs.com/olivegyr/p/6292604.html
Copyright © 2011-2022 走看看