Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

text/scanner 包详解

概述

text/scanner 包为 UTF-8 编码的文本提供了扫描器和分词器功能。它接受一个提供源代码的 io.Reader,然后通过重复调用 Scan 函数来对其进行分词。

主要用途

  • 文本词法分析
  • 源代码解析
  • 配置文件解析
  • 自定义语言解释器
  • 文本处理和转换

核心特性

  • 支持 UTF-8 编码文本
  • 自动跳过空白字符和 Go 风格注释
  • 识别 Go 语言定义的所有字面量
  • 可自定义识别的标识符和空白字符
  • 支持错误处理回调
  • 提供位置跟踪功能

重要说明

  • 不允许使用 NUL 字符(与现有工具兼容)
  • 如果源中的第一个字符是 UTF-8 BOM,它会被丢弃
  • 默认行为符合 Go 语言规范

包导入

import "text/scanner"

常量详解

扫描模式常量

const (
    ScanIdents     = 1 << -Ident      // 识别标识符
    ScanInts       = 1 << -Int        // 识别整数
    ScanFloats     = 1 << -Float      // 识别浮点数
    ScanChars      = 1 << -Char       // 识别字符字面量
    ScanStrings    = 1 << -String     // 识别字符串字面量
    ScanRawStrings = 1 << -RawString  // 识别原始字符串字面量
    ScanComments   = 1 << -Comment    // 识别注释
    
    // 跳过注释(与 ScanComments 一起使用)
    SkipComments   = 1 << -SkipComment
    
    // GoTokens:接受所有 Go 字面量标记,包括 Go 标识符
    // 注释将被跳过
    GoTokens = ScanIdents | ScanFloats | ScanChars | 
               ScanStrings | ScanRawStrings | ScanComments | SkipComments
)

说明

  • 预定义的模式位用于控制标记的识别
  • 例如,要配置 Scanner 使其只识别(Go)标识符、整数,并跳过注释,将 Scanner 的 Mode 字段设置为:
    ScanIdents | ScanInts | ScanComments | SkipComments
    
  • 除了注释(如果设置了 SkipComments 则会被跳过)之外,无法识别的标记不会被忽略
  • 相反,扫描器简单地返回各个单独的字符(或可能是子标记)
  • 例如,如果模式是 ScanIdents(不是 ScanStrings),字符串 “foo” 会被扫描为标记序列 '"' Ident '"'

示例

var s scanner.Scanner
s.Init(reader)

// 只识别标识符和整数
s.Mode = scanner.ScanIdents | scanner.ScanInts

// 使用 GoTokens 识别所有 Go 标记
s.Mode = scanner.GoTokens

标记类型常量

const (
    EOF = -(iota + 1)  // 文件结束标记
    Ident              // 标识符
    Int                // 整数
    Float              // 浮点数
    Char               // 字符字面量
    String             // 字符串字面量
    RawString          // 原始字符串字面量
)

说明

  • Scan 的结果是这些标记之一或一个 Unicode 字符
  • 负值用于避免与有效的 Unicode 码点冲突

示例

tok := s.Scan()
switch tok {
case scanner.EOF:
    fmt.Println("End of input")
case scanner.Ident:
    fmt.Println("Identifier:", s.TokenText())
case scanner.Int:
    fmt.Println("Integer:", s.TokenText())
case scanner.Float:
    fmt.Println("Float:", s.TokenText())
case scanner.String:
    fmt.Println("String:", s.TokenText())
}

空白字符常量

const GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' '

作用:Scanner 的 Whitespace 字段的默认值

说明

  • 其值选择 Go 的空白字符
  • 可以使用位掩码自定义空白字符

示例

var s scanner.Scanner
s.Init(reader)

// 使用默认的 Go 空白字符
s.Whitespace = scanner.GoWhitespace

// 自定义空白字符(例如,将制表符视为标识符的一部分)
s.Whitespace = 1<<'\n' | 1<<'\r' | 1<<' '  // 排除制表符

