github.com/kubeshop/testkube@v1.17.23/pkg/tcl/expressionstcl/parse.go (about) 1 // Copyright 2024 Testkube. 2 // 3 // Licensed as a Testkube Pro file under the Testkube Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt 8 9 package expressionstcl 10 11 import ( 12 "errors" 13 "fmt" 14 math2 "math" 15 "regexp" 16 "strings" 17 ) 18 19 func parseNextExpression(t []token, priority int) (e Expression, i int, err error) { 20 e, i, err = getNextSegment(t) 21 if err != nil { 22 return 23 } 24 25 for { 26 // End of the expression 27 if len(t) == i { 28 return e, i, nil 29 } 30 31 switch t[i].Type { 32 case tokenTypeTernary: 33 if priority >= 0 { 34 return e, i, nil 35 } 36 i += 1 37 te, ti, terr := parseNextExpression(t[i:], 0) 38 i += ti 39 if terr != nil { 40 return nil, i, terr 41 } 42 if len(t) == i { 43 return nil, i, fmt.Errorf("premature end of expression: expected ternary separator") 44 } 45 if t[i].Type != tokenTypeTernarySeparator { 46 return nil, i, fmt.Errorf("expression syntax error: expected ternary separator: found %v", t[i]) 47 } 48 i++ 49 fe, fi, ferr := parseNextExpression(t[i:], 0) 50 i += fi 51 if ferr != nil { 52 return nil, i, ferr 53 } 54 e = newConditional(e, te, fe) 55 case tokenTypeMath: 56 op := operator(t[i].Value.(string)) 57 nextPriority := getOperatorPriority(op) 58 if priority >= nextPriority { 59 return e, i, nil 60 } 61 i += 1 62 ne, ni, nerr := parseNextExpression(t[i:], nextPriority) 63 i += ni 64 if nerr != nil { 65 return nil, i, nerr 66 } 67 e = newMath(op, e, ne) 68 case tokenTypePropertyAccessor: 69 e = newPropertyAccessor(e, t[i].Value.(string)) 70 i += 1 71 default: 72 return e, i, err 73 } 74 } 75 } 76 77 func getNextSegment(t []token) (e Expression, i int, err error) { 78 if len(t) == 0 { 79 return nil, 0, errors.New("premature end of expression") 80 } 81 82 // Parentheses - (a(b) + c) 83 if t[0].Type == tokenTypeOpen { 84 e, i, err = parseNextExpression(t[1:], -1) 85 i++ 86 if err != nil { 87 return nil, i, err 88 } 89 if len(t) <= i || t[i].Type != tokenTypeClose { 90 return nil, i, fmt.Errorf("syntax error: expected parentheses close") 91 } 92 return e, i + 1, err 93 } 94 95 // Static value - "abc", 444, {"a": 10}, true, [45, 3] 96 if t[0].Type == tokenTypeJson { 97 return NewValue(t[0].Value), 1, nil 98 } 99 100 // Negation - !expr 101 if t[0].Type == tokenTypeNot { 102 e, i, err = parseNextExpression(t[1:], math2.MaxInt) 103 if err != nil { 104 return nil, 0, err 105 } 106 return newNegative(e), i + 1, nil 107 } 108 109 // Negative numbers - -5 110 if t[0].Type == tokenTypeMath && operator(t[0].Value.(string)) == operatorSubtract { 111 e, i, err = parseNextExpression(t[1:], -1) 112 if err != nil { 113 return nil, 0, err 114 } 115 return newMath(operatorSubtract, NewValue(0), e), i + 1, nil 116 } 117 118 // Call - abc(a, b, c) 119 if t[0].Type == tokenTypeAccessor && len(t) > 1 && t[1].Type == tokenTypeOpen { 120 args := make([]callArgument, 0) 121 index := 2 122 for { 123 // Ensure there is another token (for call close or next argument) 124 if len(t) <= index { 125 return nil, 2, errors.New("premature end of expression: missing call close") 126 } 127 128 // Close the call 129 if t[index].Type == tokenTypeClose { 130 break 131 } 132 133 // Ensure comma between arguments 134 if len(args) != 0 { 135 if t[index].Type != tokenTypeComma { 136 return nil, 2, errors.New("expression syntax error: expected comma or call close") 137 } 138 index++ 139 } 140 next, l, err := parseNextExpression(t[index:], -1) 141 index += l 142 if err != nil { 143 return nil, index, err 144 } 145 if len(t) > index && t[index].Type == tokenTypeSpread { 146 args = append(args, callArgument{expr: next, spread: true}) 147 index++ 148 } else { 149 args = append(args, callArgument{expr: next}) 150 } 151 } 152 return newCall(t[0].Value.(string), args), index + 1, nil 153 } 154 155 // Accessor - abc 156 if t[0].Type == tokenTypeAccessor { 157 return newAccessor(t[0].Value.(string)), 1, nil 158 } 159 160 return nil, 0, fmt.Errorf("unexpected token in expression: %v", t) 161 } 162 163 func parse(t []token) (e Expression, err error) { 164 if len(t) == 0 { 165 return None, nil 166 } 167 e, l, err := parseNextExpression(t, -1) 168 if err != nil { 169 return nil, err 170 } 171 if l < len(t) { 172 return nil, fmt.Errorf("unexpected token after end of expression: %v", t[l]) 173 } 174 return e, nil 175 } 176 177 func Compile(exp string) (Expression, error) { 178 t, _, e := tokenize(exp, 0) 179 if e != nil { 180 return nil, fmt.Errorf("tokenizer error: %v", e) 181 } 182 v, e := parse(t) 183 if e != nil { 184 return nil, fmt.Errorf("parser error: %v", e) 185 } 186 return v.Resolve() 187 } 188 189 func MustCompile(exp string) Expression { 190 v, err := Compile(exp) 191 if err != nil { 192 panic(err) 193 } 194 return v 195 } 196 197 var endExprRe = regexp.MustCompile(`^\s*}}`) 198 199 func CompileTemplate(tpl string) (Expression, error) { 200 var e Expression 201 202 offset := 0 203 for index := strings.Index(tpl[offset:], "{{"); index != -1; index = strings.Index(tpl[offset:], "{{") { 204 if index != 0 { 205 e = newMath(operatorAdd, e, NewStringValue(tpl[offset:offset+index])) 206 } 207 offset += index + 2 208 tokens, i, err := tokenize(tpl, offset) 209 offset = i 210 if err == nil { 211 return nil, errors.New("template error: expression not closed") 212 } 213 if !endExprRe.MatchString(tpl[offset:]) || !strings.Contains(err.Error(), "unknown character") { 214 return nil, fmt.Errorf("tokenizer error: %v", err) 215 } 216 offset += len(endExprRe.FindString(tpl[offset:])) 217 if len(tokens) == 0 { 218 continue 219 } 220 v, err := parse(tokens) 221 if err != nil { 222 return nil, fmt.Errorf("parser error: %v", e) 223 } 224 v, err = v.Resolve() 225 if err != nil { 226 return nil, fmt.Errorf("expression error: %v", e) 227 } 228 e = newMath(operatorAdd, e, CastToString(v)) 229 } 230 if offset < len(tpl) { 231 e = newMath(operatorAdd, e, NewStringValue(tpl[offset:])) 232 } 233 if e == nil { 234 return NewStringValue(""), nil 235 } 236 return e.Resolve() 237 } 238 239 func MustCompileTemplate(tpl string) Expression { 240 v, err := CompileTemplate(tpl) 241 if err != nil { 242 panic(err) 243 } 244 return v 245 } 246 247 func CompileAndResolve(exp string, m ...Machine) (Expression, error) { 248 e, err := Compile(exp) 249 if err != nil { 250 return e, err 251 } 252 return e.Resolve(m...) 253 } 254 255 func CompileAndResolveTemplate(tpl string, m ...Machine) (Expression, error) { 256 e, err := CompileTemplate(tpl) 257 if err != nil { 258 return e, err 259 } 260 return e.Resolve(m...) 261 } 262 263 func IsTemplateStringWithoutExpressions(tpl string) bool { 264 return !strings.Contains(tpl, "{{") 265 }