GO/ast編程--編譯原理學(xué)習之詞法分析

目錄

鏈接地址

1.編譯器和解釋器

簡單來說皂岔,一個編譯器就是一個程序,它可以把以某一語言編寫的程序(c代碼寫的源程序)翻譯成一個等價的关划、另一種語言編寫的程序(匯編代碼的目標程序)


image.png

那么用戶可以利用目標程序處理輸入產(chǎn)生輸出

解釋器它并不通過翻譯方式產(chǎn)生目標程序,而是直接攜帶用戶輸入返回輸出(典型如php經(jīng)過瀏覽器直接顯示結(jié)果)
image.png

但兩者內(nèi)部都是大同小異

1.1編譯器結(jié)構(gòu)

編譯器的翻譯過程可以看作兩個部分:

  1. 分析部分,也被叫做編譯器的前端栓辜,主要負責將字符輸入流轉(zhuǎn)為token永高,進一步轉(zhuǎn)為ast抽象語法樹隧土,步驟包括:
  • 詞法分析 生成token/記號
  • 語法分析 生成ast抽象語法樹
  • 語義分析 生成中間表示


    image.png
  1. 綜合部分,也被叫做編譯器的后端命爬,主要負責翻譯成目標機器語言


    image.png

1.2簡單的編譯器實例

b站加法編譯為匯編demo

2.詞法分析

詞法分析是編譯的第一階段曹傀,主要任務(wù)是讀取字符流,將他們轉(zhuǎn)為記號(也有說是token/單詞等)流饲宛,記號是編譯器內(nèi)部定義的數(shù)據(jù)結(jié)構(gòu)皆愉,編碼所識別出的詞法單元
如c程序

if (x > 5)
  y = "hello";
else
  z=1;

可以被詞法分析成如下
image.png
  • IF 對應(yīng)關(guān)鍵字if
  • ()對應(yīng)LPAREN和RPAREN
  • x 對應(yīng) 標識IDENT,同時還要攜帶上名字(x)
  • 大于號對應(yīng)GT
    艇抠。幕庐。。家淤。异剥。。
  • 最后加上文件結(jié)束符EOF

2.1 C語言中記號的數(shù)據(jù)結(jié)構(gòu)定義

關(guān)鍵字絮重、符號等被定義在枚舉中

enum kind {IF,LPAREN,ID,...};

他們分別對應(yīng)唯一的數(shù)字冤寿,這也是對單詞的分類

struct token {
   enum kind k;
   char *lexeme;
}

token是這些記號在c語言中的結(jié)構(gòu)體(lexeme是識別出的單詞具體值,0標識沒有賦予任何值,通常用于關(guān)鍵字)青伤,那么如下語句

if (x>5)

就會被分析成如下偽代碼

token{k=IF,lexeme=0} 
token{k=LPAREN,lexeme=0} 
token{k=IDENT,lexeme=x}
token{k=GT,lexeme=0}
token{k=RPAREN,lexeme=0}

2.2詞法分析器的實現(xiàn)方法

主要兩種:手工編碼實現(xiàn)法和詞法分析器的生成器

2.2.1手工編碼實現(xiàn)法

利用轉(zhuǎn)移圖識別標識符督怜、關(guān)鍵字和符號,來返回對應(yīng)的token數(shù)據(jù)結(jié)構(gòu)潮模,這是手工編碼的方法之一

2.2.1自動生成記號

  1. 利用正則表達式
  2. 有限狀態(tài)自動機

從字符流->正則表達式->記號流的轉(zhuǎn)換過程如下:
字符流->正則表達式RA->ANF->有限狀態(tài)機DNF->記號流
這只是其中的一種例舉手段亮蛔,并不是所有語言全都這個流程

3.go詞法分析

