github.com/kubeshop/testkube@v1.17.23/pkg/tcl/expressionstcl/parse_test.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 "strings" 15 "testing" 16 17 "github.com/stretchr/testify/assert" 18 ) 19 20 func TestCompileBasic(t *testing.T) { 21 assert.Equal(t, "value", must(MustCompile(`"value"`).Static().StringValue())) 22 } 23 24 func TestCompileTernary(t *testing.T) { 25 assert.Equal(t, "value", must(MustCompile(`true ? "value" : "another"`).Static().StringValue())) 26 assert.Equal(t, "another", must(MustCompile(`false ? "value" : "another"`).Static().StringValue())) 27 assert.Equal(t, "another", must(MustCompile(`5 == 3 ? "value" : "another"`).Static().StringValue())) 28 assert.Equal(t, "another", must(MustCompile(`5 == 3 && 2 == 4 ? "value" : "another"`).Static().StringValue())) 29 assert.Equal(t, "another", must(MustCompile(`5 == 3 || 2 == 4 ? "value" : "another"`).Static().StringValue())) 30 assert.Equal(t, "value", must(MustCompile(`3 == 3 || 2 == 4 ? "value" : "another"`).Static().StringValue())) 31 assert.Equal(t, "xyz", must(MustCompile(`false ? "value" : true ? "xyz" :"another"`).Static().StringValue())) 32 assert.Equal(t, "xyz", must(MustCompile(`false ? "value" : (true ? "xyz" :"another")`).Static().StringValue())) 33 assert.Equal(t, 5.78, must(MustCompile(`false ? 3 : (true ? 5.78 : 2)`).Static().FloatValue())) 34 } 35 36 func TestCompileMath(t *testing.T) { 37 assert.Equal(t, 5.0, must(MustCompile(`2 + 3`).Static().FloatValue())) 38 assert.Equal(t, 0.6, must(MustCompile(`3 / 5`).Static().FloatValue())) 39 assert.Equal(t, true, must(MustCompile(`3 <> 5`).Static().BoolValue())) 40 assert.Equal(t, true, must(MustCompile(`3 != 5`).Static().BoolValue())) 41 assert.Equal(t, false, must(MustCompile(`3 == 5`).Static().BoolValue())) 42 assert.Equal(t, false, must(MustCompile(`3 = 5`).Static().BoolValue())) 43 assert.Equal(t, `"3/5:"+(a+b)+"/"+5`, MustCompile(`3 + "/" + 5 + ":" + (a + b) + "/" + 5`).String()) 44 assert.Equal(t, `(a+"/")+b+":"+(a+1)+"/"+b`, MustCompile(`a + "/" + b + ":" + (a + 1) + "/" + b`).String()) 45 } 46 47 func TestCompileLogical(t *testing.T) { 48 assert.Equal(t, "true", MustCompile(`!(false && r1)`).String()) 49 assert.Equal(t, "false", MustCompile(`!true && r1`).String()) 50 assert.Equal(t, "r1", MustCompile(`true && r1`).String()) 51 assert.Equal(t, "r1", MustCompile(`!true || r1`).String()) 52 assert.Equal(t, "true", MustCompile(`true || r1`).String()) 53 assert.Equal(t, "11", MustCompile(`5 - -3 * 2`).String()) 54 assert.Equal(t, "r1&&false", MustCompile(`r1 && false`).String()) 55 assert.Equal(t, "bool(r1)", MustCompile(`bool(r1) && true`).String()) 56 assert.Equal(t, "false", MustCompile(`bool(r1) && false`).String()) 57 assert.Equal(t, "r1||false", MustCompile(`r1 || false`).String()) 58 assert.Equal(t, "bool(r1)", MustCompile(`bool(r1) || false`).String()) 59 assert.Equal(t, "r1||true", MustCompile(`r1 || true`).String()) 60 assert.Equal(t, "true", MustCompile(`bool(r1) || true`).String()) 61 } 62 63 func TestCompileMathOperationsPrecedence(t *testing.T) { 64 assert.Equal(t, 7.0, must(MustCompile(`1 + 2 * 3`).Static().FloatValue())) 65 assert.Equal(t, 11.0, must(MustCompile(`1 + (2 * 3) + 4`).Static().FloatValue())) 66 assert.Equal(t, 11.0, must(MustCompile(`1 + 2 * 3 + 4`).Static().FloatValue())) 67 assert.Equal(t, 30.0, must(MustCompile(`1 + 2 * 3 * 4 + 5`).Static().FloatValue())) 68 assert.Equal(t, true, must(MustCompile(`1 + 2 * 3 * 4 + 5 <> 3`).Static().BoolValue())) 69 70 assert.Equal(t, false, must(MustCompile(`1 + 2 * 3 * 4 + 5 == 3`).Static().BoolValue())) 71 assert.Equal(t, true, must(MustCompile(`1 + 2 * 3 * 4 + 5 = 30`).Static().BoolValue())) 72 assert.Equal(t, false, must(MustCompile(`1 + 2 * 3 * 4 + 5 <> 30`).Static().BoolValue())) 73 assert.Equal(t, false, must(MustCompile(`1 + 2 * 3 * 4 + 5 <> 20 + 10`).Static().BoolValue())) 74 assert.Equal(t, true, must(MustCompile(`1 + 2 * 3 * 4 + 5 = 20 + 10`).Static().BoolValue())) 75 assert.Equal(t, false, must(MustCompile(`1 + 2 * 3 * 4 + 5 <> 20 + 10`).Static().BoolValue())) 76 assert.Equal(t, true, must(MustCompile(`1 + 2 * 3 * 4 + 5 = 2 + 3 * 6 + 10`).Static().BoolValue())) 77 assert.Equal(t, false, must(MustCompile(`1 + 2 * 3 * 4 + 5 <> 2 + 3 * 6 + 10`).Static().BoolValue())) 78 assert.Equal(t, 8.0, must(MustCompile(`5 + 3 / 3 * 3`).Static().FloatValue())) 79 assert.Equal(t, true, must(MustCompile(`5 + 3 / 3 * 3 = 8`).Static().BoolValue())) 80 assert.Equal(t, 8.0, must(MustCompile(`5 + 3 * 3 / 3`).Static().FloatValue())) 81 assert.Equal(t, true, must(MustCompile(`5 + 3 * 3 / 3 = 8`).Static().BoolValue())) 82 assert.Equal(t, true, must(MustCompile(`5 + 3 * 3 / 3 = 2 + 3 * 2`).Static().BoolValue())) 83 assert.Equal(t, false, must(MustCompile(`5 + 3 * 3 / 3 = 3 + 3 * 2`).Static().BoolValue())) 84 85 assert.Equal(t, false, must(MustCompile(`true && false || false && true`).Static().BoolValue())) 86 assert.Equal(t, true, must(MustCompile(`true && false || true`).Static().BoolValue())) 87 assert.Equal(t, int64(0), must(MustCompile(`1 && 0 && 2`).Static().IntValue())) 88 assert.Equal(t, int64(2), must(MustCompile(`1 && 0 || 2`).Static().IntValue())) 89 assert.Equal(t, int64(1), must(MustCompile(`1 || 0 || 2`).Static().IntValue())) 90 91 assert.Equal(t, true, must(MustCompile(`10 > 2 && 5 <= 5`).Static().BoolValue())) 92 assert.Equal(t, false, must(MustCompile(`10 > 2 && 5 < 5`).Static().BoolValue())) 93 assert.Error(t, errOnly(Compile(`10 > 2 > 3`))) 94 95 assert.Equal(t, 817.0, must(MustCompile(`1 + 2 * 3 ** 4 * 5 + 6`).Static().FloatValue())) 96 assert.Equal(t, 4.5, must(MustCompile(`72 / 2 ** 4`).Static().FloatValue())) 97 assert.InDelta(t, 3.6, must(MustCompile(`3 * 5.2 % 4`).Static().FloatValue()), 0.00001) 98 99 assert.Equal(t, true, must(MustCompile(`!0 && 500`).Static().BoolValue())) 100 assert.Equal(t, false, must(MustCompile(`!5 && 500`).Static().BoolValue())) 101 102 assert.Equal(t, "(A+B*(C+D)/E*F)+G<>H**I*J**K", MustCompile(`A + B * (C + D) / E * F + G <> H ** I * J ** K`).String()) 103 } 104 105 func TestBuildTemplate(t *testing.T) { 106 assert.Equal(t, "abc", MustCompile(`"abc"`).Template()) 107 assert.Equal(t, "abcdef", MustCompile(`"abc" + "def"`).Template()) 108 assert.Equal(t, "abc9", MustCompile(`"abc" + 9`).Template()) 109 assert.Equal(t, "abc{{env.xyz}}", MustCompile(`"abc" + env.xyz`).Template()) 110 assert.Equal(t, "{{env.xyz}}abc", MustCompile(`env.xyz + "abc"`).Template()) 111 assert.Equal(t, "{{env.xyz+env.abc}}abc", MustCompile(`env.xyz + env.abc + "abc"`).Template()) 112 assert.Equal(t, "{{env.xyz+env.abc}}abc", MustCompile(`env.xyz + env.abc + "abc"`).Template()) 113 assert.Equal(t, "{{3+env.xyz+env.abc}}", MustCompile(`3 + env.xyz + env.abc`).Template()) 114 assert.Equal(t, "3{{env.xyz}}{{env.abc}}", MustCompile(`string(3) + env.xyz + env.abc`).Template()) 115 assert.Equal(t, "3{{env.xyz+env.abc}}", MustCompile(`string(3) + (env.xyz + env.abc)`).Template()) 116 assert.Equal(t, "3{{env.xyz}}{{env.abc}}", MustCompile(`"3" + env.xyz + env.abc`).Template()) 117 assert.Equal(t, "3{{env.xyz+env.abc}}", MustCompile(`"3" + (env.xyz + env.abc)`).Template()) 118 } 119 120 func TestCompileTemplate(t *testing.T) { 121 assert.Equal(t, `""`, MustCompileTemplate(``).String()) 122 assert.Equal(t, `"abc"`, MustCompileTemplate(`abc`).String()) 123 assert.Equal(t, `"abcxyz5"`, MustCompileTemplate(`abc{{ "xyz" }}{{ 5 }}`).String()) 124 assert.Equal(t, `"abc50"`, MustCompileTemplate(`abc{{ 5 + 45 }}`).String()) 125 assert.Equal(t, `"abc50def"`, MustCompileTemplate(`abc{{ 5 + 45 }}def`).String()) 126 assert.Equal(t, `"abc50def"+string(env.abc*5)+"20"`, MustCompileTemplate(`abc{{ 5 + 45 }}def{{env.abc * 5}}20`).String()) 127 128 assert.Equal(t, `abc50def`, must(MustCompileTemplate(`abc{{ 5 + 45 }}def`).Static().StringValue())) 129 } 130 131 func TestCompilePartialResolution(t *testing.T) { 132 vm := NewMachine(). 133 Register("someint", 555). 134 Register("somestring", "foo"). 135 RegisterAccessor(func(name string) (interface{}, bool) { 136 if strings.HasPrefix(name, "env.") { 137 return "[placeholder:" + name[4:] + "]", true 138 } 139 return nil, false 140 }). 141 RegisterAccessor(func(name string) (interface{}, bool) { 142 if strings.HasPrefix(name, "secrets.") { 143 return MustCompile("secret(" + name[8:] + ")"), true 144 } 145 return nil, false 146 }). 147 RegisterFunction("mainEndpoint", func(values ...StaticValue) (interface{}, bool, error) { 148 if len(values) != 0 { 149 return nil, true, errors.New("the mainEndpoint should have no parameters") 150 } 151 return MustCompile(`env.apiUrl`), true, nil 152 }) 153 154 assert.Equal(t, `555`, must(MustCompile(`someint`).Resolve(vm)).String()) 155 assert.Equal(t, `"[placeholder:name]"`, must(MustCompile(`env.name`).Resolve(vm)).String()) 156 assert.Equal(t, `secret(name)`, must(MustCompile(`secrets.name`).Resolve(vm)).String()) 157 assert.Equal(t, `"[placeholder:apiUrl]"`, must(MustCompile(`mainEndpoint()`).Resolve(vm)).String()) 158 } 159 160 func TestCompileResolution(t *testing.T) { 161 vm := NewMachine(). 162 Register("someint", 555). 163 Register("somestring", "foo"). 164 RegisterAccessor(func(name string) (interface{}, bool) { 165 if strings.HasPrefix(name, "env.") { 166 return "[placeholder:" + name[4:] + "]", true 167 } 168 return nil, false 169 }). 170 RegisterAccessor(func(name string) (interface{}, bool) { 171 if strings.HasPrefix(name, "secrets.") { 172 return MustCompile("secret(" + name[8:] + ")"), true 173 } 174 return nil, false 175 }). 176 RegisterFunction("mainEndpoint", func(values ...StaticValue) (interface{}, bool, error) { 177 if len(values) != 0 { 178 return nil, true, errors.New("the mainEndpoint should have no parameters") 179 } 180 return MustCompile(`env.apiUrl`), true, nil 181 }) 182 183 assert.Equal(t, `555`, must(MustCompile(`someint`).Resolve(vm, FinalizerFail)).String()) 184 assert.Equal(t, `"[placeholder:name]"`, must(MustCompile(`env.name`).Resolve(vm, FinalizerFail)).String()) 185 assert.Error(t, errOnly(MustCompile(`secrets.name`).Resolve(vm, FinalizerFail))) 186 assert.Equal(t, `"[placeholder:apiUrl]"`, must(MustCompile(`mainEndpoint()`).Resolve(vm, FinalizerFail)).String()) 187 } 188 189 func TestCircularResolution(t *testing.T) { 190 vm := NewMachine(). 191 RegisterFunction("one", func(values ...StaticValue) (interface{}, bool, error) { 192 return MustCompile("two()"), true, nil 193 }). 194 RegisterFunction("two", func(values ...StaticValue) (interface{}, bool, error) { 195 return MustCompile("one()"), true, nil 196 }). 197 RegisterFunction("self", func(values ...StaticValue) (interface{}, bool, error) { 198 return MustCompile("self()"), true, nil 199 }) 200 201 assert.Contains(t, fmt.Sprintf("%v", errOnly(MustCompile(`one()`).Resolve(vm, FinalizerFail))), "call stack exceeded") 202 assert.Contains(t, fmt.Sprintf("%v", errOnly(MustCompile(`self()`).Resolve(vm, FinalizerFail))), "call stack exceeded") 203 } 204 205 func TestMinusNumber(t *testing.T) { 206 assert.Equal(t, -4.0, must(MustCompile("-4").Static().FloatValue())) 207 } 208 209 func TestCompileMultilineString(t *testing.T) { 210 assert.Equal(t, `"\nabc\ndef\n"`, MustCompile(`" 211 abc 212 def 213 "`).String()) 214 } 215 216 func TestCompileEscapeTemplate(t *testing.T) { 217 assert.Equal(t, `foo{{"{{"}}barbaz{{"{{"}}`, MustCompileTemplate(`foo{{"{{bar"}}baz{{"{{"}}`).Template()) 218 } 219 220 func TestCompileStandardLib(t *testing.T) { 221 assert.Equal(t, `false`, MustCompile(`bool(0)`).String()) 222 assert.Equal(t, `true`, MustCompile(`bool(500)`).String()) 223 assert.Equal(t, `"500"`, MustCompile(`string(500)`).String()) 224 assert.Equal(t, `500`, MustCompile(`int(500)`).String()) 225 assert.Equal(t, `500`, MustCompile(`int(500.888)`).String()) 226 assert.Equal(t, `500`, MustCompile(`int("500")`).String()) 227 assert.Equal(t, `500.44`, MustCompile(`float("500.44")`).String()) 228 assert.Equal(t, `500`, MustCompile(`json("500")`).String()) 229 assert.Equal(t, `{"a":500}`, MustCompile(`json("{\"a\": 500}")`).String()) 230 assert.Equal(t, `"{\"a\":500}"`, MustCompile(`tojson({"a": 500})`).String()) 231 assert.Equal(t, `"500.8"`, MustCompile(`tojson(500.8)`).String()) 232 assert.Equal(t, `"\"500.8\""`, MustCompile(`tojson("500.8")`).String()) 233 assert.Equal(t, `"abc"`, MustCompile(`shellquote("abc")`).String()) 234 assert.Equal(t, `"'a b c'"`, MustCompile(`shellquote("a b c")`).String()) 235 assert.Equal(t, `"'a b c' 'd e f'"`, MustCompile(`shellquote("a b c", "d e f")`).String()) 236 assert.Equal(t, `"''"`, MustCompile(`shellquote(null)`).String()) 237 assert.Equal(t, `["a","b","c","a b c"]`, MustCompile(`shellparse("a b c 'a b c'")`).String()) 238 assert.Equal(t, `"abc d"`, MustCompile(`trim(" abc d \n ")`).String()) 239 assert.Equal(t, `"abc"`, MustCompile(`yaml("\"abc\"")`).String()) 240 assert.Equal(t, `{"foo":{"bar":"baz"}}`, MustCompile(`yaml("foo:\n bar: 'baz'")`).String()) 241 assert.Equal(t, `"foo:\n bar: baz\n"`, MustCompile(`toyaml({"foo":{"bar":"baz"}})`).String()) 242 assert.Equal(t, `{"a":["b","v"]}`, MustCompile(`yaml(" 243 a: 244 - b 245 - v 246 ")`).String()) 247 assert.Equal(t, `["a",10,["a",4]]`, MustCompile(`list("a", 10, ["a", 4])`).String()) 248 assert.Equal(t, `"a,10,a,4"`, MustCompile(`join(["a",10,["a",4]])`).String()) 249 assert.Equal(t, `"a---10---a,4"`, MustCompile(`join(["a",10,["a",4]], "---")`).String()) 250 assert.Equal(t, `[""]`, MustCompile(`split(null)`).String()) 251 assert.Equal(t, `["a","b","c"]`, MustCompile(`split("a,b,c")`).String()) 252 assert.Equal(t, `["a","b","c"]`, MustCompile(`split("a---b---c", "---")`).String()) 253 assert.Equal(t, `5`, MustCompile(`len("abcde")`).String()) 254 assert.Equal(t, `2`, MustCompile(`len(["a", "b"])`).String()) 255 assert.Equal(t, `2`, MustCompile(`len({"a": "b", "b": "c"})`).String()) 256 assert.Equal(t, `2`, MustCompile(`floor(2.6)`).String()) 257 assert.Equal(t, `2`, MustCompile(`ceil(1.6)`).String()) 258 assert.Equal(t, `2`, MustCompile(`round(1.6)`).String()) 259 assert.Equal(t, `2`, MustCompile(`round(1.5)`).String()) 260 assert.Equal(t, `1`, MustCompile(`round(1.4)`).String()) 261 assert.Equal(t, `[[1,2],[3,4],[5]]`, MustCompile(`chunk([1,2,3,4,5], 2)`).String()) 262 assert.Equal(t, `[2,4,6,8,10]`, MustCompile(`map([1,2,3,4,5], "_.value * 2")`).String()) 263 assert.Equal(t, `[0,2,4,6,8]`, MustCompile(`map([10,20,30,40,50], "_.index * 2")`).String()) 264 assert.Equal(t, `[2,4,6,8,10]`, MustCompile(`map([1,2,3,4,5], "_.value * 2")`).String()) 265 assert.Equal(t, `[0,2,4,6,8]`, MustCompile(`map([10,20,30,40,50], "_.index * 2")`).String()) 266 assert.Equal(t, `[3,4,5]`, MustCompile(`filter([1,2,3,4,5], "_.value > 2")`).String()) 267 assert.Equal(t, `[5]`, MustCompile(`jq([1,2,3,4,5], ". | max")`).String()) 268 assert.Equal(t, `[{"b":{"v":2}}]`, MustCompile(`jq([{"a":{"v": 1}},{"b":{"v": 2}}], ". | max_by(.v)")`).String()) 269 assert.Equal(t, `[[3,4,5]]`, MustCompile(`jq([1,2,3,4,5], "map(select(. > 2))")`).String()) 270 assert.Equal(t, `5`, MustCompile(`at([1,2,3,4,5], 4)`).String()) 271 assert.Equal(t, `"value"`, MustCompile(`at({"x": "value"}, "x")`).String()) 272 assert.Equal(t, `null`, MustCompile(`at({"x": "value"}, "unknown-key")`).String()) 273 assert.Equal(t, `"abc"`, MustCompile(`eval("\"abc\"")`).String()) 274 assert.Equal(t, `50`, MustCompile(`eval("5 * 10")`).String()) 275 assert.Equal(t, `50*something`, MustCompile(`eval("5 * 10 * something")`).String()) 276 } 277 278 func TestCompileWildcard_Unknown(t *testing.T) { 279 assert.Equal(t, `map(a.b.c,"_.value.d.e")`, MustCompile("a.b.c.*.d.e").String()) 280 assert.Equal(t, `map(map(a.b.c,"_.value"),"_.value.d.e")`, MustCompile("a.b.c.*.*.d.e").String()) 281 } 282 283 func TestCompileSpread(t *testing.T) { 284 assert.Equal(t, `"a b c 'a b c'"`, MustCompile(`shellquote(["a", "b", "c", "a b c"]...)`).String()) 285 assert.Equal(t, `"a b c 'a b c'"`, MustCompile("shellquote(shellparse(\"a b c\n'a b c'\")...)").String()) 286 assert.Equal(t, `"axb"`, MustCompile(`join([["a", "b"], "x"]...)`).String()) 287 } 288 289 func TestCompileWildcard_Map(t *testing.T) { 290 vm := NewMachine().Register("a.b.c", []map[string]interface{}{ 291 {"d": map[string]string{"e": "v1"}}, 292 {"d": map[string]string{"e": "v2"}}, 293 }) 294 assert.Equal(t, `["v1","v2"]`, must(MustCompile("a.b.c.*.d.e").Resolve(vm)).String()) 295 } 296 297 func TestCompileWildcard_Struct(t *testing.T) { 298 type S1 struct { 299 Else string `json:"e"` 300 } 301 type S2 struct { 302 Something S1 `json:"d"` 303 } 304 vm := NewMachine().Register("a.b.c", []S2{ 305 {Something: S1{Else: "v1"}}, 306 {Something: S1{Else: "v2"}}, 307 }) 308 assert.Equal(t, `["v1","v2"]`, must(MustCompile("a.b.c.*.d.e").Resolve(vm)).String()) 309 } 310 311 func TestCompileWildcard_Inner(t *testing.T) { 312 type S1 struct { 313 Else string `json:"e"` 314 } 315 type S2 struct { 316 Something S1 `json:"d"` 317 } 318 vm := NewMachine().Register("a.b", map[string]interface{}{ 319 "c": []S2{ 320 {Something: S1{Else: "v1"}}, 321 {Something: S1{Else: "v2"}}, 322 }, 323 }) 324 assert.Equal(t, `["v1","v2"]`, must(MustCompile("a.b.c.*.d.e").Resolve(vm)).String()) 325 } 326 327 func TestCompileInnerPath(t *testing.T) { 328 assert.Equal(t, `"v1"`, MustCompile(`["v1", "v2"].0`).String()) 329 assert.Equal(t, `"v1abc"`, must(MustCompile(`map(["v1", "v2"], "_.value + \"abc\"").0`).Resolve()).String()) 330 assert.Equal(t, `"v"`, must(MustCompile(`{"k": "v", "k2":"v2"}.k`).Resolve()).String()) 331 assert.Equal(t, `"v"`, must(MustCompile(`{"k": {"a": "v"}, "k2":"v2"}.k.a`).Resolve()).String()) 332 } 333 334 func TestCompileDetectAccessors(t *testing.T) { 335 assert.Equal(t, map[string]struct{}{"something": {}}, MustCompile(`something`).Accessors()) 336 assert.Equal(t, map[string]struct{}{"something": {}, "other": {}, "another": {}}, MustCompile(`calling(something, 5 * (other + 3), !another)`).Accessors()) 337 } 338 339 func TestCompileDetectFunctions(t *testing.T) { 340 assert.Equal(t, map[string]struct{}(nil), MustCompile(`something`).Functions()) 341 assert.Equal(t, map[string]struct{}{"calling": {}, "something": {}, "string": {}, "a": {}}, MustCompile(`calling(something(), 45 + 2 + 10 + string(abc * a(c)))`).Functions()) 342 } 343 344 func TestCompileImmutableNone(t *testing.T) { 345 assert.Same(t, None, NewValue(noneValue)) 346 assert.Same(t, NewValue(noneValue), NewValue(noneValue)) 347 }