fix longest failing error position
This commit is contained in:
parent
965234694e
commit
3a843b21dc
6
boot.go
6
boot.go
@ -13,6 +13,12 @@ func stringToCommitType(s string) CommitType {
|
|||||||
switch s {
|
switch s {
|
||||||
case "alias":
|
case "alias":
|
||||||
return Alias
|
return Alias
|
||||||
|
case "ws":
|
||||||
|
return Whitespace
|
||||||
|
case "nows":
|
||||||
|
return NoWhitespace
|
||||||
|
case "failpass":
|
||||||
|
return FailPass
|
||||||
case "root":
|
case "root":
|
||||||
return Root
|
return Root
|
||||||
default:
|
default:
|
||||||
|
@ -228,10 +228,12 @@ var bootSyntaxDefs = [][]string{{
|
|||||||
"chars", "ws", "none", "ws",
|
"chars", "ws", "none", "ws",
|
||||||
}, {
|
}, {
|
||||||
"chars", "nows", "none", "nows",
|
"chars", "nows", "none", "nows",
|
||||||
|
}, {
|
||||||
|
"chars", "failpass", "none", "failpass",
|
||||||
}, {
|
}, {
|
||||||
"chars", "root", "none", "root",
|
"chars", "root", "none", "root",
|
||||||
}, {
|
}, {
|
||||||
"choice", "flag", "alias", "alias", "ws", "nows", "root",
|
"choice", "flag", "alias", "alias", "ws", "nows", "failpass", "root",
|
||||||
}, {
|
}, {
|
||||||
"chars", "colon", "alias", ":",
|
"chars", "colon", "alias", ":",
|
||||||
}, {
|
}, {
|
||||||
|
@ -209,7 +209,10 @@ func (p *choiceParser) parse(c *context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if match {
|
if match {
|
||||||
if to > initialFailOffset {
|
if failOffset > to {
|
||||||
|
c.failOffset = failOffset
|
||||||
|
c.failingParser = failingParser
|
||||||
|
} else if to > initialFailOffset {
|
||||||
c.failOffset = -1
|
c.failOffset = -1
|
||||||
c.failingParser = nil
|
c.failingParser = nil
|
||||||
} else {
|
} else {
|
||||||
|
190
errors_test.go
190
errors_test.go
@ -6,21 +6,72 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type errorTestItem struct {
|
||||||
|
title string
|
||||||
|
syntax string
|
||||||
|
doc string
|
||||||
|
perr ParseError
|
||||||
|
}
|
||||||
|
|
||||||
func checkParseError(left, right ParseError) bool {
|
func checkParseError(left, right ParseError) bool {
|
||||||
left.registry = nil
|
left.registry = nil
|
||||||
right.registry = nil
|
right.registry = nil
|
||||||
return reflect.DeepEqual(left, right)
|
return reflect.DeepEqual(left, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func testParseErrorItem(s *Syntax, test errorTestItem) func(t *testing.T) {
|
||||||
type testItem struct {
|
return func(t *testing.T) {
|
||||||
title string
|
_, err := s.Parse(bytes.NewBufferString(test.doc))
|
||||||
syntax string
|
if err == nil {
|
||||||
text string
|
t.Fatal("failed to fail")
|
||||||
perr ParseError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range []testItem{{
|
perr, ok := err.(*ParseError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("invalid error type returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
perr.Input = ""
|
||||||
|
perr.registry = nil
|
||||||
|
|
||||||
|
if !checkParseError(*perr, test.perr) {
|
||||||
|
t.Error("invalid error returned")
|
||||||
|
t.Log("got: ", *perr)
|
||||||
|
t.Log("expected:", test.perr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testParseError(t *testing.T, syntax string, tests []errorTestItem) {
|
||||||
|
var s *Syntax
|
||||||
|
if syntax != "" {
|
||||||
|
var err error
|
||||||
|
s, err = openSyntaxString(syntax)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
ts := s
|
||||||
|
if test.syntax != "" {
|
||||||
|
var err error
|
||||||
|
ts, err = openSyntaxString(test.syntax)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts == nil {
|
||||||
|
t.Fatal("no syntax defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(test.title, testParseErrorItem(ts, test))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
testParseError(t, "", []errorTestItem{{
|
||||||
title: "single def, empty text",
|
title: "single def, empty text",
|
||||||
syntax: `a = "a"`,
|
syntax: `a = "a"`,
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
@ -29,21 +80,21 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "single def, wrong text",
|
title: "single def, wrong text",
|
||||||
syntax: `a = "a"`,
|
syntax: `a = "a"`,
|
||||||
text: "b",
|
doc: "b",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Definition: "a",
|
Definition: "a",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
title: "single optional def, wrong text",
|
title: "single optional def, wrong text",
|
||||||
syntax: `a = "a"?`,
|
syntax: `a = "a"?`,
|
||||||
text: "b",
|
doc: "b",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Definition: "a",
|
Definition: "a",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
title: "error on second line, second column",
|
title: "error on second line, second column",
|
||||||
syntax: `a = [a\n]*`,
|
syntax: `a = [a\n]*`,
|
||||||
text: "aa\nabaa\naa",
|
doc: "aa\nabaa\naa",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 4,
|
Offset: 4,
|
||||||
Line: 1,
|
Line: 1,
|
||||||
@ -53,7 +104,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "multiple definitions",
|
title: "multiple definitions",
|
||||||
syntax: `a = "aa"; A:root = a`,
|
syntax: `a = "aa"; A:root = a`,
|
||||||
text: "ab",
|
doc: "ab",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Column: 1,
|
Column: 1,
|
||||||
@ -62,7 +113,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "choice, options succeed",
|
title: "choice, options succeed",
|
||||||
syntax: `a = "12"; b = "1"; c:root = a | b`,
|
syntax: `a = "12"; b = "1"; c:root = a | b`,
|
||||||
text: "123",
|
doc: "123",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
@ -71,7 +122,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "choice succeeds, document fails",
|
title: "choice succeeds, document fails",
|
||||||
syntax: `a = "12"; b = "1"; c:root = a | b`,
|
syntax: `a = "12"; b = "1"; c:root = a | b`,
|
||||||
text: "13",
|
doc: "13",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Column: 1,
|
Column: 1,
|
||||||
@ -80,7 +131,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "choice fails",
|
title: "choice fails",
|
||||||
syntax: `a = "12"; b = "2"; c:root = a | b`,
|
syntax: `a = "12"; b = "2"; c:root = a | b`,
|
||||||
text: "13",
|
doc: "13",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Column: 1,
|
Column: 1,
|
||||||
@ -89,7 +140,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "choice fails, longer option reported",
|
title: "choice fails, longer option reported",
|
||||||
syntax: `a = "12"; b = "134"; c:root = a | b`,
|
syntax: `a = "12"; b = "134"; c:root = a | b`,
|
||||||
text: "135",
|
doc: "135",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
@ -98,7 +149,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "failing choice on the failing branch",
|
title: "failing choice on the failing branch",
|
||||||
syntax: `a = "123"; b:root = a | "13"`,
|
syntax: `a = "123"; b:root = a | "13"`,
|
||||||
text: "124",
|
doc: "124",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
@ -107,7 +158,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "failing choice on a shorter branch",
|
title: "failing choice on a shorter branch",
|
||||||
syntax: `a = "13"; b:root = "123" | a`,
|
syntax: `a = "13"; b:root = "123" | a`,
|
||||||
text: "124",
|
doc: "124",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
@ -116,7 +167,7 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "longer failure on a later pass",
|
title: "longer failure on a later pass",
|
||||||
syntax: `a = "12"; b = "34"; c = "1" b; d:root = a | c`,
|
syntax: `a = "12"; b = "34"; c = "1" b; d:root = a | c`,
|
||||||
text: "135",
|
doc: "135",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
@ -125,43 +176,13 @@ func TestError(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
title: "char as a choice option",
|
title: "char as a choice option",
|
||||||
syntax: `a = "12"; b = [a] | [b]; c = a b`,
|
syntax: `a = "12"; b = [a] | [b]; c = a b`,
|
||||||
text: "12c",
|
doc: "12c",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
Definition: "b",
|
Definition: "b",
|
||||||
},
|
},
|
||||||
}} {
|
}})
|
||||||
t.Run(test.title, func(t *testing.T) {
|
|
||||||
s, err := openSyntaxString(test.syntax)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.Parse(bytes.NewBufferString(test.text))
|
|
||||||
if err == nil {
|
|
||||||
t.Error("failed to fail")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
perr, ok := err.(*ParseError)
|
|
||||||
if !ok {
|
|
||||||
t.Error("invalid error returned", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if perr.Input != "<input>" {
|
|
||||||
t.Error("invalid default input name")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
perr.Input = ""
|
|
||||||
if !checkParseError(*perr, test.perr) {
|
|
||||||
t.Error("failed to return the right error")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorRecursive(t *testing.T) {
|
func TestErrorRecursive(t *testing.T) {
|
||||||
@ -173,21 +194,12 @@ func TestErrorRecursive(t *testing.T) {
|
|||||||
doc:root = (expression (";" expression)*)+;
|
doc:root = (expression (";" expression)*)+;
|
||||||
`
|
`
|
||||||
|
|
||||||
s, err := openSyntaxString(syntax)
|
testParseError(t, syntax, []errorTestItem{{
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
title string
|
|
||||||
doc string
|
|
||||||
perr ParseError
|
|
||||||
}{{
|
|
||||||
title: "simple, open",
|
title: "simple, open",
|
||||||
doc: "a(",
|
doc: "a(",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 1,
|
Offset: 2,
|
||||||
Column: 1,
|
Column: 2,
|
||||||
Definition: "function-application",
|
Definition: "function-application",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@ -202,8 +214,8 @@ func TestErrorRecursive(t *testing.T) {
|
|||||||
title: "inner, open",
|
title: "inner, open",
|
||||||
doc: "a(b()",
|
doc: "a(b()",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 1,
|
Offset: 5,
|
||||||
Column: 1,
|
Column: 5,
|
||||||
Definition: "function-application",
|
Definition: "function-application",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@ -218,8 +230,8 @@ func TestErrorRecursive(t *testing.T) {
|
|||||||
title: "outer, open",
|
title: "outer, open",
|
||||||
doc: "a()b(",
|
doc: "a()b(",
|
||||||
perr: ParseError{
|
perr: ParseError{
|
||||||
Offset: 4,
|
Offset: 5,
|
||||||
Column: 4,
|
Column: 5,
|
||||||
Definition: "function-application",
|
Definition: "function-application",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@ -230,27 +242,7 @@ func TestErrorRecursive(t *testing.T) {
|
|||||||
Column: 4,
|
Column: 4,
|
||||||
Definition: "function-application",
|
Definition: "function-application",
|
||||||
},
|
},
|
||||||
}} {
|
}})
|
||||||
t.Run(test.title, func(t *testing.T) {
|
|
||||||
_, err := s.Parse(bytes.NewBufferString(test.doc))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("failed to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
perr, ok := err.(*ParseError)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("invalid error type")
|
|
||||||
}
|
|
||||||
|
|
||||||
perr.Input = ""
|
|
||||||
|
|
||||||
if !checkParseError(*perr, test.perr) {
|
|
||||||
t.Error("failed to return the right error")
|
|
||||||
t.Log("got: ", *perr)
|
|
||||||
t.Log("expected:", test.perr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorMessage(t *testing.T) {
|
func TestErrorMessage(t *testing.T) {
|
||||||
@ -300,3 +292,27 @@ func TestErrorVerbose(t *testing.T) {
|
|||||||
t.Log("expected:", expected)
|
t.Log("expected:", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLongestFail(t *testing.T) {
|
||||||
|
const syntax = `
|
||||||
|
whitespace:ws = [ \t];
|
||||||
|
number:nows = [0-9]+;
|
||||||
|
symbol:nows = [a-z]+;
|
||||||
|
list-separator = [,\n];
|
||||||
|
function-application = expression "(" (expression (list-separator+ expression)*)? ")";
|
||||||
|
expression = number | symbol | function-application;
|
||||||
|
statement-separator = [;\n];
|
||||||
|
doc:root = (expression (statement-separator+ expression)*)?
|
||||||
|
`
|
||||||
|
|
||||||
|
testParseError(t, syntax, []errorTestItem{{
|
||||||
|
title: "choice",
|
||||||
|
doc: "f(a b c)",
|
||||||
|
perr: ParseError{
|
||||||
|
Offset: 4,
|
||||||
|
Line: 0,
|
||||||
|
Column: 4,
|
||||||
|
Definition: "function-application",
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
@ -28,6 +28,11 @@ streaming
|
|||||||
verify choice and sequence preference
|
verify choice and sequence preference
|
||||||
formatter
|
formatter
|
||||||
pretty
|
pretty
|
||||||
|
report unused parsers
|
||||||
|
go through the tests for docs
|
||||||
|
test all docs
|
||||||
|
warn nows usage in docs, e.g. spaces in symbol = [a-z]+
|
||||||
|
test error report on invalid flag
|
||||||
|
|
||||||
[optimization]
|
[optimization]
|
||||||
try preallocate larger store chunks
|
try preallocate larger store chunks
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
Alias CommitType = 1 << iota
|
Alias CommitType = 1 << iota
|
||||||
Whitespace
|
Whitespace
|
||||||
NoWhitespace
|
NoWhitespace
|
||||||
|
FailPass
|
||||||
Root
|
Root
|
||||||
|
|
||||||
userDefined
|
userDefined
|
||||||
|
@ -59,8 +59,9 @@ expression:alias = terminal
|
|||||||
alias = "alias";
|
alias = "alias";
|
||||||
ws = "ws";
|
ws = "ws";
|
||||||
nows = "nows";
|
nows = "nows";
|
||||||
|
failpass = "failpass";
|
||||||
root = "root";
|
root = "root";
|
||||||
flag:alias = alias | ws | nows | root;
|
flag:alias = alias | ws | nows | failpass | root;
|
||||||
definition-name:alias:nows = symbol (":" flag)*;
|
definition-name:alias:nows = symbol (":" flag)*;
|
||||||
definition = definition-name "=" expression;
|
definition = definition-name "=" expression;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user