go/token - Token 和位置信息
go/token 包提供了 Go 词法 token 的定义和位置信息的管理功能,是 Go 源码处理的基础组件。
概述
go/token 包定义了 Go 语言的词法 token、位置信息(Pos)和文件集(FileSet),用于词法分析、语法分析和错误报告。
包导入:
import (
"go/token"
"fmt"
)
基本使用:
// 1. 创建 FileSet
fset := token.NewFileSet()
// 2. 添加文件
file := fset.AddFile("main.go", fset.Base(), 100)
// 3. 获取 token 信息
tok := token.FUNC
fmt.Printf("Token: %s\n", tok)
fmt.Printf("字符串:%q\n", tok.String())
fmt.Printf("是否关键字:%v\n", tok.IsKeyword())
// 4. 位置转换
pos := file.Pos(10)
fmt.Printf("位置:%s\n", fset.Position(pos))
典型示例:
示例 1:使用 FileSet 管理位置:
package main
import (
"fmt"
"go/token"
)
func main() {
// 创建 FileSet
fset := token.NewFileSet()
// 添加多个文件
file1 := fset.AddFile("main.go", fset.Base(), 100)
file2 := fset.AddFile("util.go", fset.Base(), 200)
// 获取位置
pos1 := file1.Pos(10)
pos2 := file2.Pos(20)
// 转换为可读位置
fmt.Printf("main.go: %s\n", fset.Position(pos1))
fmt.Printf("util.go: %s\n", fset.Position(pos2))
// 获取文件信息
fmt.Printf("文件数:%d\n", fset.FileCount())
}
运行:
$ go run main.go
main.go: main.go:1:11
util.go: util.go:1:21
文件数:2
示例 2:Token 操作:
package main
import (
"fmt"
"go/token"
)
func main() {
// 各种 token
tokens := []token.Token{
token.FUNC,
token.VAR,
token.IDENT,
token.INT,
token.STRING,
token.ADD,
token.ASSIGN,
token.LPAREN,
token.EOF,
}
for _, tok := range tokens {
fmt.Printf("%-10s 字符串:%-10q 关键字:%v 字面量:%v\n",
tok,
tok.String(),
tok.IsKeyword(),
tok.IsLiteral(),
)
}
}
运行:
$ go run main.go
func 字符串:"func" 关键字:true 字面量:false
var 字符串:"var" 关键字:true 字面量:false
IDENT 字符串:"IDENT" 关键字:false 字面量:true
INT 字符串:"INT" 关键字:false 字面量:true
STRING 字符串:"STRING" 关键字:false 字面量:true
+ 字符串:"+" 关键字:false 字面量:false
= 字符串:"=" 关键字:false 字面量:false
( 字符串:"(" 关键字:false 字面量:false
EOF 字符串:"EOF" 关键字:false 字面量:false
一、Token 类型
Token 类型定义
Token
定义:
type Token int
说明:
- 表示 Go 语言的词法 token
- 包括关键字、标识符、字面量、操作符等
特殊 Token
ILLEGAL
定义:
const ILLEGAL Token = iota
说明:
- 非法 token
- 表示无法识别的字符序列
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.ILLEGAL
fmt.Printf("Token: %s\n", tok)
fmt.Printf("值:%d\n", tok)
}
EOF
定义:
const EOF Token = -(iota + 1)
说明:
- 文件结束标记
- 值为负数,避免与有效 token 冲突
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.EOF
fmt.Printf("Token: %s\n", tok)
fmt.Printf("值:%d\n", tok)
}
运行:
$ go run main.go
Token: EOF
值:-1
COMMENT
定义:
const COMMENT Token = -(iota + 2)
说明:
- 注释 token
- 包括单行注释(//)和多行注释(/* */)
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.COMMENT
fmt.Printf("Token: %s\n", tok)
fmt.Printf("是否字面量:%v\n", tok.IsLiteral())
}
字面量 Token
IDENT
定义:
const IDENT Token = -(iota + 3)
说明:
- 标识符 token
- 变量名、函数名、类型名等
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.IDENT
fmt.Printf("Token: %s\n", tok)
fmt.Printf("是否字面量:%v\n", tok.IsLiteral())
}
INT
定义:
const INT Token = -(iota + 4)
说明:
- 整数字面量
- 包括十进制、八进制、十六进制
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.INT
fmt.Printf("Token: %s\n", tok)
fmt.Printf("字面量类型:%s\n", tok.LitString())
}
FLOAT
定义:
const FLOAT Token = -(iota + 5)
说明:
- 浮点数字面量
- 包括小数和科学计数法
IMAG
定义:
const IMAG Token = -(iota + 6)
说明:
- 虚数字面量
- 如:1i, 2.5i
CHAR
定义:
const CHAR Token = -(iota + 7)
说明:
- 字符字面量
- 单引号括起来的字符
STRING
定义:
const STRING Token = -(iota + 8)
说明:
- 字符串字面量
- 双引号括起来的字符串
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tok := token.STRING
fmt.Printf("Token: %s\n", tok)
fmt.Printf("是否字面量:%v\n", tok.IsLiteral())
}
关键字 Token
PACKAGE
定义:
const PACKAGE Token = -(iota + 9)
说明:
- package 关键字
FUNC
定义:
const FUNC Token = -(iota + 10)
说明:
- func 关键字
VAR
定义:
const VAR Token = -(iota + 11)
说明:
- var 关键字
CONST
定义:
const CONST Token = -(iota + 12)
说明:
- const 关键字
TYPE
定义:
const TYPE Token = -(iota + 13)
说明:
- type 关键字
IMPORT
定义:
const IMPORT Token = -(iota + 14)
说明:
- import 关键字
其他关键字
// 控制流
const (
BREAK Token = -(iota + 15)
CASE Token = -(iota + 16)
CONTINUE Token = -(iota + 17)
DEFAULT Token = -(iota + 18)
DEFER Token = -(iota + 19)
ELSE Token = -(iota + 20)
FALLTHROUGH Token = -(iota + 21)
FOR Token = -(iota + 22)
GO Token = -(iota + 23)
GOTO Token = -(iota + 24)
IF Token = -(iota + 25)
RANGE Token = -(iota + 26)
RETURN Token = -(iota + 27)
SELECT Token = -(iota + 28)
SWITCH Token = -(iota + 29)
)
// 类型关键字
const (
INTERFACE Token = -(iota + 30)
STRUCT Token = -(iota + 31)
MAP Token = -(iota + 32)
CHANNEL Token = -(iota + 33)
)
// 其他
const (
TRUE Token = -(iota + 34)
FALSE Token = -(iota + 35)
NIL Token = -(iota + 36)
)
操作符 Token
ADD
定义:
const ADD Token = iota + 1
说明:
- 加法操作符(+)
SUB
定义:
const SUB Token = iota + 2
说明:
- 减法操作符(-)
MUL
定义:
const MUL Token = iota + 3
说明:
- 乘法操作符(*)
QUO
定义:
const QUO Token = iota + 4
说明:
- 除法操作符(/)
REM
定义:
const REM Token = iota + 5
说明:
- 取余操作符(%)
其他操作符
// 逻辑操作符
const (
LAND Token = iota + 6 // &&
LOR // ||
NOT // !
)
// 位操作符
const (
AND Token = iota + 9 // &
OR // |
XOR // ^
SHL // <<
SHR // >>
AND_NOT // &^
)
// 赋值操作符
const (
ASSIGN Token = iota + 15 // =
DEFINE // :=
ADD_ASSIGN // +=
SUB_ASSIGN // -=
MUL_ASSIGN // *=
QUO_ASSIGN // /=
REM_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
XOR_ASSIGN // ^=
SHL_ASSIGN // <<=
SHR_ASSIGN // >>=
AND_NOT_ASSIGN // &^=
)
// 比较操作符
const (
EQL Token = iota + 27 // ==
LSS // <
GTR // >
ASSIGN // =
NEQ // !=
LEQ // <=
GEQ // >=
)
// 其他
const (
INC Token = iota + 34 // ++
DEC // --
ARROW // <-
DOT // .
COMMA // ,
SEMICOLON // ;
COLON // :
LPAREN // (
LBRACK // [
LBRACE // {
RPAREN // )
RBRACK // ]
RBRACE // }
)
二、Pos 类型
位置类型
Pos
定义:
type Pos int
说明:
- 表示源码中的位置
- 是相对于 FileSet 基址的偏移量
方法:
IsValid() bool- 检查位置是否有效Offset() int- 获取偏移量
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
fset := token.NewFileSet()
file := fset.AddFile("test.go", fset.Base(), 100)
// 获取位置
pos := file.Pos(10)
fmt.Printf("位置:%d\n", pos)
fmt.Printf("是否有效:%v\n", pos.IsValid())
fmt.Printf("偏移量:%d\n", pos.Offset(fset.Base()))
}
三、File 和 FileSet
File 结构体
File
定义:
type File struct {
// 内部字段
}
说明:
- 表示一个源文件
- 包含文件的位置信息
方法:
Name() string- 文件名Base() int- 基址Size() int- 文件大小Pos(offset int) Pos- 从偏移量获取 PosOffset(pos Pos) int- 从 Pos 获取偏移量Line(pos Pos) int- 获取行号Position(pos Pos) Position- 获取完整位置
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
fset := token.NewFileSet()
file := fset.AddFile("main.go", fset.Base(), 1000)
fmt.Printf("文件名:%s\n", file.Name())
fmt.Printf("基址:%d\n", file.Base())
fmt.Printf("大小:%d\n", file.Size())
// 获取行号
pos := file.Pos(50)
fmt.Printf("位置 %d 的行号:%d\n", pos, file.Line(pos))
}
FileSet 结构体
FileSet
定义:
type FileSet struct {
// 内部字段
}
说明:
- 管理多个文件的位置信息
- 为每个文件分配不重叠的位置范围
方法:
NewFileSet() *FileSet- 创建新的 FileSetBase() int- 获取下一个基址AddFile(filename string, base int, size int) *File- 添加文件File(pos Pos) *File- 获取位置对应的文件Position(pos Pos) Position- 获取完整位置FileCount() int- 获取文件数量
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
fset := token.NewFileSet()
// 添加多个文件
file1 := fset.AddFile("main.go", fset.Base(), 1000)
file2 := fset.AddFile("util.go", fset.Base(), 2000)
fmt.Printf("文件数:%d\n", fset.FileCount())
fmt.Printf("下一个基址:%d\n", fset.Base())
// 获取位置
pos1 := file1.Pos(100)
pos2 := file2.Pos(200)
// 转换为可读位置
fmt.Printf("main.go: %s\n", fset.Position(pos1))
fmt.Printf("util.go: %s\n", fset.Position(pos2))
}
运行:
$ go run main.go
文件数:2
下一个基址:3001
main.go: main.go:1:101
util.go: util.go:1:201
Position 结构体
Position
定义:
type Position struct {
Filename string // 文件名
Offset int // 偏移量
Line int // 行号
Column int // 列号
}
说明:
- 表示源码中的完整位置信息
- 包含文件名、偏移量、行号、列号
方法:
IsValid() bool- 检查位置是否有效String() string- 字符串表示
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
fset := token.NewFileSet()
file := fset.AddFile("main.go", fset.Base(), 1000)
pos := file.Pos(50)
position := fset.Position(pos)
fmt.Printf("文件名:%s\n", position.Filename)
fmt.Printf("偏移量:%d\n", position.Offset)
fmt.Printf("行号:%d\n", position.Line)
fmt.Printf("列号:%d\n", position.Column)
fmt.Printf("字符串:%s\n", position.String())
fmt.Printf("是否有效:%v\n", position.IsValid())
}
运行:
$ go run main.go
文件名:main.go
偏移量:50
行号:1
列号:51
字符串:main.go:1:51
是否有效:true
四、包级别函数(按字母顺序)
判断是否为标识符
IsIdentifier
定义:
func IsIdentifier(s string) bool
说明:
- 检查字符串是否为合法的 Go 标识符
参数:
s:待检查的字符串
返回值:
bool:是否为合法标识符
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tests := []string{
"x",
"myVar",
"Func123",
"123abc",
"my-var",
"_private",
}
for _, s := range tests {
fmt.Printf("%-10s: %v\n", s, token.IsIdentifier(s))
}
}
运行:
$ go run main.go
x : true
myVar : true
Func123 : true
123abc : false
my-var : false
_private : true
判断是否为关键字
IsKeyword
定义:
func IsKeyword(s string) bool
说明:
- 检查字符串是否为 Go 关键字
参数:
s:待检查的字符串
返回值:
bool:是否为关键字
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tests := []string{
"func",
"var",
"myFunc",
"if",
"range",
"print",
}
for _, s := range tests {
fmt.Printf("%-10s: %v\n", s, token.IsKeyword(s))
}
}
运行:
$ go run main.go
func : true
var : true
myFunc : false
if : true
range : true
print : false
查找关键字
Lookup
定义:
func Lookup(ident string) Token
说明:
- 查找标识符对应的 Token
- 如果是关键字,返回对应的关键字 Token
- 否则返回 IDENT
参数:
ident:标识符字符串
返回值:
Token:对应的 Token
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
idents := []string{
"func",
"var",
"myVar",
"if",
"else",
"custom",
}
for _, ident := range idents {
tok := token.Lookup(ident)
fmt.Printf("%-10s -> %s (关键字:%v)\n",
ident, tok, tok.IsKeyword())
}
}
运行:
$ go run main.go
func -> func (关键字:true)
var -> var (关键字:true)
myVar -> IDENT (关键字:false)
if -> if (关键字:true)
else -> else (关键字:true)
custom -> IDENT (关键字:false)
创建 FileSet
NewFileSet
定义:
func NewFileSet() *FileSet
说明:
- 创建新的 FileSet
- 最常用的函数
返回值:
*FileSet:新的 FileSet
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
fset := token.NewFileSet()
// 添加文件
file := fset.AddFile("main.go", fset.Base(), 1000)
fmt.Printf("FileSet 创建成功\n")
fmt.Printf("文件数:%d\n", fset.FileCount())
fmt.Printf("文件名:%s\n", file.Name())
}
Token 字符串表示
String
定义:
func (tok Token) String() string
说明:
- 返回 Token 的字符串表示
返回值:
string:Token 的字符串
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tokens := []token.Token{
token.FUNC,
token.IDENT,
token.INT,
token.ADD,
token.EOF,
}
for _, tok := range tokens {
fmt.Printf("%s\n", tok.String())
}
}
运行:
$ go run main.go
func
IDENT
INT
+
EOF
判断是否为关键字
IsKeyword
定义:
func (tok Token) IsKeyword() bool
说明:
- 检查 Token 是否为关键字
返回值:
bool:是否为关键字
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tokens := []token.Token{
token.FUNC,
token.VAR,
token.IDENT,
token.INT,
token.IF,
token.ELSE,
}
for _, tok := range tokens {
fmt.Printf("%-10s: %v\n", tok, tok.IsKeyword())
}
}
运行:
$ go run main.go
func : true
var : true
IDENT : false
INT : false
if : true
else : true
判断是否为字面量
IsLiteral
定义:
func (tok Token) IsLiteral() bool
说明:
- 检查 Token 是否为字面量
返回值:
bool:是否为字面量
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tokens := []token.Token{
token.IDENT,
token.INT,
token.FLOAT,
token.STRING,
token.CHAR,
token.FUNC,
token.ADD,
}
for _, tok := range tokens {
fmt.Printf("%-10s: %v\n", tok, tok.IsLiteral())
}
}
运行:
$ go run main.go
IDENT : true
INT : true
FLOAT : true
STRING : true
CHAR : true
func : false
+ : false
判断是否为运算符
IsOperator
定义:
func (tok Token) IsOperator() bool
说明:
- 检查 Token 是否为运算符
返回值:
bool:是否为运算符
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tokens := []token.Token{
token.ADD,
token.SUB,
token.MUL,
token.QUO,
token.ASSIGN,
token.EQL,
token.IDENT,
}
for _, tok := range tokens {
fmt.Printf("%-10s: %v\n", tok, tok.IsOperator())
}
}
运行:
$ go run main.go
+ : true
- : true
* : true
/ : true
= : true
== : true
IDENT : false
获取字面量类型字符串
LitString
定义:
func (tok Token) LitString() string
说明:
- 返回字面量类型的字符串表示
- 仅对字面量 Token 有效
返回值:
string:字面量类型字符串
示例:
package main
import (
"fmt"
"go/token"
)
func main() {
tokens := []token.Token{
token.INT,
token.FLOAT,
token.STRING,
token.CHAR,
token.IDENT,
}
for _, tok := range tokens {
if tok.IsLiteral() {
fmt.Printf("%-10s: %s\n", tok, tok.LitString())
}
}
}
运行:
$ go run main.go
INT : int
FLOAT : float
STRING : string
CHAR : char
IDENT : ident
五、快速参考
Token 分类
| 分类 | Token 示例 | 说明 |
|---|---|---|
| 特殊 | ILLEGAL, EOF, COMMENT | 特殊标记 |
| 字面量 | IDENT, INT, FLOAT, STRING, CHAR | 标识符和字面量 |
| 关键字 | PACKAGE, FUNC, VAR, IF, FOR | Go 语言关键字 |
| 运算符 | ADD, SUB, MUL, QUO | 算术和逻辑运算符 |
| 分隔符 | LPAREN, RPAREN, LBRACE, RBRACE | 括号和分隔符 |
包级别函数
| 函数 | 说明 | 输入 | 输出 |
|---|---|---|---|
| IsIdentifier(s) | 检查标识符 | string | bool |
| IsKeyword(s) | 检查关键字 | string | bool |
| Lookup(ident) | 查找 Token | string | Token |
| NewFileSet() | 创建 FileSet | - | *FileSet |
Token 方法
| 方法 | 说明 | 返回值 |
|---|---|---|
| tok.String() | 字符串表示 | string |
| tok.IsKeyword() | 是否关键字 | bool |
| tok.IsLiteral() | 是否字面量 | bool |
| tok.IsOperator() | 是否运算符 | bool |
| tok.LitString() | 字面量类型 | string |
FileSet 方法
| 方法 | 说明 |
|---|---|
| NewFileSet() | 创建新的 FileSet |
| AddFile(filename, base, size) | 添加文件 |
| File(pos) | 获取位置对应的文件 |
| Position(pos) | 获取完整位置 |
| FileCount() | 获取文件数量 |
File 方法
| 方法 | 说明 |
|---|---|
| Name() | 文件名 |
| Base() | 基址 |
| Size() | 文件大小 |
| Pos(offset) | 从偏移量获取 Pos |
| Offset(pos) | 从 Pos 获取偏移量 |
| Line(pos) | 获取行号 |
| Position(pos) | 获取完整位置 |
Position 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| Filename | string | 文件名 |
| Offset | int | 字节偏移量 |
| Line | int | 行号(从 1 开始) |
| Column | int | 列号(从 1 开始) |
使用场景
| 场景 | 推荐函数/方法 |
|---|---|
| 创建位置管理 | NewFileSet() |
| 添加源文件 | fset.AddFile() |
| 位置转换 | fset.Position(pos) |
| 检查标识符 | IsIdentifier(s) |
| 检查关键字 | IsKeyword(s) 或 Lookup(s) |
| Token 分类 | IsKeyword(), IsLiteral(), IsOperator() |
| 错误报告 | fset.Position(pos) |
六、最佳实践
1. 错误报告
package main
import (
"fmt"
"go/token"
)
func reportError(fset *token.FileSet, pos token.Pos, msg string) {
p := fset.Position(pos)
fmt.Printf("%s:%d:%d: error: %s\n",
p.Filename, p.Line, p.Column, msg)
}
2. 扫描 Token
package main
import (
"fmt"
"go/scanner"
"go/token"
)
func scanTokens(src []byte) {
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
var s scanner.Scanner
s.Init(file, src, nil, 0)
for {
pos, tok, lit := s.Scan()
if tok == token.EOF {
break
}
fmt.Printf("%s: %s %q\n", fset.Position(pos), tok, lit)
}
}
3. 多文件管理
package main
import (
"fmt"
"go/token"
)
func manageMultipleFiles(files map[string][]byte) {
fset := token.NewFileSet()
// 添加所有文件
for name, src := range files {
fset.AddFile(name, fset.Base(), len(src))
}
fmt.Printf("管理 %d 个文件\n", fset.FileCount())
}
最后更新:2026-04-04
Go 版本:Go 1.23+