我們知道golang的編譯器自身也是用golang語言開發(fā)的(自舉),也就意味著我們能在源碼找到go的記號擎厢。在src的源碼中有兩處定義究流,一處是用于編譯器前端解析的src/cmd目錄下的源碼辣吃,另一處就是src/go目錄下的源碼,兩者實現(xiàn)邏輯不太一樣但原理一樣芬探。官方的獨立cli程序gofmt引用的就是后者神得,原理就是把代碼解析成ast語法樹,然后再把ast轉(zhuǎn)回代碼(這也是我們學(xué)習ast的目標)偷仿,出于學(xué)習ast編程的目的哩簿,我們選擇后者:

  • go/token:Token類型以及相關(guān)結(jié)構(gòu)體的定義
  • go/scanner:詞法分析成Token序列

3.1go的記號

go中的記號被叫做token,位于src/go/token/token.go和c語言中的一樣使用整形“代替”

type token int

和上面c定義記號一樣使用枚舉酝静,每個類型的字面值都有對應(yīng)的token

const (
    // Special tokens
    ILLEGAL Token = iota
    EOF
    COMMENT

    literal_beg
    // Identifiers and basic type literals
    // (these tokens stand for classes of literals)
    IDENT  // main
    INT    // 12345
    FLOAT  // 123.45
    IMAG   // 123.45i
    CHAR   // 'a'
    STRING // "abc"
    literal_end

    operator_beg
    // Operators and delimiters
    ADD // +
    SUB // -
    MUL // *
    QUO // /
    REM // %

不難看出go中的token主要有標識符节榜、關(guān)鍵字、運算符别智、分隔符等類型

  • 標識符指go對各種變量宗苍、方法、函數(shù)等命名時使用的字符序列


    image.png

    前三個是特殊類型的token薄榛,分別是錯誤讳窟、文件結(jié)束和注釋,當遇到不能識別的token時敞恋,即源文件存在詞法錯誤丽啡,統(tǒng)一返回ILLEGAL
    所有token還會被放入到一個string切片中,用于String方法被快速打印

func (tok Token) String() string {
    s := ""
    if 0 <= tok && tok < Token(len(tokens)) {
        s = tokens[tok]
    }
    if s == "" {
        s = "token(" + strconv.Itoa(int(tok)) + ")"
    }
    return s
}
var tokens = [...]string{
    ILLEGAL: "ILLEGAL",

    EOF:     "EOF",
    COMMENT: "COMMENT",

    IDENT:  "IDENT",

go/token/position.go當中定義了token相關(guān)位置硬猫,也就是屬性补箍,它也有String方法,可以被直接打印

type Position struct {
    Filename string // filename, if any
    Offset   int    // offset, starting at 0
    Line     int    // line number, starting at 1
    Column   int    // column number, starting at 1 (byte count)
}

顧名思義浦徊,標識token在文件中的位置馏予,為什么要有這個屬性?因為go程序可以由多個包鏈接成一個可執(zhí)行文件盔性,單個包又對應(yīng)多個文件霞丧,因此position.go還定義了FilSet和File結(jié)構(gòu)體,用于描述文件集和文件

type FileSet struct {
   mutex sync.RWMutex // protects the file set
   base  int          // base offset for the next file
   files []*File      // list of files in the order added to the set
   last  *File        // cache of last file looked up
}

根據(jù)注釋內(nèi)容冕香,我們得到以下信息:

  • FileSet文件集代表一組源文件
  • 文件集可能被多個協(xié)程同步調(diào)用蛹尝,因此需要讀寫鎖
  • base和size:這個base不是上面這個成員base。文件集中每個文件的字節(jié)偏移映射到不同的整數(shù)區(qū)間中[base悉尾,base+size]突那。base表示文件中的第一個字節(jié),size表示大小
    image.png
  • Pos:是上面一個區(qū)間中的一個值,通過確定pos值所屬的區(qū)間构眯,可以計算出文件的位置信息愕难,即Pos值可以轉(zhuǎn)換成token完整的position
type Pos int
image.png
  • files切片中保存了一個文件集下所有的File文件,利用base來標識文件File的偏移量,base計算從1開始猫缭,因為第0個位置被EOF占了葱弟,因此調(diào)用FileSet.AddFile()函數(shù)來添加新文件時,必須提供文件基數(shù)file base猜丹,這個值可以是任何整數(shù)芝加,但必須超過文件集中已存在的任何值。FileSet.Base()函數(shù)滿足這樣的值(FileSet.base)射窒,就是與Pos間隔的末位+1藏杖,通常也都是用這個函數(shù)作為參數(shù)輸入AddFile中

FileSet常用函數(shù)的簽名如下:

func NewFileSet() *FileSet
func (s *FileSet) AddFile(filename string, base, size int) *File
func (s *FileSet) Base() int

最后來看文件File

type File struct {
   set  *FileSet
   name string // file name as provided to AddFile
   base int    // Pos value range for this file is [base...base+size]
   size int    // file size as provided to AddFile

   // lines and infos are protected by mutex
   mutex sync.Mutex
   lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
   infos []lineInfo
}

type lineInfo struct {
   Offset       int
   Filename     string
   Line, Column int
}
  • File中的lines可以用來計算出Token的行號和列號
  • lineinfo用于存儲每個token的行列號

3.2詞法分析部分

位于go/scanner/scanner.go中,該包提供了Scanner實現(xiàn)Token掃描脉顿,是在FileSet和File抽象文件集合基礎(chǔ)上進行的蝌麸,

type Scanner struct {
    // immutable state
    file *token.File  // source file handle
    dir  string       // directory portion of file.Name()
    src  []byte       // source
    err  ErrorHandler // error reporting; or nil
    mode Mode         // scanning mode

    // scanning state
    ch         rune      // current character
    offset     int       // character offset
    rdOffset   int       // reading offset (position after current character)
    lineOffset int       // current line offset
    insertSemi bool      // insert a semicolon before next newline
    nlPos      token.Pos // position of newline in preceding comment

    // public state - ok to modify
    ErrorCount int // number of errors encountered
}

常用方法有三個

func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode)
func (s *Scanner) next()
func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string)
  • Init就是對Scanner對象的賦值初始化,再內(nèi)部調(diào)用next函數(shù)
  • next是獲取下一個要解析的字符艾疟,把它讀進scanner.ch中祥楣,因為GOlang支持utf-8編碼的源程序,所以next讀取的是字符而不是字節(jié)
  • Scan是詞法分析的核心方法汉柒,如上面的解釋,它是一個switch/case的狀態(tài)機责鳍,每一次調(diào)用都返回一個token

