initial error reporting

This commit is contained in:
Arpad Ryszka 2017-11-26 18:48:56 +01:00
parent 0ebf532a30
commit 11ba9708d9
5 changed files with 110 additions and 12 deletions

View File

@ -171,6 +171,17 @@ func (p *choiceParser) parse(c *context) {
var optionIndex int var optionIndex int
var foundMatch bool var foundMatch bool
// TODO:
// - if there is a failure already, it should be left alone
// - what if reading more means that the previous failurs don't count
initialFailOffset := c.failOffset
initialFailingParser := c.failingParser
c.failingParser = nil
var (
failOffset int
failingParser parser
)
for { for {
foundMatch = false foundMatch = false
optionIndex = 0 optionIndex = 0
@ -181,6 +192,13 @@ func (p *choiceParser) parse(c *context) {
if !c.matchLast || match && c.offset <= to { if !c.matchLast || match && c.offset <= to {
c.offset = from c.offset = from
if c.failOffset > failOffset {
failOffset = c.failOffset
failingParser = c.failingParser
}
c.failOffset = initialFailOffset
c.failingParser = nil
continue continue
} }
@ -188,7 +206,8 @@ func (p *choiceParser) parse(c *context) {
foundMatch = true foundMatch = true
to = c.offset to = c.offset
c.offset = from c.offset = from
c.failOffset = initialFailOffset
c.failingParser = nil
c.results.setMatch(from, p.id, to) c.results.setMatch(from, p.id, to)
} }
@ -198,13 +217,37 @@ func (p *choiceParser) parse(c *context) {
} }
if match { if match {
c.failOffset = initialFailOffset
c.failingParser = initialFailingParser
c.success(to) c.success(to)
c.results.unmarkPending(from, p.id) c.results.unmarkPending(from, p.id)
return return
} }
if failOffset > initialFailOffset {
// println("recording choice failure", p.name, failOffset)
c.failOffset = failOffset
if failingParser == nil && p.commit&userDefined != 0 && p.commit&Whitespace == 0 {
// println("setting failing choice parser", p.nodeName(), failOffset)
c.failingParser = p
} else {
c.failingParser = failingParser
}
} else if failOffset == initialFailOffset && initialFailingParser == nil {
// println("recording choice failure", p.name, failOffset)
c.failOffset = failOffset
if failingParser == nil && p.commit&userDefined != 0 && p.commit&Whitespace == 0 {
// println("setting failing choice parser", p.nodeName(), failOffset)
c.failingParser = p
} else {
c.failingParser = failingParser
}
} else {
c.failOffset = initialFailOffset
c.failingParser = initialFailingParser
}
c.results.setNoMatch(from, p.id) c.results.setNoMatch(from, p.id)
c.recordFailure(p)
c.fail(from) c.fail(from)
c.results.unmarkPending(from, p.id) c.results.unmarkPending(from, p.id)
} }

View File

@ -93,17 +93,22 @@ func (c *context) fail(offset int) {
c.matchLast = false c.matchLast = false
} }
func (c *context) recordFailure(p parser) { // TODO:
if c.offset < c.failOffset { // - need to know which choice branch the failure happened on because if there is a non-user branch that has the
// longest failure, it can be reported to an unrelevant user defined choice on another branch
func (c *context) recordFailure(offset int, p parser) {
if offset < c.failOffset {
return return
} }
if c.failingParser != nil && c.offset == c.failOffset { if c.failingParser != nil && offset == c.failOffset {
return return
} }
c.failOffset = c.offset c.failOffset = offset
if p.commitType()&userDefined != 0 && p.commitType()&Whitespace == 0 { if p.commitType()&userDefined != 0 && p.commitType()&Whitespace == 0 {
// println("setting failing sequence parser", p.nodeName(), offset)
c.failingParser = p c.failingParser = p
} }
} }

View File

@ -73,6 +73,34 @@ func TestError(t *testing.T) {
offset: 2, offset: 2,
column: 2, column: 2,
definition: "b", definition: "b",
}, {
title: "failing choice on the failing branch",
syntax: `a = "123"; b:root = a | "13"`,
text: "124",
offset: 2,
column: 2,
definition: "a",
}, {
title: "failing choice on a shorter branch",
syntax: `a = "13"; b:root = "123" | a`,
text: "124",
offset: 2,
column: 2,
definition: "b",
}, {
title: "longer failure on a later pass",
syntax: `a = "12"; b = "34"; c = "1" b; d:root = a | c`,
text: "135",
offset: 2,
column: 2,
definition: "b",
}, {
title: "char as a choice option",
syntax: `a = "12"; b = [a] | [b]; c = a b`,
text: "12c",
offset: 2,
column: 2,
definition: "b",
}} { }} {
t.Run(test.title, func(t *testing.T) { t.Run(test.title, func(t *testing.T) {
s, err := openSyntaxString(test.syntax) s, err := openSyntaxString(test.syntax)
@ -81,6 +109,7 @@ func TestError(t *testing.T) {
return return
} }
// println("starting")
_, err = s.Parse(bytes.NewBufferString(test.text)) _, err = s.Parse(bytes.NewBufferString(test.text))
if err == nil { if err == nil {
t.Error("failed to fail") t.Error("failed to fail")
@ -119,8 +148,28 @@ func TestError(t *testing.T) {
} }
} }
func TestErrorRecursive(t *testing.T) {
const doc = `a[b][1a]`
s, err := openSyntaxFile("examples/mml.treerack")
if err != nil {
t.Error(err)
return
}
// println("starting")
_, err = s.Parse(bytes.NewBufferString(doc))
perr, ok := err.(*ParseError)
if !ok {
t.Error("failed to return parse error")
return
}
t.Log(perr)
}
func TestErrorMessage(t *testing.T) { func TestErrorMessage(t *testing.T) {
const expected = "foo:4:10:failed to parse definition: bar" const expected = "foo:4:10:failed to parse input, expecting: bar"
perr := &ParseError{ perr := &ParseError{
Input: "foo", Input: "foo",
@ -143,9 +192,9 @@ func TestErrorVerbose(t *testing.T) {
` `
const doc = `{ const doc = `{
"a": 1, "a":1,
"b": 2, "b":2,
"c": 3, "c":3,
}` }`
s, err := openSyntaxFile("examples/json.treerack") s, err := openSyntaxFile("examples/json.treerack")

View File

@ -312,7 +312,8 @@ func (p *sequenceParser) parse(c *context) {
p.items[itemIndex].parse(c) p.items[itemIndex].parse(c)
if !c.matchLast { if !c.matchLast {
if currentCount < p.ranges[itemIndex][0] { if currentCount < p.ranges[itemIndex][0] {
c.recordFailure(p) // println("recording sequence failure", p.name, c.offset)
c.recordFailure(c.offset, p)
c.fail(from) c.fail(from)
if !p.allChars { if !p.allChars {
c.results.unmarkPending(from, p.id) c.results.unmarkPending(from, p.id)

View File

@ -159,7 +159,7 @@ func intsContain(is []int, i int) bool {
func (pe *ParseError) Error() string { func (pe *ParseError) Error() string {
return fmt.Sprintf( return fmt.Sprintf(
"%s:%d:%d:failed to parse definition: %s", "%s:%d:%d:failed to parse input, expecting: %s",
pe.Input, pe.Input,
pe.Line+1, pe.Line+1,
pe.Column+1, pe.Column+1,