從詞法分析角度看 Go 代碼的組成

以前的 Go 筆記系列,已經完成到了開發環境搭建,本來接下來的計劃就是到語法部分了,但後來一直沒有前進。主要是由於當時的工做比較忙,分散了精力,因而就暫時放下了。git

最近,準備從新把以前計劃撿起來。github

第一步,確定是瞭解 Go 基礎語法部分。本來計劃是寫 Go 編碼的一些基礎知識,但純粹聊什麼是關鍵字、標識符、字面量、操做符實在有點無聊。golang

忽然想到,詞法分析這塊知識還沒仔細研究過,那就從這個角度出發吧。經過逐步地拆解,將各個 token 進行歸類。ide

概述

咱們知道,編譯型語言(好比 Go)的源碼要通過編譯和連接才能轉化爲計算機能夠執行的程序,這個過程的第一步就是詞法分析。函數

什麼是詞法分析呢?編碼

它就是將源代碼轉化爲一個個預先定義的 token 的過程。爲了便於理解,咱們將其分爲兩個階段進行介紹。lua

第一階段,對源碼串進行掃描,按預先定義的 token 規則進行匹配並切分爲一個個有語法含義、最小單元的字符串,即詞素(lexme),並在此基礎上將其劃歸爲某一類 token。這個階段,一些字符可能會被過濾掉,好比,空白符、註釋等。spa

第二階段,經過評估器 Evaluator 評估掃描出來的詞素,並肯定它字面值,生成最終的 Token。code

是否是有點很差理解呢?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,咱們將會獲得以下的一張對應表格。

lexme name value
package PACKAGE "package"
main IDENT "main"
\n SEMICOLON "\n"
import IMPORT "import"
"fmt" STRING "\"fmt\""
\n SEMICOLON "\n"
func FUNC "func"
main IDENT "main"
( LPAREN ""
) RPAREN ""
{ LBRACE ""
fmt IDENT "fmt"
. PERIOD ""
Println IDENT "Println"
( LPAREN ""
"Hello World" STRING ""Hello World""
) RPAREN ""
\n SEMICOLON "\n"
} LBRACE ""
\n SEMICOLON "\n"

稍微有點長,由於這裏沒有省略。表格中的第一列是原始內容,第二列對應的 token 的名稱,最後一列是 token 的字面值。

從表格中能夠觀察出,其中有一些 token 並無值,好比,括號、點,名稱自己已經表示了它們的內容。

token 的分類

token 通常能夠分爲關鍵字、標識符、字面量、操做符這四個大類。這個分類其實在 Go 的源碼中有很是明顯的體現。

查看源碼文件 src/go/token/token.go,將會找到 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_begkeyword_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,連保留字都沒有,就是這麼自信。

既然你猜到了,那我仍是先不說了吧。

其餘

操做符和字面常量就不追了,思路都是同樣的。

Go 中的操做符有 47 個,好比賦值運算符、位運算符、算術運算符,比較運算符,還有其餘的操做符。相信我吧,都是從源碼中數出來的,沒有看任何資料。[此處應該放個捂臉笑]。

字面常量呢?

有 5 種類型,分別是 INT(整型)、FLOAT(浮點型)、IMG(複數類型)、CHAR(字符型)、STRING(字符串型)。

總結

文章寫完了,前面扯了那麼一堆廢話,其實就只是爲了介紹 Go 語法中用到的關鍵字、標識符、運算符、字面量從哪裏找。而且,最終它們如何使用也沒有怎麼說明。

純粹爲了好玩嗎?固然不是(是)。由於。。。,先不劇透了,避免後面尷尬。

閱讀資料

Go 程序是怎麼跑起來的

go-lexer 詞法分析

Lexical analysis

詞法分析

相關文章
相關標籤/搜索