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
}
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,

View File

@ -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)
}
}

View File

@ -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

View File

@ -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.

View File

@ -18,6 +18,6 @@ func commentContents(p *parsekit.P) {
p.RouteReturn()
default:
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.
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)

View File

@ -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")
}