123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- package ini
- // Scan a databuffer of INI-formatted data.
- import (
- "fmt"
- )
- type scanState int
- type scanFunction func(c byte, e *element) scanState
- const (
- scanContinue scanState = 1 << iota
- scanEOL
- scanEOF
- scanError
- scanBegin
- scanSection
- scanVariable
- scanVariableValue
- )
- var (
- EOF = fmt.Errorf("END OF FILE")
- EOL = fmt.Errorf("END OF LINE")
- )
- // SyntaxError is returned when the formatting is wrong.
- type SyntaxError struct {
- got byte
- exp byte
- msg string
- off int
- }
- // Error returns the error as a string
- func (s SyntaxError) Error() string {
- if s.got == 0 {
- return fmt.Sprintf("Error on position '%d: %s'", s.off, s.msg)
- }
- if s.exp == 0 {
- return fmt.Sprintf("Error on position '%d': %s. Got '%c'.", s.off, s.msg, s.got)
- }
- return fmt.Sprintf("Error on position '%d': %s. Got '%c' while expecting '%c'.", s.off, s.msg, s.got, s.exp)
- }
- type scanner struct {
- data []byte
- prev byte
- scan scanFunction
- read int
- len int
- err error
- }
- func newScanner(d []byte) *scanner {
- s := &scanner{
- data: d,
- prev: 0,
- read: 0,
- len: len(d),
- }
- s.scan = s.begin
- return s
- }
- func (s *scanner) reset() {
- s.prev = 0
- s.scan = s.begin
- s.read = 0
- s.err = nil
- }
- // loop assists next-method
- func (s *scanner) loop() (*element, error) {
- e := newElement()
- for _, c := range s.data[s.read:] {
- s.read++
- state := s.scan(c, e)
- s.prev = c
- if s.err != nil {
- return nil, s.err
- }
- switch state {
- case scanEOL:
- return e, EOL
- case scanEOF:
- return e, EOF
- }
- }
- return e, nil
- }
- // Just quickly loops through the INI and returns an error on error
- func (s *scanner) valid() error {
- defer s.reset()
- for _, err := s.next(); ; _, err = s.next() {
- switch err {
- case EOL:
- continue
- case EOF:
- return nil
- default:
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- // next returns one element and most likely an error (typically EOF/EOL) on every iteration
- func (s *scanner) next() (*element, error) {
- e, err := s.loop()
- if e != nil {
- e.trim()
- }
- return e, err
- }
- // ret ensures that we return the proper error
- func (s *scanner) ret(state scanState) scanState {
- if state != scanEOF && s.read == s.len {
- state = scanEOF
- }
- if state == scanEOL || state == scanEOF {
- s.scan = s.begin
- }
- return state
- }
- func (s *scanner) space(c byte) bool {
- return c == ' ' || c == '\n' || c == '\t' || c == '\r'
- }
- func (s *scanner) begin(c byte, e *element) scanState {
- switch {
- case s.space(c):
- return s.ret(scanContinue)
- case c == '[':
- e.setType(section)
- s.scan = s.section
- return s.ret(scanContinue)
- default:
- e.setType(variable)
- s.scan = s.variable
- return s.scan(c, e)
- }
- }
- func (s *scanner) section(c byte, e *element) scanState {
- switch {
- case s.space(c):
- s.err = SyntaxError{
- got: c,
- exp: ']',
- off: s.read,
- msg: "Invalid space character.",
- }
- return scanError
- case s.read == s.len:
- s.err = SyntaxError{
- got: 0x3,
- exp: ']',
- off: s.read,
- msg: "Invalid EOF",
- }
- return scanError
- case c == ']':
- return s.ret(scanEOL)
- default:
- e.key.add(c)
- return s.ret(scanContinue)
- }
- }
- func (s *scanner) variable(c byte, e *element) scanState {
- switch c {
- case '\n', '\r':
- return s.ret(scanEOL)
- case '=':
- s.scan = s.variableValue
- return s.ret(scanContinue)
- default:
- e.key.add(c)
- return s.ret(scanContinue)
- }
- }
- func (s *scanner) variableValue(c byte, e *element) scanState {
- switch c {
- case '\n', '\r':
- if e.val.lastByteMatching('\\') >= 0 {
- e.val.removeLastByteMatching('\\')
- e.val.add('\n')
- return s.ret(scanContinue)
- }
- return s.ret(scanEOL)
- default:
- e.val.add(c)
- return s.ret(scanContinue)
- }
- }
|