// Let's write the hello world of parsers: a calculator that can interpret // calculations that looks like: // // " -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 " // // More formally, a calculation is defined as: // // calculation : expr EOF // expr : term ((ADD|SUB) term)* // term : factor ((MUL|DIV) factor)* // space : (SPACE|TAB)* // factor : space (FLOAT | LPAREN expr RPAREN) space package parsekit_test import ( "fmt" "math" "strconv" "git.makaay.nl/mauricem/go-parsekit" ) func Example_basicCalculator2() { for _, c := range []struct { input string expected float64 }{ {"1", 1}, {"(123.10)", 123.10}, {"1 + 2 + 3 + 4 + 5", 15}, {"1 * 2 * 3 * 4 * 5 * 0.6", 72}, {"(3.05+2)*(4.3+5.12)", 47.571}, {"8.10 + 999/233", 12.387554}, {" -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 ", -20.168}, {"", 0}, {"(", 0}, {"10+20-", 0}, {"10+20-(4*10))", 0}, {"10+20-((4*10) + 17", 0}, } { output, err := Compute(c.input) output = math.Round(output*1000000) / 1000000 // to make the expectation comparisons usable if err != nil { fmt.Printf("Input: %q, got error: %s\n", c.input, err) } else { fmt.Printf("Input: %q, got outcome: %f, correct = %t\n", c.input, output, output == c.expected) } } // Output: // Input: "1", got outcome: 1.000000, correct = true // Input: "(123.10)", got outcome: 123.100000, correct = true // Input: "1 + 2 + 3 + 4 + 5", got outcome: 15.000000, correct = true // Input: "1 * 2 * 3 * 4 * 5 * 0.6", got outcome: 72.000000, correct = true // Input: "(3.05+2)*(4.3+5.12)", got outcome: 47.571000, correct = true // Input: "8.10 + 999/233", got outcome: 12.387554, correct = true // Input: " -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 ", got outcome: -20.168000, correct = true // Input: "", got error: unexpected end of file // Input: "(", got error: unexpected end of file // Input: "10+20-", got error: unexpected end of file // Input: "10+20-(4*10))", got error: unexpected character ')' (expected end of file) // Input: "10+20-((4*10) + 17", got error: unexpected end of file (expected ')') } // --------------------------------------------------------------------------- // Implementation of the calculator // --------------------------------------------------------------------------- // calculator implements a recursive descent parser that is responsible for parsing // the input computation string according to the grammar. // It offloads the actual computation to a separate interpreter. type calculator struct { interpreter interpreter result float64 } // 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 // computation fails for some reason. func Compute(input string) (float64, *parsekit.Error) { c := &calculator{} parser := parsekit.NewParser(c.computation) _, err, _ := parser.Parse(input).Next() return c.result, err } func (c *calculator) computation(p *parsekit.ParseAPI) { p.Handle(c.expr) p.ExpectEndOfFile() p.Handle(c.factor) c.result = c.interpreter.result } // expr : term ((ADD|SUB) term)* func (c *calculator) expr(p *parsekit.ParseAPI) { c.interpreter.push() var pc, a = parsekit.C, parsekit.A p.Handle(c.term) for p.On(pc.Any(a.Add, a.Subtract)).Skip() { c.interpreter.pushOperator(p.LastMatch) p.Handle(c.term) c.interpreter.eval() } c.interpreter.pop() } // term : factor ((MUL|DIV) factor)* func (c *calculator) term(p *parsekit.ParseAPI) { c.interpreter.push() var pc, a = parsekit.C, parsekit.A p.Handle(c.factor) for p.On(pc.Any(a.Multiply, a.Divide)).Skip() { c.interpreter.pushOperator(p.LastMatch) p.Handle(c.factor) c.interpreter.eval() } c.interpreter.pop() } // factor : space (FLOAT | LPAREN expr RPAREN) space func (c *calculator) factor(p *parsekit.ParseAPI) { var pc, a = parsekit.C, parsekit.A p.On(a.Whitespace).Skip() switch { case p.On(pc.Signed(a.Float)).Accept(): floatStr := p.BufLiteral() p.BufClear() value, err := strconv.ParseFloat(floatStr, 64) if err != nil { p.EmitError("invalid number %s: %s", floatStr, err) } else { c.interpreter.pushValue(value) } case p.On(a.LeftParen).Skip(): p.Handle(c.expr) if !p.On(a.RightParen).Skip() { p.Expects("')'") p.UnexpectedInput() } default: p.UnexpectedInput() } p.On(a.Whitespace).Skip() } // --------------------------------------------------------------------------- // The computational interpreter, used by the calculator. // --------------------------------------------------------------------------- type stackFrame struct { a float64 b float64 op func(a, b float64) float64 } type interpreter struct { stack []*stackFrame top *stackFrame result float64 } func (i *interpreter) push() *stackFrame { f := &stackFrame{} i.stack = append(i.stack, f) i.top = f i.pushOperator("VAL") return f } func (i *interpreter) pop() float64 { value := i.eval() i.stack = i.stack[0 : len(i.stack)-1] if len(i.stack) > 0 { i.top = i.stack[len(i.stack)-1] i.pushValue(value) } else { i.result = i.top.b i.top = nil } return value } func (i *interpreter) pushValue(value float64) { i.top.a, i.top.b = i.top.b, value } func (i *interpreter) pushOperator(op string) { switch op { case "VAL": i.top.op = func(a, b float64) float64 { return b } case "+": i.top.op = func(a, b float64) float64 { return a + b } case "-": i.top.op = func(a, b float64) float64 { return a - b } case "*": i.top.op = func(a, b float64) float64 { return a * b } case "/": i.top.op = func(a, b float64) float64 { return a / b } default: panic(fmt.Sprintf("Unhandled op name: %s", op)) } } func (i *interpreter) eval() float64 { value := i.top.op(i.top.a, i.top.b) i.pushValue(value) i.pushOperator("VAL") return value }