Browse Source

Init commit. Started on scanner, for decoding.

Joachim M. Giæver 6 years ago
parent
commit
dfa396f8b1
7 changed files with 380 additions and 0 deletions
  1. 0 0
      data/file.ini
  2. 73 0
      go-ini.go
  3. 27 0
      ini/decode.go
  4. 85 0
      ini/element.go
  5. 11 0
      ini/encode.go
  6. 1 0
      ini/ini.go
  7. 183 0
      ini/scanner.go

+ 0 - 0
data/file.ini


+ 73 - 0
go-ini.go

@@ -0,0 +1,73 @@
+package main
+
+import (
+	"fmt"
+
+	"git.giaever.org/joachimmg/go-ini.git/ini"
+)
+
+type Test struct {
+	String         string
+	UnquotedString string
+	MultipleLines  string
+	WithoutValue   bool
+
+	Int struct {
+		Int   int
+		Int8  int8
+		Int16 int16
+		Int32 int32
+		Int64 int64
+
+		Uint struct {
+			Uint    uint
+			Uint8   uint8
+			Unint16 uint16
+			Uint32  uint32
+			Uint64  uint64
+		}
+	}
+}
+
+func main() {
+
+	text := `
+
+String = "This is some text"
+UnquotedString = This is some unquoted text
+MultipleLines = This is \
+multiple lines \
+going on
+WithoutValue
+
+[int]
+Int = -1
+Int8 = -8
+Int16 = -16
+Int32 = -32
+Int64 = -64
+
+[int.uint]
+Uint = 1
+Uint8 = 8
+Uint16 = 16
+Uint32 = 32
+Uint64 = 64
+
+Uint64 = 64
+
+`
+
+	fmt.Println("INI", text)
+
+	test1 := Test{}
+	test2 := Test{
+		String:         "This is some text",
+		UnquotedString: "Helloooo... Not possible to not quote this",
+		MultipleLines:  "This is \nmultiple lines \ngoing on",
+		WithoutValue:   true,
+	}
+	//test2 := &Test{}
+	ini.Unmarshal([]byte(text), test1)
+	ini.Marshal(test2)
+}

+ 27 - 0
ini/decode.go

@@ -0,0 +1,27 @@
+package ini
+
+import (
+	"fmt"
+)
+
+type Unmarshaler interface {
+	UnmarshalINI([]byte) error
+}
+
+type decode struct {
+	scan *scanner
+}
+
+func Unmarshal(d []byte, v interface{}) error {
+
+	dec := &decode{
+		newScanner(d),
+	}
+
+	if err := dec.scan.valid(); err != nil {
+		fmt.Println("ERROR, invalid", err)
+		return err
+	}
+
+	return nil
+}

+ 85 - 0
ini/element.go

@@ -0,0 +1,85 @@
+package ini
+
+import (
+	"fmt"
+)
+
+type elementType int
+
+const (
+	section  elementType = 1 << iota
+	variable elementType = 1 << iota
+)
+
+type element struct {
+	eType elementType
+	eKey  []byte
+	eVal  []byte
+}
+
+func newElement() *element {
+	return &element{}
+}
+
+func (e *element) setValue(v interface{}) bool {
+	switch v.(type) {
+	case byte:
+		e.eVal = append(e.eVal, v.(byte))
+		return true
+	case []byte:
+		e.eVal = v.([]byte)
+		return true
+	}
+
+	fmt.Printf("Uknown type: %T", v)
+	return false
+}
+
+func (e *element) removeLastValueByteMatching(c byte) {
+	if n := len(e.eVal); n != 0 && e.eVal[n-1] == c {
+		e.eVal = e.eVal[0 : n-1]
+	}
+}
+
+func (e *element) setKey(k interface{}) bool {
+	switch k.(type) {
+	case byte:
+		e.eKey = append(e.eKey, k.(byte))
+		return true
+	case []byte:
+		e.eKey = k.([]byte)
+		return true
+	}
+
+	return false
+}
+
+func (e *element) removeLastKeyByteMatching(c byte) {
+	if n := len(e.eKey); n != 0 && e.eKey[n-1] == c {
+		e.eKey = e.eKey[0 : n-1]
+	}
+}
+
+func (e *element) setType(t elementType) {
+	e.eType = t
+}
+
+func (e *element) String() string {
+	switch e.eType {
+	case section:
+		return fmt.Sprintf(
+			"section: %s", e.eKey,
+		)
+	case variable:
+		if len(e.eVal) == 0 {
+			return fmt.Sprintf(
+				"variable: %s -> true", e.eKey,
+			)
+		}
+		return fmt.Sprintf(
+			"variable: %s -> %s", e.eKey, e.eVal,
+		)
+	default:
+		return "unknown element"
+	}
+}

+ 11 - 0
ini/encode.go

@@ -0,0 +1,11 @@
+package ini
+
+import (
+	"fmt"
+)
+
+func Marshal(v interface{}) ([]byte, error) {
+
+	fmt.Println(v)
+	return nil, nil
+}

+ 1 - 0
ini/ini.go

@@ -0,0 +1 @@
+package ini

+ 183 - 0
ini/scanner.go

@@ -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)
+	}
+}