3.2.1使用案例

我們看同目錄下example.go下提供的案例來看如何解析"cos(x) + 1i*sin(x) // Euler"這句

func ExampleScanner_Scan() {
    src := []byte("cos(x) + 1i*sin(x) // Euler")

    var s scanner.Scanner
       //創(chuàng)建文件集FileSet碾褂,token的位置必須通過文件集定位
    fset := token.NewFileSet()  
//向文件集中添加新文件File,文件名為空""
    file := fset.AddFile("", fset.Base(), len(src))
//初始化掃描器,第三個參數(shù)表示自定義錯誤處理函數(shù)
//最后一個參數(shù)表示不用忽略注釋token
    s.Init(file, src, nil, scanner.ScanComments)

//循環(huán)讀取字符流解析token
    for {
        pos, tok, lit := s.Scan()
        if tok == token.EOF {
            break
        }
//打印token的位置
        fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
    }

    // output:
    // 1:1  IDENT   "cos"
    // 1:4  (   ""
    // 1:5  IDENT   "x"
    // 1:6  )   ""
    // 1:8  +   ""
    // 1:10 IMAG    "1i"
    // 1:12 *   ""
    // 1:13 IDENT   "sin"
    // 1:16 (   ""
    // 1:17 IDENT   "x"
    // 1:18 )   ""
    // 1:20 COMMENT "http:// Euler"
    // 1:28 ;   "\n"
}

