// Package parser parses dosini-style files package parser import ( "io" "log" "asciigoat.org/core/lexer" ) // Parser parses a dosini-style document type Parser struct { src *lexer.Reader pos lexer.Position // OnToken is called for each identified token. if it returns an error // parsing is interrupted. OnToken func(pos lexer.Position, typ TokenType, value string) error // OnError is called in case of a parsing error, and it's allowed // to replace the error returned by [Parser.Run]. // OnError is called for io.EOF, but [Parser.Run] will consider it // normal termination. OnError func(pos lexer.Position, content string, err error) error } func defaultOnToken(pos lexer.Position, typ TokenType, value string) error { log.Printf("%s:%v:%v: %q", typ, pos.Line, pos.Column, value) return nil } func defaultOnError(pos lexer.Position, content string, err error) error { log.Printf("%s:%v:%v: %q: %s", "error", pos.Line, pos.Column, content, err) return lexer.Error{ Line: pos.Line, Column: pos.Column, Content: content, Err: err, } } func (p *Parser) setDefaults() { if p.OnToken == nil { p.OnToken = defaultOnToken } if p.OnError == nil { p.OnError = defaultOnError } } func (p *Parser) emitString(typ TokenType) error { s := p.src.Emit() err := p.OnToken(p.pos, typ, s) p.pos.StepN(len(s)) return err } 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) } // stepLine discards the data and moves the position // to the next line. func (p *Parser) stepLine() { p.src.Discard() p.pos.StepLine() } // stepRune discards the data and moves the position // one rune forward on the same line. func (p *Parser) stepRune() { p.src.Discard() p.pos.Step() } // stepString discards the data and moves the position // forward on the same line the length of the discarded // content. func (p *Parser) stepString() { s := p.src.Emit() p.pos.StepN(len(s)) } // 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), } }