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
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 ( 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 Offset int Line int Column int }
|
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 base int size int lines []int infos []lineInfo }
|
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 } } 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.
这里的 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 { 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
总结