parser: introduce TextParser and refactor Parser #7
Merged
amery
merged 5 commits from pr-amery-textparser
into main
1 year ago
7 changed files with 182 additions and 76 deletions
@ -0,0 +1,103 @@ |
|||||||
|
package parser |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"asciigoat.org/core/lexer" |
||||||
|
) |
||||||
|
|
||||||
|
// TextParser is a generic text parser.
|
||||||
|
type TextParser struct { |
||||||
|
*lexer.Reader |
||||||
|
pos lexer.Position |
||||||
|
} |
||||||
|
|
||||||
|
// Init initializes the [TextParser] with a non-nil [io.Reader].
|
||||||
|
func (p *TextParser) Init(r io.Reader) { |
||||||
|
switch { |
||||||
|
case p == nil || r == nil: |
||||||
|
panic("invalid call") |
||||||
|
case p.Reader != nil: |
||||||
|
panic("parser already initialized") |
||||||
|
default: |
||||||
|
p.Reader = lexer.NewReader(r) |
||||||
|
p.pos.Reset() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// InitBytes initializes the [TextParser] with a byte array
|
||||||
|
func (p *TextParser) InitBytes(b []byte) { |
||||||
|
p.Init(bytes.NewReader(b)) |
||||||
|
} |
||||||
|
|
||||||
|
// InitString initializes the [TextParser] with a byte array
|
||||||
|
func (p *TextParser) InitString(s string) { |
||||||
|
p.Init(strings.NewReader(s)) |
||||||
|
} |
||||||
|
|
||||||
|
// Discard shadows [lexer.Reader]'s, and takes in consideration
|
||||||
|
// new lines on the discarded data when moving the position
|
||||||
|
func (p *TextParser) Discard() { |
||||||
|
s := p.Reader.Emit() |
||||||
|
l := GetPositionalLength(s) |
||||||
|
p.pos.Add(l) |
||||||
|
} |
||||||
|
|
||||||
|
// Emit returns the accepted text, its position, and
|
||||||
|
// moves the cursor position accordingly
|
||||||
|
func (p *TextParser) Emit() (lexer.Position, string) { |
||||||
|
pos := p.pos |
||||||
|
s := p.Reader.Emit() |
||||||
|
l := GetPositionalLength(s) |
||||||
|
p.pos.Add(l) |
||||||
|
|
||||||
|
return pos, s |
||||||
|
} |
||||||
|
|
||||||
|
// Step discards what's been accepted and increments the
|
||||||
|
// position assuming they all increment the column counter
|
||||||
|
func (p *TextParser) Step() { |
||||||
|
s := p.Reader.Emit() |
||||||
|
p.pos.StepN(len(s)) |
||||||
|
} |
||||||
|
|
||||||
|
// StepLine discards what's been accepted and moves then
|
||||||
|
// position to the beginning of the next line
|
||||||
|
func (p *TextParser) StepLine() { |
||||||
|
p.Reader.Discard() |
||||||
|
p.pos.StepLine() |
||||||
|
} |
||||||
|
|
||||||
|
// Position returns the position of the first character
|
||||||
|
// of the accepted text
|
||||||
|
func (p *TextParser) Position() lexer.Position { |
||||||
|
return p.pos |
||||||
|
} |
||||||
|
|
||||||
|
// AcceptNewLine checks if next is a new line.
|
||||||
|
// It accepts "\n", "\n\r", "\r" and "\r\n".
|
||||||
|
func (p *TextParser) AcceptNewLine() bool { |
||||||
|
r1, _, err := p.ReadRune() |
||||||
|
switch { |
||||||
|
case err != nil: |
||||||
|
return false |
||||||
|
case r1 == '\n': |
||||||
|
p.AcceptRune('\r') |
||||||
|
return true |
||||||
|
case r1 == '\r': |
||||||
|
p.AcceptRune('\n') |
||||||
|
return true |
||||||
|
default: |
||||||
|
p.UnreadRune() |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// AcceptRune checks if next is the specified rune
|
||||||
|
func (p *TextParser) AcceptRune(r rune) bool { |
||||||
|
return p.Accept(func(r2 rune) bool { |
||||||
|
return r == r2 |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package parser |
||||||
|
|
||||||
|
import ( |
||||||
|
"io" |
||||||
|
|
||||||
|
"asciigoat.org/core/lexer" |
||||||
|
) |
||||||
|
|
||||||
|
type positionLengthParser struct { |
||||||
|
TextParser |
||||||
|
|
||||||
|
lexer.Position |
||||||
|
} |
||||||
|
|
||||||
|
func (p *positionLengthParser) lexStart() (lexer.StateFn, error) { |
||||||
|
for { |
||||||
|
switch { |
||||||
|
case p.AcceptNewLine(): |
||||||
|
p.Position.StepLine() |
||||||
|
case p.Accept(IsAny): |
||||||
|
p.Position.StepN(1) |
||||||
|
default: |
||||||
|
return nil, io.EOF |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetPositionalLength calculates the [lexer.Position] at
|
||||||
|
// the end of a text.
|
||||||
|
func GetPositionalLength(s string) lexer.Position { |
||||||
|
var p positionLengthParser |
||||||
|
if s == "" { |
||||||
|
p.InitString(s) |
||||||
|
|
||||||
|
_ = lexer.Run(p.lexStart) |
||||||
|
} |
||||||
|
return p.Position |
||||||
|
} |
Loading…
Reference in new issue