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  }