Интерфейс fmt.ScanState

Интерфейс fmt.ScanState представляет собой состояние сканера, которое передается в пользовательские методы сканирования. Он позволяет получить доступ к токенам, прочитанным из входного потока, и управлять процессом сканирования.

Данный интерфейс реализуется внутренними структурами пакета fmt и используется в функциях семейства Scan и Fscan. Основное назначение интерфейса - предоставить разработчикам возможность создавать собственные типы, которые могут быть считаны из входного потока.

Методы интерфейса

Интерфейс fmt.ScanState содержит следующие методы:

gotype ScanState interface {
  ReadRune() (r rune, size int, err error)
  UnreadRune() error
  SkipSpace() error
  Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
  Width() (wid int, ok bool)
  Read(buf []byte) (n int, err error)
}

Метод ReadRune читает один рунический символ из потока. Метод UnreadRune отменяет чтение последнего рунического символа.

Метод SkipSpace пропускает все пробельные символы в потоке. Метод Token читает токен из потока, используя функцию для определения границ токена.

Метод Width возвращает ширину, указанную в формате сканирования. Метод Read читает байты в переданный срез.

Пример создания пользовательского типа для сканирования

Создадим пользовательский тип, который реализует интерфейс Scanner и использует fmt.ScanState для чтения данных:

gopackage main

import (
  "fmt"
  "strings"
)

type Point struct {
  X, Y int
}

func (p *Point) Scan(state fmt.ScanState, verb rune) error {
  state.SkipSpace()
  state.ReadRune() // skip '('

  fmt.Fscanf(state, "%d", &p.X)
  state.ReadRune() // skip ','

  fmt.Fscanf(state, "%d", &p.Y)
  state.ReadRune() // skip ')'

  return nil
}

func main() {
  var p Point
  input := "(10,20)"
  fmt.Sscan(input, &p)
  fmt.Printf("%+v", p)
}

Результат выполнения кода:

go{X:10 Y:20}

В данном примере мы создали структуру Point, которая реализует метод Scan. Внутри метода мы используем state.SkipSpace для пропуска пробелов и state.ReadRune для чтения символов.

Пример использования метода Token

Метод Token позволяет читать токены, определяя их границы с помощью пользовательской функции:

gopackage main

import (
  "fmt"
  "strings"
)

type Word struct {
  Value string
}

func (w *Word) Scan(state fmt.ScanState, verb rune) error {
  token, err := state.Token(false, func(r rune) bool {
    return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z'
  })
  if err != nil {
    return err
  }
  w.Value = string(token)
  return nil
}

func main() {
  var w Word
  input := "Hello123World"
  fmt.Sscan(input, &w)
  fmt.Printf("%+v", w)
}

Результат выполнения кода:

go{Value:Hello}

В этом примере метод Token читает только буквенные символы, останавливаясь на цифрах. В результате мы получаем первое слово "Hello".

Пример использования ширины

Метод Width позволяет узнать, задана ли ширина в формате и использовать ее для ограничения чтения:

gopackage main

import (
  "fmt"
  "strings"
)

type FixedString struct {
  Value string
}

func (fs *FixedString) Scan(state fmt.ScanState, verb rune) error {
  width, ok := state.Width()
  if !ok {
    return fmt.Errorf("width not specified")
  }

  buf := make([]byte, width)
  n, err := state.Read(buf)
  if err != nil {
    return err
  }
  fs.Value = string(buf[:n])
  return nil
}

func main() {
  var fs FixedString
  input := "abcdefghij"
  fmt.Sscanf(input, "%5s", &fs)
  fmt.Printf("%+v", fs)
}

Результат выполнения кода:

go{Value:abcde}

В этом примере мы используем ширину для чтения ровно 5 символов. Формат %5s указывает, что нужно прочитать 5 байт.

Пример обработки ошибок

Рассмотрим пример с обработкой ошибок при сканировании:

gopackage main

import (
  "fmt"
  "strings"
)

type IntPair struct {
  First, Second int
}

func (ip *IntPair) Scan(state fmt.ScanState, verb rune) error {
  state.SkipSpace()

  _, err := state.ReadRune()
  if err != nil {
    return fmt.Errorf("expected '(' but got EOF")
  }

  _, err = fmt.Fscan(state, &ip.First)
  if err != nil {
    return fmt.Errorf("invalid first number: %v", err)
  }

  _, err = state.ReadRune()
  if err != nil {
    return fmt.Errorf("expected ',' but got EOF")
  }

  _, err = fmt.Fscan(state, &ip.Second)
  if err != nil {
    return fmt.Errorf("invalid second number: %v", err)
  }

  _, err = state.ReadRune()
  if err != nil {
    return fmt.Errorf("expected ')' but got EOF")
  }

  return nil
}

func main() {
  var ip IntPair
  input := "(5,10)"
  _, err := fmt.Sscan(input, &ip)
  if err != nil {
    fmt.Printf("Error: %v", err)
    return
  }
  fmt.Printf("%+v", ip)
}

Результат выполнения кода:

go{First:5 Second:10}

Данный пример демонстрирует правильную обработку ошибок при чтении различных частей входных данных.

Смотрите также

  • интерфейс fmt.Scanner,
    который реализуется для создания пользовательских типов
  • интерфейс fmt.State,
    который используется при форматировании
  • функцию fmt.Sscan,
    которая сканирует строку
  • функцию fmt.Sscanf,
    которая сканирует строку с форматом