主頁 >  其他 > 實作一個簡單的解釋器(1)

實作一個簡單的解釋器(1)

2020-09-10 11:47:32 其他

譯自:https://ruslanspivak.com/lsbasi-part1/
(已獲作者授權)

“如果你不知道編譯器的作業方式,那么你將不知道計算機的作業方式,如果你不能100%確定是否知道編譯器的作業方式,那么你將不知道它們的作業方式,”
——史蒂夫·耶格

無論你是新手還是經驗豐富的軟體開發人員,如果你不知道編譯器和解釋器的作業方式,那么你也不知道計算機的作業方式,就是這么簡單,
那么,你知道編譯器和解釋器如何作業嗎? 你是否100%確定知道它們的作業原理? 如果沒有的話:

或者,如果你確實不知道,并且你為此感到不安的話:

別擔心,如果你堅持不懈地學習本系列文章,并與我一起實作解釋器和編譯器,你將最終了解它們是如何作業的,

你為什么要學習解釋器和編譯器?我會給你三個理由,

1、要撰寫解釋器或編譯器,你必須具有很多需要結合使用的技能,撰寫解釋器或編譯器將幫助你提高這些技能,并成為更好的軟體開發人員,同樣,你學到的技能對于撰寫任何軟體(不僅僅是解釋器或編譯器)都很有用,
2、你想知道計算機如何作業,通常解釋器和編譯器看起來像魔術,你不應該對這種魔術感到滿意,你想揭露實作解釋器和編譯器的程序的神秘性,了解它們的作業方式并控制一切,
3、你要創建自己的編程語言或特定于某一領域的語言(domain specific language),如果創建一個,則你還需要為其創建解釋器或編譯器,最近,人們對新的編程語言重新產生了興趣,你幾乎可以每天看到一種新的編程語言:Elixir,Go和Rust等,

好的,但是解釋器和編譯器是什么?

解釋器或編譯器的目標是將某種高級語言的源程式轉換為其他形式,很模糊,不是嗎?請耐心等待,在本系列的后面部分,你將確切地了解源程式被翻譯成什么,

此時,你可能還想知道解釋器和編譯器之間的區別是什么,就本系列而言,如果我們將源程式翻譯成機器語言,則它是編譯器,如果我們在不先將其翻譯成機器語言的情況下處理和執行源程式,則它就是解釋器,看起來像這樣:

我希望到現在為止,你已經確信要學習并實作解釋器和編譯器,

你和我將為Pascal這門編程語言的大部分子集實作一個簡單的解釋器,在本系列的最后,你將擁有一個可運行的Pascal解釋器和一個源代碼除錯器,例如Python的pdb,

你可能會問,為什么是Pascal?一方面,這不是我在本系列中提出的一種組合語言:它是一種真正的編程語言,具有許多重要的語言構造(language constructs),還有一些古老但有用的CS書籍在其示例中使用Pascal編程語言(我了解,這不是選擇我們選擇實作Pascal解釋器的主要理由,但我認為學習一門非主流(non-mainstream)的編程語言也是很好的:)

這是Pascal中階乘函式的示例,你將能夠使用自己的解釋器對這段程式進行解釋,并使用我們實作的互動式源代碼除錯器進行除錯:

program factorial;

function factorial(n: integer): longint;
begin
    if n = 0 then
        factorial := 1
    else
        factorial := n * factorial(n - 1);
end;

var
    n: integer;

begin
    for n := 0 to 16 do
        writeln(n, '! = ', factorial(n));
end.

我們這里將使用Python來實作Pascal解釋器,你也可以使用任何所需的語言,因為實作解釋器的思路并不局限于任何特定的編程語言,
好吧,讓我們開始吧,預備,準備,開始!

我們的首次嘗試是撰寫簡單的算術運算式(arithmetic expressions)解釋器(也稱為計算器),今天的目標很容易:使你的計算器能夠處理個位數字的加法,比如3+5, 這是你的解釋器的源代碼:

# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF'


