diff --git a/decoder.go b/decoder.go new file mode 100644 index 0000000..b33d6f5 --- /dev/null +++ b/decoder.go @@ -0,0 +1,72 @@ +package ini + +import ( + "bytes" + "io" + "strings" + + "asciigoat.org/core" + "asciigoat.org/core/reflection" + "asciigoat.org/ini/parser" +) + +// Decoder ... +type Decoder[T any] struct { + io.Closer + + out *reflection.Reflection[T] + p *parser.Parser + queue []*token +} + +// Decode ... +func (dec *Decoder[T]) Decode(v *T) error { + defer dec.Close() + + r, err := reflection.New(v) + switch e := err.(type) { + case *reflection.InvalidUnmarshalError: + // customize error + e.Prefix = "ini" + e.Method = "Decode" + case nil: + // good reflection. Go! + dec.out = r + err = dec.p.Run() + } + + return err +} + +// NewDecoder creates a Decoder using the provided [io.Reader] +// as source +func NewDecoder[T any](r io.Reader) *Decoder[T] { + rc := core.NewReadCloser(r) + switch { + case rc == nil: + return nil + default: + dec := &Decoder[T]{ + p: parser.NewParser(rc), + Closer: rc, + } + dec.init() + return dec + } +} + +func (dec *Decoder[T]) init() { + dec.p.OnToken = dec.parserOnToken + dec.p.OnError = dec.parserOnError +} + +// NewDecoderBytes creates a Decoder using the provided bytes array +// as source +func NewDecoderBytes[T any](b []byte) *Decoder[T] { + return NewDecoder[T](bytes.NewReader(b)) +} + +// NewDecoderString creates a Decoder over a provided string of data +func NewDecoderString[T any](s string) *Decoder[T] { + return NewDecoder[T](strings.NewReader(s)) +} diff --git a/decoder_error.go b/decoder_error.go new file mode 100644 index 0000000..18b857e --- /dev/null +++ b/decoder_error.go @@ -0,0 +1,33 @@ +package ini + +import ( + "errors" + "log" + + "asciigoat.org/core/lexer" +) + +var ( + errInvalidToken = errors.New("invalid token") +) + +func newError(pos lexer.Position, content, hint string, err error) *lexer.Error { + return &lexer.Error{ + Line: pos.Line, + Column: pos.Column, + Content: content, + Hint: hint, + Err: err, + } +} +func (*Decoder[T]) newErrInvalidToken(t *token) *lexer.Error { + return newError(t.pos, t.value, "", errInvalidToken) +} + +// parserOnError is the callback for lexer errors +func (dec *Decoder[T]) parserOnError(pos lexer.Position, content string, err error) error { + log.Printf("%s: %s %s: %q: %v", "ini", pos, "error:", content, err) + + dec.executeFinal() + return newError(pos, content, "", err) +} diff --git a/decoder_token.go b/decoder_token.go new file mode 100644 index 0000000..b276ac3 --- /dev/null +++ b/decoder_token.go @@ -0,0 +1,56 @@ +package ini + +import ( + "fmt" + "log" + + "asciigoat.org/core/lexer" + "asciigoat.org/ini/parser" +) + +type token struct { + pos lexer.Position + typ parser.TokenType + value string +} + +func (t token) String() string { + return fmt.Sprintf("%s %s: %q", t.pos, t.typ, t.value) +} + +// typeOK tells if a token of the specified type is acceptable +// at this time. +func (dec *Decoder[T]) typeOK(typ parser.TokenType) bool + +// execute is called after each acceptable token is appended to the queue +func (dec *Decoder[T]) execute() error + +// executeFinal is called after an error +func (dec *Decoder[T]) executeFinal() + +// parserOnToken is the callback from the parser +func (dec *Decoder[T]) parserOnToken(pos lexer.Position, typ parser.TokenType, value string) error { + var err error + + log.Printf("%s: %s %s %q", "ini", pos, typ, value) + + t := &token{pos, typ, value} + switch { + case typ == parser.TokenComment: + // ignore comments + case dec.typeOK(typ): + // acceptable token + dec.queue = append(dec.queue, t) + err = dec.execute() + default: + // unacceptable + err = dec.newErrInvalidToken(t) + } + + if err != nil { + dec.executeFinal() + return err + } + + return nil +}