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