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) }