diff --git a/parsekit/matching.go b/parsekit/matching.go index e1853d5..81b9a8e 100644 --- a/parsekit/matching.go +++ b/parsekit/matching.go @@ -109,14 +109,14 @@ func (p *P) AcceptAny() bool { return false } -type afterFollowup struct { +type action struct { p *P runes []rune widths []int ok bool } -func (a *afterFollowup) Store() bool { +func (a *action) Accept() bool { if a.ok { for i, r := range a.runes { a.p.buffer.writeRune(r) @@ -126,7 +126,7 @@ func (a *afterFollowup) Store() bool { return a.ok } -func (a *afterFollowup) Ignore() bool { +func (a *action) Skip() bool { if a.ok { for i, r := range a.runes { a.p.advanceCursor(r, a.widths[i]) @@ -135,13 +135,20 @@ func (a *afterFollowup) Ignore() bool { return a.ok } -func (a *afterFollowup) Backup() bool { +func (a *action) RouteTo(state StateFn) bool { + if a.ok { + a.p.RouteTo(state) + } return a.ok } -func (p *P) After(patterns ...interface{}) *afterFollowup { +func (a *action) Stay() bool { + return a.ok +} + +func (p *P) On(patterns ...interface{}) *action { runes, widths, ok := p.Match(patterns...) - return &afterFollowup{ + return &action{ p: p, runes: runes, widths: widths, diff --git a/parsekit/parsekit.go b/parsekit/parsekit.go index b669890..506d7f5 100644 --- a/parsekit/parsekit.go +++ b/parsekit/parsekit.go @@ -30,10 +30,13 @@ func (p *P) Next() (Item, *Error, bool) { return i, nil, true } default: + // When implementing a parser, it is mandatory to provide + // a conscious state routing decision for every cycle. + // This helps preventing bugs during implementation. if p.nextState == nil { panic("No next state was scheduled for the parser") } - p.state = p.nextState + p.state, p.nextState = p.nextState, nil p.state(p) } } diff --git a/parsekit/staterouting.go b/parsekit/staterouting.go index b6e0831..3182480 100644 --- a/parsekit/staterouting.go +++ b/parsekit/staterouting.go @@ -1,50 +1,40 @@ package parsekit -func (p *P) RouteRepeat() { +func (p *P) Repeat() { p.nextState = p.state return } -type RouteFollowup struct { +func (p *P) RouteTo(state StateFn) *routeFollowup { + p.nextState = state + return &routeFollowup{p} +} + +type routeFollowup struct { p *P } -func (p *P) RouteTo(state StateFn) *RouteFollowup { - p.nextState = state - return &RouteFollowup{p} -} - -func (r *RouteFollowup) ThenTo(state StateFn) *RouteFollowup { - r.p.PushState(state) +func (r *routeFollowup) ThenTo(state StateFn) *routeFollowup { + r.p.pushState(state) return r } -func (r *RouteFollowup) ThenReturnHere() { - r.p.PushState(r.p.state) +func (r *routeFollowup) ThenReturnHere() { + r.p.pushState(r.p.state) } func (p *P) RouteReturn() { - p.nextState = p.PopState() -} - -func (p *P) ToChildState(state StateFn) StateFn { - p.PushState(p.state) - return state -} - -func (p *P) ToParentState() StateFn { - state := p.PopState() - return state + p.nextState = p.popState() } // PushState adds the state function to the state stack. // This is used for implementing nested parsing. -func (p *P) PushState(state StateFn) { +func (p *P) pushState(state StateFn) { p.stack = append(p.stack, state) } // PopState pops the last pushed state from the state stack. -func (p *P) PopState() StateFn { +func (p *P) popState() StateFn { last := len(p.stack) - 1 head, tail := p.stack[:last], p.stack[last] p.stack = head diff --git a/parsekit/types.go b/parsekit/types.go index e07eb5d..a8d3500 100644 --- a/parsekit/types.go +++ b/parsekit/types.go @@ -17,8 +17,8 @@ type P struct { err *Error // an error when lexing failed, retrieved by Error() } -// StateFn represents the state of the parser as a function -// that returns the next state. +// StateFn defines the type of function that can be used to +// handle a parser state. type StateFn func(*P) // ItemType represents the type of a parser Item. diff --git a/parser/syn_comments.go b/parser/syn_comments.go index e15cc89..0f00be1 100644 --- a/parser/syn_comments.go +++ b/parser/syn_comments.go @@ -18,6 +18,6 @@ func commentContents(p *parsekit.P) { p.RouteReturn() default: p.AcceptAny() - p.RouteRepeat() + p.Repeat() } } diff --git a/parser/syn_key.go b/parser/syn_key.go index 13a1fd4..4a3c363 100644 --- a/parser/syn_key.go +++ b/parser/syn_key.go @@ -5,12 +5,11 @@ import "github.com/mmakaay/toml/parsekit" // The primary building block of a TOML document is the key/value pair. func startKeyValuePair(p *parsekit.P) { switch { - case p.After(whitespace + carriageReturn + newline).Ignore(): - p.RouteRepeat() - case p.After(hash).Backup(): + case p.On(whitespace + carriageReturn + newline).Skip(): + p.Repeat() + case p.On(hash).Stay(): p.RouteTo(startComment).ThenReturnHere() - case p.After(startOfKey).Backup(): - p.RouteTo(startKey) + case p.On(startOfKey).RouteTo(startKey): default: p.RouteTo(endOfFile) } @@ -18,9 +17,9 @@ func startKeyValuePair(p *parsekit.P) { // A key may be either bare, quoted or dotted. func startKey(p *parsekit.P) { - if p.After(bareKeyChars).Backup() { - p.RouteTo(startBareKey) - } else { + switch { + case p.On(bareKeyChars).RouteTo(startBareKey): + default: p.UnexpectedInput("a valid key name") } } @@ -41,7 +40,7 @@ func endOfKeyOrDot(p *parsekit.P) { // Whitespace around dot-separated parts is ignored, however, // best practice is to not use any extraneous whitespace. p.SkipConsecutive(whitespace) - if p.After(dot).Store() { + if p.On(dot).Accept() { p.SkipConsecutive(whitespace) p.EmitLiteral(ItemKeyDot) p.RouteTo(startKey) @@ -56,7 +55,7 @@ func endOfKeyOrDot(p *parsekit.P) { // be broken over multiple lines). func startKeyAssignment(p *parsekit.P) { p.SkipConsecutive(whitespace) - if p.After(equal).Store() { + if p.On(equal).Accept() { p.EmitLiteral(ItemAssignment) p.SkipConsecutive(whitespace) p.RouteTo(startValue) diff --git a/parser/syn_strings.go b/parser/syn_strings.go index 910db83..90775b9 100644 --- a/parser/syn_strings.go +++ b/parser/syn_strings.go @@ -8,10 +8,8 @@ import "github.com/mmakaay/toml/parsekit" // * Basic strings are surrounded by quotation marks. func startString(p *parsekit.P) { switch { - case p.After(doubleQuote3).Ignore(): - p.RouteTo(startMultiLineBasicString) - case p.After(doubleQuote).Ignore(): - p.RouteTo(startBasicString) + case p.On(doubleQuote3).RouteTo(startMultiLineBasicString): + case p.On(doubleQuote).RouteTo(startBasicString): default: p.UnexpectedInput("a string value") } @@ -35,22 +33,23 @@ func parseBasicString(p *parsekit.P) { switch { case p.AtEndOfFile(): p.UnexpectedEndOfFile("basic string token") - case p.After(backslash, validEscapeChars).Store() || - p.After(shortUtf8Match).Store() || - p.After(longUtf8Match).Store(): - p.RouteRepeat() - case p.After(mustBeEscaped).Backup(): + case p.On(backslash, validEscapeChars).Accept() || + p.On(shortUtf8Match).Accept() || + p.On(longUtf8Match).Accept(): + p.Repeat() + case p.On(mustBeEscaped).Stay(): r, _, _ := p.Match(mustBeEscaped) p.EmitError("Invalid character in basic string: %q (must be escaped)", r[0]) - case p.After(backslash).Backup() || p.After(doubleQuote).Backup(): + case p.On(backslash).Stay() || p.On(doubleQuote).Stay(): p.RouteReturn() default: p.AcceptAny() - p.RouteRepeat() + p.Repeat() } } func startBasicString(p *parsekit.P) { + p.On(doubleQuote).Skip() p.RouteTo(parseBasicString).ThenTo(basicStringSpecifics) } @@ -61,13 +60,13 @@ func startBasicString(p *parsekit.P) { // produce an error."" func basicStringSpecifics(p *parsekit.P) { switch { - case p.After(doubleQuote).Ignore(): + case p.On(doubleQuote).Skip(): if err := p.EmitInterpreted(ItemString); err != nil { // TODO testcase? p.EmitError("Invalid data in string: %s", err) } else { p.RouteTo(startKeyValuePair) } - case p.After(backslash).Backup(): + case p.On(backslash).Stay(): p.EmitError("Invalid escape sequence") default: p.RouteTo(startBasicString) @@ -75,5 +74,6 @@ func basicStringSpecifics(p *parsekit.P) { } func startMultiLineBasicString(p *parsekit.P) { + p.On(doubleQuote3).Skip() p.EmitError("Not yet implemented") }