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

背景

  • 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现
  • 设计思路:
  1. 设计函数绘图语言的文法,使其适合递归下降分析;
  2. 设计语法树的结构,用于存放表达式的语法树;
  3. 设计递归下降子程序,分析句子并构造表达式的语法树;
  4. 设计测试程序和测试用例,检验分析器是否正确。
  • 消除无二义/无左递归完整的EBNF文法:

python编译器实现 python编译器原理_语法树

  • 表达式的语法树:

python编译器实现 python编译器原理_编译原理_02

  • 用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)+'\n')
    def back(self,x):
        print('exit from ' + str(x) + '\n')
    def call_match(self,x):
        print('match token ' + str(x) + '\n')
    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} !\n".format(LineNo, descrip, string))
        self.scanner.CloseScanner()
        sys.exit(1)

    def PrintSyntaxTree(self,root,indent):      #打印语法树 - 先序遍历并打印表达式的语法树
        for temp in range(indent):  # 缩进
            print('\t',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 !\n")
            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 !\n")
        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));的测试运行结果如下:

python编译器实现 python编译器原理_递归_03

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

python编译器实现 python编译器原理_python_04