Hmm... even beter wording! Fully from the parser writer's perspective, hiding internals.

This commit is contained in:
Maurice Makaay 2019-05-18 13:21:56 +00:00
parent a569c430d5
commit 55e23874f7
7 changed files with 56 additions and 57 deletions

View File

@ -109,14 +109,14 @@ func (p *P) AcceptAny() bool {
return false return false
} }
type afterFollowup struct { type action struct {
p *P p *P
runes []rune runes []rune
widths []int widths []int
ok bool ok bool
} }
func (a *afterFollowup) Store() bool { func (a *action) Accept() bool {
if a.ok { if a.ok {
for i, r := range a.runes { for i, r := range a.runes {
a.p.buffer.writeRune(r) a.p.buffer.writeRune(r)
@ -126,7 +126,7 @@ func (a *afterFollowup) Store() bool {
return a.ok return a.ok
} }
func (a *afterFollowup) Ignore() bool { func (a *action) Skip() bool {
if a.ok { if a.ok {
for i, r := range a.runes { for i, r := range a.runes {
a.p.advanceCursor(r, a.widths[i]) a.p.advanceCursor(r, a.widths[i])
@ -135,13 +135,20 @@ func (a *afterFollowup) Ignore() bool {
return a.ok 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 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...) runes, widths, ok := p.Match(patterns...)
return &afterFollowup{ return &action{
p: p, p: p,
runes: runes, runes: runes,
widths: widths, widths: widths,

View File

@ -30,10 +30,13 @@ func (p *P) Next() (Item, *Error, bool) {
return i, nil, true return i, nil, true
} }
default: 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 { if p.nextState == nil {
panic("No next state was scheduled for the parser") panic("No next state was scheduled for the parser")
} }
p.state = p.nextState p.state, p.nextState = p.nextState, nil
p.state(p) p.state(p)
} }
} }

View File

@ -1,50 +1,40 @@
package parsekit package parsekit
func (p *P) RouteRepeat() { func (p *P) Repeat() {
p.nextState = p.state p.nextState = p.state
return return
} }
type RouteFollowup struct { func (p *P) RouteTo(state StateFn) *routeFollowup {
p.nextState = state
return &routeFollowup{p}
}
type routeFollowup struct {
p *P p *P
} }
func (p *P) RouteTo(state StateFn) *RouteFollowup { func (r *routeFollowup) ThenTo(state StateFn) *routeFollowup {
p.nextState = state r.p.pushState(state)
return &RouteFollowup{p}
}
func (r *RouteFollowup) ThenTo(state StateFn) *RouteFollowup {
r.p.PushState(state)
return r return r
} }
func (r *RouteFollowup) ThenReturnHere() { func (r *routeFollowup) ThenReturnHere() {
r.p.PushState(r.p.state) r.p.pushState(r.p.state)
} }
func (p *P) RouteReturn() { func (p *P) RouteReturn() {
p.nextState = p.PopState() 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
} }
// PushState adds the state function to the state stack. // PushState adds the state function to the state stack.
// This is used for implementing nested parsing. // 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) p.stack = append(p.stack, state)
} }
// PopState pops the last pushed state from the state stack. // 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 last := len(p.stack) - 1
head, tail := p.stack[:last], p.stack[last] head, tail := p.stack[:last], p.stack[last]
p.stack = head p.stack = head

View File

@ -17,8 +17,8 @@ type P struct {
err *Error // an error when lexing failed, retrieved by Error() err *Error // an error when lexing failed, retrieved by Error()
} }
// StateFn represents the state of the parser as a function // StateFn defines the type of function that can be used to
// that returns the next state. // handle a parser state.
type StateFn func(*P) type StateFn func(*P)
// ItemType represents the type of a parser Item. // ItemType represents the type of a parser Item.

View File

@ -18,6 +18,6 @@ func commentContents(p *parsekit.P) {
p.RouteReturn() p.RouteReturn()
default: default:
p.AcceptAny() p.AcceptAny()
p.RouteRepeat() p.Repeat()
} }
} }

View File

@ -5,12 +5,11 @@ import "github.com/mmakaay/toml/parsekit"
// The primary building block of a TOML document is the key/value pair. // The primary building block of a TOML document is the key/value pair.
func startKeyValuePair(p *parsekit.P) { func startKeyValuePair(p *parsekit.P) {
switch { switch {
case p.After(whitespace + carriageReturn + newline).Ignore(): case p.On(whitespace + carriageReturn + newline).Skip():
p.RouteRepeat() p.Repeat()
case p.After(hash).Backup(): case p.On(hash).Stay():
p.RouteTo(startComment).ThenReturnHere() p.RouteTo(startComment).ThenReturnHere()
case p.After(startOfKey).Backup(): case p.On(startOfKey).RouteTo(startKey):
p.RouteTo(startKey)
default: default:
p.RouteTo(endOfFile) p.RouteTo(endOfFile)
} }
@ -18,9 +17,9 @@ func startKeyValuePair(p *parsekit.P) {
// A key may be either bare, quoted or dotted. // A key may be either bare, quoted or dotted.
func startKey(p *parsekit.P) { func startKey(p *parsekit.P) {
if p.After(bareKeyChars).Backup() { switch {
p.RouteTo(startBareKey) case p.On(bareKeyChars).RouteTo(startBareKey):
} else { default:
p.UnexpectedInput("a valid key name") p.UnexpectedInput("a valid key name")
} }
} }
@ -41,7 +40,7 @@ func endOfKeyOrDot(p *parsekit.P) {
// Whitespace around dot-separated parts is ignored, however, // Whitespace around dot-separated parts is ignored, however,
// best practice is to not use any extraneous whitespace. // best practice is to not use any extraneous whitespace.
p.SkipConsecutive(whitespace) p.SkipConsecutive(whitespace)
if p.After(dot).Store() { if p.On(dot).Accept() {
p.SkipConsecutive(whitespace) p.SkipConsecutive(whitespace)
p.EmitLiteral(ItemKeyDot) p.EmitLiteral(ItemKeyDot)
p.RouteTo(startKey) p.RouteTo(startKey)
@ -56,7 +55,7 @@ func endOfKeyOrDot(p *parsekit.P) {
// be broken over multiple lines). // be broken over multiple lines).
func startKeyAssignment(p *parsekit.P) { func startKeyAssignment(p *parsekit.P) {
p.SkipConsecutive(whitespace) p.SkipConsecutive(whitespace)
if p.After(equal).Store() { if p.On(equal).Accept() {
p.EmitLiteral(ItemAssignment) p.EmitLiteral(ItemAssignment)
p.SkipConsecutive(whitespace) p.SkipConsecutive(whitespace)
p.RouteTo(startValue) p.RouteTo(startValue)

View File

@ -8,10 +8,8 @@ import "github.com/mmakaay/toml/parsekit"
// * Basic strings are surrounded by quotation marks. // * Basic strings are surrounded by quotation marks.
func startString(p *parsekit.P) { func startString(p *parsekit.P) {
switch { switch {
case p.After(doubleQuote3).Ignore(): case p.On(doubleQuote3).RouteTo(startMultiLineBasicString):
p.RouteTo(startMultiLineBasicString) case p.On(doubleQuote).RouteTo(startBasicString):
case p.After(doubleQuote).Ignore():
p.RouteTo(startBasicString)
default: default:
p.UnexpectedInput("a string value") p.UnexpectedInput("a string value")
} }
@ -35,22 +33,23 @@ func parseBasicString(p *parsekit.P) {
switch { switch {
case p.AtEndOfFile(): case p.AtEndOfFile():
p.UnexpectedEndOfFile("basic string token") p.UnexpectedEndOfFile("basic string token")
case p.After(backslash, validEscapeChars).Store() || case p.On(backslash, validEscapeChars).Accept() ||
p.After(shortUtf8Match).Store() || p.On(shortUtf8Match).Accept() ||
p.After(longUtf8Match).Store(): p.On(longUtf8Match).Accept():
p.RouteRepeat() p.Repeat()
case p.After(mustBeEscaped).Backup(): case p.On(mustBeEscaped).Stay():
r, _, _ := p.Match(mustBeEscaped) r, _, _ := p.Match(mustBeEscaped)
p.EmitError("Invalid character in basic string: %q (must be escaped)", r[0]) 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() p.RouteReturn()
default: default:
p.AcceptAny() p.AcceptAny()
p.RouteRepeat() p.Repeat()
} }
} }
func startBasicString(p *parsekit.P) { func startBasicString(p *parsekit.P) {
p.On(doubleQuote).Skip()
p.RouteTo(parseBasicString).ThenTo(basicStringSpecifics) p.RouteTo(parseBasicString).ThenTo(basicStringSpecifics)
} }
@ -61,13 +60,13 @@ func startBasicString(p *parsekit.P) {
// produce an error."" // produce an error.""
func basicStringSpecifics(p *parsekit.P) { func basicStringSpecifics(p *parsekit.P) {
switch { switch {
case p.After(doubleQuote).Ignore(): case p.On(doubleQuote).Skip():
if err := p.EmitInterpreted(ItemString); err != nil { // TODO testcase? if err := p.EmitInterpreted(ItemString); err != nil { // TODO testcase?
p.EmitError("Invalid data in string: %s", err) p.EmitError("Invalid data in string: %s", err)
} else { } else {
p.RouteTo(startKeyValuePair) p.RouteTo(startKeyValuePair)
} }
case p.After(backslash).Backup(): case p.On(backslash).Stay():
p.EmitError("Invalid escape sequence") p.EmitError("Invalid escape sequence")
default: default:
p.RouteTo(startBasicString) p.RouteTo(startBasicString)
@ -75,5 +74,6 @@ func basicStringSpecifics(p *parsekit.P) {
} }
func startMultiLineBasicString(p *parsekit.P) { func startMultiLineBasicString(p *parsekit.P) {
p.On(doubleQuote3).Skip()
p.EmitError("Not yet implemented") p.EmitError("Not yet implemented")
} }