aboutsummaryrefslogtreecommitdiff
path: root/driver/syntax_error_test.go
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2021-08-26 23:16:09 +0900
committerRyo Nihei <nihei.dev@gmail.com>2021-08-26 23:18:49 +0900
commit7271e46bbcb11acf860c91eddfe12dd7eed5ccad (patch)
treefafbf797ca806ff1e4cc68acaaaa6db66aec632d /driver/syntax_error_test.go
parentUpdate CHANGELOG (diff)
downloadcotia-7271e46bbcb11acf860c91eddfe12dd7eed5ccad.tar.gz
cotia-7271e46bbcb11acf860c91eddfe12dd7eed5ccad.tar.xz
Add error symbol and #recover directive to recover from an error state
Diffstat (limited to 'driver/syntax_error_test.go')
-rw-r--r--driver/syntax_error_test.go130
1 files changed, 130 insertions, 0 deletions
diff --git a/driver/syntax_error_test.go b/driver/syntax_error_test.go
new file mode 100644
index 0000000..427f0d1
--- /dev/null
+++ b/driver/syntax_error_test.go
@@ -0,0 +1,130 @@
+package driver
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/nihei9/vartan/grammar"
+ "github.com/nihei9/vartan/spec"
+)
+
+func TestParserWithSyntaxErrors(t *testing.T) {
+ tests := []struct {
+ caption string
+ specSrc string
+ src string
+ synErrCount int
+ }{
+ {
+ caption: "the parser can report a syntax error",
+ specSrc: `
+s
+ : foo
+ ;
+
+foo: 'foo';
+`,
+ src: `bar`,
+ synErrCount: 1,
+ },
+ {
+ caption: "when the parser reduced a production having the reduce directive, the parser will recover from an error state",
+ specSrc: `
+seq
+ : seq elem ';'
+ | elem ';'
+ | error ';' #recover
+ ;
+elem
+ : a b c
+ ;
+
+ws: "[\u{0009}\u{0020}]+" #skip;
+a: 'a';
+b: 'b';
+c: 'c';
+`,
+ src: `!; a!; ab!;`,
+ synErrCount: 3,
+ },
+ {
+ caption: "After the parser shifts the error symbol, symbols are ignored until a symbol the parser can perform shift appears",
+ specSrc: `
+seq
+ : seq elem ';'
+ | elem ';'
+ | error ';' #recover
+ ;
+elem
+ : a b c
+ ;
+
+ws: "[\u{0009}\u{0020}]+" #skip;
+a: 'a';
+b: 'b';
+c: 'c';
+`,
+ // After the parser trasits to the error state reading the first invalid symbol ('!'),
+ // the second and third invalid symbols ('!') are ignored.
+ src: `! ! !; a!; ab!;`,
+ synErrCount: 3,
+ },
+ {
+ caption: "when the parser performs shift three times, the parser recovers from the error state",
+ specSrc: `
+seq
+ : seq elem ';'
+ | elem ';'
+ | error '*' '*' ';'
+ ;
+elem
+ : a b c
+ ;
+
+ws: "[\u{0009}\u{0020}]+" #skip;
+a: 'a';
+b: 'b';
+c: 'c';
+`,
+ src: `!**; a!**; ab!**; abc!`,
+ synErrCount: 4,
+ },
+ }
+ for i, tt := range tests {
+ t.Run(fmt.Sprintf("#%v", i), func(t *testing.T) {
+ ast, err := spec.Parse(strings.NewReader(tt.specSrc))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b := grammar.GrammarBuilder{
+ AST: ast,
+ }
+ g, err := b.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ gram, err := grammar.Compile(g, grammar.SpecifyClass(grammar.ClassLALR))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := NewParser(gram, strings.NewReader(tt.src), MakeAST(), MakeCST())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = p.Parse()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ synErrs := p.SyntaxErrors()
+ if len(synErrs) != tt.synErrCount {
+ t.Fatalf("unexpected syntax error; want: %v error(s), got: %v error(s)", tt.synErrCount, len(synErrs))
+ }
+ })
+ }
+}