<編譯原理 - 函數繪圖語言解釋器(2)語法分析器 - python>

<編譯原理 - 函數繪圖語言解釋器(2)語法分析器 - python>

背景

  • 編譯原理上機實現一個對函數繪圖語言的解釋器 - 用除C外的不一樣種語言實現python

  • 設計思路:
    1. 設計函數繪圖語言的文法,使其適合遞歸降低分析;函數

    2. 設計語法樹的結構,用於存放表達式的語法樹;測試

    3. 設計遞歸降低子程序,分析句子並構造表達式的語法樹;設計

    4. 設計測試程序和測試用例,檢驗分析器是否正確。3d

  • 消除無二義/無左遞歸完整的EBNF文法:指針

  • 表達式的語法樹:

  • 用Pycharm寫了三個.py文件:
    • parserclass.pycode

    • parserfunc.pyorm

    • parsermain.pyblog

    • 輸入流是詞法分析器獲得的記號流,輸出流是語法樹遞歸

    • 測試文本序列(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));的測試運行結果以下:

  • 換一組測試文本(2)進行的測試運行結果以下:

相關文章
相關標籤/搜索