Hmm... even beter wording! Fully from the parser writer's perspective, hiding internals.
This commit is contained in:
parent
a569c430d5
commit
55e23874f7
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -18,6 +18,6 @@ func commentContents(p *parsekit.P) {
|
|||
p.RouteReturn()
|
||||
default:
|
||||
p.AcceptAny()
|
||||
p.RouteRepeat()
|
||||
p.Repeat()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue