zoukankan      html  css  js  c++  java
  • <编译原理

    <编译原理 - 函数绘图语言解释器(2)语法分析器 - python>

    背景

    • 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现

    • 设计思路:

      1. 设计函数绘图语言的文法,使其适合递归下降分析;

      2. 设计语法树的结构,用于存放表达式的语法树;

      3. 设计递归下降子程序,分析句子并构造表达式的语法树;

      4. 设计测试程序和测试用例,检验分析器是否正确。

    • 消除无二义/无左递归完整的EBNF文法:

    • 表达式的语法树:

    • 用Pycharm写了三个.py文件:
      • parserclass.py

      • parserfunc.py

      • parsermain.py

      • 输入流是词法分析器得到的记号流,输出流是语法树

      • 测试文本序列(1)

    FOR T FROM 0 TO 2*PI STEP PI/50 DRAW(COS(T),SIN(T));
    
    + 测试文本序列 (2)
    
    //------------------This is zhushi!!------------------------
    ORIGIN IS (100,300);                        // Sets the offset of the origin
    ROT IS 0;                                   // Set rotation Angle.
    SCALE IS (1,1);                             // Set the abscissa and ordinate scale.
    FOR T FROM 0 TO 200 STEP 1 DRAW (T,0);      // The trajectory of the x-coordinate.
    FOR T FROM 0 TO 150 STEP 1 DRAW (0,-T);     // The trajectory of the y-coordinate.
    FOR T FROM 0 TO 120 STEP 1 DRAW (T,-T);     // The trajectory of the function f[t]=t.
    

    Step 1 :parserclass.py - 构造语法树节点类

    import scannerclass as sc
    
    
    class ExprNode(object):     ## 语法树节点类
        def __init__(self,item):        ## item 表示符号类型Token_Type
            self.item = item #表示对应的元素
            if self.item == sc.Token_Type.PLUS or self.item == sc.Token_Type.MINUS or 
                    self.item == sc.Token_Type.MUL or self.item == sc.Token_Type.DIV or 
                    self.item == sc.Token_Type.POWER:
                # 运算符 - 两个孩子
                self.left=None
                self.right=None
            elif self.item == sc.Token_Type.FUNC:
                self.FuncPtr = None
                self.center = None      ## 一个孩子
            self.value = None       ## 传入的所有类型都有value
        def __str__(self):      ## 叶子节点
            return str(self.item)  #print 一个 Node 类时会打印 __str__ 的返回值
    
        def GetExprValue(self):
            if self.item == sc.Token_Type.PLUS:
                self.value = self.right.value + self.left.value
            elif self.item == sc.Token_Type.MINUS:
                self.value = self.left.value - self.right.value
            elif self.item == sc.Token_Type.MUL:
                self.value = self.left.value * self.right.value
            elif self.item == sc.Token_Type.DIV:
                self.value = self.left.value / self.right.value
            elif self.item == sc.Token_Type.POWER:
                self.value = self.left.value ** self.right.value
            elif self.item == sc.Token_Type.FUNC:
                self.value = self.FuncPtr(self.center.value)
            return self.value
    
    

    Step 2 :parserfunc.py - 构造语法分析器类

    #!/usr/bin/env python
    # encoding: utf-8
    '''
    @author: 黄龙士
    @license: (C) Copyright 2019-2021,China.
    @contact: iym070010@163.com
    @software: xxxxxxx
    @file: parserfunc.py
    @time: 2019/11/26 19:47
    @desc:
    '''
    import parserclass as ps
    import scannerclass as sc
    import scannerfunc as sf
    import sys
    import numpy as np
    
    class Parsef(object):
    
        def __init__(self,scanner):
            self.scanner = scanner  ## 传入的一个初始化后的scanner
            self.token = None
            self.Parameter,self.Origin_x,self.Origin_y,self.Scale_x,self.Scale_y,self.Rot_angle = (0,0,0,1,1,0)
            self.x_ptr, self.y_ptr = (None,None)
            self.Tvalue = 0     # len(Tvalue)==1
    
        def enter(self,x):
            print('enter in '+str(x)+'
    ')
        def back(self,x):
            print('exit from ' + str(x) + '
    ')
        def call_match(self,x):
            print('match token ' + str(x) + '
    ')
        def Tree_trace(self,x):
            self.PrintSyntaxTree(x, 1)   #打印语法树
        def FetchToken(self):   # 调用词法分析器的GetToken,保存得到结果
            self.token = self.scanner.GetToken()
            while self.token.type == sc.Token_Type.NONTOKEN:        ## 如果读取的是空格,则再继续读取,所以token不可能是空格
                self.token = self.scanner.GetToken()
            if (self.token.type == sc.Token_Type.ERRTOKEN):
                self.SyntaxError(1)     ## 如果得到的记号是非法输入errtoken,则指出一个词法错误
        ## 匹配当前记号
        def MatchToken(self,The_Token):
            if (self.token.type != The_Token):
                self.SyntaxError(2) # 若失败,则指出一个语法错误
            if The_Token == sc.Token_Type.SEMICO:
                self.scanner.fp.readline()
                last = self.scanner.fp.tell()
                str = self.scanner.fp.readline()
                if len(str) == 0:    ## 最后一行读完,直接退出,token还是sc.Token_Type.SEMICO
                    return
                else:
                    self.scanner.fp.seek(last)  ## 否则就返回到之前的位置
            self.FetchToken() # 若成功,则获取下一个
    
        ### //语法错误处理
        def SyntaxError(self,x):
            if x == 1:
                print(self.token.type)
                self.ErrMsg(self.scanner.LineNo, " 错误记号 ", self.token.lexeme)
            elif x == 2:
                self.ErrMsg(self.scanner.LineNo, " 不是预期记号 ", self.token.lexeme)
    
        ## 打印错误信息
        def ErrMsg(self,LineNo,descrip,string):
            print("Line No {:3d}:{:s} {:s} !
    ".format(LineNo, descrip, string))
            self.scanner.CloseScanner()
            sys.exit(1)
    
        def PrintSyntaxTree(self,root,indent):      #打印语法树 - 先序遍历并打印表达式的语法树
            for temp in range(indent):  # 缩进
                print('	',end=" ")
            if root.item == sc.Token_Type.PLUS:
                print('+ ')
            elif root.item == sc.Token_Type.MINUS:
                print('- ')
            elif root.item == sc.Token_Type.MUL:
                print('* ')
            elif root.item == sc.Token_Type.DIV:
                print('/ ')
            elif root.item == sc.Token_Type.POWER:
                print('** ')
            elif root.item == sc.Token_Type.FUNC:
                print('{} '.format(root.FuncPtr))
            elif root.item == sc.Token_Type.CONST_ID:       ##
                print('{:5f} '.format(root.value))
            elif root.item == sc.Token_Type.T:
                print('{} '.format(root.value))
            else:
                print("Error Tree Node !
    ")
                sys.exit(0)
            if root.item == sc.Token_Type.CONST_ID or root.item == sc.Token_Type.T: # 叶子节点返回
                return ## 常数和参数只有叶子节点  #常数:右值;参数:左值地址
            elif root.item == sc.Token_Type.FUNC: #递归打印一个孩子节点
                self.PrintSyntaxTree(root.center, indent + 1)   # 函数有孩子节点和指向函数名的指针
            else:   # 递归打印两个孩子节点    二元运算:左右孩子的内部节点
                self.PrintSyntaxTree(root.left, indent + 1)
                self.PrintSyntaxTree(root.right, indent + 1)
    
    
        #绘图语言解释器入口(与主程序的外部接口)
        def Parser(self):    #语法分析器的入口
            self.enter("Parser")
            if (self.scanner.fp == None):  #初始化词法分析器
                print("Open Source File Error !
    ")
            else:
                self.FetchToken()   #获取第一个记号
                self.Program()   #递归下降分析
                self.scanner.CloseScanner()  #关闭词法分析器
                self.back("Parser")
    
        def Program(self):
            self.enter("Program")       # 每句话
            while (self.token.type != sc.Token_Type.SEMICO):  #记号类型不是分隔符 - 如果最后一行读完了,则记号仍是分隔符;否则不会是分隔符
                self.Statement()     #转到每一种文法
                self.MatchToken(sc.Token_Type.SEMICO)      #匹配到分隔符
                self.call_match(";")
            self.back("Program")
    
        ##----------Statement的递归子程序     开始状态
        def Statement(self):    ##转到每一种文法       ## 构造的文法
            self.enter("Statement")
            if self.token.type == sc.Token_Type.ORIGIN:
                self.OriginStatement()
            elif self.token.type == sc.Token_Type.SCALE:
                self.ScaleStatement()
            elif self.token.type == sc.Token_Type.ROT:
                self.RotStatement()
            elif self.token.type == sc.Token_Type.FOR:
                self.ForStatement()
            elif self.token.type == sc.Token_Type.CONST_ID or self.token.type == sc.Token_Type.L_BRACKET or 
                    self.token.type == sc.Token_Type.MINUS:
                self.Expression()
            else:   self.SyntaxError(2)
            self.back("Statement")
    
      ##----------OriginStatement的递归子程序
      ##eg:origin is (20, 200);
        def OriginStatement(self):
            self.enter("OriginStatement")
            self.MatchToken(sc.Token_Type.ORIGIN)
            self.call_match("ORIGIN")
            self.MatchToken(sc.Token_Type.IS)
            self.call_match("IS")
            self.MatchToken(sc.Token_Type.L_BRACKET)     ## eg:origin is (
            self.call_match("(")
            tmp = self.Expression()
            self.Origin_x = tmp.GetExprValue()    # 获取横坐标点平移距离
            self.MatchToken(sc.Token_Type.COMMA)   ## eg: ,
            self.call_match(",")
            tmp = self.Expression()
            self.Origin_y = tmp.GetExprValue()                     #获取纵坐标的平移距离
            self.MatchToken(sc.Token_Type.R_BRACKET)       ##eg: )
            self.call_match(")")
            self.back("OriginStatement")
    
        ## ----------ScaleStatement的递归子程序
        ## eg: scale is (40, 40);
        def ScaleStatement(self):
            self.enter("ScaleStatement")
            self.MatchToken(sc.Token_Type.SCALE)
            self.call_match("SCALE")
            self.MatchToken(sc.Token_Type.IS)
            self.call_match("IS")
            self.MatchToken(sc.Token_Type.L_BRACKET)  ## eg: scale is (
            self.call_match("(")
            tmp = self.Expression()
            self.Scale_x = tmp.GetExprValue()  ## 获取横坐标的比例因子
            self.MatchToken(sc.Token_Type.COMMA)          ## eg:,
            self.call_match(",")
            tmp = self.Expression()
            self.Scale_y = tmp.GetExprValue()      ## 获取纵坐标的比例因子
            self.MatchToken(sc.Token_Type.R_BRACKET)  ## eg:)
            self.call_match(")")
            self.back("ScaleStatement")
    
        ## ----------RotStatement的递归子程序
        ## eg: rot is 0;
        def RotStatement(self):
            self.enter("RotStatement")
            self.MatchToken(sc.Token_Type.ROT)
            self.call_match("ROT")
            self.MatchToken(sc.Token_Type.IS) ## eg: rot is
            self.call_match("IS")
            tmp = self.Expression()
            self.Rot_angle = tmp.GetExprValue()   ## 获取旋转角度
            self.back("RotStatement")
    
        ## ----------ForStatement的递归子程序
        ## 对右部文法符号的展开->遇到终结符号直接匹配,遇到非终结符就调用相应子程序
        ## ForStatement中唯一的非终结符是Expression,他出现在5个不同位置,分别代表循环的起始、终止、步长、横坐标、纵坐标,需要5个树节点指针保存这5棵语法树
        ## eg:for T from 0 to 2 * pi step pi / 50 draw (t, -sin(t))
        ## ExprNode *start_ptr, *end_ptr, *step_ptr, *x_ptr, *y_ptr;//指向各表达式语法树根节点
        def ForStatement(self):
            Start, End, Step = (0.0,0.0,0.0) ## 绘图起点、终点、步长
            self.enter("ForStatement")
            ## 遇到非终结符就调用相应子程序
            self.MatchToken(sc.Token_Type.FOR)
            self.call_match("FOR")
            self.MatchToken(sc.Token_Type.T)
            self.call_match("T")
            self.MatchToken(sc.Token_Type.FROM)
            self.call_match("FROM") ## eg:for T from
            start_ptr = self.Expression() ## 获得参数起点表达式的语法树
                ## 'NoneType' object has no attribute 'GetExprValue'
            Start = start_ptr.GetExprValue() ## 计算参数起点表达式的值
            self.MatchToken(sc.Token_Type.TO)
            self.call_match("TO") ## eg: to
            end_ptr = self.Expression() ## 构造参数终点表达式语法树
            End = end_ptr.GetExprValue() ## 计算参数终点表达式的值 eg: step 2 * pi
            self.MatchToken(sc.Token_Type.STEP)
            self.call_match("STEP") ## eg: step
            step_ptr = self.Expression() ## 构造参数步长表达式语法树
            Step = step_ptr.GetExprValue() ## 计算参数步长表达式的值   eg: pi / 50 并存起来
            self.Tvalue = np.arange(Start,End,Step)
            self.MatchToken(sc.Token_Type.DRAW)
            self.call_match("DRAW")
            self.MatchToken(sc.Token_Type.L_BRACKET)
            self.call_match("(") ## eg: draw(
            self.x_ptr = self.Expression() ## 跟节点 eg: t     把x_ptr存起来
            self.x_ptr = self.x_ptr.value
            # self.x_ptr = self.x_ptr.GetExprValue()      ## 直接存储二元组即可
            self.MatchToken(sc.Token_Type.COMMA)
            self.call_match(",") ## eg:,
            self.y_ptr = self.Expression() ## 根节点   把x_ptr存起来
            self.y_ptr = self.y_ptr.value
            # self.y_ptr = self.y_ptr.GetExprValue()
            self.MatchToken(sc.Token_Type.R_BRACKET)
            self.call_match(")")
            self.back("ForStatement")
    
        ## ----------Expression的递归子程序
        ## 把函数设计为语法树节点的指针,在函数内引进2个语法树节点的指针变量,分别作为Expression左右操作数(Term)的语法树节点指针
        ## 表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。
        def Expression(self): ## 展开右部,并且构造语法树
            self.enter("Expression")
            left = self.Term() ## 分析左操作数且得到其语法树
            while (self.token.type == sc.Token_Type.PLUS or self.token.type == sc.Token_Type.MINUS):
                token_tmp = self.token.type
                self.MatchToken(token_tmp)
                right = self.Term() ## 分析右操作数且得到其语法树
                left = self.MakeExprNode_Operate(token_tmp, left, right) ## 构造运算的语法树,结果为左子树
            self.Tree_trace(left) ## 打印表达式的语法树
            self.back("Expression")
            return left ## 返回最终表达式的语法树
    
        ## ----------Term的递归子程序
        ## 项是由若干个因子以乘除号连接而成
        def Term(self):
            left = self.Factor()
            while (self.token.type == sc.Token_Type.MUL or self.token.type == sc.Token_Type.DIV):
                token_tmp = self.token.type
                self.MatchToken(token_tmp)
                right = self.Factor()
                left = self.MakeExprNode_Operate(token_tmp, left, right)
            return left
    
        ## ----------Factor的递归子程序
        ## 因子则可能是一个标识符或一个数字,或是一个以括号括起来的子表达式
        def Factor(self):
            if self.token.type == sc.Token_Type.PLUS: ## 匹配一元加运算
                self.MatchToken(sc.Token_Type.PLUS)
                right = self.Factor()   ## 一元加:+E 转化为 E;
                left = None                                             ## 到时候如果左孩子是None则不打印
                right = self.MakeExprNode_Operate(sc.Token_Type.PLUS, left, right)
            elif self.token.type == sc.Token_Type.MINUS:
                self.MatchToken(sc.Token_Type.MINUS)
                right = self.Factor()
                left = ps.ExprNode(sc.Token_Type.CONST_ID)
                left.value = 0.0
                right = self.MakeExprNode_Operate(sc.Token_Type.MINUS,left,right)
            else:
                right = self.Component() ## 匹配非终结符Component
            return right
    
        ## ----------Component的递归子程序
        ## 幂
        def Component(self):        ## 右结合
            left = self.Atom()
            if self.token.type == sc.Token_Type.POWER:  ## 幂运算
                self.MatchToken(sc.Token_Type.POWER)
                right = self.Component() ## 递归调用Component以实现POWER的右结合
                left = self.MakeExprNode_Operate(sc.Token_Type.POWER, left, right)
            return left
    
        ## ----------Atom的递归子程序
        ## 包括括号函数常数参数
        def Atom(self):
            if self.token.type == sc.Token_Type.CONST_ID:
                const_value = self.token.value      ## 保存当前常数值
                self.MatchToken(sc.Token_Type.CONST_ID)
                address = self.MakeExprNode_Const(sc.Token_Type.CONST_ID, const_value)
            elif self.token.type == sc.Token_Type.T:
                self.MatchToken(sc.Token_Type.T)
                if len(self.Tvalue) == 1:
                    address = self.MakeExprNode_Const(sc.Token_Type.T,0.0)      ## 暂时用0替代
                else:
                    address = self.MakeExprNode_Const(sc.Token_Type.T, self.Tvalue)
            elif self.token.type == sc.Token_Type.FUNC:
                funcptr_value = self.token.funcptr  ## 保存当前函数指针
                self.MatchToken(sc.Token_Type.FUNC)
                self.MatchToken(sc.Token_Type.L_BRACKET)
                tmp = self.Expression()
                address = self.MakeExprNode_Operate(sc.Token_Type.FUNC, funcptr_value, tmp)
                self.MatchToken(sc.Token_Type.R_BRACKET)
                self.call_match(")")
            elif self.token.type == sc.Token_Type.L_BRACKET:
                self.MatchToken(sc.Token_Type.L_BRACKET)
                self.call_match("(")
                address = self.Expression()
                self.MatchToken(sc.Token_Type.R_BRACKET)
                self.call_match(")")
            else:
                self.SyntaxError(2)
            return address      ## 根节点
    
        ## 生成语法树的一个节点 - 运算节点 函数节点
        def MakeExprNode_Operate(self,item,left,right):
            ExprPtr = ps.ExprNode(item) ## 接收记号的类别
            if item == sc.Token_Type.FUNC:
                ExprPtr.FuncPtr = left
                ExprPtr.center = right
            else:
                ExprPtr.left = left
                ExprPtr.right = right
            ExprPtr.GetExprValue()      ## 更新下自己的value
            return ExprPtr
    
        ##  常数节点 变量节点
        def MakeExprNode_Const(self,item,value):
            ExprPtr = ps.ExprNode(item) ## 接收记号的类别
            ExprPtr.value = value
            return ExprPtr
    
    

    Step 3 :parsermain.py - 完成I/O流

    #!/usr/bin/env python
    # encoding: utf-8
    '''
    @author: 黄龙士
    @license: (C) Copyright 2019-2021,China.
    @contact: iym070010@163.com
    @software: xxxxxxx
    @file: parsermain.py
    @time: 2019/11/26 22:31
    @desc:
    '''
    
    import scannerfunc as sf
    import parserfunc as pf
    import semanticfunc as paint
    import os
    
    file_name = 'test.txt'
    scanner = sf.scanner(file_name)
    ##semantic = paint.semantic(scanner)
    ##semantic.initPaint()
    ##semantic.Parser()
    parser = pf.Parsef(scanner)
    parser.Parser()
    
    # os.system("pause")
    
    

    实现结果

    • 对于测试文本(1)FOR T FROM 0 TO 2*PI STEP PI/50 DRAW(COS(t),sin(t));的测试运行结果如下:

    • 换一组测试文本(2)进行的测试运行结果如下:

  • 相关阅读:
    hibernate_0100_HelloWorld
    MYSQL子查询的五种形式
    JSF是什么?它与Struts是什么关系?
    nop指令的作用
    htmlparser实现从网页上抓取数据(收集)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts tags are only usable when the
    FCKeditor 在JSP上的完全安装
    Java遍历文件夹的2种方法
    充电电池和充电时间说明
    吃知了有什么好处
  • 原文地址:https://www.cnblogs.com/ymjun/p/11963230.html
Copyright © 2011-2022 走看看