diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-09-03 14:49:37 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-09-03 14:55:13 +0900 |
commit | f4e3fef07e8e38e37e63989254718e6c4cb543a9 (patch) | |
tree | 0eeb7b043800282d921d81159e8d5d9ec87c1211 /driver/semantic_action_test.go | |
parent | Rename describe command to show command (diff) | |
download | urubu-f4e3fef07e8e38e37e63989254718e6c4cb543a9.tar.gz urubu-f4e3fef07e8e38e37e63989254718e6c4cb543a9.tar.xz |
Make semantic actions user-configurable
Diffstat (limited to 'driver/semantic_action_test.go')
-rw-r--r-- | driver/semantic_action_test.go | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/driver/semantic_action_test.go b/driver/semantic_action_test.go new file mode 100644 index 0000000..a2c2bb0 --- /dev/null +++ b/driver/semantic_action_test.go @@ -0,0 +1,203 @@ +package driver + +import ( + "fmt" + "strings" + "testing" + + mldriver "github.com/nihei9/maleeni/driver" + "github.com/nihei9/vartan/grammar" + "github.com/nihei9/vartan/spec" +) + +type testSemAct struct { + gram *spec.CompiledGrammar + actLog []string +} + +func (a *testSemAct) Shift(tok *mldriver.Token) { + a.actLog = append(a.actLog, fmt.Sprintf("shift/%v", tok.KindName)) +} + +func (a *testSemAct) ShiftError() { + a.actLog = append(a.actLog, "shift/error") +} + +func (a *testSemAct) Reduce(prodNum int) { + lhsSym := a.gram.ParsingTable.LHSSymbols[prodNum] + lhsText := a.gram.ParsingTable.NonTerminals[lhsSym] + a.actLog = append(a.actLog, fmt.Sprintf("reduce/%v", lhsText)) +} + +func (a *testSemAct) Accept() { + a.actLog = append(a.actLog, "accept") +} + +func (a *testSemAct) TrapError(n int) { + a.actLog = append(a.actLog, fmt.Sprintf("trap/%v", n)) +} + +func (a *testSemAct) MissError() { + a.actLog = append(a.actLog, "miss") +} + +func TestParserWithSemanticAction(t *testing.T) { + specSrcWithErrorProd := ` +seq + : seq elem semicolon + | elem semicolon + | error semicolon #recover + ; +elem + : char char char + ; + +ws: "[\u{0009}\u{0020}]+" #skip; +semicolon: ';'; +char: "[a-z]"; +` + + specSrcWithoutErrorProd := ` +seq + : seq elem semicolon + | elem semicolon + ; +elem + : char char char + ; + +ws: "[\u{0009}\u{0020}]+" #skip; +semicolon: ';'; +char: "[a-z]"; +` + + tests := []struct { + caption string + specSrc string + src string + actLog []string + }{ + { + caption: "when an input contains no syntax error, the driver calls `Shift`, `Reduce`, and `Accept`.", + specSrc: specSrcWithErrorProd, + src: `a b c; d e f;`, + actLog: []string{ + "shift/char", + "shift/char", + "shift/char", + "reduce/elem", + "shift/semicolon", + "reduce/seq", + + "shift/char", + "shift/char", + "shift/char", + "reduce/elem", + "shift/semicolon", + "reduce/seq", + + "accept", + }, + }, + { + caption: "when a grammar has `error` symbol, the driver calls `TrapError` and `ShiftError`.", + specSrc: specSrcWithErrorProd, + src: `a; b !; c d !; e f g;`, + actLog: []string{ + "shift/char", + "trap/1", + "shift/error", + "shift/semicolon", + "reduce/seq", + + "shift/char", + "trap/2", + "shift/error", + "shift/semicolon", + "reduce/seq", + + "shift/char", + "shift/char", + "trap/3", + "shift/error", + "shift/semicolon", + "reduce/seq", + + "shift/char", + "shift/char", + "shift/char", + "reduce/elem", + "shift/semicolon", + "reduce/seq", + + // Even if the input contains syntax errors, the driver calls `Accept` when the input is accepted + // according to the error production. + "accept", + }, + }, + { + caption: "the driver doesn't call `Accept` when a syntax error is trapped, but the input doesn't meet the error production", + specSrc: specSrcWithErrorProd, + src: `a !`, + actLog: []string{ + "shift/char", + "trap/1", + "shift/error", + }, + }, + { + caption: "when a syntax error isn't trapped, the driver calls `MissError`.", + specSrc: specSrcWithoutErrorProd, + src: `a !`, + actLog: []string{ + "shift/char", + + "miss", + }, + }, + } + for _, tt := range tests { + t.Run(tt.caption, 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) + } + + semAct := &testSemAct{ + gram: gram, + } + p, err := NewParser(gram, strings.NewReader(tt.src), SemanticAction(semAct)) + if err != nil { + t.Fatal(err) + } + + err = p.Parse() + if err != nil { + t.Fatal(err) + } + + if len(semAct.actLog) != len(tt.actLog) { + t.Fatalf("unexpected action log; want: %+v, got: %+v", tt.actLog, semAct.actLog) + } + + for i, e := range tt.actLog { + if semAct.actLog[i] != e { + t.Fatalf("unexpected action log; want: %+v, got: %+v", tt.actLog, semAct.actLog) + } + } + }) + } +} |