go-parsekit/examples/example_basiccalculator2_te...

204 lines
6.2 KiB
Go

// 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:
//
// <calculation> = <expr> <EOF>
// <expr> = (<term> | <term> (ADD|SUB) <term>)
// <term> = (<factor> | <factor> (MUL|DIV) <factor>)
// <space> = (<space> (SPACE|TAB) | "")
// <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
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
}
// <calculation> = <expr> <EOF>
func (calc *calculator) calculation(p *parse.API) {
if p.Handle(calc.expr) {
p.ExpectEndOfFile()
calc.result = calc.interpreter.result
}
}
// <expr> = (<term> | <term> (ADD|SUB) <term>)
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.Byte(0)
if !p.Handle(calc.term) {
return
}
calc.interpreter.eval(op)
}
}
calc.interpreter.pop()
}
// <term> = (<factor> | <factor> (MUL|DIV) <factor>)
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.Byte(0)
if !p.Handle(calc.factor) {
return
}
calc.interpreter.eval(op)
}
}
calc.interpreter.pop()
}
// <space> = (<space> (SPACE|TAB) | "")
// <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
func (calc *calculator) factor(p *parse.API) {
var A, T = tokenize.A, tokenize.T
p.Skip(A.Blanks)
switch {
case p.Accept(T.Float64(nil, A.Signed(A.Decimal))):
value := p.Result.Token(0).Value.(float64)
calc.interpreter.pushValue(value)
case p.Skip(A.LeftParen):
if !p.Handle(calc.expr) {
return
}
if !p.Skip(A.RightParen) {
p.Expected("')'")
return
}
default:
p.Expected("factor or parenthesized expression")
return
}
p.Skip(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 byte) 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
}