函数详解

T

TokenString

func TokenString(tok rune) string

作用:返回标记或 Unicode 字符的可打印字符串表示

参数说明

  • tok:标记或 Unicode 字符

返回值

  • 可读的字符串表示

示例

var s scanner.Scanner
s.Init(strings.NewReader("42"))

tok := s.Scan()
fmt.Printf("Token: %s\n", scanner.TokenString(tok))
// 输出:Token: Int

tok = s.Scan()
fmt.Printf("Token: %s\n", scanner.TokenString(tok))
// 输出:Token: EOF

类型详解(按 A-Z 分层归类)

P

Position

type Position struct {
    Filename string // 文件名
    Offset   int    // 偏移量(从 0 开始)
    Line     int    // 行号(从 1 开始)
    Column   int    // 列号(从 1 开始)
}

作用:表示源代码位置的值

说明

  • 如果 Line > 0,则位置有效
  • 用于跟踪扫描过程中的位置信息

示例

var s scanner.Scanner
s.Init(strings.NewReader("hello"))
s.Filename = "test.txt"

tok := s.Scan()
pos := s.Pos()
fmt.Printf("At %s:%d:%d\n", pos.Filename, pos.Line, pos.Column)
// 输出:At test.txt:1:6

Position 方法

IsValid

func (pos *Position) IsValid() bool

作用:报告位置是否有效

返回值

  • 如果 Line > 0 返回 true,否则返回 false

示例

var pos scanner.Position
if !pos.IsValid() {
    fmt.Println("Invalid position")
}

// 扫描后获取有效位置
var s scanner.Scanner
s.Init(strings.NewReader("text"))
s.Scan()
pos = s.Pos()
if pos.IsValid() {
    fmt.Printf("Valid position: %s\n", pos.String())
}

String

func (pos Position) String() string

作用:返回位置的可打印字符串表示

返回值

  • 格式为 “filename:line:column” 的字符串

示例

var s scanner.Scanner
s.Init(strings.NewReader("hello world"))
s.Filename = "example.txt"

s.Scan()
pos := s.Pos()
fmt.Println(pos.String())
// 输出:example.txt:1:6

s.Scan()
pos = s.Pos()
fmt.Println(pos.String())
// 输出:example.txt:1:12

S

Scanner

type Scanner struct {
    // 包含导出或未导出的字段
    
    // 配置字段
    Filename    string        // 文件名(用于错误消息和位置)
    Mode        uint          // 扫描模式控制
    Whitespace  uint          // 空白字符位掩码
    IsIdentRune func(ch rune, i int) bool  // 自定义标识符字符判断
    
    // 状态字段
    Error       func(*Scanner, string)  // 错误处理函数
    ErrorCount  int                     // 错误计数
}

作用:实现从 io.Reader 读取 Unicode 字符和标记的功能

字段说明

  • Filename:文件名,用于错误消息和 Position
  • Mode:控制识别哪些标记的模式位
  • Whitespace:空白字符的位掩码
  • IsIdentRune:自定义函数,用于判断字符是否是标识符的一部分
  • Error:错误处理函数,如果为 nil 则打印到 os.Stderr
  • ErrorCount:错误计数

示例

var s scanner.Scanner
s.Init(reader)
s.Filename = "myfile.txt"
s.Mode = scanner.ScanIdents | scanner.ScanInts
s.Error = func(s *scanner.Scanner, msg string) {
    log.Printf("Error at %s: %s", s.Position, msg)
}

Scanner 方法详解(按 A-Z 分层归类)

I

Init

func (s *Scanner) Init(src io.Reader) *Scanner

作用:用新源初始化 Scanner 并返回 s

参数说明

  • src:输入源

返回值

  • 初始化后的 Scanner(支持链式调用)

