From 11ba9708d976254d0ed5beb2f69d46fdf3d32c07 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Sun, 26 Nov 2017 18:48:56 +0100 Subject: [PATCH] initial error reporting --- choice.go | 47 +++++++++++++++++++++++++++++++++++++++-- context.go | 13 ++++++++---- errors_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++---- sequence.go | 3 ++- syntax.go | 2 +- 5 files changed, 110 insertions(+), 12 deletions(-) diff --git a/choice.go b/choice.go index 62e4105..46b4265 100644 --- a/choice.go +++ b/choice.go @@ -171,6 +171,17 @@ func (p *choiceParser) parse(c *context) { var optionIndex int 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 { foundMatch = false optionIndex = 0 @@ -181,6 +192,13 @@ func (p *choiceParser) parse(c *context) { if !c.matchLast || match && c.offset <= to { c.offset = from + if c.failOffset > failOffset { + failOffset = c.failOffset + failingParser = c.failingParser + } + + c.failOffset = initialFailOffset + c.failingParser = nil continue } @@ -188,7 +206,8 @@ func (p *choiceParser) parse(c *context) { foundMatch = true to = c.offset c.offset = from - + c.failOffset = initialFailOffset + c.failingParser = nil c.results.setMatch(from, p.id, to) } @@ -198,13 +217,37 @@ func (p *choiceParser) parse(c *context) { } if match { + c.failOffset = initialFailOffset + c.failingParser = initialFailingParser c.success(to) c.results.unmarkPending(from, p.id) 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.recordFailure(p) c.fail(from) c.results.unmarkPending(from, p.id) } diff --git a/context.go b/context.go index 277fedf..05d106a 100644 --- a/context.go +++ b/context.go @@ -93,17 +93,22 @@ func (c *context) fail(offset int) { c.matchLast = false } -func (c *context) recordFailure(p parser) { - if c.offset < c.failOffset { +// TODO: +// - 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 } - if c.failingParser != nil && c.offset == c.failOffset { + if c.failingParser != nil && offset == c.failOffset { return } - c.failOffset = c.offset + c.failOffset = offset if p.commitType()&userDefined != 0 && p.commitType()&Whitespace == 0 { + // println("setting failing sequence parser", p.nodeName(), offset) c.failingParser = p } } diff --git a/errors_test.go b/errors_test.go index 9b95aa9..09ac352 100644 --- a/errors_test.go +++ b/errors_test.go @@ -73,6 +73,34 @@ func TestError(t *testing.T) { offset: 2, column: 2, 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) { s, err := openSyntaxString(test.syntax) @@ -81,6 +109,7 @@ func TestError(t *testing.T) { return } + // println("starting") _, err = s.Parse(bytes.NewBufferString(test.text)) if err == nil { 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) { - const expected = "foo:4:10:failed to parse definition: bar" + const expected = "foo:4:10:failed to parse input, expecting: bar" perr := &ParseError{ Input: "foo", @@ -143,9 +192,9 @@ func TestErrorVerbose(t *testing.T) { ` const doc = `{ - "a": 1, - "b": 2, - "c": 3, + "a":1, + "b":2, + "c":3, }` s, err := openSyntaxFile("examples/json.treerack") diff --git a/sequence.go b/sequence.go index 63ba992..542b4dd 100644 --- a/sequence.go +++ b/sequence.go @@ -312,7 +312,8 @@ func (p *sequenceParser) parse(c *context) { p.items[itemIndex].parse(c) if !c.matchLast { 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) if !p.allChars { c.results.unmarkPending(from, p.id) diff --git a/syntax.go b/syntax.go index 5573761..ed0f622 100644 --- a/syntax.go +++ b/syntax.go @@ -159,7 +159,7 @@ func intsContain(is []int, i int) bool { func (pe *ParseError) Error() string { return fmt.Sprintf( - "%s:%d:%d:failed to parse definition: %s", + "%s:%d:%d:failed to parse input, expecting: %s", pe.Input, pe.Line+1, pe.Column+1,