code.gitea.io/gitea@v1.22.3/modules/templates/eval/eval.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package eval 5 6 import ( 7 "fmt" 8 "strconv" 9 "strings" 10 11 "code.gitea.io/gitea/modules/util" 12 ) 13 14 type Num struct { 15 Value any // int64 or float64, nil on error 16 } 17 18 var opPrecedence = map[string]int{ 19 // "(": 1, this is for low precedence like function calls, they are handled separately 20 "or": 2, 21 "and": 3, 22 "not": 4, 23 "==": 5, "!=": 5, "<": 5, "<=": 5, ">": 5, ">=": 5, 24 "+": 6, "-": 6, 25 "*": 7, "/": 7, 26 } 27 28 type stack[T any] struct { 29 name string 30 elems []T 31 } 32 33 func (s *stack[T]) push(t T) { 34 s.elems = append(s.elems, t) 35 } 36 37 func (s *stack[T]) pop() T { 38 if len(s.elems) == 0 { 39 panic(s.name + " stack is empty") 40 } 41 t := s.elems[len(s.elems)-1] 42 s.elems = s.elems[:len(s.elems)-1] 43 return t 44 } 45 46 func (s *stack[T]) peek() T { 47 if len(s.elems) == 0 { 48 panic(s.name + " stack is empty") 49 } 50 return s.elems[len(s.elems)-1] 51 } 52 53 type operator string 54 55 type eval struct { 56 stackNum stack[Num] 57 stackOp stack[operator] 58 funcMap map[string]func([]Num) Num 59 } 60 61 func newEval() *eval { 62 e := &eval{} 63 e.stackNum.name = "num" 64 e.stackOp.name = "op" 65 return e 66 } 67 68 func toNum(v any) (Num, error) { 69 switch v := v.(type) { 70 case string: 71 if strings.Contains(v, ".") { 72 n, err := strconv.ParseFloat(v, 64) 73 if err != nil { 74 return Num{n}, err 75 } 76 return Num{n}, nil 77 } 78 n, err := strconv.ParseInt(v, 10, 64) 79 if err != nil { 80 return Num{n}, err 81 } 82 return Num{n}, nil 83 case float32, float64: 84 n, _ := util.ToFloat64(v) 85 return Num{n}, nil 86 default: 87 n, err := util.ToInt64(v) 88 if err != nil { 89 return Num{n}, err 90 } 91 return Num{n}, nil 92 } 93 } 94 95 func truth(b bool) int64 { 96 if b { 97 return int64(1) 98 } 99 return int64(0) 100 } 101 102 func applyOp2Generic[T int64 | float64](op operator, n1, n2 T) Num { 103 switch op { 104 case "+": 105 return Num{n1 + n2} 106 case "-": 107 return Num{n1 - n2} 108 case "*": 109 return Num{n1 * n2} 110 case "/": 111 return Num{n1 / n2} 112 case "==": 113 return Num{truth(n1 == n2)} 114 case "!=": 115 return Num{truth(n1 != n2)} 116 case "<": 117 return Num{truth(n1 < n2)} 118 case "<=": 119 return Num{truth(n1 <= n2)} 120 case ">": 121 return Num{truth(n1 > n2)} 122 case ">=": 123 return Num{truth(n1 >= n2)} 124 case "and": 125 t1, _ := util.ToFloat64(n1) 126 t2, _ := util.ToFloat64(n2) 127 return Num{truth(t1 != 0 && t2 != 0)} 128 case "or": 129 t1, _ := util.ToFloat64(n1) 130 t2, _ := util.ToFloat64(n2) 131 return Num{truth(t1 != 0 || t2 != 0)} 132 } 133 panic("unknown operator: " + string(op)) 134 } 135 136 func applyOp2(op operator, n1, n2 Num) Num { 137 float := false 138 if _, ok := n1.Value.(float64); ok { 139 float = true 140 } else if _, ok = n2.Value.(float64); ok { 141 float = true 142 } 143 if float { 144 f1, _ := util.ToFloat64(n1.Value) 145 f2, _ := util.ToFloat64(n2.Value) 146 return applyOp2Generic(op, f1, f2) 147 } 148 return applyOp2Generic(op, n1.Value.(int64), n2.Value.(int64)) 149 } 150 151 func toOp(v any) (operator, error) { 152 if v, ok := v.(string); ok { 153 return operator(v), nil 154 } 155 return "", fmt.Errorf(`unsupported token type "%T"`, v) 156 } 157 158 func (op operator) hasOpenBracket() bool { 159 return strings.HasSuffix(string(op), "(") // it's used to support functions like "sum(" 160 } 161 162 func (op operator) isComma() bool { 163 return op == "," 164 } 165 166 func (op operator) isCloseBracket() bool { 167 return op == ")" 168 } 169 170 type ExprError struct { 171 msg string 172 tokens []any 173 err error 174 } 175 176 func (err ExprError) Error() string { 177 sb := strings.Builder{} 178 sb.WriteString(err.msg) 179 sb.WriteString(" [ ") 180 for _, token := range err.tokens { 181 _, _ = fmt.Fprintf(&sb, `"%v" `, token) 182 } 183 sb.WriteString("]") 184 if err.err != nil { 185 sb.WriteString(": ") 186 sb.WriteString(err.err.Error()) 187 } 188 return sb.String() 189 } 190 191 func (err ExprError) Unwrap() error { 192 return err.err 193 } 194 195 func (e *eval) applyOp() { 196 op := e.stackOp.pop() 197 if op == "not" { 198 num := e.stackNum.pop() 199 i, _ := util.ToInt64(num.Value) 200 e.stackNum.push(Num{truth(i == 0)}) 201 } else if op.hasOpenBracket() || op.isCloseBracket() || op.isComma() { 202 panic(fmt.Sprintf("incomplete sub-expression with operator %q", op)) 203 } else { 204 num2 := e.stackNum.pop() 205 num1 := e.stackNum.pop() 206 e.stackNum.push(applyOp2(op, num1, num2)) 207 } 208 } 209 210 func (e *eval) exec(tokens ...any) (ret Num, err error) { 211 defer func() { 212 if r := recover(); r != nil { 213 rErr, ok := r.(error) 214 if !ok { 215 rErr = fmt.Errorf("%v", r) 216 } 217 err = ExprError{"invalid expression", tokens, rErr} 218 } 219 }() 220 for _, token := range tokens { 221 n, err := toNum(token) 222 if err == nil { 223 e.stackNum.push(n) 224 continue 225 } 226 227 op, err := toOp(token) 228 if err != nil { 229 return Num{}, ExprError{"invalid expression", tokens, err} 230 } 231 232 switch { 233 case op.hasOpenBracket(): 234 e.stackOp.push(op) 235 case op.isCloseBracket(), op.isComma(): 236 var stackTopOp operator 237 for len(e.stackOp.elems) > 0 { 238 stackTopOp = e.stackOp.peek() 239 if stackTopOp.hasOpenBracket() || stackTopOp.isComma() { 240 break 241 } 242 e.applyOp() 243 } 244 if op.isCloseBracket() { 245 nums := []Num{e.stackNum.pop()} 246 for !e.stackOp.peek().hasOpenBracket() { 247 stackTopOp = e.stackOp.pop() 248 if !stackTopOp.isComma() { 249 return Num{}, ExprError{"bracket doesn't match", tokens, nil} 250 } 251 nums = append(nums, e.stackNum.pop()) 252 } 253 for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 { 254 nums[i], nums[j] = nums[j], nums[i] // reverse nums slice, to get the right order for arguments 255 } 256 stackTopOp = e.stackOp.pop() 257 fn := string(stackTopOp[:len(stackTopOp)-1]) 258 if fn == "" { 259 if len(nums) != 1 { 260 return Num{}, ExprError{"too many values in one bracket", tokens, nil} 261 } 262 e.stackNum.push(nums[0]) 263 } else if f, ok := e.funcMap[fn]; ok { 264 e.stackNum.push(f(nums)) 265 } else { 266 return Num{}, ExprError{"unknown function: " + fn, tokens, nil} 267 } 268 } else { 269 e.stackOp.push(op) 270 } 271 default: 272 for len(e.stackOp.elems) > 0 && len(e.stackNum.elems) > 0 { 273 stackTopOp := e.stackOp.peek() 274 if stackTopOp.hasOpenBracket() || stackTopOp.isComma() || precedence(stackTopOp, op) < 0 { 275 break 276 } 277 e.applyOp() 278 } 279 e.stackOp.push(op) 280 } 281 } 282 for len(e.stackOp.elems) > 0 && !e.stackOp.peek().isComma() { 283 e.applyOp() 284 } 285 if len(e.stackNum.elems) != 1 { 286 return Num{}, ExprError{fmt.Sprintf("expect 1 value as final result, but there are %d", len(e.stackNum.elems)), tokens, nil} 287 } 288 return e.stackNum.pop(), nil 289 } 290 291 func precedence(op1, op2 operator) int { 292 p1 := opPrecedence[string(op1)] 293 p2 := opPrecedence[string(op2)] 294 if p1 == 0 { 295 panic("unknown operator precedence: " + string(op1)) 296 } else if p2 == 0 { 297 panic("unknown operator precedence: " + string(op2)) 298 } 299 return p1 - p2 300 } 301 302 func castFloat64(nums []Num) bool { 303 hasFloat := false 304 for _, num := range nums { 305 if _, hasFloat = num.Value.(float64); hasFloat { 306 break 307 } 308 } 309 if hasFloat { 310 for i, num := range nums { 311 if _, ok := num.Value.(float64); !ok { 312 f, _ := util.ToFloat64(num.Value) 313 nums[i] = Num{f} 314 } 315 } 316 } 317 return hasFloat 318 } 319 320 func fnSum(nums []Num) Num { 321 if castFloat64(nums) { 322 var sum float64 323 for _, num := range nums { 324 sum += num.Value.(float64) 325 } 326 return Num{sum} 327 } 328 var sum int64 329 for _, num := range nums { 330 sum += num.Value.(int64) 331 } 332 return Num{sum} 333 } 334 335 // Expr evaluates the given expression tokens and returns the result. 336 // It supports the following operators: +, -, *, /, and, or, not, ==, !=, >, >=, <, <=. 337 // Non-zero values are treated as true, zero values are treated as false. 338 // If no error occurs, the result is either an int64 or a float64. 339 // If all numbers are integer, the result is an int64, otherwise if there is any float number, the result is a float64. 340 func Expr(tokens ...any) (Num, error) { 341 e := newEval() 342 e.funcMap = map[string]func([]Num) Num{"sum": fnSum} 343 return e.exec(tokens...) 344 }