204 lines
6.2 KiB
Go
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
|
|
}
|