diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2022-06-10 16:41:41 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2022-06-11 19:09:19 +0900 |
commit | 7403c18fbc04f3045df1e69b63d7ffd5f04d77db (patch) | |
tree | 0933a8e875bac01b1b5b385b5069eb069aaad219 /driver | |
parent | Support testable tree output in vartan-parse command (diff) | |
download | cotia-7403c18fbc04f3045df1e69b63d7ffd5f04d77db.tar.gz cotia-7403c18fbc04f3045df1e69b63d7ffd5f04d77db.tar.xz |
Remove the kind field from a node corresponding to an anonymous terminal symbol
Diffstat (limited to 'driver')
-rw-r--r-- | driver/parser_test.go | 20 | ||||
-rw-r--r-- | driver/semantic_action.go | 22 | ||||
-rw-r--r-- | driver/syntax_error_test.go | 170 |
3 files changed, 202 insertions, 10 deletions
diff --git a/driver/parser_test.go b/driver/parser_test.go index 5c7addd..9e232f7 100644 --- a/driver/parser_test.go +++ b/driver/parser_test.go @@ -18,6 +18,10 @@ func termNode(kind string, text string, children ...*Node) *Node { } } +func anonTermNode(text string, children ...*Node) *Node { + return termNode("", text, children...) +} + func errorNode() *Node { return &Node{ Type: NodeTypeError, @@ -65,7 +69,7 @@ id: "[A-Za-z_][0-9A-Za-z_]*"; nonTermNode("term", nonTermNode("term", nonTermNode("factor", - termNode("x_3", "("), + anonTermNode("("), nonTermNode("expr", nonTermNode("expr", nonTermNode("term", @@ -74,10 +78,10 @@ id: "[A-Za-z_][0-9A-Za-z_]*"; ), ), ), - termNode("x_1", "+"), + anonTermNode("+"), nonTermNode("term", nonTermNode("factor", - termNode("x_3", "("), + anonTermNode("("), nonTermNode("expr", nonTermNode("expr", nonTermNode("term", @@ -86,27 +90,27 @@ id: "[A-Za-z_][0-9A-Za-z_]*"; ), ), ), - termNode("x_1", "+"), + anonTermNode("+"), nonTermNode("term", nonTermNode("factor", termNode("id", "c"), ), ), ), - termNode("x_4", ")"), + anonTermNode(")"), ), ), ), - termNode("x_4", ")"), + anonTermNode(")"), ), ), - termNode("x_2", "*"), + anonTermNode("*"), nonTermNode("factor", termNode("id", "d"), ), ), ), - termNode("x_1", "+"), + anonTermNode("+"), nonTermNode("term", nonTermNode("factor", termNode("id", "e"), diff --git a/driver/semantic_action.go b/driver/semantic_action.go index 73f3bb0..54d3291 100644 --- a/driver/semantic_action.go +++ b/driver/semantic_action.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "strconv" ) // SemanticActionSet is a set of semantic actions a parser calls. @@ -270,6 +271,19 @@ func (n *Node) MarshalJSON() ([]byte, error) { KindName: n.KindName, }) case NodeTypeTerminal: + if n.KindName == "" { + return json.Marshal(struct { + Type NodeType `json:"type"` + Text string `json:"text"` + Row int `json:"row"` + Col int `json:"col"` + }{ + Type: n.Type, + Text: n.Text, + Row: n.Row, + Col: n.Col, + }) + } return json.Marshal(struct { Type NodeType `json:"type"` KindName string `json:"kind_name"` @@ -324,9 +338,13 @@ func printTree(w io.Writer, node *Node, ruledLine string, childRuledLinePrefix s switch node.Type { case NodeTypeError: - fmt.Fprintf(w, "%v!%v\n", ruledLine, node.KindName) + fmt.Fprintf(w, "%v%v\n", ruledLine, node.KindName) case NodeTypeTerminal: - fmt.Fprintf(w, "%v%v %#v\n", ruledLine, node.KindName, node.Text) + if node.KindName == "" { + fmt.Fprintf(w, "%v<anonymous> %v\n", ruledLine, strconv.Quote(node.Text)) + } else { + fmt.Fprintf(w, "%v%v %v\n", ruledLine, node.KindName, strconv.Quote(node.Text)) + } case NodeTypeNonTerminal: fmt.Fprintf(w, "%v%v\n", ruledLine, node.KindName) diff --git a/driver/syntax_error_test.go b/driver/syntax_error_test.go index 1480390..f68f595 100644 --- a/driver/syntax_error_test.go +++ b/driver/syntax_error_test.go @@ -2,6 +2,7 @@ package driver import ( "fmt" + "sort" "strings" "testing" @@ -153,3 +154,172 @@ c }) } } + +func TestParserWithSyntaxErrorAndExpectedLookahead(t *testing.T) { + tests := []struct { + caption string + specSrc string + src string + cause string + expected []string + }{ + { + caption: "the parser reports an expected lookahead symbol", + specSrc: ` +#name test; + +s + : foo + ; + +foo + : 'foo'; +`, + src: `bar`, + cause: `bar`, + expected: []string{ + "foo", + }, + }, + { + caption: "the parser reports expected lookahead symbols", + specSrc: ` +#name test; + +s + : foo + | bar + ; + +foo + : 'foo'; +bar + : 'bar'; +`, + src: `baz`, + cause: `baz`, + expected: []string{ + "foo", + "bar", + }, + }, + { + caption: "the parser may report the EOF as an expected lookahead symbol", + specSrc: ` +#name test; + +s + : foo + ; + +foo + : 'foo'; +`, + src: `foobar`, + cause: `bar`, + expected: []string{ + "<eof>", + }, + }, + { + caption: "the parser may report the EOF and others as expected lookahead symbols", + specSrc: ` +#name test; + +s + : foo + | + ; + +foo + : 'foo'; +`, + src: `bar`, + cause: `bar`, + expected: []string{ + "foo", + "<eof>", + }, + }, + { + caption: "when an anonymous symbol is expected, an expected symbol list contains an alias of the anonymous symbol", + specSrc: ` +#name test; + +s + : foo 'bar' + ; + +foo + : 'foo'; +`, + src: `foobaz`, + cause: `baz`, + expected: []string{ + "bar", + }, + }, + } + 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) + if err != nil { + t.Fatal(err) + } + + toks, err := NewTokenStream(gram, strings.NewReader(tt.src)) + if err != nil { + t.Fatal(err) + } + + p, err := NewParser(toks, NewGrammar(gram)) + if err != nil { + t.Fatal(err) + } + + err = p.Parse() + if err != nil { + t.Fatal(err) + } + + synErrs := p.SyntaxErrors() + if synErrs == nil { + t.Fatalf("expected one syntax error, but it didn't occur") + } + if len(synErrs) != 1 { + t.Fatalf("too many syntax errors: %v errors", len(synErrs)) + } + synErr := synErrs[0] + if string(synErr.Token.Lexeme()) != tt.cause { + t.Fatalf("unexpected lexeme: want: %v, got: %v", tt.cause, string(synErr.Token.Lexeme())) + } + if len(synErr.ExpectedTerminals) != len(tt.expected) { + t.Fatalf("unexpected lookahead symbols: want: %v, got: %v", tt.expected, synErr.ExpectedTerminals) + } + sort.Slice(tt.expected, func(i, j int) bool { + return tt.expected[i] < tt.expected[j] + }) + sort.Slice(synErr.ExpectedTerminals, func(i, j int) bool { + return synErr.ExpectedTerminals[i] < synErr.ExpectedTerminals[j] + }) + for i, e := range tt.expected { + if synErr.ExpectedTerminals[i] != e { + t.Errorf("unexpected lookahead symbol: want: %v, got: %v", e, synErr.ExpectedTerminals[i]) + } + } + }) + } +} |