Added a nice example that shows how a []string-based type can be turned into a parser that fills its own slice elements during parsing.

This commit is contained in:
Maurice Makaay 2019-05-28 14:38:04 +00:00
parent 2d851103e5
commit 7aff3fc43e
6 changed files with 66 additions and 41 deletions

View File

@ -54,7 +54,7 @@ func Example_basicCalculator1() {
// Implementation of the parser // Implementation of the parser
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// CalculateSimple interprets a simple calculation, consisting of only integers // ComputeSimple interprets a simple calculation, consisting of only integers
// and add or subtract operators. It returns the result of the calculation. // and add or subtract operators. It returns the result of the calculation.
// An error is returned in case the calculation failed. // An error is returned in case the calculation failed.
func ComputeSimple(calculation string) (int64, *parsekit.Error) { func ComputeSimple(calculation string) (int64, *parsekit.Error) {
@ -72,13 +72,12 @@ type simpleCalculator struct {
op int64 // represents operation for next term (+1 = add, -1 = subtract) op int64 // represents operation for next term (+1 = add, -1 = subtract)
} }
func (c *simpleCalculator) number(p *parsekit.ParseAPI) { // A definition of bareInteger, which conveniently drops surrounding whitespace.
// A definition of integer, which conveniently drops surrounding whitespace. var dropWhitespace = parsekit.M.Drop(parsekit.C.Opt(parsekit.A.Whitespace))
pc, a, m := parsekit.C, parsekit.A, parsekit.M var bareInteger = parsekit.C.Seq(dropWhitespace, parsekit.A.Integer, dropWhitespace)
whitespace := m.Drop(pc.Opt(a.Whitespace))
integer := pc.Seq(whitespace, a.Integer, whitespace)
if p.On(integer).Accept() { func (c *simpleCalculator) number(p *parsekit.ParseAPI) {
if p.On(bareInteger).Accept() {
value, err := strconv.ParseInt(p.BufLiteral(), 10, 64) value, err := strconv.ParseInt(p.BufLiteral(), 10, 64)
p.BufClear() p.BufClear()
if err != nil { if err != nil {

View File

@ -3,13 +3,13 @@
// //
// " -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 " // " -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 "
// //
// More formally, a calculation is defined as: // In terms of a somewhat formal grammar, a calculation is defined as:
// //
// calculation : expr EOF // <calculation> = <expr> <EOF>
// expr : term ((ADD|SUB) term)* // <expr> = (<term> | <term> (ADD|SUB) <term>)
// term : factor ((MUL|DIV) factor)* // <term> = (<factor> | <factor> (MUL|DIV) <factor>)
// space : (SPACE|TAB)* // <space> = (<space> (SPACE|TAB) | "")
// factor : space (FLOAT | LPAREN expr RPAREN) space // <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
package parsekit_test package parsekit_test
import ( import (
@ -66,8 +66,8 @@ func Example_basicCalculator2() {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// calculator implements a recursive descent parser that is responsible for parsing // calculator implements a recursive descent parser that is responsible for parsing
// the input computation string according to the grammar. // the input calculation string according to the grammar.
// It offloads the actual computation to a separate interpreter. // It offloads the actual calculation to a separate interpreter.
type calculator struct { type calculator struct {
interpreter interpreter interpreter interpreter
result float64 result float64
@ -75,22 +75,23 @@ type calculator struct {
// Compute takes a calculation string as input and returns the interpreted result // Compute takes a calculation string as input and returns the interpreted result
// value for the calculation. An error can be returned as well, in case the // value for the calculation. An error can be returned as well, in case the
// computation fails for some reason. // calculation fails for some reason.
func Compute(input string) (float64, *parsekit.Error) { func Compute(input string) (float64, *parsekit.Error) {
c := &calculator{} c := &calculator{}
parser := parsekit.NewParser(c.computation) parser := parsekit.NewParser(c.calculation)
err := parser.Execute(input) err := parser.Execute(input)
return c.result, err return c.result, err
} }
func (c *calculator) computation(p *parsekit.ParseAPI) { // <calculation> = <expr> <EOF>
func (c *calculator) calculation(p *parsekit.ParseAPI) {
if p.Handle(c.expr) { if p.Handle(c.expr) {
p.ExpectEndOfFile() p.ExpectEndOfFile()
c.result = c.interpreter.result c.result = c.interpreter.result
} }
} }
// expr : term ((ADD|SUB) term)* // <expr> = (<term> | <term> (ADD|SUB) <term>)
func (c *calculator) expr(p *parsekit.ParseAPI) { func (c *calculator) expr(p *parsekit.ParseAPI) {
c.interpreter.push() c.interpreter.push()
@ -108,7 +109,7 @@ func (c *calculator) expr(p *parsekit.ParseAPI) {
c.interpreter.pop() c.interpreter.pop()
} }
// term : factor ((MUL|DIV) factor)* // <term> = (<factor> | <factor> (MUL|DIV) <factor>)
func (c *calculator) term(p *parsekit.ParseAPI) { func (c *calculator) term(p *parsekit.ParseAPI) {
c.interpreter.push() c.interpreter.push()
@ -126,7 +127,8 @@ func (c *calculator) term(p *parsekit.ParseAPI) {
c.interpreter.pop() c.interpreter.pop()
} }
// factor : space (FLOAT | LPAREN expr RPAREN) space // <space> = (<space> (SPACE|TAB) | "")
// <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
func (c *calculator) factor(p *parsekit.ParseAPI) { func (c *calculator) factor(p *parsekit.ParseAPI) {
var pc, a = parsekit.C, parsekit.A var pc, a = parsekit.C, parsekit.A
p.On(a.Whitespace).Skip() p.On(a.Whitespace).Skip()

View File

@ -78,16 +78,6 @@ func (h *helloparser2) Parse(input string) (string, *parsekit.Error) {
return h.greetee, err return h.greetee, err
} }
// Note:
// For efficiency, we could have either:
//
// 1) added a return after every call to p.Error()
// 2) done an 'else if' for every 'if' after the first
//
// For code readability, I omitted these however. The ParseAPI knows it
// should ignore any upcoming call after an error has been set, so after
// an error the p.On() calls will be invoked, however they will always
// return false.
func (h *helloparser2) start(p *parsekit.ParseAPI) { func (h *helloparser2) start(p *parsekit.ParseAPI) {
c, a, m := parsekit.C, parsekit.A, parsekit.M c, a, m := parsekit.C, parsekit.A, parsekit.M
if !p.On(c.StrNoCase("hello")).Skip() { if !p.On(c.StrNoCase("hello")).Skip() {

34
examples_state_test.go Normal file
View File

@ -0,0 +1,34 @@
// In this example, we show that any type can be extended into a parser,
// filling that type with data from the ParseHandler methods.
//
// Here, we create a custom type 'letterCollection', which is an alias
// for []string. We add a ParseHandler method directly to that type
// and let the parsing code fill the slice with strings during parsing.
package parsekit_test
import (
"fmt"
"git.makaay.nl/mauricem/go-parsekit"
)
type letterCollection []string
func (l *letterCollection) parseStart(p *parsekit.ParseAPI) {
for p.On(parsekit.C.MinMax(1, 3, parsekit.A.AnyRune)).Accept() {
*l = append(*l, p.BufLiteral())
p.BufClear()
}
p.ExpectEndOfFile()
}
func Example_usingSliceAsParserState() {
letters := &letterCollection{}
parser := parsekit.NewParser(letters.parseStart)
err := parser.Execute("¡Any will dö!")
fmt.Printf("Matches = %q, Error = %s\n", *letters, err)
// Output:
// Matches = ["¡An" "y w" "ill" " dö" "!"], Error = <nil>
}

View File

@ -36,7 +36,7 @@ package parsekit
// if p.On(parsekit.C.Str("hi")).Accept() { // if p.On(parsekit.C.Str("hi")).Accept() {
// p.Emit(SomeItemType, p.BufLiteral()) // p.Emit(SomeItemType, p.BufLiteral())
// } // }
func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction { func (p *ParseAPI) On(tokenHandler TokenHandler) *ParseAPIOnAction {
p.panicWhenStoppedOrInError() p.panicWhenStoppedOrInError()
// Perform the matching operation. // Perform the matching operation.
@ -54,7 +54,7 @@ func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction {
// } // }
p.LastMatch = string(m.input) p.LastMatch = string(m.input)
return &MatchAction{ return &ParseAPIOnAction{
p: p, p: p,
ok: ok, ok: ok,
input: m.input, input: m.input,
@ -63,9 +63,9 @@ func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction {
} }
} }
// MatchAction is a struct that is used for building the On()-method chain. // ParseAPIOnAction is a struct that is used for building the On()-method chain.
// The On() method will return an initialized struct of this type. // The On() method will return an initialized struct of this type.
type MatchAction struct { type ParseAPIOnAction struct {
p *ParseAPI p *ParseAPI
ok bool ok bool
input []rune input []rune
@ -79,7 +79,7 @@ type MatchAction struct {
// //
// Returns true in case a match was found. // Returns true in case a match was found.
// When no match was found, then no action is taken and false is returned. // When no match was found, then no action is taken and false is returned.
func (a *MatchAction) Accept() bool { func (a *ParseAPIOnAction) Accept() bool {
if a.ok { if a.ok {
a.p.buffer.writeString(string(a.output)) a.p.buffer.writeString(string(a.output))
a.advanceCursor() a.advanceCursor()
@ -92,7 +92,7 @@ func (a *MatchAction) Accept() bool {
// //
// Returns true in case a match was found. // Returns true in case a match was found.
// When no match was found, then no action is taken and false is returned. // When no match was found, then no action is taken and false is returned.
func (a *MatchAction) Skip() bool { func (a *ParseAPIOnAction) Skip() bool {
if a.ok { if a.ok {
a.advanceCursor() a.advanceCursor()
} }
@ -101,14 +101,14 @@ func (a *MatchAction) Skip() bool {
// Stay tells the parser to not move the cursor after finding a match. // Stay tells the parser to not move the cursor after finding a match.
// Returns true in case a match was found, false otherwise. // Returns true in case a match was found, false otherwise.
func (a *MatchAction) Stay() bool { func (a *ParseAPIOnAction) Stay() bool {
return a.ok return a.ok
} }
// advanceCursor advances the input position in the input data. // advanceCursor advances the input position in the input data.
// While doing so, it keeps tracks of newlines that are encountered, so we // While doing so, it keeps tracks of newlines that are encountered, so we
// can report on line + column positions on error. // can report on line + column positions on error.
func (a *MatchAction) advanceCursor() { func (a *ParseAPIOnAction) advanceCursor() {
a.p.inputPos = a.inputPos a.p.inputPos = a.inputPos
for _, r := range a.input { for _, r := range a.input {
if a.p.newline { if a.p.newline {

View File

@ -598,8 +598,8 @@ func modifyTrim(handler TokenHandler, cutset string, trimLeft bool, trimRight bo
} }
// ModifyTrimSpace creates a TokenHandler that checks if the provided TokenHandler applies. // ModifyTrimSpace creates a TokenHandler that checks if the provided TokenHandler applies.
// If it does, then its output is taken and whitespace characters as defined by unicode // If it does, then its output is taken and all leading and trailing whitespace charcters,
// are are trimmed from the left and right of the output. // as defined by Unicode (spaces, tabs, carriage returns and newlines) are removed from it.
func ModifyTrimSpace(handler TokenHandler) TokenHandler { func ModifyTrimSpace(handler TokenHandler) TokenHandler {
return ModifyByCallback(handler, strings.TrimSpace) return ModifyByCallback(handler, strings.TrimSpace)
} }