|
@@ -0,0 +1,183 @@
|
|
|
+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)
|
|
|
+ }
|
|
|
+}
|