class Token(object):
    def __init__(self, type, value):
        # token type: INTEGER, PLUS, or EOF
        self.type = type
        # token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', or None
        self.value = https://www.cnblogs.com/Xlgd/p/value

    def __str__(self):
        """String representation of the class instance.

        Examples:
            Token(INTEGER, 3)
            Token(PLUS'+')
        """
        return 'Token({type}, {value})'.format(
            type=self.type,
            value=https://www.cnblogs.com/Xlgd/p/repr(self.value)
        )

    def __repr__(self):
        return self.__str__()


class Interpreter(object):
    def __init__(self, text):
        # client string input, e.g. "3+5"
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        # current token instance
        self.current_token = None

    def error(self):
        raise Exception('Error parsing input')

    def get_next_token(self):
        """Lexical analyzer (also known as scanner or tokenizer)

        This method is responsible for breaking a sentence
        apart into tokens. One token at a time.
        """
        text = self.text

        # is self.pos index past the end of the self.text ?
        # if so, then return EOF token because there is no more
        # input left to convert into tokens
        if self.pos > len(text) - 1:
            return Token(EOF, None)

        # get a character at the position self.pos and decide
        # what token to create based on the single character
        current_char = text[self.pos]

        # if the character is a digit then convert it to
        # integer, create an INTEGER token, increment self.pos
        # index to point to the next character after the digit,
        # and return the INTEGER token
        if current_char.isdigit():
            token = Token(INTEGER, int(current_char))
            self.pos += 1
            return token

        if current_char == '+':
            token = Token(PLUS, current_char)
            self.pos += 1
            return token

        self.error()

    def eat(self, token_type):
        # compare the current token type with the passed token
        # type and if they match then "eat" the current token
        # and assign the next token to the self.current_token,
        # otherwise raise an exception.
        if self.current_token.type == token_type:
            self.current_token = self.get_next_token()
        else:
            self.error()

    def expr(self):
        """expr -> INTEGER PLUS INTEGER"""
        # set current token to the first token taken from the input
        self.current_token = self.get_next_token()

        # we expect the current token to be a single-digit integer
        left = self.current_token
        self.eat(INTEGER)

        # we expect the current token to be a '+' token
        op = self.current_token
        self.eat(PLUS)

        # we expect the current token to be a single-digit integer
        right = self.current_token
        self.eat(INTEGER)
        # after the above call the self.current_token is set to
        # EOF token

        # at this point INTEGER PLUS INTEGER sequence of tokens
        # has been successfully found and the method can just
        # return the result of adding two integers, thus
        # effectively interpreting client input
        result = left.value + right.value
        return result


def main():
    while True:
        try:
            # To run under Python3 replace 'raw_input' call
            # with 'input'
            text = raw_input('calc> ')
        except EOFError:
            break
        if not text:
            continue
        interpreter = Interpreter(text)
        result = interpreter.expr()
        print(result)


if __name__ == '__main__':
    main()

將以上代碼保存為calc1.py,或直接從GitHub下載, 在開始深入地研究代碼之前,請在命令列上運行并查看其運行情況,
這是我的筆記本電腦上的一個運行效果(如果你使用的是Python3,則需要用input來替換raw_input):

$ python calc1.py
calc> 3+4
7
calc> 3+5
8
calc> 3+9
12
calc>

為了使你的簡單計算器正常作業而不會引發例外,你的輸入需要遵循某些規則:

1、輸入中僅允許一位數(single digit)的整數
2、目前唯一支持的算術運算是加法
3、輸入中的任何地方都不允許有空格
這些限制是使計算器簡單化所必需的,不用擔心,你很快就會使它變得非常復雜,

好的,現在讓我們深入了解一下解釋器的作業原理以及它如何計算算術運算式,

在命令列上輸入運算式3+5時,解釋器將獲得字串"3+5",為了使解釋器真正理解如何處理該字串,首先需要將輸入"3+5"分解為Token,Token是具有型別(type)和值(value)的物件(object),例如,對于字符"3",Token的型別將是INTEGER,而對應的值將是整數3,

將輸入字串分解為Token的程序稱為詞法分析(lexical analysis),因此,你的解釋器需要做的第一步是讀取字輸入字符并將其轉換為Token流,解釋器執行此操作的部分稱為詞法分析器(lexical analyzer),簡稱lexer,你可能還會遇到其他的名稱,例如 scanner或者tokenizer,它們的含義都一樣:解釋器或編譯器中將字符輸入轉換為Token流的部分,

