Golang源码分析 - scan.go 超详细讲解
scan.go
是 Go 标准库 text/scanner
包的核心文件,实现了一个高效的文本扫描器,用于将输入文本分解为一系列 token(标记)。这个扫描器被广泛用于 Go 语言工具链中,如 go/parser
等。
1. 核心数据结构
Scanner 结构体
type Scanner struct {
// 输入源
src []byte // 源文本
// 扫描位置
pos Pos // 当前位置
offset int // 字节偏移量
// 错误处理
err error // 遇到的错误
// 模式控制
Mode uint // 扫描模式
// 扫描状态
ch rune // 当前字符
whitespace uint64 // 空格字符集合
// 位置信息
line int // 当前行号
column int // 当前列号
// 标识符解析
IsIdentRune func(ch rune, i int) bool // 标识符字符判断函数
// 错误报告
Error func(s *Scanner, msg string) // 错误处理函数
}
重要常量
const (
ScanIdents = 1 << iota // 扫描标识符
ScanInts // 扫描整型
ScanFloats // 扫描浮点型
ScanChars // 扫描字符
ScanStrings // 扫描字符串
ScanRawStrings // 扫描原始字符串
ScanComments // 扫描注释
SkipComments // 跳过注释
)
2. 核心方法解析
初始化方法
func (s *Scanner) Init(src []byte)
- 初始化扫描器,设置输入源
- 重置所有位置信息
- 准备开始扫描
扫描主方法
func (s *Scanner) Scan() rune
- 扫描下一个 token 并返回其类型
- 内部调用
scan()
方法完成实际工作 - 返回的 rune 是预定义的 token 类型常量
实际扫描方法
func (s *Scanner) scan() rune
- 核心扫描逻辑实现
- 处理各种 token 类型的识别
- 调用特定方法处理不同类型的 token
3. 详细扫描流程
1. 跳过空白字符
func (s *Scanner) skipWhitespace()
- 根据
whitespace
位图判断空白字符 - 处理换行符并更新行列计数
- 支持 Unicode 空白字符
2. 标识符扫描
func (s *Scanner) scanIdentifier() rune
- 使用
IsIdentRune
函数判断标识符字符 - 收集连续的标识符字符
- 检查是否为关键字
3. 数字扫描
func (s *Scanner) scanNumber(ch rune) rune
- 处理整数和小数
- 识别不同进制(十进制、十六进制等)
- 处理科学计数法
- 区分整型和浮点型
4. 字符串扫描
func (s *Scanner) scanString(quote rune) rune
- 处理双引号和反引号字符串
- 处理转义字符
- 验证字符串闭合
5. 字符扫描
func (s *Scanner) scanChar() rune
- 处理单引号字符
- 处理转义字符
- 验证字符长度
6. 注释扫描
func (s *Scanner) scanComment() rune
- 处理行注释和块注释
- 根据模式决定是否返回注释内容
- 更新位置信息
4. 位置信息处理
Pos 类型
type Pos struct {
Offset int // 字节偏移量
Line int // 行号
Column int // 列号
}
位置更新机制
- 每次读取字符时更新位置
- 特殊处理换行符 (
\n
,\r\n
) - 提供
Position()
方法获取当前位置
5. 错误处理
错误报告
func (s *Scanner) error(msg string)
- 调用用户设置的
Error
函数 - 记录错误信息
- 设置错误状态
常见错误类型
- 非法字符
- 未闭合的字符串/字符/注释
- 无效的数字格式
- 意外的文件结束
6. 使用示例
基本使用
package main
import (
"fmt"
"text/scanner"
"strings"
)
func main() {
src := strings.NewReader("var x = 123 + 'a' // 注释")
var s scanner.Scanner
s.Init(src)
s.Filename = "example"
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
fmt.Printf("%s: %s\n", s.Position, scanner.TokenString(tok))
}
}
自定义标识符
s.IsIdentRune = func(ch rune, i int) bool {
return ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0
}
错误处理
s.Error = func(s *scanner.Scanner, msg string) {
fmt.Printf("错误: %s at %s\n", msg, s.Position)
}
7. 性能优化
- 直接访问源数据:使用
[]byte
而非io.Reader
减少 IO 开销 - 位图查找:使用
whitespace
位图快速判断空白字符 - 最小化分配:尽可能复用内存,减少分配
- 内联优化:关键方法使用编译器内联优化
8. 设计亮点
- 灵活的模式控制:通过模式位掩码控制扫描行为
- 可扩展的标识符识别:通过
IsIdentRune
自定义标识符 - 精确的位置跟踪:记录每个 token 的精确位置
- 可定制的错误处理:允许自定义错误报告
9. 在 Go 工具链中的应用
- go/parser:解析 Go 源代码
- go/doc:提取文档注释
- template:模板解析
- encoding/json:JSON 解析的初始阶段
10. 扩展阅读
-
与 bufio.Scanner 的区别:
text/scanner
提供更丰富的 token 类型bufio.Scanner
更专注于行处理text/scanner
保留位置信息
-
实现词法分析器:
- 可作为编写 DSL 的词法分析基础
- 比正则表达式更高效
- 比完整 parser 生成器更轻量
这个扫描器实现展示了 Go 标准库中高效文本处理的优秀实践,其设计平衡了灵活性、性能和易用性。
立即点击链接,开启你的全栈开发之路:Golang全栈开发完整课程