欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Python編寫一個(gè)解析器

 更新時(shí)間:2023年08月18日 08:22:27   作者:Huterox  
這篇文章主要給大家介紹了如何基于Python編寫一個(gè)解析器,文章通過代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下

前言

先前,我們實(shí)現(xiàn)了一個(gè)基本的詞法分析器。那么現(xiàn)在的話,我們要在這個(gè)基礎(chǔ)上面,實(shí)現(xiàn)一個(gè)解析器,那么實(shí)現(xiàn)效果的話,是這樣的:

在這里插入圖片描述

注意此時(shí)此刻,我們還啥都沒有,目前我們還沒有自己的變量等等,我們目前還是只是對 數(shù)學(xué)運(yùn)算進(jìn)行處理并且只是對+-*/進(jìn)行簡單處理 。當(dāng)然這里的話,對比上次做的是做出了一點(diǎn)改動(dòng),當(dāng)然這都不是重點(diǎn),因?yàn)樵谶@里都會進(jìn)行說明。

語法樹

由于,我們在這里的話,我們要做一個(gè)解析器,因此的話,我們毫無疑問的是,我們需要去構(gòu)建一個(gè)簡單的語法樹。那么在這里的話,先前,我們是實(shí)現(xiàn)了一個(gè)簡單的詞法解析器,也就是我們把這些東西都解析為了Token。

在這里插入圖片描述

當(dāng)然為了適應(yīng)這部分的代碼,我們對先前的代碼進(jìn)行了一點(diǎn)改動(dòng)。至于是哪里改動(dòng)了,這里會進(jìn)行說明(由于代碼不難超過文章的60%,所以這里無法貼出全部代碼,后期做好了,再給倉庫地址吧~

那么在這里的話,我們看到了剛剛的效果,所以的話,我們這里的話其實(shí)還是只是實(shí)現(xiàn)了一個(gè)簡單的運(yùn)算表達(dá)式的語法解析。

也就是其實(shí),我們今天實(shí)現(xiàn)的效果是這樣的,假設(shè)用戶在終端輸入:

在這里插入圖片描述

在我們的代碼里面將構(gòu)造出這個(gè):

在這里插入圖片描述

換一句話說,我們今天實(shí)現(xiàn)的效果,其實(shí)就是中綴表達(dá)式用一顆樹來表示。只是這個(gè)樹,在我們這里叫做語法樹。目前我們只是解析這個(gè),后面我們再解析變量等等,這樣的話,我們就可以非常愉快地實(shí)現(xiàn)這個(gè)小語言了。

所以在這塊我們先引入幾個(gè)東西。

節(jié)點(diǎn)

在這里的話,我們要做的是把剛剛的這個(gè)轉(zhuǎn)化為一顆語法樹,也就是(目前)輸入的合法的數(shù)學(xué)表達(dá)式。所以的話,為了能夠去構(gòu)建這棵樹,我們定義了如下節(jié)點(diǎn):

"""
定義節(jié)點(diǎn):
NumberNode:表示數(shù)字節(jié)點(diǎn),用于存儲數(shù)字的令牌(token)信息。
BinOpNode:表示二元操作符節(jié)點(diǎn)。其中:
            left_node(左節(jié)點(diǎn)),
            op_tok(操作符令牌),
            right_node(右節(jié)點(diǎn))
UnaryOpNode:表示一元操作符節(jié)點(diǎn)。
            op_tok(操作符令牌)
            node(子節(jié)點(diǎn))
"""
class NumberNode:
    def __init__(self, tok):
        self.tok = tok
    def __repr__(self):
        return f'{self.tok}'
class BinOpNode:
    def __init__(self, left_node, op_tok, right_node):
        self.left_node = left_node
        self.op_tok = op_tok
        self.right_node = right_node
    def __repr__(self):
        return f'({self.left_node}, {self.op_tok}, {self.right_node})'
class UnaryOpNode:
    def __init__(self, op_tok, node):
        self.op_tok = op_tok
        self.node = node
    def __repr__(self):
        return f'({self.op_tok}, {self.node})'

語法描述

接下來,我們把我們提到的這個(gè)節(jié)點(diǎn)和我的剛剛的節(jié)點(diǎn)進(jìn)行對應(yīng),進(jìn)行查抽象, 并且他們的代碼關(guān)系大致是這樣的:

在這里插入圖片描述

于是得到語法描述

expr(表達(dá)式):由一個(gè)項(xiàng)(term)后面跟隨零個(gè)或多個(gè)類似于加法(PLUS)或減法(MINUS)操作符的項(xiàng)組成。

term(項(xiàng)):由一個(gè)因子(factor)后面跟隨零個(gè)或多個(gè)類似于乘法(MUL)或除法(DIV)操作符的因子組成。

factor(因子)> :可以是整數(shù)(INT)或浮點(diǎn)數(shù)(FLOAT) > :是一個(gè)加法(PLUS)或減法(MINUS)操作符后面跟隨一個(gè)因子 > :或者是括號內(nèi)的一個(gè)表達(dá)式(expr)。

異常處理

okey,在開始我們的具體核心實(shí)現(xiàn)之前的話,我們還是來看到我們對異常的處理吧。

InvalidSyntaxError

那么在這里我們首當(dāng)其沖的就是我們的這個(gè)語法錯(cuò)誤的報(bào)錯(cuò)。

class InvalidSyntaxError(HlangError):
    def __init__(self, pos_start, pos_end, details=''):
            super().__init__(pos_start, pos_end, 'Invalid Syntax:語法錯(cuò)誤', details)

也是我們在學(xué)習(xí)過程當(dāng)中,遇到的第一個(gè)錯(cuò)誤。

那么在這里的話,對比上次的代碼,在這里我對Error父類也進(jìn)行了改動(dòng):

class HlangError:
    def __init__(self, pos_start, pos_end, error_name, details):
        """
        :param pos_start: 錯(cuò)誤開始未知
        :param pos_end: 錯(cuò)誤結(jié)束未知
        :param error_name: 錯(cuò)位類型
        :param details: 細(xì)節(jié)
        """
        self.pos_start = pos_start
        self.pos_end = pos_end
        self.error_name = error_name
        self.details = details
    def as_string(self):
        red_code = "\033[91m"
        reset_code = "\033[0m"
        result = f'{self.error_name}: {self.details}\n'
        result += f'File {self.pos_start.fn}, line {self.pos_start.ln + 1}'
        result += '\n\n' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end)
        return red_code+result+reset_code

