233 lines
4.1 KiB
Go
233 lines
4.1 KiB
Go
package treerack
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type context struct {
|
|
reader io.RuneReader
|
|
keywords []parser
|
|
offset int
|
|
readOffset int
|
|
consumed int
|
|
offsetLimit int
|
|
failOffset int
|
|
failingParser parser
|
|
readErr error
|
|
eof bool
|
|
results *results
|
|
tokens []rune
|
|
matchLast bool
|
|
level int
|
|
tr []TraceEntry
|
|
maxTraceLength int
|
|
}
|
|
|
|
func newContext(r io.RuneReader, keywords []parser, maxTraceLength int) *context {
|
|
return &context{
|
|
reader: r,
|
|
keywords: keywords,
|
|
results: &results{},
|
|
offsetLimit: -1,
|
|
failOffset: -1,
|
|
maxTraceLength: maxTraceLength,
|
|
}
|
|
}
|
|
|
|
func (c *context) read() bool {
|
|
if c.eof || c.readErr != nil {
|
|
return false
|
|
}
|
|
|
|
token, n, err := c.reader.ReadRune()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
if n == 0 {
|
|
c.eof = true
|
|
return false
|
|
}
|
|
} else {
|
|
c.readErr = err
|
|
return false
|
|
}
|
|
}
|
|
|
|
c.readOffset++
|
|
|
|
if token == unicode.ReplacementChar {
|
|
c.readErr = ErrInvalidUnicodeCharacter
|
|
return false
|
|
}
|
|
|
|
c.tokens = append(c.tokens, token)
|
|
return true
|
|
}
|
|
|
|
func (c *context) token() (rune, bool) {
|
|
if c.offset == c.offsetLimit {
|
|
return 0, false
|
|
}
|
|
|
|
if c.offset == c.readOffset {
|
|
if !c.read() {
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
return c.tokens[c.offset], true
|
|
}
|
|
|
|
func (c *context) fromResults(p parser) bool {
|
|
to, m, ok := c.results.longestResult(c.offset, p.nodeID())
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if m {
|
|
c.success(to)
|
|
} else {
|
|
c.fail(c.offset)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (c *context) isKeyword(from, to int) bool {
|
|
ol := c.offsetLimit
|
|
c.offsetLimit = to
|
|
defer func() { c.offsetLimit = ol }()
|
|
for _, kw := range c.keywords {
|
|
c.offset = from
|
|
kw.parse(c)
|
|
if c.matchLast && c.offset == to {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *context) success(to int) {
|
|
c.offset = to
|
|
c.matchLast = true
|
|
if to > c.consumed {
|
|
c.consumed = to
|
|
}
|
|
}
|
|
|
|
func (c *context) fail(offset int) {
|
|
c.offset = offset
|
|
c.matchLast = false
|
|
}
|
|
|
|
func findLine(tokens []rune, offset int) (line, column int) {
|
|
if offset < 0 {
|
|
return 0, 0
|
|
}
|
|
|
|
tokens = tokens[:offset]
|
|
for i := range tokens {
|
|
column++
|
|
if tokens[i] == '\n' {
|
|
column = 0
|
|
line++
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *context) parseError(p parser, unexpectedInput bool, root int) error {
|
|
definition := p.nodeName()
|
|
flagIndex := strings.Index(definition, ":")
|
|
if flagIndex > 0 {
|
|
definition = definition[:flagIndex]
|
|
}
|
|
|
|
if c.failingParser == nil {
|
|
c.failOffset = c.consumed
|
|
}
|
|
|
|
line, col := findLine(c.tokens, c.failOffset)
|
|
ueLine, ueCol := -1, -1
|
|
if unexpectedInput {
|
|
to, _, _ := c.results.longestResult(0, root)
|
|
ueLine, ueCol = findLine(c.tokens, to)
|
|
}
|
|
|
|
for i := range c.tr {
|
|
c.tr[i].FromLine, c.tr[i].FromCol = findLine(c.tokens, c.tr[i].From)
|
|
c.tr[i].ToLine, c.tr[i].ToCol = findLine(c.tokens, c.tr[i].To)
|
|
}
|
|
|
|
return &ParseError{
|
|
inputContent: c.tokens,
|
|
Offset: c.failOffset,
|
|
Line: line,
|
|
Column: col,
|
|
Definition: definition,
|
|
Trace: c.tr,
|
|
UnexpectedInputLine: ueLine,
|
|
UnexpectedInputCol: ueCol,
|
|
}
|
|
}
|
|
|
|
func (c *context) finalizeParse(root parser) error {
|
|
fp := c.failingParser
|
|
if fp == nil {
|
|
fp = root
|
|
}
|
|
|
|
to, match, found := c.results.longestResult(0, root.nodeID())
|
|
if !found || !match || found && match && to < c.readOffset {
|
|
return c.parseError(fp, found && match && to < c.readOffset, root.nodeID())
|
|
}
|
|
|
|
c.read()
|
|
if c.eof {
|
|
return nil
|
|
}
|
|
|
|
if c.readErr != nil {
|
|
return c.readErr
|
|
}
|
|
|
|
return c.parseError(root, false, root.nodeID())
|
|
}
|
|
|
|
func (c *context) trace(p parser, from, to int, event TraceEvent, reason ...string) {
|
|
if c.maxTraceLength <= 0 {
|
|
return
|
|
}
|
|
|
|
if p.commitType()&userDefined == 0 || p.commitType()&FailPass != 0 {
|
|
return
|
|
}
|
|
|
|
if len(c.tr) == c.maxTraceLength {
|
|
c.tr = c.tr[1:]
|
|
}
|
|
|
|
switch event {
|
|
case Success, Fail:
|
|
c.level--
|
|
}
|
|
|
|
c.tr = append(c.tr, TraceEntry{
|
|
Level: c.level,
|
|
Parser: p.nodeName(),
|
|
From: from,
|
|
To: to,
|
|
Event: event,
|
|
Reason: strings.Join(reason, "; "),
|
|
})
|
|
|
|
switch event {
|
|
case Enter:
|
|
c.level++
|
|
}
|
|
}
|