mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-20 03:46:09 +08:00
277 lines
7.4 KiB
Go
277 lines
7.4 KiB
Go
package v2
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
exprparser "gitea.com/gitea/act_runner/internal/expr"
|
|
)
|
|
|
|
// EvaluationContext holds variables that can be referenced in expressions.
|
|
type EvaluationContext struct {
|
|
Variables ReadOnlyObject[any]
|
|
Functions ReadOnlyObject[Function]
|
|
}
|
|
|
|
func NewEvaluationContext() *EvaluationContext {
|
|
return &EvaluationContext{}
|
|
}
|
|
|
|
type Function interface {
|
|
Evaluate(eval *Evaluator, args []exprparser.Node) (*EvaluationResult, error)
|
|
}
|
|
|
|
// Evaluator evaluates workflow expressions using the lexer and parser from workflow.
|
|
type Evaluator struct {
|
|
ctx *EvaluationContext
|
|
}
|
|
|
|
// NewEvaluator creates an Evaluator with the supplied context.
|
|
func NewEvaluator(ctx *EvaluationContext) *Evaluator {
|
|
return &Evaluator{ctx: ctx}
|
|
}
|
|
|
|
func (e *Evaluator) Context() *EvaluationContext {
|
|
return e.ctx
|
|
}
|
|
|
|
func (e *Evaluator) Evaluate(root exprparser.Node) (*EvaluationResult, error) {
|
|
result, err := e.evalNode(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// EvaluateBoolean parses and evaluates the expression, returning a boolean result.
|
|
func (e *Evaluator) EvaluateBoolean(expr string) (bool, error) {
|
|
root, err := exprparser.Parse(expr)
|
|
if err != nil {
|
|
return false, fmt.Errorf("parse error: %w", err)
|
|
}
|
|
result, err := e.evalNode(root)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return result.IsTruthy(), nil
|
|
}
|
|
|
|
func (e *Evaluator) ToRaw(result *EvaluationResult) (any, error) {
|
|
if col, ok := result.TryGetCollectionInterface(); ok {
|
|
switch node := col.(type) {
|
|
case ReadOnlyObject[any]:
|
|
rawMap := map[string]any{}
|
|
for k, v := range node.GetEnumerator() {
|
|
rawRes, err := e.ToRaw(CreateIntermediateResult(e.Context(), v))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rawMap[k] = rawRes
|
|
}
|
|
return rawMap, nil
|
|
case ReadOnlyArray[any]:
|
|
rawArray := []any{}
|
|
for _, v := range node.GetEnumerator() {
|
|
rawRes, err := e.ToRaw(CreateIntermediateResult(e.Context(), v))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rawArray = append(rawArray, rawRes)
|
|
}
|
|
return rawArray, nil
|
|
}
|
|
}
|
|
return result.Value(), nil
|
|
}
|
|
|
|
// Evaluate parses and evaluates the expression, returning a boolean result.
|
|
func (e *Evaluator) EvaluateRaw(expr string) (any, error) {
|
|
root, err := exprparser.Parse(expr)
|
|
if err != nil {
|
|
return false, fmt.Errorf("parse error: %w", err)
|
|
}
|
|
result, err := e.evalNode(root)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return e.ToRaw(result)
|
|
}
|
|
|
|
type FilteredArray []any
|
|
|
|
func (a FilteredArray) GetAt(i int64) any {
|
|
if int(i) > len(a) {
|
|
return nil
|
|
}
|
|
return a[i]
|
|
}
|
|
|
|
func (a FilteredArray) GetEnumerator() []any {
|
|
return a
|
|
}
|
|
|
|
// evalNode recursively evaluates a parser node and returns an EvaluationResult.
|
|
func (e *Evaluator) evalNode(n exprparser.Node) (*EvaluationResult, error) {
|
|
switch node := n.(type) {
|
|
case *exprparser.ValueNode:
|
|
return e.evalValueNode(node)
|
|
case *exprparser.FunctionNode:
|
|
return e.evalFunctionNode(node)
|
|
case *exprparser.BinaryNode:
|
|
return e.evalBinaryNode(node)
|
|
case *exprparser.UnaryNode:
|
|
return e.evalUnaryNode(node)
|
|
}
|
|
return nil, errors.New("unknown node type")
|
|
}
|
|
|
|
func (e *Evaluator) evalValueNode(node *exprparser.ValueNode) (*EvaluationResult, error) {
|
|
if node.Kind == exprparser.TokenKindNamedValue {
|
|
if e.ctx != nil {
|
|
val := e.ctx.Variables.Get(node.Value.(string))
|
|
if val == nil {
|
|
return nil, fmt.Errorf("undefined variable %s", node.Value)
|
|
}
|
|
return CreateIntermediateResult(e.Context(), val), nil
|
|
}
|
|
return nil, errors.New("no evaluation context")
|
|
}
|
|
return CreateIntermediateResult(e.Context(), node.Value), nil
|
|
}
|
|
|
|
func (e *Evaluator) evalFunctionNode(node *exprparser.FunctionNode) (*EvaluationResult, error) {
|
|
fn := e.ctx.Functions.Get(node.Name)
|
|
if fn == nil {
|
|
return nil, fmt.Errorf("unknown function %v", node.Name)
|
|
}
|
|
return fn.Evaluate(e, node.Args)
|
|
}
|
|
|
|
func (e *Evaluator) evalBinaryNode(node *exprparser.BinaryNode) (*EvaluationResult, error) {
|
|
left, err := e.evalNode(node.Left)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res, err := e.evalBinaryNodeLeft(node, left); res != nil || err != nil {
|
|
return res, err
|
|
}
|
|
right, err := e.evalNode(node.Right)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return e.evalBinaryNodeRight(node, left, right)
|
|
}
|
|
|
|
func (e *Evaluator) evalBinaryNodeLeft(node *exprparser.BinaryNode, left *EvaluationResult) (*EvaluationResult, error) {
|
|
switch node.Op {
|
|
case "&&":
|
|
if left.IsFalsy() {
|
|
return left, nil
|
|
}
|
|
case "||":
|
|
if left.IsTruthy() {
|
|
return left, nil
|
|
}
|
|
case ".":
|
|
if v, ok := node.Right.(*exprparser.ValueNode); ok && v.Kind == exprparser.TokenKindWildcard {
|
|
var ret FilteredArray
|
|
if col, ok := left.TryGetCollectionInterface(); ok {
|
|
if farray, ok := col.(FilteredArray); ok {
|
|
for _, subcol := range farray.GetEnumerator() {
|
|
ret = processStar(CreateIntermediateResult(e.Context(), subcol).Value(), ret)
|
|
}
|
|
} else {
|
|
ret = processStar(col, ret)
|
|
}
|
|
}
|
|
return CreateIntermediateResult(e.Context(), ret), nil
|
|
}
|
|
}
|
|
return nil, errors.ErrUnsupported
|
|
}
|
|
|
|
func (e *Evaluator) evalBinaryNodeRight(node *exprparser.BinaryNode, left *EvaluationResult, right *EvaluationResult) (*EvaluationResult, error) {
|
|
switch node.Op {
|
|
case "&&":
|
|
return right, nil
|
|
case "||":
|
|
return right, nil
|
|
case "==":
|
|
// Use abstract equality per spec
|
|
return CreateIntermediateResult(e.Context(), left.AbstractEqual(right)), nil
|
|
case "!=":
|
|
return CreateIntermediateResult(e.Context(), left.AbstractNotEqual(right)), nil
|
|
case ">":
|
|
return CreateIntermediateResult(e.Context(), left.AbstractGreaterThan(right)), nil
|
|
case "<":
|
|
return CreateIntermediateResult(e.Context(), left.AbstractLessThan(right)), nil
|
|
case ">=":
|
|
return CreateIntermediateResult(e.Context(), left.AbstractGreaterThanOrEqual(right)), nil
|
|
case "<=":
|
|
return CreateIntermediateResult(e.Context(), left.AbstractLessThanOrEqual(right)), nil
|
|
case ".", "[":
|
|
if farray, ok := left.Value().(FilteredArray); ok {
|
|
var ret FilteredArray
|
|
for _, subcol := range farray.GetEnumerator() {
|
|
res := processIndex(CreateIntermediateResult(e.Context(), subcol).Value(), right)
|
|
if res != nil {
|
|
ret = append(ret, res)
|
|
}
|
|
}
|
|
if ret == nil {
|
|
return CreateIntermediateResult(e.Context(), nil), nil
|
|
}
|
|
return CreateIntermediateResult(e.Context(), ret), nil
|
|
}
|
|
col, _ := left.TryGetCollectionInterface()
|
|
result := processIndex(col, right)
|
|
return CreateIntermediateResult(e.Context(), result), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported operator %s", node.Op)
|
|
}
|
|
}
|
|
|
|
func (e *Evaluator) evalUnaryNode(node *exprparser.UnaryNode) (*EvaluationResult, error) {
|
|
operand, err := e.evalNode(node.Operand)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch node.Op {
|
|
case "!":
|
|
return CreateIntermediateResult(e.Context(), !operand.IsTruthy()), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported unary operator %s", node.Op)
|
|
}
|
|
}
|
|
|
|
func processIndex(col any, right *EvaluationResult) any {
|
|
if mapVal, ok := col.(ReadOnlyObject[any]); ok {
|
|
key, ok := right.Value().(string)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
val := mapVal.Get(key)
|
|
return val
|
|
}
|
|
if arrayVal, ok := col.(ReadOnlyArray[any]); ok {
|
|
key, ok := right.Value().(float64)
|
|
if !ok || key < 0 {
|
|
return nil
|
|
}
|
|
val := arrayVal.GetAt(int64(key))
|
|
return val
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func processStar(subcol any, ret FilteredArray) FilteredArray {
|
|
if array, ok := subcol.(ReadOnlyArray[any]); ok {
|
|
ret = append(ret, array.GetEnumerator()...)
|
|
} else if obj, ok := subcol.(ReadOnlyObject[any]); ok {
|
|
for _, v := range obj.GetEnumerator() {
|
|
ret = append(ret, v)
|
|
}
|
|
}
|
|
return ret
|
|
}
|