錯(cuò)誤內(nèi)容定位

那么在這里的話,我之所以做這個(gè)改動(dòng),主要是因?yàn)?,我們要?shí)現(xiàn)這個(gè)顯示錯(cuò)誤的內(nèi)容提示,那么這個(gè)時(shí)候的話,當(dāng)出現(xiàn)異常的時(shí)候,之所以,可以這樣,還有一個(gè)原因是,我們對這個(gè)類Position也進(jìn)行了修改,這個(gè)家伙,現(xiàn)在會記錄當(dāng)前的文本了:

class Position:
    def __init__(self, idx, ln, col, fn, ftxt):
            self.idx = idx
            self.ln = ln
            self.col = col
            self.fn = fn
            self.ftxt = ftxt
    def advance(self, current_char=None):
            self.idx += 1
            self.col += 1
            if current_char == '\n':
                    self.ln += 1
                    self.col = 0
            return self
    def copy(self):
            return Position(self.idx, self.ln, self.col, self.fn, self.ftxt)

這樣做的好處是能夠進(jìn)行快速定位顯示錯(cuò)誤。

之后的話,為了顯示出錯(cuò)誤:

在這里插入圖片描述

這里有一個(gè)專門用來顯示錯(cuò)誤的方法:

def string_with_arrows(text, pos_start, pos_end):
    result = ''
    # 計(jì)算索引位置
    idx_start = max(text.rfind('\n', 0, pos_start.idx), 0)
    idx_end = text.find('\n', idx_start + 1)
    if idx_end < 0:
        idx_end = len(text)
    # 生成每一行
    line_count = pos_end.ln - pos_start.ln + 1
    for i in range(line_count):
        # 計(jì)算行的列數(shù)
        line = text[idx_start:idx_end]
        col_start = pos_start.col if i == 0 else 0
        col_end = pos_end.col if i == line_count - 1 else len(line) - 1
        # 添加到結(jié)果中
        result += line + '\n'
        result += ' ' * col_start + '^' * (col_end - col_start)
        # 重新計(jì)算索引位置
        idx_start = idx_end
        idx_end = text.find('\n', idx_start + 1)
        if idx_end < 0:
            idx_end = len(text)
    return result.replace('\t', '')

解析實(shí)現(xiàn)

那么在這里之后的話,就是我們解析的實(shí)現(xiàn)了。

那么首先在這里的話,我們對這個(gè)節(jié)點(diǎn)在進(jìn)行一個(gè)簡單的封裝:

class ParseResult:
    def __init__(self):
        self.error = None
        self.node = None
    def register(self, res):
        if isinstance(res, ParseResult):
            if res.error: self.error = res.error
            return res.node
        return res
    def success(self, node):
        self.node = node
        return self
    def failure(self, error):
        self.error = error
        return self

這樣做的目的只要是為了,能夠去記錄一下異常。記錄一下這個(gè)報(bào)錯(cuò),不記錄不行。

