go-parsekit/example_basiccalculator2_te...

214 lines
5.7 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 "
//
// 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
}