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