它的核心其實(shí)就是這幾個(gè)方法:

在這里插入圖片描述

那么在這里的話主要有兩個(gè)比較核心的代碼:

bin_op函數(shù)

因?yàn)槲覀冞@里要構(gòu)造的就是一棵樹,所以i的話,我們要遞歸處理,我們要去合并左右子樹。所以這個(gè)函數(shù)的作用就是去生成當(dāng)前的語法解析樹。

    def bin_op(self, func, ops):
        res = ParseResult()
        #構(gòu)造左邊的解析樹
        left = res.register(func())
        if res.error: return res
        while self.current_tok.type in ops:
            #如果,當(dāng)前的token,是token,令牌是這個(gè)符合要求的運(yùn)算符
            #那么拿到下一個(gè)token,然后看看能不能構(gòu)造左邊的解析樹
            #記住我們這里是中綴表達(dá)式,從左到右的
            op_tok = self.current_tok
            res.register(self.advance())
            right = res.register(func())
            if res.error: return res
            #此時(shí)我們把左右合并,組裝成新的樹,但是如果沒有,
            # 那么左子樹就是當(dāng)前的樹,不用構(gòu)造,沒有新的層級
            left = BinOpNode(left, op_tok, right)
        return res.success(left)

factory函數(shù)

之后是這個(gè)函數(shù),這個(gè)函數(shù)的唯一作用其實(shí)就是構(gòu)造當(dāng)前的節(jié)點(diǎn)。這里的話有一點(diǎn)要說明的是,那就是我們先解析的token是封裝好的,然后我們是按照從左到右進(jìn)行拿到的,我們讀取的時(shí)候也是按照順序讀取的,因此這個(gè)就意味著在我們進(jìn)行解析的時(shí)候,針對這個(gè)數(shù)學(xué)表達(dá)式,其實(shí)就是按照中綴的順序來進(jìn)行處理 的。

    def factor(self):
        res = ParseResult()
        tok = self.current_tok
        if tok.type in (TT_PLUS, TT_MINUS):
            #如果是運(yùn)算符號,那么它是一元操作符,找到下一個(gè)數(shù)字
            res.register(self.advance())
            factor = res.register(self.factor())
            if res.error: return res
            #返回到右子樹
            return res.success(UnaryOpNode(tok, factor))
        elif tok.type in (TT_INT, TT_FLOAT):
            #如果是一個(gè)數(shù)字,那么直接返回節(jié)點(diǎn)
            res.register(self.advance())
            return res.success(NumberNode(tok))
        #如果是(,那么說明這個(gè)玩意又是一個(gè)表達(dá)式
        elif tok.type == TT_LPAREN:
            res.register(self.advance())
            expr = res.register(self.expr())
            if res.error: return res
            if self.current_tok.type == TT_RPAREN:
                res.register(self.advance())
                return res.success(expr)
            else:
                return res.failure(InvalidSyntaxError(
                    self.current_tok.pos_start, self.current_tok.pos_end,
                    "應(yīng)該是: ')'"
                ))
        return res.failure(InvalidSyntaxError(
            tok.pos_start, tok.pos_end,
            "應(yīng)為:整型或浮點(diǎn)型"
        ))

模擬流程

如果你熟悉遞歸的話,那么這個(gè)案例,你就不需要看了。怎么去想這個(gè)遞歸的話,給點(diǎn)提示就是,按照不同的類型,去構(gòu)建不同的節(jié)點(diǎn)。然后遞歸只要關(guān)系當(dāng)前的函數(shù)的作用即可。具體的,遞歸思想可以看到我總結(jié)的7W藍(lán)橋杯算法的部分。里面關(guān)于遞歸的寫法是我總結(jié)了的,按照那個(gè)套路寫,leetcode分分鐘秒殺大部分中檔題目,hard 題目那就需要點(diǎn)技巧了。

假設(shè)要解析的表達(dá)式是`(5+3)*2`,那么我們這個(gè)解析器的流程是這樣的:
目標(biāo)是這個(gè):
            *
           / \
          +   2
         / \
        5   3
1. 表達(dá)式:`(5+3)*2`
2. 調(diào)用 `expr()` 方法開始解析表達(dá)式。
3. 在 `bin_op()` 方法內(nèi)部,調(diào)用 `term()` 方法解析左操作數(shù)。
   - 調(diào)用 `term()` 方法開始解析項(xiàng)。
   - 在 `bin_op()` 方法內(nèi)部,調(diào)用 `factor()` 方法解析左操作數(shù)。
     - 獲取數(shù)字 `5` 的令牌,并創(chuàng)建一個(gè) NumberNode 對象表示該數(shù)字。
   - 返回左操作數(shù)的解析結(jié)果。
