go-parsekit/example_basiccalculator_tes...

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
}