初始化效果

  • Scanner.Error 设置为 nil
  • Scanner.ErrorCount 设置为 0
  • Scanner.Mode 设置为 GoTokens
  • Scanner.Whitespace 设置为 GoWhitespace

示例

// 基本用法
var s scanner.Scanner
s.Init(strings.NewReader("hello world"))

// 链式调用
s := new(scanner.Scanner).Init(reader)

// 从文件读取
file, _ := os.Open("input.txt")
defer file.Close()
var s scanner.Scanner
s.Init(file)

N

Next

func (s *Scanner) Next() rune

作用:读取并返回下一个 Unicode 字符

返回值

  • 下一个 Unicode 字符
  • 在源末尾返回 EOF

说明

  • 通过调用 s.Error 报告读取错误(如果 Error 不为 nil)
  • 否则打印错误消息到 os.Stderr
  • Next 不更新 Scanner.Position 字段
  • 使用 Scanner.Pos() 获取当前位置

示例

var s scanner.Scanner
s.Init(strings.NewReader("abc"))

for {
    ch := s.Next()
    if ch == scanner.EOF {
        break
    }
    fmt.Printf("Character: %c\n", ch)
}
// 输出:
// Character: a
// Character: b
// Character: c

P

Peek

func (s *Scanner) Peek() rune

作用:返回源中的下一个 Unicode 字符而不推进扫描器

返回值

  • 下一个 Unicode 字符
  • 如果扫描器位置在源的最后一个字符处,返回 EOF

说明

  • 用于前瞻而不消耗字符
  • 常用于词法分析中的多字符标记识别

示例

var s scanner.Scanner
s.Init(strings.NewReader("42"))

// 查看下一个字符
ch := s.Peek()
fmt.Printf("Next char: %c\n", ch)  // 输出:4

// 再次查看(仍在同一位置)
ch = s.Peek()
fmt.Printf("Next char: %c\n", ch)  // 输出:4

// 实际读取
ch = s.Next()
fmt.Printf("Read char: %c\n", ch)  // 输出:4

Pos

func (s *Scanner) Pos() (pos Position)

作用:返回最后一个调用 Scanner.Next 或 Scanner.Scan 返回的字符或标记之后的字符位置

返回值

  • 当前位置

说明

  • 使用 Scanner.Position 字段获取最近扫描的标记的起始位置

示例

var s scanner.Scanner
s.Init(strings.NewReader("hello world"))
s.Filename = "test.txt"

tok := s.Scan()
startPos := s.Position  // 标记的起始位置
endPos := s.Pos()       // 标记的结束位置

fmt.Printf("Token '%s' from %s to %s\n", 
    s.TokenText(), startPos.String(), endPos.String())

S

Scan

func (s *Scanner) Scan() rune

作用:从源读取下一个标记或 Unicode 字符并返回它

返回值

  • 标记或 Unicode 字符
  • 在源末尾返回 EOF

说明

  • 只识别 Scanner.Mode 位(1<<-t)设置的标记 t
  • 通过调用 s.Error 报告扫描器错误(读取和标记错误)
  • 如果 Error 为 nil 则打印错误消息到 os.Stderr

示例

var s scanner.Scanner
s.Init(strings.NewReader(`name = "John" age = 30`))
s.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings

for {
    tok := s.Scan()
    if tok == scanner.EOF {
        break
    }
    fmt.Printf("%s: %s\n", scanner.TokenString(tok), s.TokenText())
}
// 输出:
// Ident: name
// =: =
// Ident: age
// =: =
// Int: 30

TokenText

func (s *Scanner) TokenText() string

作用:返回与最近扫描的标记对应的字符串

返回值

  • 标记文本

说明

  • 在调用 Scanner.Scan 后有效
  • 在 Scanner.Error 调用中也有效

示例

var s scanner.Scanner
s.Init(strings.NewReader(`"hello" 42 3.14 'x'`))
s.Mode = scanner.GoTokens

