https://segmentfault.com/a/

前言

token package 包含了 golang 词法分析相关的数据结构和方法,源代码位于 <go-src>/src/go/token

token.go

源代码中的注释很赞!

Token type

Token is the set of lexical tokens of the Go programming language

1
type Token int

tokens

The list of tokens(token ids)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const (
// Special tokens
ILLEGAL Token = iota
EOF
COMMENT
literal_begin
...
literal_end
operator_beg
...
operator_end
keyword_beg
...
keyword_end
)

使用 const 定义了 Go 语言 tokens,这里有一个地方值得学习:使用 xxx_beg 和 xxx_end 这一对伪 token 作为不同的 token group 分界,方便快速判断 token 类型,比如判断 token id 是否是一个关键字

1
func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end }

接下来是 token 对应的字符串描述(token string) 和 上述的 const 一一对应

1
2
3
4
5
6
7
var tokens = [...]string {
ILLEGAL: "ILLEGAL",
EOF: "EOF",
COMMENT: "COMMENT",
...
}

根据 token id 查询 token string

查询 tokens 数组,在此之前检查数组越界

1
2
3
4
5
6
7
8
9
10
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
}

keywords

使用 map 保存关键字和 token id 对应关系

1
var keywords map[string]Token

查询一个 string 是标识符还是关键字

1
2
3
4
5
6
func Lookup(ident string) Token {
if tok, is_keyword := keywords[ident]; is_keyword {
return tok
}
return IDENT
}

position.go

Position type

Position describes an arbitrary source position including the file, line, and column location

1
2
3
4
5
6
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)
}

File type

A File is a handle for a file belonging to a FileSet,it has a name, size and line offset table

1
2
3
4
5
6
7
8
9
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 []int
infos []lineInfo
}
  • lines and infos are protected by set.mutex

  • lines contains the offset of the first character for each line (the first entry is always 0)

lines(line table)

有多种方法可以设置(初始化)File line table,比如根据文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (f *File) SetLinesForContent(content []byte) {
var lines []int
line := 0
for offset, b := range content {
if line >= 0 {
lines = append(lines, line)
}
line = -1
if b == '\n' {
line = offset + 1
}
}
// set lines table
f.set.mutex.Lock()
f.lines = lines
f.set.mutex.Unlock()
}
  • lines 被定义成一个 slice,由于初始化时 line = 0,所以第一个被添加到 lines 中的值为 0

  • 当检测到一个换行符 ‘n’ 时保存新行 offset + 1 到 line,这里 +1 是为了跳过 n 字符

  • 设置 File lines 字段时使用了 FileSet 的 mutex 进行锁保护

Pos type

Pos is a compact encoding of a source position within a file set.

1
type Pos int

这里的 Pos 容易和上文的 Position 混淆,Position 描述的是 File 内的位置信息,Pos 描述的是 FileSet 内的(编码后的)位置信息,它们之间可以相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) {
if p != NoPos {
if int(p) < f.base || int(p) > f.base+f.size {
panic("illegal Pos value")
}
pos = f.position(p, adjusted)
}
return
}
func (f *File) position(p Pos, adjusted bool) (pos Position) {
offset := int(p) - f.base
pos.Offset = offset
pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted)
return
}

如上文所述,File 的 base 属性代表 File 在 FileSet 中的”起始”位置,所以 position 方法中 int(p) - f.base 得到 p 所代表的 Position 在文件的偏移量,f.unpack 根据得到的偏移量对 lines 和 lineInfos 进行二分查找计算行,列偏移量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) {
filename = f.name
if i := searchInts(f.lines, offset); i >= 0 {
line, column = i+1, offset-f.lines[i]+1
}
if adjusted && len(f.infos) > 0 {
// almost no files have extra line infos
if i := searchLineInfos(f.infos, offset); i >= 0 {
alt := &f.infos[i]
filename = alt.Filename
if i := searchInts(f.lines, alt.Offset); i >= 0 {
line += alt.Line - i - 1
}
}
}
return
}

FileSet type

总结