calc.gno
2.84 Kb · 118 lines
1package calc
2
3import (
4 "chain"
5 "errors"
6 "strconv"
7 "strings"
8)
9
10// stack holds the shared RPN operand stack, bottom (index 0) to top.
11var stack []int
12
13// ErrUnderflow is raised when an operator needs more operands than available.
14var ErrUnderflow = errors.New("stack underflow: operator needs two operands")
15
16// ErrDivByZero is raised on division by zero.
17var ErrDivByZero = errors.New("division by zero")
18
19// Push consumes a single token: an integer is pushed onto the stack; an
20// operator (+, -, *, /) pops two operands and pushes the result.
21func Push(cur realm, token string) {
22 token = strings.TrimSpace(token)
23 if token == "" {
24 panic("empty token")
25 }
26
27 switch token {
28 case "+", "-", "*", "/":
29 if len(stack) < 2 {
30 panic(ErrUnderflow.Error())
31 }
32 b := stack[len(stack)-1]
33 a := stack[len(stack)-2]
34 stack = stack[:len(stack)-2]
35
36 var r int
37 switch token {
38 case "+":
39 r = a + b
40 case "-":
41 r = a - b
42 case "*":
43 r = a * b
44 case "/":
45 if b == 0 {
46 panic(ErrDivByZero.Error())
47 }
48 r = a / b
49 }
50 stack = append(stack, r)
51 chain.Emit("Op", "op", token, "result", strconv.Itoa(r))
52 default:
53 n, err := strconv.Atoi(token)
54 if err != nil {
55 panic("invalid token: expected integer or one of + - * /, got " + token)
56 }
57 stack = append(stack, n)
58 chain.Emit("Push", "value", strconv.Itoa(n))
59 }
60}
61
62// Clear empties the shared stack.
63func Clear(cur realm) {
64 stack = nil
65 chain.Emit("Clear")
66}
67
68// Depth returns the number of values currently on the stack.
69func Depth() int {
70 return len(stack)
71}
72
73// Top returns the top value and whether the stack is non-empty.
74func Top() (int, bool) {
75 if len(stack) == 0 {
76 return 0, false
77 }
78 return stack[len(stack)-1], true
79}
80
81// Render renders the current stack, the top value as the result, and a
82// short usage guide.
83func Render(path string) string {
84 var sb strings.Builder
85 sb.WriteString("# RPN Calculator\n\n")
86
87 sb.WriteString("## Result\n\n")
88 if top, ok := Top(); ok {
89 sb.WriteString("**")
90 sb.WriteString(strconv.Itoa(top))
91 sb.WriteString("**\n\n")
92 } else {
93 sb.WriteString("_(empty stack)_\n\n")
94 }
95
96 sb.WriteString("## Stack (bottom → top)\n\n")
97 if len(stack) == 0 {
98 sb.WriteString("_empty_\n\n")
99 } else {
100 for i, v := range stack {
101 sb.WriteString(strconv.Itoa(i))
102 sb.WriteString(". ")
103 sb.WriteString(strconv.Itoa(v))
104 sb.WriteString("\n")
105 }
106 sb.WriteString("\n")
107 }
108
109 sb.WriteString("## Usage\n\n")
110 sb.WriteString("Reverse Polish Notation: operands first, operator last.\n\n")
111 sb.WriteString("- `Push(\"5\")` then `Push(\"3\")` then `Push(\"+\")` → `8`\n")
112 sb.WriteString("- Supported operators: `+`, `-`, `*`, `/` (integer division)\n")
113 sb.WriteString("- An operator pops the top two values (a, b) and pushes `a op b`.\n")
114 sb.WriteString("- `Clear()` empties the stack.\n")
115 sb.WriteString("- Divide-by-zero and underflow abort the transaction.\n")
116
117 return sb.String()
118}