for {
    tok := s.Scan()
    if tok == scanner.EOF {
        break
    }
    text := s.TokenText()
    fmt.Printf("%s: %q\n", scanner.TokenString(tok), text)
}
// 输出:
// String: "\"hello\""
// Int: "42"
// Float: "3.14"
// Char: "'x'"

典型示例

1. 基本扫描

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader(`name = "John" age = 30`))
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("%s: %s\n", scanner.TokenString(tok), s.TokenText())
    }
}

2. 只识别标识符

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader("hello world 123"))
    s.Mode = scanner.ScanIdents  // 只识别标识符
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        if tok == scanner.Ident {
            fmt.Printf("Identifier: %s\n", s.TokenText())
        } else {
            fmt.Printf("Other: %c\n", tok)
        }
    }
}

3. 位置跟踪

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    src := `line1
line2
line3`
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    s.Filename = "example.txt"
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        pos := s.Position
        fmt.Printf("%s:%d:%d: %s (%s)\n",
            pos.Filename, pos.Line, pos.Column,
            s.TokenText(), scanner.TokenString(tok))
    }
}

4. 错误处理

package main

import (
    "fmt"
    "log"
    "strings"
    "text/scanner"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader("valid invalid content"))
    s.Mode = scanner.ScanIdents
    
    // 自定义错误处理
    s.Error = func(s *scanner.Scanner, msg string) {
        log.Printf("Error at %s: %s", s.Position, msg)
        s.ErrorCount++
    }
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("Token: %s\n", s.TokenText())
    }
    
    fmt.Printf("Total errors: %d\n", s.ErrorCount)
}

5. 扫描注释

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    src := `// This is a comment
/* Multi-line
   comment */
code`
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    
    // 包含注释
    s.Mode = scanner.GoTokens | scanner.ScanComments
    s.Whitespace &^= scanner.SkipComments  // 不跳过注释
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        if tok == scanner.Comment {
            fmt.Printf("Comment: %s\n", s.TokenText())
        }
    }
}

6. 自定义标识符字符

package main

import (
    "fmt"
    "strings"
    "text/scanner"
    "unicode"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader("hello-world test_case"))
    
    // 自定义标识符判断:允许连字符
    s.IsIdentRune = func(ch rune, i int) bool {
        return ch == '-' || ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch)
    }
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("%s: %s\n", scanner.TokenString(tok), s.TokenText())
    }
    // 输出:
    // Ident: hello-world
    // Ident: test_case
}

7. 扫描数字字面量

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    src := `42 3.14 1e10 0xFF 0b1010`
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    s.Mode = scanner.ScanInts | scanner.ScanFloats
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("%s: %s\n", scanner.TokenString(tok), s.TokenText())
    }
    // 输出:
    // Int: 42
    // Float: 3.14
    // Float: 1e10
    // Int: 0xFF
    // Int: 0b1010
}

8. 扫描字符串字面量

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    src := `"hello" 'x' \`raw string\``
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    s.Mode = scanner.ScanStrings | scanner.ScanChars | scanner.ScanRawStrings
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("%s: %s\n", scanner.TokenString(tok), s.TokenText())
    }
    // 输出:
    // String: "hello"
    // Char: 'x'
    // RawString: `raw string`
}

9. 使用 Peek 进行前瞻

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader(":= : = ::"))
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        text := s.TokenText()
        next := s.Peek()
        
        if next != scanner.EOF {
            fmt.Printf("Current: %q, Next: %c\n", text, next)
        } else {
            fmt.Printf("Current: %q, Next: EOF\n", text)
        }
    }
}

10. 扫描多行文本

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    src := `line 1
line 2
line 3`
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    s.Filename = "multiline.txt"
    
    lastLine := 0
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        pos := s.Position
        if pos.Line != lastLine {
            fmt.Printf("\nLine %d: ", pos.Line)
            lastLine = pos.Line
        }
        fmt.Printf("%s ", s.TokenText())
    }
    fmt.Println()
}

