9 Commits

Author SHA1 Message Date
amery bfbf7861cb Unmarshal: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:59:16 +00:00
amery 75d79f3d3d Decoder: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:59:16 +00:00
amery 36da7c086e basic: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:59:16 +00:00
amery 5514583962 parser: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:57:09 +00:00
amery 8e98b927dd parser: emitError
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:57:09 +00:00
amery 72d69a2b48 build-sys: use local asciigoat.org/core [DO-NOT-MERGE]
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:38:07 +00:00
amery 27ca07b8a0 parser: introduce parsing callbacks
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 18:10:39 +00:00
amery 235c8ad85c parser: implement initial tokeniser
only logging position, errors and non-whitespace elements

Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 17:53:40 +00:00
amery 1dba6d6b48 parser: add placeholder for ini Parser
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-30 17:53:34 +00:00
9 changed files with 309 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
// Package basic provides a basic representation of dosini-style documents
package basic
+23
View File
@@ -0,0 +1,23 @@
package basic
// Document ...
type Document struct {
Global []Field
Sections []Section
}
// Section ...
type Section struct {
Name string
Key string
HadKey bool
Fields []Field
}
// Field ...
type Field struct {
Key string
Value string
}
+48
View File
@@ -0,0 +1,48 @@
package ini
import (
"bytes"
"io"
"asciigoat.org/core"
"asciigoat.org/ini/parser"
)
// Decoder ...
type Decoder struct {
io.Closer
p *parser.Parser
}
// Decode ...
func (dec *Decoder) Decode() error {
defer dec.Close()
return dec.p.Run()
}
// NewDecoder creates a Decoder over the provided [io.Reader]
func NewDecoder(r io.Reader) *Decoder {
rc := core.NewReadCloser(r)
switch {
case rc == nil:
return nil
default:
dec := &Decoder{
p: parser.NewParser(rc),
Closer: rc,
}
return dec
}
}
// NewDecoderBytes creates a Decoder over a provided bytes array
func NewDecoderBytes(b []byte) *Decoder {
return NewDecoder(bytes.NewBuffer(b))
}
// NewDecoderString creates a Decoder over a provided string of data
func NewDecoderString(s string) *Decoder {
return NewDecoder(bytes.NewBufferString(s))
}
+6 -1
View File
@@ -2,7 +2,12 @@ module asciigoat.org/ini
go 1.19
require github.com/mgechev/revive v1.3.3
replace asciigoat.org/core => ../core
require (
asciigoat.org/core v0.3.6
github.com/mgechev/revive v1.3.3
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
+99
View File
@@ -0,0 +1,99 @@
package parser
import (
"log"
"asciigoat.org/core/lexer"
)
// Run parses the source
func (p *Parser) Run() error {
p.setDefaults()
p.pos.Reset()
return lexer.Run(p.lexStart)
}
func (p *Parser) lexStart() (lexer.StateFn, error) {
for {
r, _, err := p.src.ReadRune()
switch {
case err != nil:
// read error
return p.emitError("", err)
case IsNewLine(r):
// new line
p.lexNewLine(r)
p.src.Discard()
p.pos.StepLine()
case IsSpace(r):
// whitespace
p.src.Discard()
p.pos.Step()
case IsCommentStart(r):
// switch to comment lexer
p.src.UnreadRune()
return p.lexComment, nil
case IsSectionStart(r):
// section
p.src.Discard()
p.pos.Step()
return p.lexSectionStart, nil
default:
// entry
p.src.UnreadRune()
return p.lexEntryStart, nil
}
}
}
func (p *Parser) lexNewLine(r1 rune) {
// r1 is warrantied to be either \n or \r
r2, _, err := p.src.ReadRune()
switch r1 {
case '\r':
switch {
case r2 == '\n':
// CR LN
case err == nil:
// CR
p.src.UnreadRune()
default:
// CR EOF
}
case '\n':
switch {
case r2 == '\r':
// LN CR
case err == nil:
// LN
p.src.UnreadRune()
default:
// LN EOF
}
default:
panic("unreachable")
}
}
func (p *Parser) lexComment() (lexer.StateFn, error) {
// until the end of the line
p.src.AcceptAll(IsNotNewLine)
err := p.OnComment(p.pos, p.src.Emit())
return p.lexStart, err
}
func (*Parser) lexSectionStart() (lexer.StateFn, error) { return nil, nil }
func (*Parser) lexEntryStart() (lexer.StateFn, error) { return nil, nil }
func (p *Parser) lexToken() (lexer.StateFn, error) {
p.src.AcceptAll(IsNotSpace)
s := p.src.Emit()
log.Printf("%s: %s: %q", p.pos, "token", s)
p.pos.StepN(len(s))
return p.lexStart, nil
}
+19
View File
@@ -0,0 +1,19 @@
package parser
import "asciigoat.org/core/lexer"
func (p *Parser) emitError(content string, err error) (lexer.StateFn, error) {
err2 := p.OnError(p.pos, content, err)
switch {
case err2 != nil:
// return wrapped error
return nil, err2
default:
// return original error
return nil, err
}
}
func (p *Parser) emitInvalidRune(r rune) (lexer.StateFn, error) {
return p.emitError(string([]rune{r}), lexer.ErrUnacceptableRune)
}
+20
View File
@@ -0,0 +1,20 @@
package parser
import "asciigoat.org/core/lexer"
var (
// IsNewLine tells if a rune represents a line break or the start of one
IsNewLine = lexer.NewIsIn("\n\r")
// IsSpace tells if a rune is considered whitespace by unicode
IsSpace = lexer.IsSpace
// IsNotNewLine tells if a rune is anything other than line breaks
IsNotNewLine = lexer.NewIsNot(IsNewLine)
// IsNotSpace tells if a rune is anything other than whitespace
IsNotSpace = lexer.NewIsNot(IsSpace)
IsCommentStart = lexer.NewIsIn(";#")
)
func IsSectionStart(r rune) bool {
return r == '['
}
+77
View File
@@ -0,0 +1,77 @@
// Package parser parses dosini-style files
package parser
import (
"io"
"asciigoat.org/core/lexer"
)
// Parser parses a dosini-style document
type Parser struct {
src *lexer.Reader
pos lexer.Position
// OnSection is called after a [section] is parsed.
// Returning an error will abort the process.
OnSection func(pos lexer.Position, name, subname string, hasSubname bool) error
// OnField is called after a `key = value` entry is parsed
// Returning an error will abort the process.
OnField func(pos lexer.Position, key, value string) error
// OnComment is called after a comment is parsed
// Returning an error will abort the process.
OnComment func(pos lexer.Position, comment string) error
// OnError is called after each parsing error, which you are allowed to
// override.
// OnError is called for EOF as well, but this error isn't returned as such by
// Parser.Run(). The caller will receive (nil, nil) instead indicating the
// processes terminated correctly.
OnError func(pos lexer.Position, content string, err error) error
}
func defaultOnSection(_ lexer.Position, _, _ string, _ bool) error { return nil }
func defaultOnField(_ lexer.Position, _, _ string) error { return nil }
func defaultOnComment(_ lexer.Position, _ string) error { return nil }
func defaultOnError(pos lexer.Position, content string, err error) error {
return &lexer.Error{
Line: pos.Line,
Column: pos.Column,
Content: content,
Err: err,
}
}
func (p *Parser) setDefaults() {
if p.OnSection == nil {
p.OnSection = defaultOnSection
}
if p.OnField == nil {
p.OnField = defaultOnField
}
if p.OnComment == nil {
p.OnComment = defaultOnComment
}
if p.OnError == nil {
p.OnError = defaultOnError
}
}
// NewParser creates a dosini-style parser using
// an [io.Reader] as source
func NewParser(r io.Reader) *Parser {
if r == nil {
return nil
}
return &Parser{
src: lexer.NewReader(r),
}
}
+15
View File
@@ -0,0 +1,15 @@
package ini
import "io"
// ReadInto ...
func ReadInto(v any, r io.Reader) error {
dec := NewDecoder(r)
return dec.Unmarshal(v)
}
// Unmarshal ...
func (*Decoder) Unmarshal(any) error {
return nil
}