4. 循環(huán)檢查當(dāng)前令牌,發(fā)現(xiàn)下一個(gè)令牌是 `+` 號。
5. 調(diào)用 `bin_op()` 方法解析加法運(yùn)算符。
   - 調(diào)用 `term()` 方法作為參數(shù)傳遞給 `bin_op()` 方法,解析右操作數(shù)。
     - 調(diào)用 `term()` 方法開始解析項(xiàng)。
     - 在 `bin_op()` 方法內(nèi)部,調(diào)用 `factor()` 方法解析右操作數(shù)。
       - 獲取數(shù)字 `3` 的令牌,并創(chuàng)建一個(gè) NumberNode 對象表示該數(shù)字。
     - 返回右操作數(shù)的解析結(jié)果。
   - 創(chuàng)建一個(gè) BinOpNode 對象,表示加法運(yùn)算,左操作數(shù)為前面解析得到的 NumberNode 對象,右操作數(shù)為剛剛解析得到的數(shù)字 `3` 的 NumberNode 對象。
6. 返回加法運(yùn)算的結(jié)果,即 BinOpNode 對象。
7. 循環(huán)檢查當(dāng)前令牌,發(fā)現(xiàn)下一個(gè)令牌是 `*` 號。
8. 調(diào)用 `bin_op()` 方法解析乘法運(yùn)算符。
   - 調(diào)用 `term()` 方法作為參數(shù)傳遞給 `bin_op()` 方法,解析右操作數(shù)。
     - 獲取數(shù)字 `2` 的令牌,并創(chuàng)建一個(gè) NumberNode 對象表示該數(shù)字。
   - 創(chuàng)建一個(gè) BinOpNode 對象,表示乘法運(yùn)算,左操作數(shù)為前面解析得到的 BinOpNode 對象,右操作數(shù)為剛剛解析得到的數(shù)字 `2` 的 NumberNode 對象。
9. 返回乘法運(yùn)算的結(jié)果,即 BinOpNode 對象。
10. 返回表達(dá)式的解析結(jié)果,即最終得到的抽象語法樹的根節(jié)點(diǎn)。

以上就是基于Python編寫一個(gè)解析器的詳細(xì)內(nèi)容,更多關(guān)于Python解析器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Python中Pyyaml模塊的使用

    詳解Python中Pyyaml模塊的使用

    這篇文章主要介紹了Python中Pyyaml模塊的使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • python中對%、~含義的解釋

    python中對%、~含義的解釋

    這篇文章主要介紹了python中對%、~含義的解釋,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Python FtpLib模塊應(yīng)用操作詳解

    Python FtpLib模塊應(yīng)用操作詳解

    這篇文章主要介紹了Python FtpLib模塊應(yīng)用操作,結(jié)合實(shí)例形式詳細(xì)分析了FtpLib模塊的功能、配置、應(yīng)用相關(guān)操作技巧與使用注意事項(xiàng),需要的朋友可以參考下
    2019-12-12
  • 為什么相對PHP黑python的更少

    為什么相對PHP黑python的更少

    在本篇內(nèi)容里小編給各位整理了關(guān)于為什么相對PHP黑python的更少的原因和知識點(diǎn),需要的朋友們可以參考下。
    2020-06-06
  • matplotlib 輸出保存指定尺寸的圖片方法

    matplotlib 輸出保存指定尺寸的圖片方法

    今天小編就為大家分享一篇matplotlib 輸出保存指定尺寸的圖片方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • python+pywinauto+lackey實(shí)現(xiàn)PC端exe自動(dòng)化的示例代碼

    python+pywinauto+lackey實(shí)現(xiàn)PC端exe自動(dòng)化的示例代碼

    這篇文章主要介紹了python+pywinauto+lackey實(shí)現(xiàn)PC端exe自動(dòng)化的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Python NumPy中diag函數(shù)的使用說明

    Python NumPy中diag函數(shù)的使用說明

    這篇文章主要介紹了Python NumPy中diag函數(shù)的使用說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • matplotlib設(shè)置legend圖例代碼示例

    matplotlib設(shè)置legend圖例代碼示例

    這篇文章主要介紹了matplotlib設(shè)置legend圖例代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • python列表刪除元素的三種實(shí)現(xiàn)方法

    python列表刪除元素的三種實(shí)現(xiàn)方法

    本文主要介紹了python列表刪除元素的三種實(shí)現(xiàn)方法,主要包括pop方法,remove方法,del方法這三種,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Java編程迭代地刪除文件夾及其下的所有文件實(shí)例

    Java編程迭代地刪除文件夾及其下的所有文件實(shí)例

    這篇文章主要介紹了Java編程迭代地刪除文件夾及其下的所有文件實(shí)例,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02

最新評論