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

使用Python制作一個極簡四則運算解釋器

 更新時間:2023年04月06日 10:03:50   作者:CrazyDragon_King  
這篇文章主要介紹了使用Python制作一個極簡四則運算解釋器,在使用工具之前,至少也要了解工具的作用,需要的朋友可以參考下

前言:

這是最近完成的一個小的 demo,一個極簡四則運算解釋器。前面,已經(jīng)基于這個想法發(fā)了兩篇博客了:
淺談一下四則運算和二叉樹
python的簡單四則運算語法樹可視化
然后,前兩天也就完成了這個總體的 demo 程序。本來整個程序的思路大致上有了,但是卡在了生成 AST 上了,因為不知道如何生成表達式樹的二叉樹,這個問題困擾了我很久。最后我參考了下面參考資料的第一篇博客的做法,不過它的樹的節(jié)點我感覺有點簡單了,而且最后底層葉子節(jié)點為 tree=tree(tree, "+", 0),感覺莫名其妙的,雖然這樣也是可以的。不過,我最終是定義了兩個節(jié)點來表示這個,一個 BinOp 來表示二元運算節(jié)點,另一個 Constant 來表示常量節(jié)點。正好元旦到了,我也能抽出來時間了,正好寫一篇博客記錄一下。

演示

這里來簡單放幾個程序的運行截圖,給大家看一下演示的效果。

計算功能演示

這里先展示了程序的幫助信息,然后是幾個簡單的四則運算測試,看起來是沒問題了(我可不敢保證,程序沒有bug!)。

在這里插入圖片描述

輸出 tokens

在這里插入圖片描述

輸出 AST

這個格式化的 JSON 信息太長了,不利于直接看到。我們將它渲染出來看最后生成的樹形圖(方法見前兩個博客)。保存下面這個 JSON 在一個文件中,這里我叫做 demo.json,然后執(zhí)行如下命令:pytm-cli -d LR -i demo.json -o demo.html,然后再瀏覽器打開生成的 html 文件。

在這里插入圖片描述

在這里插入圖片描述

代碼

所有的代碼都在這里了,只需要一個文件 my_eval.py,想要運行的話,復(fù)制、粘貼,然后按照演示的步驟執(zhí)行即可。

Node、BinOp、Constan 是用來表示節(jié)點的類.
Calculator 中 lexizer 方法是進行分詞的,本來我是打算使用正則的,如果你看過我前面的博客的話,可以發(fā)現(xiàn)我是用的正則來分詞的(因為 Python 的官方文檔正則表達式中有一個簡易的分詞程序)。不過我看其他人都是手寫的分詞,所以我也這樣做了,不過感覺并不是很好,很繁瑣,而且容易出錯。
parse 方法是進行解析的,主要是解析表達式的結(jié)構(gòu),判斷是否符合四則運算的文法,最終生成表達式樹(它的 AST)。

"""
Grammar

G -> E
E -> T E'
E' -> '+' T E' | '-' T E' | ?
T -> F T'
T' -> '*' F T' | '/' F T' | ?
F -> '(' E ')' | num | name

"""

import json
import argparse


class Node:
    """
    簡單的抽象語法樹節(jié)點,定義一些需要使用到的具有層次結(jié)構(gòu)的節(jié)點
    """

    def eval(self) -> float: ...   # 節(jié)點的計算方法
    def visit(self): ...           # 節(jié)點的訪問方法


class BinOp(Node):
    """
    BinOp Node
    """

    def __init__(self, left, op, right) -> None:
        self.left = left
        self.op = op
        self.right = right

    def eval(self) -> float:
        if self.op == "+":
            return self.left.eval() + self.right.eval()
        if self.op == "-":
            return self.left.eval() - self.right.eval()
        if self.op == "*":
            return self.left.eval() * self.right.eval()
        if self.op == "/":
            return self.left.eval() / self.right.eval()
        return 0

    def visit(self):
        """
        遍歷樹的各個節(jié)點,并生成 JSON 表示
        """

        return {
            "name": "BinOp",
            "children": [
                self.left.visit(),
                {
                    "name": "OP",
                    "children": [
                        {
                            "name": self.op
                        }
                    ]
                },
                self.right.visit()
            ]
        }


class Constant(Node):
    """
    Constant Node
    """

    def __init__(self, value) -> None:
        self.value = value

    def eval(self) -> float:
        return self.value

    def visit(self):
        return {
            "name": "NUMBER",
            "children": [
                {
                    "name": str(self.value)  # 轉(zhuǎn)成字符是因為渲染成圖像時,需要該字段為 str
                }
            ]
        }


