意外的EOF与fmt.Scanner

huangapple go评论81阅读模式
英文:

unexpected EOF with fmt.Scanner

问题

如果我想遍历一个字符串,可以这样做:

package main

import (
   "fmt"
   "strings"
)

func main() {
   r := strings.NewReader("west north east")
   for {
      var s string
      _, e := fmt.Fscan(r, &s)
      fmt.Printf("%q %v\n", s, e)
      if e != nil { break }
   }
}

结果:

"west" <nil>
"north" <nil>
"east" <nil>
"" EOF

我最近发现了fmt.Scanner[1],所以我想尝试实现它。我想出了这个:

package main

import (
   "fmt"
   "strings"
)

type comma struct { tok string }

func (c *comma) Scan(state fmt.ScanState, verb rune) error {
   tok, err := state.Token(false, func(r rune) bool {
      return r != ','
   })
   if err != nil {
      return err
   }
   if _, _, err := state.ReadRune(); err != nil {
      if len(tok) == 0 {
         return err
      }
   }
   c.tok = string(tok)
   return nil
}

func main() {
   r := strings.NewReader("west,north,east")
   for {
      var c comma
      _, e := fmt.Fscan(r, &c)
      fmt.Printf("%q %v\n", c.tok, e)
      if e != nil { break }
   }
}

结果:

"west" <nil>
"north" <nil>
"east" <nil>
"" unexpected EOF

所以结果非常接近,但是让我困扰的是unexpected EOF。是否可能只使用自定义的fmt.Scanner获得常规的EOF?我在这里做错了什么,还是这是一个错误?

  1. https://golang.org/pkg/fmt#Scanner
英文:

If I want to scan through a string, I can do this:

package main

import (
   &quot;fmt&quot;
   &quot;strings&quot;
)

func main() {
   r := strings.NewReader(&quot;west north east&quot;)
   for {
      var s string
      _, e := fmt.Fscan(r, &amp;s)
      fmt.Printf(&quot;%q %v\n&quot;, s, e)
      if e != nil { break }
   }
}

Result:

&quot;west&quot; &lt;nil&gt;
&quot;north&quot; &lt;nil&gt;
&quot;east&quot; &lt;nil&gt;
&quot;&quot; EOF

I recently discovered fmt.Scanner [1], so I thought I would try to implement
it. I came up with this:

package main

import (
   &quot;fmt&quot;
   &quot;strings&quot;
)

type comma struct { tok string }

func (c *comma) Scan(state fmt.ScanState, verb rune) error {
   tok, err := state.Token(false, func(r rune) bool {
      return r != &#39;,&#39;
   })
   if err != nil {
      return err
   }
   if _, _, err := state.ReadRune(); err != nil {
      if len(tok) == 0 {
         return err
      }
   }
   c.tok = string(tok)
   return nil
}

func main() {
   r := strings.NewReader(&quot;west,north,east&quot;)
   for {
      var c comma
      _, e := fmt.Fscan(r, &amp;c)
      fmt.Printf(&quot;%q %v\n&quot;, c.tok, e)
      if e != nil { break }
   }
}

Result:

&quot;west&quot; &lt;nil&gt;
&quot;north&quot; &lt;nil&gt;
&quot;east&quot; &lt;nil&gt;
&quot;&quot; unexpected EOF

So the result is pretty close, but what bothers me is the unexpected EOF. Is
it possible to just get a regular EOF with a custom fmt.Scanner? Am I doing
something wrong here, or is this a bug?

  1. https://golang.org/pkg/fmt#Scanner

答案1

得分: 1

感谢 golang-nuts 列表上的 Ian Lance Taylor 的建议,他建议使用 panic 而不是 return 来处理错误。在 Go 代码中,Fscan 调用了一个名为 doScan 的函数,该函数又调用了一个名为 errorHandler 的函数 [1]。最后这个函数使用 recover 将任何 panic 转换为普通错误。这个程序产生了与我原始示例相同的输出:

package main

import (
   "fmt"
   "strings"
)

type comma struct { tok string }

func (c *comma) Scan(state fmt.ScanState, verb rune) error {
   tok, err := state.Token(false, func(r rune) bool {
      return r != ','
   })
   if err != nil { return err }
   if _, _, err := state.ReadRune(); err != nil {
      if len(tok) == 0 {
         panic(err)
      }
   }
   c.tok = string(tok)
   return nil
}

func main() {
   r := strings.NewReader("west,north,east")
   for {
      var c comma
      _, err := fmt.Fscan(r, &c)
      fmt.Printf("%q %v\n", c.tok, err)
      if err != nil { break }
   }
}
  1. https://github.com/golang/go/blob/go1.16.4/src/fmt/scan.go#L1056-L1067
英文:

Thanks to Ian Lance Taylor on the golang-nuts list, he suggested to panic
the error instead of return. In the Go code, Fscan calls a function
doScan, which in turn calls a function errorHandler [1]. This last function
uses recover to turn any panic into regular error. This program gives
idential output to my original example:

package main

import (
   &quot;fmt&quot;
   &quot;strings&quot;
)

type comma struct { tok string }

func (c *comma) Scan(state fmt.ScanState, verb rune) error {
   tok, err := state.Token(false, func(r rune) bool {
      return r != &#39;,&#39;
   })
   if err != nil { return err }
   if _, _, err := state.ReadRune(); err != nil {
      if len(tok) == 0 {
         panic(err)
      }
   }
   c.tok = string(tok)
   return nil
}

func main() {
   r := strings.NewReader(&quot;west,north,east&quot;)
   for {
      var c comma
      _, err := fmt.Fscan(r, &amp;c)
      fmt.Printf(&quot;%q %v\n&quot;, c.tok, err)
      if err != nil { break }
   }
}
  1. https://github.com/golang/go/blob/go1.16.4/src/fmt/scan.go#L1056-L1067

huangapple
  • 本文由 发表于 2021年5月27日 08:27:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/67714217.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定