最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Python編寫一個解析器

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

前言

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

在這里插入圖片描述

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

語法樹

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

在這里插入圖片描述

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

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

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

在這里插入圖片描述

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

在這里插入圖片描述

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

所以在這塊我們先引入幾個東西。

節(jié)點

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

"""
定義節(jié)點:
NumberNode:表示數(shù)字節(jié)點,用于存儲數(shù)字的令牌(token)信息。
BinOpNode:表示二元操作符節(jié)點。其中:
            left_node(左節(jié)點),
            op_tok(操作符令牌),
            right_node(右節(jié)點)
UnaryOpNode:表示一元操作符節(jié)點。
            op_tok(操作符令牌)
            node(子節(jié)點)
"""
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})'

語法描述

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

在這里插入圖片描述

于是得到語法描述

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

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

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

異常處理

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

InvalidSyntaxError

那么在這里我們首當(dāng)其沖的就是我們的這個語法錯誤的報錯。

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

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

那么在這里的話,對比上次的代碼,在這里我對Error父類也進行了改動:

class HlangError:
    def __init__(self, pos_start, pos_end, error_name, details):
        """
        :param pos_start: 錯誤開始未知
        :param pos_end: 錯誤結(jié)束未知
        :param error_name: 錯位類型
        :param details: 細節(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

錯誤內(nèi)容定位

那么在這里的話,我之所以做這個改動,主要是因為,我們要實現(xiàn)這個顯示錯誤的內(nèi)容提示,那么這個時候的話,當(dāng)出現(xiàn)異常的時候,之所以,可以這樣,還有一個原因是,我們對這個類Position也進行了修改,這個家伙,現(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)

這樣做的好處是能夠進行快速定位顯示錯誤。

之后的話,為了顯示出錯誤:

在這里插入圖片描述

這里有一個專門用來顯示錯誤的方法:

def string_with_arrows(text, pos_start, pos_end):
    result = ''
    # 計算索引位置
    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):
        # 計算行的列數(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)
        # 重新計算索引位置
        idx_start = idx_end
        idx_end = text.find('\n', idx_start + 1)
        if idx_end < 0:
            idx_end = len(text)
    return result.replace('\t', '')

解析實現(xiàn)

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

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

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

這樣做的目的只要是為了,能夠去記錄一下異常。記錄一下這個報錯,不記錄不行。

它的核心其實就是這幾個方法:

在這里插入圖片描述

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

bin_op函數(shù)

因為我們這里要構(gòu)造的就是一棵樹,所以i的話,我們要遞歸處理,我們要去合并左右子樹。所以這個函數(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,令牌是這個符合要求的運算符
            #那么拿到下一個token,然后看看能不能構(gòu)造左邊的解析樹
            #記住我們這里是中綴表達式,從左到右的
            op_tok = self.current_tok
            res.register(self.advance())
            right = res.register(func())
            if res.error: return res
            #此時我們把左右合并,組裝成新的樹,但是如果沒有,
            # 那么左子樹就是當(dāng)前的樹,不用構(gòu)造,沒有新的層級
            left = BinOpNode(left, op_tok, right)
        return res.success(left)

factory函數(shù)

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

    def factor(self):
        res = ParseResult()
        tok = self.current_tok
        if tok.type in (TT_PLUS, TT_MINUS):
            #如果是運算符號,那么它是一元操作符,找到下一個數(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):
            #如果是一個數(shù)字,那么直接返回節(jié)點
            res.register(self.advance())
            return res.success(NumberNode(tok))
        #如果是(,那么說明這個玩意又是一個表達式
        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)為:整型或浮點型"
        ))

模擬流程

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

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

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

相關(guān)文章

  • Python 類的私有屬性和私有方法實例分析

    Python 類的私有屬性和私有方法實例分析

    這篇文章主要介紹了Python 類的私有屬性和私有方法,結(jié)合實例形式分析了Python面向?qū)ο蟪绦蛟O(shè)計中類的私有屬性與私有方法簡單操作技巧,需要的朋友可以參考下
    2019-09-09
  • selenium在scrapy中的使用代碼

    selenium在scrapy中的使用代碼

    本文給大家分享selenium在scrapy中的使用代碼,使用selenium可以很好的幫助我們獲取一些重要數(shù)據(jù)信息,本文通過代碼給大家詳細介紹,感興趣的朋友跟隨小編一起看看吧
    2021-05-05
  • python使用時間間隔的操作及技巧分享

    python使用時間間隔的操作及技巧分享

    Python 中處理時間間隔和日期時間的操作通常涉及 datetime 模塊,它提供了豐富的功能來處理日期和時間,本文給大家介紹了一些關(guān)于時間間隔操作的技巧和示例,并通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2024-12-12
  • pyinstaller打包遇到的問題解決

    pyinstaller打包遇到的問題解決

    本文主要介紹了pyinstaller打包遇到的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Python爬蟲之Selenium鼠標事件的實現(xiàn)

    Python爬蟲之Selenium鼠標事件的實現(xiàn)

    這篇文章主要介紹了Python爬蟲之Selenium鼠標事件的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • python?函數(shù)、變量中單下劃線和雙下劃線的區(qū)別詳解

    python?函數(shù)、變量中單下劃線和雙下劃線的區(qū)別詳解

    本文主要介紹了python?函數(shù)、變量中單下劃線和雙下劃線的區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Python數(shù)據(jù)分析之Matplotlib數(shù)據(jù)可視化

    Python數(shù)據(jù)分析之Matplotlib數(shù)據(jù)可視化

    這篇文章主要介紹了Python數(shù)據(jù)分析之Matplotlib數(shù)據(jù)可視化,Matplotlib?是?Python?中常用的?2D?繪圖庫,它能輕松地將數(shù)據(jù)進行可視化,作出精美的圖表
    2022-08-08
  • Python字典中items()函數(shù)案例詳解

    Python字典中items()函數(shù)案例詳解

    這篇文章主要介紹了Python字典中items()函數(shù)案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • python中的Pytorch建模流程匯總

    python中的Pytorch建模流程匯總

    這篇文章主要介紹了python中的Pytorch建模流程匯總,主要幫大家?guī)椭蠹沂崂砩窠?jīng)網(wǎng)絡(luò)訓(xùn)練的架構(gòu),具有一的的參考價值,需要的小伙伴可以參考一下,希望對你的學(xué)習(xí)有所幫助
    2022-03-03
  • python3學(xué)生名片管理v2.0版

    python3學(xué)生名片管理v2.0版

    這篇文章主要為大家詳細介紹了python3學(xué)生名片管理v2.0版,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11

最新評論

凤台县| 太和县| 沾益县| 万州区| 若尔盖县| 弋阳县| 海淀区| 红桥区| 七台河市| 巴东县| 安龙县| 南召县| 江阴市| 石屏县| 岗巴县| 伊春市| 阳东县| 永兴县| 赞皇县| 景宁| 常德市| 都安| 乌兰察布市| 磐安县| 承德县| 长治市| 涪陵区| 夏津县| 无锡市| 南阳市| 内乡县| 伊金霍洛旗| 蒙自县| 三门峡市| 大竹县| 贵州省| 沙洋县| 韶山市| 姜堰市| 科尔| 浦县|