携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情
前言
本系列文章将以源码和文档为依据,梳理标准库的内容,拓展对标准库的认识,并进一步探索标准库的使用方法。
第一章的主角是 fmt 包,它包括 format print scan errors 这四个部分,我们将按照这个顺序来依次分析。
在上一篇文章中,我们已经将 print.go 的所有执行逻辑都分析完毕,fmt 的半壁江山已经拿下。本篇文章,我们会开始 scan.go 的分析。
备注:本系列文章使用的是 go 1.19 源码:
结果注释中的 · 代表一个空格
scan
按照流程,我们还是先来看看 scan.go 中都定义了哪些常量与类型,它们的用途都是什么。
常量与类型
type ScanState interface {
ReadRune() (r rune, size int, err error)
UnreadRune() error
SkipSpace()
Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
Width() (wid int, ok bool)
Read(buf []byte) (n int, err error)
}
在分析 scan.go 的类型时,可以对照着 print.go 理解。首先是 ScanState 接口,它代表传递给自定义扫描器的扫描器状态,扫描器在扫描时可能会询问它去寻找下一个空格分隔的令牌。它包括六个方法。
-
ReadRune:用于从输入中读取下一个字符;如果在
-ln系列函数中调用,函数会在第一个\n或读取宽度超过指定宽度时返回 EOF -
UnreadRune:退回上一个读取的字符,下一次调用 ReadRune 会返回同一个字符
-
SkipSpace:从输入中读取时跳过空白符
-
Token:跳过空白符,然后返回符合
f()的字节切片;切片所指向的共享数据可能会在下一次调用时被覆盖 -
Width:返回宽度值以及是否设置了宽度
-
Read:仅用来实现 io.Reader 接口,因为有 ReadRune 了,所以 Read 一般永远都不会被使用,在被调用时会返回错误
如果你学过编译原理的话,可能分析到这里时你会感到非常的熟悉,ScanState 非常像一个小型的编译器前端,不过它不需要生成 AST。
type Scanner interface {
Scan(state ScanState, verb rune) error
}
Scanner 接口可以对应 Formatter 接口理解,用于自定义某个类型的扫描方法。
type stringReader string
该类型有 Read 方法,用于实现 io.Reader 接口。它只在 Sscan 系列函数中使用,为了调用 Fscan 系列函数,所以要将传入的字符串转换为实现 io.Reader 接口的类型。
type scanError struct {
err error
}
scanError 用作恢复恐慌时区分错误的唯一签名。
type ss struct {
rs io.RuneScanner // 读取输入的地方
buf buffer // 令牌累加器
count int // 已读取的字符个数
atEOF bool // 已经到达 EOF
ssave
}
ss 是 fmt 包对于 ScanState 的实现。
type ssave struct {
validSave bool // 是或者曾是 ss 的一部分
nlIsEnd bool // 换行符是否终止扫描
nlIsSpace bool // 换行符是否算作空白符
argLimit int // 参数的 ss.count 的最大值;argLimit <= limit
limit int // ss.count 的最大值
maxWid int // 参数的宽度
}
ssave 存储 ss 中在递归扫描时需要保存和恢复的部分。
type readRune struct {
reader io.Reader
buf [utf8.UTFMax]byte // 4字节大小的缓存,存储一个字符
pending int // pendBuf 中字节的个数,大于0说明上一次读取时遇到了不正确的字符
pendBuf [utf8.UTFMax]byte // 上一次读取时剩余的字节
peekRune rune // 大于等于0代表下一个字符,小于0代表上一个字符的取反
}
readRune 从 io.Reader 中读取字符,它会在传递给扫描器的 Reader 没有实现 io.RuneScanner 时被使用。
io.RuneScanner 需要
ReadRune() (r rune, size int, err error)和UnreadRune() error方法。
const eof = -1
var space = [][2]uint16{ // 范围内的字符为空白符,避免依赖 unicode 包
{0x0009, 0x000d},
{0x0020, 0x0020},
{0x0085, 0x0085},
{0x00a0, 0x00a0},
{0x1680, 0x1680},
{0x2000, 0x200a},
{0x2028, 0x2029},
{0x202f, 0x202f},
{0x205f, 0x205f},
{0x3000, 0x3000},
}
const ( // 定义不同类型的数值,用于读取下一个字符时的检查
binaryDigits = "01"
octalDigits = "01234567"
decimalDigits = "0123456789"
hexadecimalDigits = "0123456789aAbBcCdDeEfF"
sign = "+-"
period = "."
exponent = "eEpP"
)
const (
floatVerbs = "beEfFgGv"
hugeWid = 1 << 30
intBits = 32 << (^uint(0) >> 63)
uintptrBits = 32 << (^uintptr(0) >> 63)
)
后两行的写法,在这篇文章中讲过原理,像 int uint 这样的类型,它的表示位数与系统有关,所以这里需要通过取反运算得到。
总结
本篇文章中,我们分析了 scan.go 中的常量与类型,初步了解了它们的内容和用途。在下一篇文章中,我们将开始分析扫描函数的执行过程。
最后,如果本篇文章对您有所帮助,希望您可以 点赞、收藏、评论,感谢支持 ✧(≖ ◡ ≖✿