Interpreter類的get_next_token函式是詞法分析器,每次呼叫它時,都會從字符輸入中獲得下一個Token,讓我們仔細看看這個函式,看看它如何完成將字符轉換為Token,字符輸入存盤在text變數中,pos變數是該字符輸入的索引(index)(將字串視為字符陣列), pos最初設定為0,并指向字符"3",函式首先檢查字符是否為數字,如果是數字,則遞增pos并回傳型別為INTEGER、值為整數3的Token:

pos現在指向文本中的"+"字符,下次呼叫該函式時,它將測驗pos所指的字符是否為數字,然后測驗該字符是否為加號,然后該函式遞增pos并回傳一個新創建的Token,其型別為PLUS,值為"+":

pos現在指向字符"5",當再次呼叫get_next_token函式時,將檢查它是否為數字,以便遞增pos并回傳一個新的Token,其型別為INTEGER,值為5:

現在pos索引已超過字串"3+5"的末尾,如果再呼叫get_next_token函式的話,將回傳一個型別為EOF的Token:

試試看,親自看看計算器的詞法分析器如何作業:

>>> from calc1 import Interpreter
>>>
>>> interpreter = Interpreter('3+5')
>>> interpreter.get_next_token()
Token(INTEGER, 3)
>>>
>>> interpreter.get_next_token()
Token(PLUS, '+')
>>>
>>> interpreter.get_next_token()
Token(INTEGER, 5)
>>>
>>> interpreter.get_next_token()
Token(EOF, None)
>>>

因此,既然解釋器現在可以訪問由輸入字符組成的Token流,那么解釋器就需要對其進行處理:它需要在從Token流中查找結構(structure),解釋器希望在Token流中找到以下結構:
INTEGER-> PLUS-> INTEGER
也就是說,它嘗試查找Token序列:先是一個整數,后面跟加號,最后再跟一個整數,

負責查找和解釋該結構的函式為expr,它驗證Token序列是否與預期的Token序列相對應,即INTEGER-> PLUS-> INTEGER,成功確認結構后,它會通過將PLUS左右兩側的Token的值相加來生成結果,從而成功解釋了傳遞給解釋器的算術運算式,

expr函式本身使用輔助函式(helper method)eat來驗證傳遞給eat函式的Token型別是否與當前正在處理的Token型別一致(match),在確保型別一致后,eat函式將獲取下一個Token并將其分配給current_token變數,從而有效地“消耗”已經驗證過的Token并在Token流中推進pos向前,如果Token流中的結構與預期的INTEGER PLUS INTEGER 序列不對應,那么eat函式將引發例外,

讓我們來回顧一下解釋器為決議算術運算式所做的事情:

1、解釋器接受輸入字串,例如"3+5"
2、解釋器呼叫expr函式以在詞法分析器get_next_token回傳的Token流中找到預期的結構,它嘗試查找的結構的形式為INTEGER PLUS INTEGER,查找到結構后,它就將輸入字符解釋為把兩個型別為INTEGER的Token的值加起來,也就是將兩個整數3和5相加,

恭喜你!剛剛學習了如何構實作你的第一個解釋器!

現在該做練習了:

1、修改代碼以允許輸入中包含多位數(multiple-digit)的整數,例如"12+3"
2、添加一種跳過空格的方法,以便計算器可以處理帶有"12 + 3"之類帶有空格的字符輸入
3、修改代碼,使計算器能夠處理減法,例如"7-5"

最后再來復習回憶一下:

1、什么是解釋器?
2、什么是編譯器?
3、解釋器和編譯器有什么區別?
4、什么是Token?
5、將輸入分解為Token的程序的名稱是什么?
6、解釋器中進行詞法分析的部分是什么?

今天到這就結束了,在下一篇文章中,我們將擴展計算器以處理更多的算術運算式,敬請關注,

PS:
這也是我第一次翻譯技術文章,如有錯誤和不恰當的地方,希望大家能及時批評指正,謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/2084.html

標籤:其他

上一篇:論文翻譯:2000_wideband extension of telephone speech using a hidden Markov model

下一篇:實作一個簡單的解釋器(2)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more