calculator.gno
6.01 Kb ยท 233 lines
1package calculator
2
3import (
4 "net/url"
5 "strconv"
6 "strings"
7
8 "gno.land/p/moul/md"
9 "gno.land/p/moul/mdtable"
10 "gno.land/p/moul/realmpath"
11 "gno.land/r/leon/hor"
12)
13
14type Node struct {
15 value string // Value of the current node
16 left *Node
17 right *Node
18}
19
20const (
21 specialCharacters = "+-*/."
22 specialCharactersWithoutMinus = "+*/."
23 topPriority = "*/"
24 lowPriority = "+-"
25 realmPath = "/r/miko/calculator"
26)
27
28var (
29 val float64
30 displayVal string
31
32 operationMap = map[string]func(left float64, right float64) float64{
33 "+": func(left float64, right float64) float64 { return left + right },
34 "-": func(left float64, right float64) float64 { return left - right },
35 "*": func(left float64, right float64) float64 { return left * right },
36 "/": func(left float64, right float64) float64 {
37 if right == 0 {
38 panic("Division by 0 is forbidden")
39 }
40 return left / right
41 },
42 }
43)
44
45func init() {
46 hor.Register(cross, "Miko's calculator", "")
47}
48
49func evaluateValidity(line string) (bool, string) {
50 if len(line) == 0 {
51 return false, "Invalid empty input"
52 } // edge case empty line
53 if strings.Index(specialCharactersWithoutMinus, string(line[0])) != -1 ||
54 strings.Index(specialCharacters, string(line[len(line)-1])) != -1 {
55 return false, "Invalid equation"
56 } // edge case special character at begining or end
57
58 isPriorSpecial := false
59 countParenthesis := 0
60
61 for i := 0; i < len(line); i++ {
62 if line[i] == '(' {
63 countParenthesis += 1
64 continue
65 }
66 if line[i] == ')' {
67 if isPriorSpecial == true {
68 return false, "Invalid equation"
69 }
70 countParenthesis -= 1
71 isPriorSpecial = false
72 continue
73 }
74 if strings.Index(specialCharacters, string(line[i])) != -1 {
75 if isPriorSpecial && !(line[i] == '-' && i < (len(line)-1) && line[i+1] >= '0' && line[i+1] <= '9') { // if we have two subsequent operator and the second one isn't a - before a number (negative number)
76 return false, "Invalid equation"
77 }
78 isPriorSpecial = true
79 continue
80 }
81 if line[i] < '0' || line[i] > '9' {
82 return false, "Invalid character encountered "
83 }
84 isPriorSpecial = false
85 }
86
87 if countParenthesis != 0 {
88 return false, "Invalid equation"
89 }
90 return true, ""
91}
92
93func searchForPriority(priorityList string, line string) *Node {
94 countParenthesis := 0
95 for iPrio := 0; iPrio < len(priorityList); iPrio++ {
96 for idx := 0; idx < len(line); idx++ {
97 if line[idx] == '(' {
98 countParenthesis += 1
99 }
100 if line[idx] == ')' {
101 countParenthesis -= 1
102 }
103 if countParenthesis == 0 && line[idx] == priorityList[iPrio] &&
104 !(line[idx] == '-' && (idx == 0 || strings.Index(specialCharacters, string(line[idx-1])) != -1)) { // - is not a substract sign if at the begining or after another sign
105 return &Node{string(line[idx]), createTree(line[:idx]), createTree(line[idx+1:])}
106 }
107
108 }
109 }
110 return nil
111}
112
113// checks if the expression in line is contained in one big parenthesis
114func isInOneParenthesis(line string) bool {
115 if line[0] != '(' || line[len(line)-1] != ')' {
116 return false
117 }
118 countParenthesis := 1
119 for i := 1; i < len(line)-1; i++ {
120 if line[i] == '(' {
121 countParenthesis += 1
122 }
123 if line[i] == ')' {
124 countParenthesis -= 1
125 }
126 if countParenthesis == 0 {
127 return false
128 }
129 }
130 return true
131}
132
133func createTree(line string) *Node {
134 if isInOneParenthesis(line) {
135 return createTree(line[1 : len(line)-1])
136 }
137 node := searchForPriority(lowPriority, line) // we put the lowest priority at the top of the tree, these operations will be executed last
138 if node != nil {
139 return node
140 }
141 node = searchForPriority(topPriority, line)
142 if node != nil {
143 return node
144 }
145
146 // if this code is reached, the only value possible in line is a number
147 return &Node{line, nil, nil}
148}
149
150func readTree(tree *Node) float64 {
151 operation, exists := operationMap[tree.value]
152
153 if exists { // check if the current node is an operator
154 return operation(readTree(tree.left), readTree(tree.right))
155 }
156
157 parsedValue, _ := strconv.ParseFloat(tree.value, 64)
158
159 return parsedValue
160}
161
162// expression is the equation you want to solve (p replaces the + symbol)
163// exemple: 2p4/2
164func ComputeResult(expression string) string {
165 valid, errString := evaluateValidity(expression)
166
167 if !valid { // If a basic error is encountered, return the expression without the = at the end and display the same expression
168 println(errString) // display error for debug
169 return expression
170 }
171 tree := createTree(expression)
172 val = readTree(tree)
173 displayVal = strconv.FormatFloat(val, 'g', 6, 64)
174 return displayVal
175}
176
177func removeLast(path string) string {
178 lenPath := len(path)
179 if lenPath > 0 {
180 path = path[:lenPath-1]
181 }
182 return path
183}
184
185func createTable(req *realmpath.Request, query url.Values, expression string) mdtable.Table {
186 line := make([]string, 0, 4)
187 query.Set("expression", "")
188 line = append(line, md.Link("res", req.String()))
189 for _, str := range []string{"(", ")"} {
190 query.Set("expression", expression+str)
191 line = append(line, md.Link(str, req.String()))
192 }
193 query.Set("expression", removeLast(expression))
194 line = append(line, md.Link("del", req.String())) // req and del are two special cases
195 table := mdtable.Table{
196 Headers: line,
197 }
198 line = []string{}
199 for _, c := range "789+456-123*0.=/" {
200 query.Set("expression", expression+string(c))
201 line = append(line, md.Link(string(c), req.String()))
202 if len(line) == 4 {
203 table.Append(line)
204 line = []string{}
205 }
206 }
207 return table
208}
209
210func Render(path string) string {
211 req := realmpath.Parse(path)
212 query := req.Query
213 expression := query.Get("expression")
214
215 if expression == "" {
216 displayVal = "0"
217 } else {
218 if expression[len(expression)-1] == '=' {
219 expression = ComputeResult(expression[:len(expression)-1])
220 } else {
221 displayVal = expression
222 }
223 }
224 out := md.H1("Calculator page")
225 out += md.H3("Have you ever wanted to do maths but never actually found a calculator ?")
226 out += md.H3("Do I have the realm for you...")
227 out += "---------------\n"
228 out += md.H2("Result: " + displayVal)
229
230 table := createTable(req, query, expression)
231 out += table.String()
232 return out
233}