141 lines
4.4 KiB
Go
141 lines
4.4 KiB
Go
// Let's write a small example for parsing a really basic calculator.
|
|
// The calculator understands input that looks like:
|
|
//
|
|
// 10 + 20 - 8+4
|
|
//
|
|
// So positive numbers that can be either added or substracted, and whitespace
|
|
// is ignored.
|
|
package parsekit_test
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"git.makaay.nl/mauricem/go-parsekit"
|
|
)
|
|
|
|
// When writing a parser, it's a good start to use the parser/combinator
|
|
// functionality of parsekit to create some Matcher functions. These functions
|
|
// can later be used in the parser state machine to check for matching strings
|
|
// on the input data.
|
|
//
|
|
// For the calculator, we only need a definition of "number, surrounded by
|
|
// optional whitespace". Skipping whitespace could be a part of the StateHandler
|
|
// functions below too, but including it in a Matcher makes things really
|
|
// practical.
|
|
func createNumberMatcher() parsekit.Matcher {
|
|
// Easy access to parsekit definition.
|
|
c, a, m := parsekit.C, parsekit.A, parsekit.M
|
|
|
|
whitespace := m.Drop(c.Opt(a.Whitespace))
|
|
return c.Seq(whitespace, c.OneOrMore(a.Digit), whitespace)
|
|
}
|
|
|
|
var calcNumber = createNumberMatcher()
|
|
|
|
// We need to define the ItemTypes that we will use for emitting Items
|
|
// during the parsing process.
|
|
const (
|
|
numberType parsekit.ItemType = iota
|
|
addType
|
|
subtractType
|
|
)
|
|
|
|
// We also need to define the state machine for parsing the input.
|
|
// The state machine is built up from functions that match the StateHandler
|
|
// signature: func(*parsekit.P)
|
|
// The P struct holds the internal state for the parser and it provides
|
|
// some methods that form the API for your StateHandler implementation.
|
|
|
|
// State: expect a number. When a number is found on the input,
|
|
// it is accepted in the output buffer, after which the output buffer is
|
|
// emitted as a numberType item. Then we tell the state machine to continue
|
|
// with the calcWaitForOperatorOrEndOfInput state.
|
|
// When no number is found, the parser will emit an error, explaining that
|
|
// "a number" was expected.
|
|
func calcWaitForNumber(p *parsekit.P) {
|
|
p.Expects("a number")
|
|
if p.On(calcNumber).Accept() {
|
|
p.EmitLiteral(numberType)
|
|
p.RouteTo(calcWaitForOperatorOrEndOfInput)
|
|
}
|
|
}
|
|
|
|
// State: expect a plus or minus operator. When one of those
|
|
// is found, the appropriate Item is emitted and the parser is sent back
|
|
// to the numberHandler to find the next number on the input.
|
|
// When no operator is found, then the parser is told to expect the end of
|
|
// the input. When more input data is available (which is obviously wrong
|
|
// data since it does not match our syntax), the parser will emit an error.
|
|
func calcWaitForOperatorOrEndOfInput(p *parsekit.P) {
|
|
switch {
|
|
case p.On(a.Plus).Accept():
|
|
p.EmitLiteral(addType)
|
|
p.RouteTo(calcWaitForNumber)
|
|
case p.On(a.Minus).Accept():
|
|
p.EmitLiteral(subtractType)
|
|
p.RouteTo(calcWaitForNumber)
|
|
default:
|
|
p.ExpectEndOfFile()
|
|
}
|
|
}
|
|
|
|
// All is ready for our parser. We now can create a new Parser struct.
|
|
// We need to tell it what the start state is. In our case, it is the
|
|
// calcWaitForNumber state, since the calculation must start with a number.
|
|
var calcParser = parsekit.NewParser(calcWaitForNumber)
|
|
|
|
func Example_basicCalculator() {
|
|
// Let's feed the parser some input to work with.
|
|
run := calcParser.Parse(" 153+22 + 31-4 -\t 6+42 ")
|
|
|
|
// We can now step through the results of the parsing process by repeated
|
|
// calls to run.Next(). Next() returns either the next parse item, a parse
|
|
// error or an end of file. Let's dump the parse results and handle the
|
|
// computation while we're at it.
|
|
sum := 0
|
|
op := +1
|
|
for {
|
|
item, err, ok := run.Next()
|
|
switch {
|
|
case !ok && err == nil:
|
|
fmt.Println("End of file reached")
|
|
fmt.Println("Outcome of computation:", sum)
|
|
return
|
|
case !ok:
|
|
fmt.Printf("Error: %s\n", err)
|
|
return
|
|
default:
|
|
fmt.Printf("Type: %d, Value: %q\n", item.Type, item.Value)
|
|
switch {
|
|
case item.Type == addType:
|
|
op = +1
|
|
case item.Type == subtractType:
|
|
op = -1
|
|
case item.Type == numberType:
|
|
nr, err := strconv.Atoi(item.Value)
|
|
if err != nil {
|
|
fmt.Printf("Error: invalid number %s: %s\n", item.Value, err)
|
|
return
|
|
}
|
|
sum += op * nr
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output:
|
|
// Type: 0, Value: "153"
|
|
// Type: 1, Value: "+"
|
|
// Type: 0, Value: "22"
|
|
// Type: 1, Value: "+"
|
|
// Type: 0, Value: "31"
|
|
// Type: 2, Value: "-"
|
|
// Type: 0, Value: "4"
|
|
// Type: 2, Value: "-"
|
|
// Type: 0, Value: "6"
|
|
// Type: 1, Value: "+"
|
|
// Type: 0, Value: "42"
|
|
// End of file reached
|
|
// Outcome of computation: 238
|
|
}
|