213 lines
5.7 KiB
Go
213 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 parser
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// 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.Execute(input)
|
|
return c.result, err
|
|
}
|
|
|
|
func (c *calculator) computation(p *parsekit.ParseAPI) {
|
|
p.Handle(c.expr)
|
|
p.ExpectEndOfFile()
|
|
|
|
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
|
|
}
|