11. 跳过特定空白

package main

import (
    "fmt"
    "strings"
    "text/scanner"
)

func main() {
    var s scanner.Scanner
    s.Init(strings.NewReader("a b\tc\nd"))
    
    // 只跳过空格和换行,保留制表符
    s.Whitespace = 1<<' ' | 1<<'\n'
    
    for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
        fmt.Printf("%q ", s.TokenText())
    }
    // 输出:"a" "b" "\t" "c" "d"
}

12. 解析简单表达式

package main

import (
    "fmt"
    "strconv"
    "strings"
    "text/scanner"
)

func main() {
    src := "10 + 20 * 3"
    
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    s.Mode = scanner.ScanInts
    
    // 简单解析:读取第一个数字
    tok := s.Scan()
    if tok == scanner.Int {
        value, _ := strconv.Atoi(s.TokenText())
        fmt.Printf("First number: %d\n", value)
    }
    
    // 读取操作符
    tok = s.Scan()
    fmt.Printf("Operator: %s\n", s.TokenText())
    
    // 读取第二个数字
    tok = s.Scan()
    if tok == scanner.Int {
        value, _ := strconv.Atoi(s.TokenText())
        fmt.Printf("Second number: %d\n", value)
    }
}

最佳实践

1. 正确初始化

// 推荐的做法
var s scanner.Scanner
s.Init(reader)

// 或链式调用
s := new(scanner.Scanner).Init(reader)

2. 设置合适的模式

// 只扫描标识符
s.Mode = scanner.ScanIdents

// 扫描所有 Go 标记
s.Mode = scanner.GoTokens

// 包含注释
s.Mode = scanner.GoTokens | scanner.ScanComments
s.Whitespace &^= scanner.SkipComments

3. 使用位置信息

s.Filename = "input.txt"

for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
    pos := s.Position
    fmt.Printf("%s:%d:%d: %s\n", 
        pos.Filename, pos.Line, pos.Column, s.TokenText())
}

4. 自定义错误处理

s.Error = func(s *scanner.Scanner, msg string) {
    fmt.Fprintf(os.Stderr, "Error at %s: %s\n", s.Position, msg)
    s.ErrorCount++
}

5. 使用 TokenText

// 在 Scan 后立即调用 TokenText
tok := s.Scan()
if tok != scanner.EOF {
    text := s.TokenText()
    // 处理 text
}

6. 使用 Peek 进行前瞻

tok := s.Scan()
next := s.Peek()

// 根据下一个字符决定如何处理
if next == '=' {
    // 处理复合操作符
    s.Next()  // 消耗下一个字符
}

与其他包配合

io 包

import (
    "io"
    "text/scanner"
)

func ScanFromReader(r io.Reader) {
    var s scanner.Scanner
    s.Init(r)
    // 扫描...
}

strings 包

import (
    "strings"
    "text/scanner"
)

func ScanString(src string) {
    var s scanner.Scanner
    s.Init(strings.NewReader(src))
    // 扫描...
}

os 包

import (
    "os"
    "text/scanner"
)

func ScanFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    var s scanner.Scanner
    s.Init(file)
    s.Filename = filename
    // 扫描...
    return nil
}

bufio 包

import (
    "bufio"
    "text/scanner"
)

func ScanBuffered(r io.Reader) {
    reader := bufio.NewReader(r)
    var s scanner.Scanner
    s.Init(reader)
    // 扫描...
}

注意事项

1. NUL 字符限制

// NUL 字符不被允许
src := "text\x00more"  // 会导致错误

2. BOM 处理

// UTF-8 BOM 会被自动丢弃
src := "\xEF\xBB\xBFhello"  // BOM 被忽略,从 "hello" 开始扫描

3. 模式位设置

// 正确:使用位或运算
s.Mode = scanner.ScanIdents | scanner.ScanInts

