aboutsummaryrefslogtreecommitdiff
path: root/driver/parser/semantic_action_test.go
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2022-11-06 21:31:46 +0900
committerRyo Nihei <nihei.dev@gmail.com>2022-11-10 00:40:16 +0900
commitf89d021bbe134e3efa0d015a41e9712960cdd009 (patch)
tree28c6d49611f09dad186b0f6fc4c1a42864a2f7cb /driver/parser/semantic_action_test.go
parentSplit SymbolTable's APIs into reader/writer (diff)
downloadurubu-f89d021bbe134e3efa0d015a41e9712960cdd009.tar.gz
urubu-f89d021bbe134e3efa0d015a41e9712960cdd009.tar.xz
Import source code of lexer generator
From: https://github.com/nihei9/maleeni
Diffstat (limited to 'driver/parser/semantic_action_test.go')
-rw-r--r--driver/parser/semantic_action_test.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/driver/parser/semantic_action_test.go b/driver/parser/semantic_action_test.go
new file mode 100644
index 0000000..c98a12f
--- /dev/null
+++ b/driver/parser/semantic_action_test.go
@@ -0,0 +1,227 @@
+package parser
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/nihei9/vartan/grammar"
+ spec "github.com/nihei9/vartan/spec/grammar"
+ "github.com/nihei9/vartan/spec/grammar/parser"
+)
+
+type testSemAct struct {
+ gram *spec.CompiledGrammar
+ actLog []string
+}
+
+func (a *testSemAct) Shift(tok VToken, recovered bool) {
+ t := a.gram.Syntactic.Terminals[tok.TerminalID()]
+ if recovered {
+ a.actLog = append(a.actLog, fmt.Sprintf("shift/%v/recovered", t))
+ } else {
+ a.actLog = append(a.actLog, fmt.Sprintf("shift/%v", t))
+ }
+}
+
+func (a *testSemAct) Reduce(prodNum int, recovered bool) {
+ lhsSym := a.gram.Syntactic.LHSSymbols[prodNum]
+ lhsText := a.gram.Syntactic.NonTerminals[lhsSym]
+ if recovered {
+ a.actLog = append(a.actLog, fmt.Sprintf("reduce/%v/recovered", lhsText))
+ } else {
+ a.actLog = append(a.actLog, fmt.Sprintf("reduce/%v", lhsText))
+ }
+}
+
+func (a *testSemAct) Accept() {
+ a.actLog = append(a.actLog, "accept")
+}
+
+func (a *testSemAct) TrapAndShiftError(cause VToken, popped int) {
+ a.actLog = append(a.actLog, fmt.Sprintf("trap/%v/shift/error", popped))
+}
+
+func (a *testSemAct) MissError(cause VToken) {
+ a.actLog = append(a.actLog, "miss")
+}
+
+func TestParserWithSemanticAction(t *testing.T) {
+ specSrcWithErrorProd := `
+#name test;
+
+seq
+ : seq elem semicolon
+ | elem semicolon
+ | error star star semicolon
+ | error semicolon #recover
+ ;
+elem
+ : char char char
+ ;
+
+ws #skip
+ : "[\u{0009}\u{0020}]+";
+semicolon
+ : ';';
+star
+ : '*';
+char
+ : "[a-z]";
+`
+
+ specSrcWithoutErrorProd := `
+#name test;
+
+seq
+ : seq elem semicolon
+ | elem semicolon
+ ;
+elem
+ : char char char
+ ;
+
+ws #skip
+ : "[\u{0009}\u{0020}]+";
+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 `TrapAndShiftError`.",
+ specSrc: specSrcWithErrorProd,
+ src: `a; b !; c d !; e ! * *; h i j;`,
+ actLog: []string{
+ "shift/char",
+ "trap/1/shift/error",
+ "shift/semicolon",
+ "reduce/seq/recovered",
+
+ "shift/char",
+ "trap/2/shift/error",
+ "shift/semicolon",
+ "reduce/seq/recovered",
+
+ "shift/char",
+ "shift/char",
+ "trap/3/shift/error",
+ "shift/semicolon",
+ "reduce/seq/recovered",
+
+ "shift/char",
+ "trap/2/shift/error",
+ "shift/star",
+ "shift/star",
+ // When the driver shifts three times, it recovers from an error.
+ "shift/semicolon/recovered",
+ "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: "when the input doesn't meet the error production, the driver calls `MissError`.",
+ specSrc: specSrcWithErrorProd,
+ src: `a !`,
+ actLog: []string{
+ "shift/char",
+ "trap/1/shift/error",
+
+ "miss",
+ },
+ },
+ {
+ 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 := parser.Parse(strings.NewReader(tt.specSrc))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b := grammar.GrammarBuilder{
+ AST: ast,
+ }
+ gram, _, err := b.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ toks, err := NewTokenStream(gram, strings.NewReader(tt.src))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ semAct := &testSemAct{
+ gram: gram,
+ }
+ p, err := NewParser(toks, NewGrammar(gram), 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)
+ }
+ }
+ })
+ }
+}