package ini import ( "fmt" ) type scanState int type scanFunction func(c byte, e *element) scanState const ( scanContinue scanState = 1 << iota scanEOL scanEOF scanBegin scanSection scanVariable scanVariableValue ) var ( EOF = fmt.Errorf("END OF FILE") EOL = fmt.Errorf("END OF LINE") ) type SyntaxError struct { got byte exp byte msg string off int } 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) 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 } func (s *scanner) valid() error { for _, err := s.next(); ; _, err = s.next() { if err != EOF && err != EOL { return err } } return nil } func (s *scanner) next() (*element, error) { e, err := s.loop() return e, err } func (s *scanner) ret(state scanState) scanState { if state != scanEOF && s.read == s.len { s.scan = s.begin return scanEOF } if state == scanEOL { s.scan = s.begin } return state } func (s *scanner) begin(c byte, e *element) scanState { switch c { case ' ', '\n', '\t', '\r': return s.ret(scanContinue) case '[': 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 c { case '\n': s.err = &SyntaxError{ msg: "Missing valid character", got: c, exp: ']', off: s.read, } fallthrough case ']': return s.ret(scanEOL) default: e.setKey(c) return s.ret(scanContinue) } } func (s *scanner) variable(c byte, e *element) scanState { switch c { case '\n', '\r': return s.ret(scanEOL) case '=': e.removeLastKeyByteMatching(' ') s.scan = s.variableValue return s.ret(scanContinue) default: e.setKey(c) return s.ret(scanContinue) } } func (s *scanner) variableValue(c byte, e *element) scanState { switch c { case '"', '\'': return s.ret(scanContinue) case '\n', '\r': if s.prev == '\\' { e.setValue(byte('n')) return s.ret(scanContinue) } return s.ret(scanEOL) default: if s.prev == '=' && c == ' ' { return scanContinue } e.setValue(c) return s.ret(scanContinue) } }