扫描仪提前终止

时间:2013-11-12 20:28:40

标签: go

我正在尝试在Go中编写一个扫描器来扫描延续线,并在返回之前清理该线,以便您可以返回逻辑线。因此,给定以下SplitLine函数(Play):

func ScanLogicalLines(data []byte, atEOF bool) (int, []byte, error) {
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }

    i := bytes.IndexByte(data, '\n')
    for i > 0 && data[i-1] == '\\' {
        fmt.Printf("i: %d, data[i] = %q\n", i, data[i])
        i = i + bytes.IndexByte(data[i+1:], '\n')
    }

    var match []byte = nil
    advance := 0
    switch {
    case i >= 0:
        advance, match = i + 1, data[0:i]
    case atEOF: 
        advance, match = len(data), data
    }
    token := bytes.Replace(match, []byte("\\\n"), []byte(""), -1)
    return advance, token, nil
}

func main() {
    simple := `
Just a test.

See what is returned. \
when you have empty lines.

Followed by a newline.
`

    scanner := bufio.NewScanner(strings.NewReader(simple))
    scanner.Split(ScanLogicalLines)
    for scanner.Scan() {
        fmt.Printf("line: %q\n", scanner.Text())
    }
}

我希望代码返回类似的内容:

line: "Just a test."
line: ""
line: "See what is returned, when you have empty lines."
line: ""
line: "Followed by a newline."

然而,它在返回第一行后停止。第二个调用返回1, "", nil

任何人都有任何想法,或者它是一个错误?

1 个答案:

答案 0 :(得分:6)

我认为这是一个bug,因为提前值> 0 即使返回的令牌为零(bufio.SplitFunc),也不打算进行进一步的读取调用:

  

如果数据尚未保存完整的令牌,例如,如果在扫描线时没有换行符,则SplitFunc可以返回(0,nil)以通知扫描器将更多数据读入切片并再次尝试更长时间切片从输入中的同一点开始。

这是怎么回事

bufio.Scanner的输入缓冲区默认为4096字节。这意味着它读到了这一点 如果可以,则立即执行,然后执行拆分功能。在您的情况下,扫描仪可以一次读取您的输入,因为它远低于4096字节。 这意味着下次阅读会导致EOF 结果,这是主要问题。

一步一步

  1. scanner.Scan reads all your data
  2. You get all the text that is there
  3. 您查找令牌,找到第一个换行符,只有一个换行符
  4. 您可以通过从匹配项中删除换行符作为令牌返回nil
  5. scanner.Scan assumes: user needs more data
  6. scanner.Scan attempts to read more
  7. EOF happens
  8. scanner.Scan tries to tokenize one last time
  9. 您找到了"Just a test."
  10. scanner.Scan tries to tokenize one last time
  11. 你找一个令牌,你找到第三行只有一个换行符
  12. 您可以通过从匹配项中删除换行符作为令牌返回nil
  13. scanner.Scan sees nil token and set error (EOF)
  14. 执行结束
  15. 如何规避

    任何非零的令牌都会阻止这种情况发生。只要你返回非零令牌即可 扫描程序不会检查EOF并继续执行您的标记生成器。

    您的代码返回nil令牌的原因是bytes.Replace返回 有nothing to be done的时候nilappend([]byte(nil), nil...) == nil。 您可以通过返回具有容量但没有元素的切片来防止这种情况 这将是非零的:make([]byte, 0, 1) != nil