// Let's write a parser for 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" ) func Example_basicCalculator1() { for _, c := range []struct { input string expected int64 }{ {"0", 0}, {"1", 1}, {"1+2+3", 6}, {" 10 + \t20 - 3 + 7 -10 ", 24}, {"", 0}, {" \t ", 0}, {"+", 0}, {"10.8 + 12", 0}, {"42+ ", 0}, {"9999999999999999999 + 8888888", 0}, } { output, err := ComputeSimple(c.input) if err != nil { fmt.Printf("Input: %q, got error: %s\n", c.input, err) } else { fmt.Printf("Input: %q, got outcome: %d, correct = %t\n", c.input, output, output == c.expected) } } // Output: // Input: "0", got outcome: 0, correct = true // Input: "1", got outcome: 1, correct = true // Input: "1+2+3", got outcome: 6, correct = true // Input: " 10 + \t20 - 3 + 7 -10 ", got outcome: 24, correct = true // Input: "", got error: unexpected end of file (expected integer number) // Input: " \t ", got error: unexpected character ' ' (expected integer number) // Input: "+", got error: unexpected character '+' (expected integer number) // Input: "10.8 + 12", got error: unexpected character '.' (expected operator, '+' or '-') // Input: "42+ ", got error: unexpected character ' ' (expected integer number) // Input: "9999999999999999999 + 8888888", got error: invalid value: strconv.ParseInt: parsing "9999999999999999999": value out of range } // --------------------------------------------------------------------------- // Implementation of the parser // --------------------------------------------------------------------------- // CalculateSimple interprets a simple calculation, consisting of only integers // and add or subtract operators. It returns the result of the calculation. // An error is returned in case the calculation failed. func ComputeSimple(calculation string) (int64, *parsekit.Error) { calculator := &simpleCalculator{op: +1} parser := parsekit.NewParser(calculator.number) err := parser.Execute(calculation) return calculator.Result, err } // simpleCalculator defines the parsing state machine. We do this using methods // on a struct, so the parser can make use of state data inside that struct // during the parsing. type simpleCalculator struct { Result int64 // holds the resulting outcome of the computation op int64 // represents operation for next term (+1 = add, -1 = subtract) } func (c *simpleCalculator) number(p *parsekit.ParseAPI) { // A definition of integer, which conveniently drops surrounding whitespace. pc, a, m := parsekit.C, parsekit.A, parsekit.M whitespace := m.Drop(pc.Opt(a.Whitespace)) integer := pc.Seq(whitespace, a.Integer, whitespace) if p.On(integer).Accept() { value, err := strconv.ParseInt(p.BufLiteral(), 10, 64) p.BufClear() if err != nil { p.EmitError("invalid value: %s", err) } else { c.Result += c.op * value p.Handle(c.operatorOrEndOfFile) } } else { p.Expects("integer number") p.UnexpectedInput() } } func (c *simpleCalculator) operatorOrEndOfFile(p *parsekit.ParseAPI) { var a = parsekit.A switch { case p.On(a.Add).Skip(): c.op = +1 p.Handle(c.number) case p.On(a.Subtract).Skip(): c.op = -1 p.Handle(c.number) case !p.On(a.EndOfFile).Stay(): p.Expects("operator, '+' or '-'") p.UnexpectedInput() default: p.ExpectEndOfFile() } }