class Calculator:
    """
    Simple Expression Parser
    """

    def __init__(self, expr) -> None:
        self.expr = expr           # 輸入的表達式
        self.parse_end = False     # 解析是否結(jié)束,默認未結(jié)束
        self.toks = []             # 解析的 tokens
        self.index = 0             # 解析的下標

    def lexizer(self):
        """
        分詞
        """
        index = 0
        while index < len(self.expr):
            ch = self.expr[index]
            if ch in [" ", "\r", "\n"]:
                index += 1
                continue
            if '0' <= ch <= '9':
                num_str = ch
                index += 1
                while index < len(self.expr):
                    n = self.expr[index]
                    if '0' <= n <= '9':
                        if ch == '0':
                            raise Exception("Invalid number!")
                        num_str = n
                        index += 1
                        continue
                    break
                self.toks.append({
                    "kind": "INT",
                    "value": int(num_str)
                })
            elif ch in ['+', '-', '*', '/', '(', ')']:
                self.toks.append({
                    "kind": ch,
                    "value": ch
                })
                index += 1
            else:
                raise Exception("Unkonwn character!")

    def get_token(self):
        """
        獲取當(dāng)前位置的 token
        """
        if 0 <= self.index < len(self.toks):
            tok = self.toks[self.index]
            return tok
        if self.index == len(self.toks):  # token解析結(jié)束
            return {
                "kind": "EOF",
                "value": "EOF"
            }
        raise Exception("Encounter Error, invalid index = ", self.index)

    def move_token(self):
        """
        下標向后移動一位
        """
        self.index += 1

    def parse(self) -> Node:
        """
        G -> E
        """
        # 分詞
        self.lexizer()
        # 解析
        expr_tree = self.parse_expr()
        if self.parse_end:
            return expr_tree
        else:
            raise Exception("Invalid expression!")

    def parse_expr(self):
        """
        E -> T E'
        E' -> + T E' | - T E' | ?
        """
        # E -> E E'
        left = self.parse_term()
        # E' -> + T E' | - T E' | ?
        while True:
            tok = self.get_token()
            kind = tok["kind"]
            value = tok["value"]

            if tok["kind"] == "EOF":
                # 解析結(jié)束的標志
                self.parse_end = True
                break
            if kind in ["+", "-"]:
                self.move_token()
                left = BinOp(left, value, self.parse_term())
            else:
                break

        return left

    def parse_term(self):
        """
        T -> F T'
        T' -> * F T' | / F T' | ?
        """
        # T -> F T'
        left = self.parse_factor()
        # T' -> * F T' | / F T' | ?
        while True:
            tok = self.get_token()
            kind = tok["kind"]
            value = tok["value"]

            if kind in ["*", "/"]:
                self.move_token()
                right = self.parse_factor()
                left = BinOp(left, value, right)
            else:
                break

        return left

    def parse_factor(self):
        """
        F -> '(' E ')' | num | name
        """
        tok = self.get_token()
        kind = tok["kind"]
        value = tok["value"]
        if kind == '(':
            self.move_token()
            expr_node = self.parse_expr()
            if self.get_token()["kind"] != ")":
                raise Exception("Encounter Error, expected )!")
            self.move_token()
            return expr_node
        if kind == "INT":
            self.move_token()
            return Constant(value=value)

        raise Exception("Encounter Error, unknown factor: ", kind)


if __name__ == "__main__":
    # 添加命令行參數(shù)解析器
    cmd_parser = argparse.ArgumentParser(
        description="Simple Expression Interpreter!")
    group = cmd_parser.add_mutually_exclusive_group()
    group.add_argument("--tokens", help="print tokens", action="store_true")
    group.add_argument("--ast", help="print ast in JSON", action="store_true")
    cmd_parser.add_argument(
        "expr", help="expression, contains ['+', '-', '*', '/', '(', ')', 'num']")
    args = cmd_parser.parse_args()

    calculator = Calculator(expr=args.expr)
    tree = calculator.parse()
    if args.tokens:   # 輸出 tokens
        for t in calculator.toks:
            print(f"{t['kind']:3s} ==> {t['value']}")
    elif args.ast:    # 輸出 JSON 表示的 AST
        print(json.dumps(tree.visit(), indent=4))
    else:             # 計算結(jié)果
        print(tree.eval())

總結(jié)

本來想在前面說一下為什么叫 my_eval.py,但是感覺看到后面的人不多,那就在這里說好了。如果寫了一個復(fù)雜的表達式,那么怎么驗證是否正確的。這里我們直接利用 Python 這個最完美的解釋器就好了,哈哈。這里用 Python 的 eval 函數(shù),你當(dāng)然是不需要調(diào)用這個函數(shù),直接復(fù)制計算的表達式即可。我用 eval 函數(shù),只是想表達為什么我的程序會叫 my_eval 這個名字。

在這里插入圖片描述

這樣實現(xiàn)下來,也算是完成了一個簡單的四則運算解釋器了。不過,如果你也做一遍的話,也估計會和我一樣感覺到整個過程很繁瑣。因為分詞和語法解析都有現(xiàn)成的工具可以來完成,而且不容易出錯,可以大大減少工作量。不過,自己來一遍也是很有必要的,在使用工具之前,至少也要了解工具的作用。

到此這篇關(guān)于使用Python制作一個極簡四則運算解釋器的文章就介紹到這了,更多相關(guān)Python極簡四則運算解釋器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論