Implement REPL package with Run(io.Reader, io.Writer)
- Read-eval-print loop: reads lines, tokenizes, parses, evaluates, prints result
- Prints prompt '>> ' before each input
- Error handling: prints 'error: <msg>' and continues loop
- Skips empty lines
- Formats results with %g (clean integers, no trailing zeros)
- 10 unit tests covering: single/multiple expressions, error recovery,
division by zero, empty lines, prompt display, float results,
invalid chars, empty input, integer formatting
diff --git a/repl/repl.go b/repl/repl.go
new file mode 100644
index 0000000..bc14567
--- /dev/null
+++ b/repl/repl.go
@@ -0,0 +1,62 @@
+package repl
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "math"
+ "strings"
+
+ "matheval/evaluator"
+ "matheval/lexer"
+ "matheval/parser"
+)
+
+const prompt = ">> "
+
+// Run starts the read-eval-print loop, reading from r and writing to w.
+func Run(r io.Reader, w io.Writer) {
+ scanner := bufio.NewScanner(r)
+ fmt.Fprint(w, prompt)
+
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ fmt.Fprint(w, prompt)
+ continue
+ }
+
+ result, err := evalLine(line)
+ if err != nil {
+ fmt.Fprintf(w, "error: %s\n", err)
+ } else {
+ fmt.Fprintln(w, formatResult(result))
+ }
+
+ fmt.Fprint(w, prompt)
+ }
+}
+
+// evalLine tokenizes, parses, and evaluates a single expression string.
+func evalLine(line string) (float64, error) {
+ tokens, err := lexer.Tokenize(line)
+ if err != nil {
+ return 0, err
+ }
+
+ tree, err := parser.Parse(tokens)
+ if err != nil {
+ return 0, err
+ }
+
+ return evaluator.Eval(tree)
+}
+
+// formatResult formats a float64 for display.
+// Whole numbers are printed without decimal points.
+func formatResult(val float64) string {
+ if val == math.Trunc(val) && !math.IsInf(val, 0) && !math.IsNaN(val) {
+ return fmt.Sprintf("%g", val)
+ }
+ return fmt.Sprintf("%g", val)
+}