1
0
treerack/docs/examples/acalc/main.go

133 lines
2.9 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"strings"
)
var errExit = errors.New("exit")
// repl runs the Read-Eval-Print Loop.
func repl(input io.Reader, output io.Writer) {
// use buffered io, to read the input line-by-line:
buf := bufio.NewReader(os.Stdin)
// our REPL:
for {
// print a input prompt marker:
if _, err := output.Write([]byte("> ")); err != nil {
log.Fatalln(err)
}
// read the input and handle the errors:
expr, err := read(buf)
// handle EOF (Ctrl+D):
if errors.Is(err, io.EOF) {
output.Write([]byte{'\n'})
os.Exit(0)
}
// handle the explicit exit command:
if errors.Is(err, errExit) {
os.Exit(0)
}
// handle parser errors (allow the user to retry):
var perr *parseError
if errors.As(err, &perr) {
log.Println(err)
continue
}
// handle possible I/O errors:
if err != nil {
log.Fatalln(err)
}
// evaluate and print:
result := eval(expr)
if err := print(output, result); err != nil {
log.Fatalln(err)
}
}
}
func read(input *bufio.Reader) (*node, error) {
line, err := input.ReadString('\n')
if err != nil {
return nil, err
}
// parse the line using the generated parser:
expr, err := parse(bytes.NewBufferString(line))
if err != nil {
return nil, err
}
if strings.TrimSpace(expr.Text()) == "exit" {
return nil, errExit
}
// based on our syntax, the root node always has exactly one child: either a number or a binary operation.
return expr.Nodes[0], nil
}
// eval always returns the calculated result as a float64:
func eval(expr *node) float64 {
var value float64
switch expr.Name {
case "num":
// the number format in our syntax is based on the JSON spec, so we can piggy-back on it for the number
// parsing. In a real application, we would need to handle the errors here anyway, even if our parser
// already validated the input:
json.Unmarshal([]byte(expr.Text()), &value)
return value
default:
// evaluate binary expressions. Format: Operand [Operator Operand]...
value, expr.Nodes = eval(expr.Nodes[0]), expr.Nodes[1:]
for len(expr.Nodes) > 0 {
var (
operator string
operand float64
)
operator, operand, expr.Nodes = expr.Nodes[0].Name, eval(expr.Nodes[1]), expr.Nodes[2:]
switch operator {
case "add":
value += operand
case "sub":
value -= operand
case "mul":
value *= operand
case "div":
value /= operand // Go returns on division by zero +/-Inf
}
}
}
return value
}
func print(output io.Writer, result float64) error {
// we can use the stdlib fmt package to print float64:
_, err := fmt.Fprintln(output, result)
return err
}
func main() {
// for testability, we define the REPL in a separate function so that the test code can call it with
// in-memory buffers as input and output. Our main function calls it with the stdio handles:
repl(os.Stdin, os.Stdout)
}