Go 語言的詞法分析和語法分析(2)—Import宣告的解析

godruoyi 發表於 2021-03-26
Go

上篇文章 Go 語言的詞法分析和語法分析(1) 作者闡述了漸漸式詞法分析的過程;通過這一步,原始檔已被轉化為 Syntax.File 結構體,其屬性情況如下:

go編譯原理

File 結構體中的 PkgName 屬性已被初始化為 syntax.Name{Value: “main”}。Go 語言掃描器 Scanner 再處理完包名 PkgName 後,下一次解析將掃描出 import token;此時 *scanner 的 b, r, e 屬性如下圖所示:

go編譯原理

同樣,通過 buf[b:r] 計算出當前掃描的 token 為 import 字串,對比 Go 關鍵字列表後將 tok 設定為內建 _Import,此時 *scanner 內部各屬性如下圖所示:

go編譯原理

然後掃描器將開始迴圈解析原始檔的 import 宣告,直至掃描到的 token 不等於 _Import,並把 import 的 path 部分(如 net/http, main)等儲存到掃描器的 DeclList 陣列(slice) 中。

for p.got(_Import) {
    f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
}

func (p *parser) got(tok token) bool {
    if p.tok == tok {
        p.next()
        return true
    }
    return false
}

Import 有多種不同格式的宣告,常見文法有:

import . path/package
import alias path/package
import path/package
import _ path/package

// by group
import (
    . path/package
    alias path/package
    path/package
    _ path/package
)

Go 語言內部用 ImportDecl 結構體來描述 import 宣告,其結構如下:

ImportDecl struct {
    Group *Group       // 分組,同一括號下的為同一組
    LocalPkgName *Name // 別名
    Path *BasicLit     // 路徑,如 net/http
}

在進行 import 詞法解析時, Go 語言會嘗試初始化 ImportDecl 結構體併為其各屬性賦值;下面作者針對不同情況來檢視賦值後的 ImportDecl 結構體內容。

  • 假設申明語句為 import "net/http",解析完該語句後結構體內容如下:

go編譯原理

結構體中的 PkgName 屬性(包別名)被設定為 syntax.Name{Value: ""},Group 被設定為 nil,Path 部分被設定為 syntax.BasicLit{Value: "net/http"}

  • 假設申明語句為 import alias "net/http",解析完該語句後結構體內容如下:

go編譯原理

  • 假設申明語句為 import . "net/http",解析完該語句後結構體內容如下:

go編譯原理

  • 假設申明語句為下面分組
import (
    "fmt"
    "net/http"
)

解析完該語句後結構體內容如下(多個 *ImportDecl 指向同一 Group):

go編譯原理

至此,Import 語法申明已基本解析完畢,接下來 Go 語言將解析 const, val, func, typeTopLevelDecl 級別的宣告;畢竟 Go 語言的頂級申明就只有這六個關鍵字,全部解析完畢後,Go 語言的詞法語法分析就完成了。

package
import
const
var
type
func

針對上篇文章 Go 語言的詞法分析和語法分析(1) 中的 main.go 檔案,進過這一步解析後,syntax.File 結構體資訊如下:

go編譯原理

下面是 Go 語言處理完 import 宣告後開始處理其他 TopLevelDecl 的函式程式碼,程式碼所在位置 src/cmd/compile/internal/syntax/parser.go:399

// { ImportDecl ";" }
for p.got(_Import) {
    f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
}

// { TopLevelDecl ";" }
for p.tok != _EOF {
    switch p.tok {
    case _Const:
        // 處理常量
    case _Type:
        // 處理型別定義
    case _Var:
        // 處理變數
    case _Func:
        // 處理函式
    default:
        p.syntaxError("non-declaration statement outside function body")
    }
}

從這裡也能看出,import 宣告只能放在 package 宣告後面,其他頂級宣告前面;因為在 import 宣告解析完成後,掃描器將進入下一個 for 迴圈繼續解析其他 TopLevelDecl,若這時再遇到 _Import token,程式將走到 switch 的 default 分支,從而發生編譯錯誤。

./prog.go:11:1: syntax error: non-declaration statement outside function body

文章釋出於作者部落格二愣的閒談雜魚,歡迎大家來觀摩 Go 語言編譯原理 — Import宣告的解析

接下來閱讀

下篇文章將探討 Go 語言對常量的解析,這部分作者將在後面的文章中繼續輸出。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
二愣的閒談雜魚