携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情
前言
本系列文章将以源码和文档为依据,梳理标准库的内容,拓展对标准库的认识,并进一步探索标准库的使用方法。
第一章的主角是 fmt 包,它包括 format print scan errors 这四个部分,我们将按照这个顺序来依次分析。
在上一篇文章中,我们浏览了 scan 的前三层函数中的一部分。本篇文章,我们先来看一下 ScanState 和 readRune 接口的方法们。
备注:本系列文章使用的是 go 1.19 源码:
结果注释中的 · 代表一个空格
接口的方法们
在对接口的方法们进行更加详细的学习之前,您可以先复习一下scan 中的常量与类型
在下面的分析中,我们会略过一些简易的函数。
*ss
ReadRune
func (s *ss) ReadRune() (r rune, size int, err error) {
if s.atEOF || s.count >= s.argLimit { // 达到 EOF 了,或者字符数超过范围了,返回错误
err = io.EOF
return
}
r, size, err = s.rs.ReadRune()
if err == nil {
s.count++ // 已扫描的字符数加一
if s.nlIsEnd && r == '\n' { // 对于 -ln 函数,扫描到 \n 就结束
s.atEOF = true
}
} else if err == io.EOF {
s.atEOF = true
}
return
}
本函数是对 io.RuneScanner 接口的 ReadRune 方法的封装,并提供了对 EOF 和扫描结束的检查与处理。
本函数还有一些进一步封装的方法,它们只返回扫描到的字符值,并具有一些特性:
-
getRune: EOF 作为值 -1 返回
-
mustReadRune:一般用于在读取固定长度的数据过程中,遇到 EOF 时报错
Token
func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
defer func() {
if e := recover(); e != nil {
if se, ok := e.(scanError); ok {
err = se.err
} else {
panic(e)
}
}
}()
if f == nil {
f = notSpace
}
s.buf = s.buf[:0]
tok = s.token(skipSpace, f)
return
}
首先通过 defer 处理错误,如果是自定义处理的错误则恢复,不能处理的错误保持恐慌。
然后,返回下一个令牌,函数 f 决定何时停止,默认为被空白符分隔的下一个字符串。
token
如果 skipSpace 被设为 true,会先跳过空白符。然后,不断获取字符并写入到缓存中,直到:
-
遇到 EOF,停止
-
函数
f返回 false,退回读取的字符并停止
skipSpace
不断获取字符,并依次做以下判断:
-
如果是 EOF,则返回
-
如果是 \r,并且下一个字符是 \n(通过 peek 判断,该字符此时没有被获取),则
continue -
如果是 \n,并且它被当做空白符,则
continue;否则,报错s.errorString("unexpected newline") -
如果不是空白符,退回读取的字符并返回
\r\n 与 \n 的意义是一样的,但由于 \r 本身是空白符,所以对于 \r\n 要特殊处理(实际上,这种情况下是跳过 \r 处理 \n)
peek & indexRune
peek 这个函数在获取字符后,会再把字符退回去,然后判断传入的字符串是否包含该字符
indexRune 遍历字符串并匹配字符,返回字符索引或者-1
consume
func (s *ss) consume(ok string, accept bool) bool
读取下一个字符并判断是否在字符串 ok 中,当 accept 为 false 时,读取的字符不会退回。
当 accept 为 true 时,如果读取的字符在 ok 中,就会把它写入到缓存里;否则,会退回字符。
*readRune
ReadRune
if r.peekRune >= 0 {
rr = r.peekRune
r.peekRune = ^r.peekRune
size = utf8.RuneLen(rr)
return
}
peekRune 大于等于 0,表示的是下一个字符;小于 0 ,则是上一个字符的取反。
如果下一个字符已经读取了,直接返回相应的参数。
r.buf[0], err = r.readByte()
否则,通过 readByte 去读取。
if r.buf[0] < utf8.RuneSelf {
rr = rune(r.buf[0])
size = 1
r.peekRune = ^rr
return
}
如果小于 128,说明是 ASCII 字符,直接转换并返回。
var n int
for n = 1; !utf8.FullRune(r.buf[:n]); n++ {
r.buf[n], err = r.readByte()
if err != nil {
if err == io.EOF {
err = nil
break
}
return
}
}
rr, size = utf8.DecodeRune(r.buf[:n])
if size < n {
copy(r.pendBuf[r.pending:], r.buf[size:n])
r.pending += n - size
}
r.peekRune = ^rr
return
如果字符大于一字节,则不断获取字节直至字符完整。如果解码出来的字符宽度小于遍历获取的字节数,说明出现了错误,把多出的部分放入到 pendBuf 中存储。
readByte
如果 pendBuf 中有遗留的字节,先从其中获取。
n, err := io.ReadFull(r.reader, r.pendBuf[:1])
否则,通过 io.ReadFull 来从 reader 中获取一个字节。
UnreadRune
func (r *readRune) UnreadRune() error {
if r.peekRune >= 0 {
return errors.New("fmt: scanning called UnreadRune with no rune available")
}
r.peekRune = ^r.peekRune
return nil
}
退回字符操作,实际上直接对 peekRune 取反就可以了。
总结
本篇文章中,我们学习了 ScanState 和 readRune 接口的方法,这些方法是扫描操作的底层函数,就像 fmt.go 对于 print.go 的关系一样,但是这里把它们都写在一个文件里了。弄明白字符流是如何形成之后,我们就可以愉快地分析具体的扫描操作了。
最后,如果本篇文章对您有所帮助,希望您可以 点赞、收藏、评论,感谢支持 ✧(≖ ◡ ≖✿