之前的 Go 筆記系列,已經完成到了開發環境搭建,原本接下來的計劃就是到語法部分了,但後來一直沒有前進。主要是因為當時的工作比較忙,分散了精力,於是就暫時放下了。
最近,準備重新把之前計劃撿起來。
第一步,肯定是瞭解 Go 基礎語法部分。原本計劃是寫 Go 編碼的一些基礎知識,但純粹聊什麼是關鍵字、標識符、字面量、操作符實在有點無聊。
突然想到,詞法分析這塊知識還沒仔細研究過,那就從這個角度出發吧。通過逐步地拆解,將各個 token 進行歸類。
概述
我們知道,編譯型語言(比如 Go)的源碼要經過編譯和鏈接才能轉化為計算機可以執行的程序,這個過程的第一步就是詞法分析。
什麼是詞法分析呢?
它就是將源代碼轉化為一個個預先定義的 token。為了便於理解,我們將詞法分析分為兩個階段。
第一階段,對源碼串進行掃描,按預先定義的 token 規則進行匹配並切分為一個個有語法含義、最小單元的字符串,即詞素(lexme),並在此基礎上將其劃歸為某一類 token。這個階段,一些字符可能會被過濾掉,比如,空白符、註釋等。
第二階段,通過評估器 Evaluator 評估掃描出來的詞素,並確定它字面值,生成最終的 Token。
是不是有點不好理解呢?如果之前從未接觸過這塊內容,可能沒有直觀感受。其實,看著很複雜,但的確非常簡單。
一個簡單的示例
先看一段代碼,經典的 hello world,如下:
package main import "fmt" func main() { fmt.Println("Hello World") }
我們可以通過這個例子的源碼逐步拆解詞法分析的整個流程。
什麼是詞素
理論性的概念就不說了,直接看效果吧。
首先,將這段示例代碼通過詞法分析的第一階段,我們將會得到如下內容:
package main \n import "fmt" \n func main ( ) { \n fmt . Println ( "Hello World" ) \n }
輸出的這一個個獨立的字符序列就是詞素。
詞素的切分規劃和語言的語法規則有關。此處的輸出中除了一些可見的字符,換行符同樣也具有語法含義,因為 Go 不像 C/C++ 必須是分號分隔語句,也可以通過換行符分隔。
源碼分割為一個個詞素的過程是有一定的規則的,這和具體的語言有關。但雖有差異,其實規則都差不多,無非兩種,一是通過無語法含義的字符(空格符、製表符等)切分,還有是每個詞素可以用作為分隔符。
什麼是 token
token,也稱為詞法單元、記號等,它由名稱和字面值兩部分組成。從詞素到 token 有固定的對應關係,而且並非所有的 token 都有字面值。
將 hello world 的源碼轉化為 token,我們將會得到如下的一張對應表格。
稍微有點長,因為這裡沒有省略。表格中的第一列是原始內容,第二列對應的 token 的名稱,最後一列是 token 的字面值。
從表格中可以觀察出,其中有一些 token 並沒有值,比如,括號、點,名稱本身已經表示了它們的內容。
token 的分類
token 一般可以分為關鍵字、標識符、字面量、操作符這四個大類。這個分類其實在 Go 的源碼中有非常明顯的體現。
查看源碼文件 src/go/token/token.go[1],將會找到 Token 類型如下的幾個方法。
// 是否是字面常量 func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } // 是否是操作符 func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } // 是否是關鍵字 func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end }
代碼非常簡單,通過比較確定 Token 是否位於指定範圍確定它的類型。上面的這三個方法分別對應於判斷 Token 是字面常量、操作符還是關鍵字。
額?怎麼沒有標識符呢?
當然也有啦,只不過它不是 Token 的方法,而是單獨的一個函數。如下:
func IsIdentifier(name string) bool { for i, c := range name { if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) { return false } } return name != "" && !IsKeyword(name) }
我們常說的變量、常量、函數、方法的名稱不能為關鍵字,且必須是由字母、下劃線或數字組成,且名稱的開頭不能為數字的規則,看到這個函數是不是一些就明白了。
到這裡,其實已經寫的差不多了。但想想還是拿其中一個類型再簡單說說吧。
關鍵字
就以關鍵字為例吧,Go 中的關鍵字有哪些呢?
繼續看源碼。將之前那段如何判斷一個 token 是關鍵字的代碼再看一遍。如下:
func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end }
只要 Token 大於 keyword_beg 且小於 keyword_end 即為關鍵字,看起來還挺好理解的。那在 keyword_beg 和 keyword_end 之間有哪些關鍵字呢?代碼如下:
const ( ... keyword_beg // Keywords BREAK CASE CHAN CONST CONTINUE ... SELECT STRUCT SWITCH TYPE VAR keyword_end ... )
總共梳理出了 25 個關鍵字。如下:
break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var
關鍵字的確挺少的。可見。。。
嗯?!
是不是猜到我要說,Go 語言就是簡潔,關鍵字的都這麼少。你看 Java,足足有 53 個關鍵字,其中有兩個是保留字。
既然你猜到了,那我還是先不說了吧。
其他
操作符和字面常量就不追了,思路都是一樣的。
Go 中的操作符有 47 個,比如賦值運算符、位運算符、算術運算符,比較運算符,還有其他的操作符。相信我吧,都是數出來的,沒有看任何資料。[此處應該放個捂臉笑]。
字面常量呢?有 5 種類型,分別是 INT(整型)、FLOAT(浮點型)、IMG(複數類型)、CHAR(字符型)、STRING(字符串型),
總結
文章寫完了,前面扯了那麼一堆廢話,其實就只是為了介紹 Go 語法中用到的關鍵字、標識符、運算符、字面量從哪裡找。並且,最終它們如何使用也沒有怎麼說明。
純粹為了好玩嗎?當然不是(是)。因為。。。,先不劇透了,避免後面尷尬。
閱讀資料
Go 程序是怎麼跑起來的[2]
go-lexer 詞法分析[3]
Lexical analysis[4]
詞法分析[5]
參考資料
[1]
src/go/token/token.go: https://github.com/golang/go/blob/master/src/go/token/token.go
[2]
Go 程序是怎麼跑起來的: https://zhuanlan.zhihu.com/p/71993748
[3]
go-lexer 詞法分析: https://studygolang.com/articles/6708
[4]
Lexical analysis: https://en.wikipedia.org/wiki/Lexical_analysis#Evaluator
[5]
詞法分析: https://cs.nju.edu.cn/changxu/2_compiler/slides/Chapter_3.pdf