這個Scanner對象就是詞法分析器历葛,它的主要方法是Scan正塌,主流程如下,是一個switch case的有限狀態(tài)確定機:

  • 碰到字符掃調(diào)用sacnString掃描
  • 碰到數(shù)字調(diào)用scanNumber方法掃描恤溶,同上
  • 碰到計算符同上
    乓诽。。咒程。鸠天。。
  • 每次返回一個被掃描的token,壓縮表示的位置和字面值的字符串

代碼太長就不貼出了帐姻,回頭再看scanner對象

type Scanner struct {
   // immutable state
   file *token.File  // source file handle
   dir  string       // directory portion of file.Name()
   src  []byte       // source
   err  ErrorHandler // error reporting; or nil
   mode Mode         // scanning mode

   // 詞法分析器使用的核心變量
   ch         rune // 當前字符
   offset     int  // 字符偏移量
   rdOffset   int  // 可以理解為ch的偏移量
   lineOffset int  // 當前的行偏移量
   insertSemi bool // 在換行前插入分號

   // public state - ok to modify
   ErrorCount int // number of errors encountered
}

遍歷完當前token后稠集,ch會指向下一個字符,實際上就是scanner.next方法把下一個字符讀進這個變量

3.3語法錯誤檢驗

詞法分析只輸出token饥瓷,并不檢測語法錯誤

func main() {
    str :=
        `
package main

func main() {
    a = 1
}
    `
    src := []byte(str)
    var s scanner.Scanner
    fset := token.NewFileSet()
    file := fset.AddFile("my.go", fset.Base(), len(src))
    s.Init(file, src, func(pos token.Position, msg string) {
        fmt.Print(msg)
    }, scanner.ScanComments)
    for {
        pos, tok, lit := s.Scan()
        if tok == token.EOF {
            break
        }
        fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
    }
}

my.go:2:1       package "package"
my.go:2:9       IDENT   "main"
my.go:2:13      ;       "\n"  
my.go:4:1       func    "func"
my.go:4:6       IDENT   "main"
my.go:4:10      (       ""    
my.go:4:11      )       ""    
my.go:4:13      {       ""    
my.go:5:2       IDENT   "a"   
my.go:5:4       =       ""    
my.go:5:6       INT     "1"   
my.go:5:7       ;       "\n"  
my.go:6:1       }       ""    
my.go:6:2       ;       "\n" 

至此剥纷,go詞法分析探索完結(jié)

參考

1.mooc的中科大《編譯原理》

  1. go詞法分析刨析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市呢铆,隨后出現(xiàn)的幾起案子晦鞋,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悠垛,死亡現(xiàn)場離奇詭異线定,居然都是意外死亡,警方通過查閱死者的電腦和手機鼎文,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門渔肩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拇惋,你說我怎么就攤上這事周偎。” “怎么了撑帖?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵蓉坎,是天一觀的道長。 經(jīng)常有香客問我胡嘿,道長蛉艾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任衷敌,我火速辦了婚禮勿侯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缴罗。我一直安慰自己助琐,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布面氓。 她就那樣靜靜地躺著兵钮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舌界。 梳的紋絲不亂的頭發(fā)上掘譬,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音呻拌,去河邊找鬼葱轩。 笑死,一個胖子當著我的面吹牛藐握,可吹牛的內(nèi)容都是我干的酿箭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼趾娃,長吁一口氣:“原來是場噩夢啊……” “哼缭嫡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抬闷,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妇蛀,失蹤者是張志新(化名)和其女友劉穎耕突,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體评架,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡眷茁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纵诞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片上祈。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浙芙,靈堂內(nèi)的尸體忽然破棺而出登刺,到底是詐尸還是另有隱情,我是刑警寧澤嗡呼,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布纸俭,位于F島的核電站,受9級特大地震影響南窗,放射性物質(zhì)發(fā)生泄漏揍很。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一万伤、第九天 我趴在偏房一處隱蔽的房頂上張望窒悔。 院中可真熱鬧,春花似錦敌买、人聲如沸蛉迹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荐操,卻和暖如春芜抒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背托启。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工宅倒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屯耸。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓拐迁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疗绣。 傳聞我的和親對象是個殘疾皇子线召,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容