// 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 " // // In terms of a somewhat formal grammar, a calculation is defined as: // // = // = ( | (ADD|SUB) ) // = ( | (MUL|DIV) ) // = ( (SPACE|TAB) | "") // = (FLOAT | LPAREN RPAREN) package examples import ( "fmt" "math" "git.makaay.nl/mauricem/go-parsekit/parse" "git.makaay.nl/mauricem/go-parsekit/tokenize" ) 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 (expected factor or parenthesized expression) at start of file // Input: "(", got error: unexpected end of file (expected factor or parenthesized expression) at line 1, column 2 // Input: "10+20-", got error: unexpected end of file (expected factor or parenthesized expression) at line 1, column 7 // Input: "10+20-(4*10))", got error: unexpected input (expected end of file) at line 1, column 13 // Input: "10+20-((4*10) + 17", got error: unexpected end of file (expected ')') at line 1, column 19 } // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― // Implementation of the parser // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― // calculator implements a recursive descent parser that is responsible for parsing // the input calculation string according to the grammar. // It offloads the actual calculation 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 // calculation fails for some reason. func Compute(input string) (float64, error) { calc := &calculator{} parseCalculation := parse.New(calc.calculation) err := parseCalculation(input) return calc.result, err } // = func (calc *calculator) calculation(p *parse.API) { if p.Handle(calc.expr) { p.ExpectEndOfFile() calc.result = calc.interpreter.result } } // = ( | (ADD|SUB) ) func (calc *calculator) expr(p *parse.API) { calc.interpreter.push() var A = tokenize.A if p.Handle(calc.term) { for p.Accept(A.Add.Or(A.Subtract)) { op := p.Result().Rune(0) if !p.Handle(calc.term) { return } calc.interpreter.eval(op) } } calc.interpreter.pop() } // = ( | (MUL|DIV) ) func (calc *calculator) term(p *parse.API) { calc.interpreter.push() var A = tokenize.A if p.Handle(calc.factor) { for p.Accept(A.Multiply.Or(A.Divide)) { op := p.Result().Rune(0) if !p.Handle(calc.factor) { return } calc.interpreter.eval(op) } } calc.interpreter.pop() } // = ( (SPACE|TAB) | "") // = (FLOAT | LPAREN RPAREN) func (calc *calculator) factor(p *parse.API) { var A, T = tokenize.A, tokenize.T p.Accept(A.Blanks) switch { case p.Accept(T.Float64(nil, A.Signed(A.Float))): value := p.Result().Value(0).(float64) calc.interpreter.pushValue(value) case p.Accept(A.LeftParen): if !p.Handle(calc.expr) { return } if !p.Accept(A.RightParen) { p.Expected("')'") return } default: p.Expected("factor or parenthesized expression") return } p.Accept(A.Blanks) } // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― // The computational interpreter, used by the calculator. // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― type stackFrame struct { a float64 b float64 } type interpreter struct { stack []*stackFrame top *stackFrame result float64 } func (i *interpreter) push() { f := &stackFrame{} i.top, i.stack = f, append(i.stack, f) } func (i *interpreter) pop() { popped := i.top i.stack = i.stack[0 : len(i.stack)-1] if len(i.stack) > 0 { i.top = i.stack[len(i.stack)-1] i.pushValue(popped.b) } else { i.result = popped.b } } func (i *interpreter) pushValue(value float64) { i.top.a, i.top.b = i.top.b, value } func (i *interpreter) eval(op rune) float64 { value := i.top.a switch op { case '+': value += i.top.b case '-': value -= i.top.b case '*': value *= i.top.b case '/': value /= i.top.b } i.top.b = value return value }