diff --git a/driver/parser.go b/driver/parser.go index 1e7af29..af257e2 100644 --- a/driver/parser.go +++ b/driver/parser.go @@ -85,20 +85,22 @@ ACTION_LOOP: case act < 0: // Shift nextState := act * -1 + recovered := false if p.onError { + p.shiftCount++ + // When the parser performs shift three times, the parser recovers from the error state. - if p.shiftCount < 3 { - p.shiftCount++ - } else { + if p.shiftCount >= 3 { p.onError = false p.shiftCount = 0 + recovered = true } } p.shift(nextState) if p.semAct != nil { - p.semAct.Shift(tok) + p.semAct.Shift(tok, recovered) } tok, err = p.nextToken() @@ -108,9 +110,11 @@ ACTION_LOOP: case act > 0: // Reduce prodNum := act + recovered := false if p.onError && p.gram.ParsingTable.RecoverProductions[prodNum] != 0 { p.onError = false p.shiftCount = 0 + recovered = true } accepted := p.reduce(prodNum) @@ -123,7 +127,7 @@ ACTION_LOOP: } if p.semAct != nil { - p.semAct.Reduce(prodNum) + p.semAct.Reduce(prodNum, recovered) } default: // Error if p.onError { diff --git a/driver/semantic_action.go b/driver/semantic_action.go index b31c35b..421a722 100644 --- a/driver/semantic_action.go +++ b/driver/semantic_action.go @@ -10,8 +10,8 @@ import ( type SemanticActionSet interface { // Shift runs when the driver shifts a symbol onto the state stack. `tok` is a token corresponding to - // the symbol. - Shift(tok *mldriver.Token) + // the symbol. When the driver recovered from an error state by shifting the token, `recovered` is true. + Shift(tok *mldriver.Token, recovered bool) // Shift runs when the driver shifts a symbol onto the state stack. `tok` is a token corresponding to // the symbol. This function doesn't take a token as an argument because a token corresponding to @@ -19,8 +19,9 @@ type SemanticActionSet interface { ShiftError() // Reduce runs when the driver reduces an RHS of a production to its LHS. `prodNum` is a number of - // the production. - Reduce(prodNum int) + // the production. When the driver recovered from an error state by reducing the production, + // `recovered` is true. + Reduce(prodNum int, recovered bool) // Accept runs when the driver accepts an input. Accept() @@ -96,7 +97,7 @@ func NewSyntaxTreeActionSet(gram *spec.CompiledGrammar, makeAST bool, makeCST bo } } -func (a *SyntaxTreeActionSet) Shift(tok *mldriver.Token) { +func (a *SyntaxTreeActionSet) Shift(tok *mldriver.Token, recovered bool) { term := a.tokenToTerminal(tok) var ast *Node @@ -146,7 +147,7 @@ func (a *SyntaxTreeActionSet) ShiftError() { }) } -func (a *SyntaxTreeActionSet) Reduce(prodNum int) { +func (a *SyntaxTreeActionSet) Reduce(prodNum int, recovered bool) { lhs := a.gram.ParsingTable.LHSSymbols[prodNum] // When an alternative is empty, `n` will be 0, and `handle` will be empty slice. diff --git a/driver/semantic_action_test.go b/driver/semantic_action_test.go index a2c2bb0..0856b3d 100644 --- a/driver/semantic_action_test.go +++ b/driver/semantic_action_test.go @@ -15,18 +15,26 @@ type testSemAct struct { actLog []string } -func (a *testSemAct) Shift(tok *mldriver.Token) { - a.actLog = append(a.actLog, fmt.Sprintf("shift/%v", tok.KindName)) +func (a *testSemAct) Shift(tok *mldriver.Token, recovered bool) { + if recovered { + a.actLog = append(a.actLog, fmt.Sprintf("shift/%v/recovered", tok.KindName)) + } else { + 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) { +func (a *testSemAct) Reduce(prodNum int, recovered bool) { lhsSym := a.gram.ParsingTable.LHSSymbols[prodNum] lhsText := a.gram.ParsingTable.NonTerminals[lhsSym] - a.actLog = append(a.actLog, fmt.Sprintf("reduce/%v", lhsText)) + 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() { @@ -46,6 +54,7 @@ func TestParserWithSemanticAction(t *testing.T) { seq : seq elem semicolon | elem semicolon + | error star star semicolon | error semicolon #recover ; elem @@ -54,6 +63,7 @@ elem ws: "[\u{0009}\u{0020}]+" #skip; semicolon: ';'; +star: '*'; char: "[a-z]"; ` @@ -102,25 +112,34 @@ char: "[a-z]"; { caption: "when a grammar has `error` symbol, the driver calls `TrapError` and `ShiftError`.", specSrc: specSrcWithErrorProd, - src: `a; b !; c d !; e f g;`, + src: `a; b !; c d !; e ! * *; h i j;`, actLog: []string{ "shift/char", "trap/1", "shift/error", "shift/semicolon", - "reduce/seq", + "reduce/seq/recovered", "shift/char", "trap/2", "shift/error", "shift/semicolon", - "reduce/seq", + "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",