// 错误:直接赋值会丢失其他位
s.Mode = scanner.ScanIdents  // 只识别标识符

4. 注释处理

// 默认跳过注释
s.Mode = scanner.GoTokens  // 注释被跳过

// 要识别注释
s.Mode = scanner.GoTokens | scanner.ScanComments
s.Whitespace &^= scanner.SkipComments  // 不跳过注释

5. 错误处理

// 设置错误处理函数
s.Error = func(s *scanner.Scanner, msg string) {
    // 处理错误
}

// 检查错误计数
if s.ErrorCount > 0 {
    // 有错误发生
}

6. TokenText 的有效性

// TokenText 只在 Scan 后有效
tok := s.Scan()
text := s.TokenText()  // 正确

// 在 Next 或 Peek 后可能无效
ch := s.Next()
text := s.TokenText()  // 可能不是预期的结果

7. 位置跟踪

// Position 是标记的起始位置
tok := s.Scan()
startPos := s.Position  // 标记起始

// Pos() 返回当前位置(标记后)
endPos := s.Pos()  // 标记结束

快速参考

常量速查表

常量说明
EOF文件结束标记
Ident标识符
Int整数
Float浮点数
Char字符字面量
String字符串字面量
RawString原始字符串
Comment注释
GoTokens所有 Go 标记
GoWhitespaceGo 空白字符

模式位速查表

模式位说明
ScanIdents识别标识符
ScanInts识别整数
ScanFloats识别浮点数
ScanChars识别字符
ScanStrings识别字符串
ScanRawStrings识别原始字符串
ScanComments识别注释
SkipComments跳过注释

方法速查表

方法说明
Init初始化扫描器
Scan扫描下一个标记
Next读取下一个字符
Peek查看下一个字符
Pos获取当前位置
TokenText获取标记文本

Position 字段速查表

字段说明
Filename文件名
Offset偏移量
Line行号(从 1 开始)
Column列号(从 1 开始)

常见模式

// 基本扫描
var s scanner.Scanner
s.Init(reader)
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
    // 处理标记
}

// 只扫描标识符
s.Mode = scanner.ScanIdents

// 扫描所有 Go 标记
s.Mode = scanner.GoTokens

// 包含注释
s.Mode = scanner.GoTokens | scanner.ScanComments
s.Whitespace &^= scanner.SkipComments

// 位置跟踪
s.Filename = "input.txt"
pos := s.Position
fmt.Printf("%s:%d:%d", pos.Filename, pos.Line, pos.Column)

// 错误处理
s.Error = func(s *scanner.Scanner, msg string) {
    log.Printf("Error: %s", msg)
}

总结

text/scanner 包提供了强大的文本扫描和分词功能:

核心功能

  • UTF-8 文本扫描
  • 标记识别和分词
  • 位置跟踪
  • 错误处理
  • 可自定义行为

主要类型

  • Scanner:扫描器主体
  • Position:位置信息
  • 标记类型常量

配置选项

  • Mode:控制识别哪些标记
  • Whitespace:定义空白字符
  • IsIdentRune:自定义标识符判断
  • Error:错误处理函数

使用建议

  1. 正确初始化 Scanner
  2. 设置合适的扫描模式
  3. 使用位置信息进行错误报告
  4. 实现自定义错误处理
  5. 使用 Peek 进行前瞻
  6. 注意 TokenText 的有效时机

典型用法

var s scanner.Scanner
s.Init(reader)
s.Filename = "input.txt"
s.Mode = scanner.ScanIdents | scanner.ScanInts

for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
    pos := s.Position
    fmt.Printf("%s:%d:%d: %s (%s)\n",
        pos.Filename, pos.Line, pos.Column,
        s.TokenText(), scanner.TokenString(tok))
}

通过 text/scanner 包,可以方便地实现词法分析器、解析器和各种文本处理工具。