Marcel van Lohuizen | 17157ea | 2018-12-11 10:41:10 +0100 | [diff] [blame] | 1 | // Copyright 2018 The CUE Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package cue |
| 16 | |
| 17 | import ( |
| 18 | "flag" |
| 19 | "fmt" |
| 20 | "testing" |
| 21 | |
| 22 | "cuelang.org/go/cue/errors" |
| 23 | "cuelang.org/go/cue/parser" |
| 24 | "cuelang.org/go/cue/token" |
| 25 | ) |
| 26 | |
| 27 | var traceOn = flag.Bool("debug", false, "enable tracing") |
| 28 | |
| 29 | func compileFileWithErrors(t *testing.T, body string) (*context, *structLit, errors.List) { |
| 30 | t.Helper() |
| 31 | ctx, inst, errs := compileInstance(t, body) |
| 32 | return ctx, inst.rootValue.evalPartial(ctx).(*structLit), errs |
| 33 | } |
| 34 | |
| 35 | func compileFile(t *testing.T, body string) (*context, *structLit) { |
| 36 | t.Helper() |
| 37 | ctx, inst, errs := compileInstance(t, body) |
| 38 | if errs != nil { |
| 39 | t.Fatal(errs) |
| 40 | } |
| 41 | return ctx, inst.rootValue.evalPartial(ctx).(*structLit) |
| 42 | } |
| 43 | |
| 44 | func compileInstance(t *testing.T, body string) (*context, *Instance, errors.List) { |
| 45 | t.Helper() |
| 46 | |
| 47 | fset := token.NewFileSet() |
| 48 | x := newIndex(fset).NewInstance(nil) |
| 49 | f, err := parser.ParseFile(fset, "test", body, parser.ParseLambdas) |
| 50 | ctx := x.newContext() |
| 51 | |
| 52 | switch errs := err.(type) { |
| 53 | case nil: |
| 54 | x.insertFile(f) |
| 55 | case errors.List: |
| 56 | return ctx, x, errs |
| 57 | default: |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | return ctx, x, nil |
| 61 | } |
| 62 | |
| 63 | func rewriteHelper(t *testing.T, cases []testCase, r rewriteMode) { |
| 64 | t.Helper() |
| 65 | for _, tc := range cases { |
| 66 | t.Run(tc.desc, func(t *testing.T) { |
| 67 | t.Helper() |
| 68 | ctx, obj := compileFile(t, tc.in) |
| 69 | ctx.trace = *traceOn |
| 70 | root := testResolve(ctx, obj, r) |
| 71 | |
| 72 | got := debugStr(ctx, root) |
| 73 | if v := ctx.processDelayedConstraints(); v != nil { |
| 74 | got += fmt.Sprintf("\n%s", debugStr(ctx, v)) |
| 75 | } |
| 76 | |
| 77 | // Copy the result |
| 78 | if got != tc.out { |
| 79 | fn := t.Errorf |
| 80 | if tc.skip { |
| 81 | fn = t.Skipf |
| 82 | } |
| 83 | fn("output differs:\ngot %s\nwant %s", got, tc.out) |
| 84 | } |
| 85 | }) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | type testCase struct { |
| 90 | desc string |
| 91 | in string |
| 92 | out string |
| 93 | skip bool |
| 94 | } |
| 95 | |
| 96 | func TestBasicRewrite(t *testing.T) { |
| 97 | testCases := []testCase{{ |
| 98 | desc: "errors", |
| 99 | in: ` |
| 100 | a: _|_ & _|_ |
| 101 | b: null & _|_ |
| 102 | c: b.a == _|_ |
| 103 | d: _|_ != b.a |
| 104 | e: _|_ == _|_ |
| 105 | `, |
| 106 | out: `<0>{a: _|_(from source), b: _|_(from source), c: true, d: false, e: true}`, |
| 107 | }, { |
| 108 | desc: "arithmetic", |
| 109 | in: ` |
| 110 | sum: -1 + +2 // 1 |
| 111 | str: "foo" + "bar" // "foobar" |
| 112 | div1: 2.0 / 3 * 6 // 4 |
| 113 | div2: 2 / 3 * 6 // 4 |
| 114 | rem: 2 % 3 // 2 |
| 115 | e: 2 + "a" // _|_: unsupported op +(int, string)) |
| 116 | b: 1 != 4 |
| 117 | `, |
| 118 | out: `<0>{sum: 1, str: "foobar", div1: 4.00000000000000000000000, div2: 4.00000000000000000000000, rem: 2, e: _|_((2 + "a"):unsupported op +(number, string)), b: true}`, |
| 119 | }, { |
| 120 | desc: "integer-specific arithmetic", |
| 121 | in: ` |
| 122 | q1: 5 quo 2 // 2 |
| 123 | q2: 5 quo -2 // -2 |
| 124 | q3: -5 quo 2 // -2 |
| 125 | q4: -5 quo -2 // 2 |
| 126 | qe1: 2.0 quo 1 |
| 127 | qe2: 2 quo 1.0 |
| 128 | |
| 129 | r1: 5 rem 2 // 1 |
| 130 | r2: 5 rem -2 // 1 |
| 131 | r3: -5 rem 2 // -1 |
| 132 | r4: -5 rem -2 // -1 |
| 133 | re1: 2.0 rem 1 |
| 134 | re2: 2 rem 1.0 |
| 135 | |
| 136 | d1: 5 div 2 // 2 |
| 137 | d2: 5 div -2 // -2 |
| 138 | d3: -5 div 2 // -3 |
| 139 | d4: -5 div -2 // 3 |
| 140 | de1: 2.0 div 1 |
| 141 | de2: 2 div 1.0 |
| 142 | |
| 143 | m1: 5 mod 2 // 1 |
| 144 | m2: 5 mod -2 // 1 |
| 145 | m3: -5 mod 2 // 1 |
| 146 | m4: -5 mod -2 // 1 |
| 147 | me1: 2.0 mod 1 |
| 148 | me2: 2 mod 1.0 |
| 149 | |
| 150 | // TODO: handle divide by zero |
| 151 | `, |
| 152 | out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` + |
| 153 | `qe1: _|_((2.0 quo 1):unsupported op quo(float, number)), ` + |
| 154 | `qe2: _|_((2 quo 1.0):unsupported op quo(number, float)), ` + |
| 155 | `r1: 1, r2: 1, r3: -1, r4: -1, re1: ` + |
| 156 | `_|_((2.0 rem 1):unsupported op rem(float, number)), ` + |
| 157 | `re2: _|_((2 rem 1.0):unsupported op rem(number, float)), ` + |
| 158 | `d1: 2, d2: -2, d3: -3, d4: 3, ` + |
| 159 | `de1: _|_((2.0 div 1):unsupported op div(float, number)), ` + |
| 160 | `de2: _|_((2 div 1.0):unsupported op div(number, float)), ` + |
| 161 | `m1: 1, m2: 1, m3: 1, m4: 1, ` + |
| 162 | `me1: _|_((2.0 mod 1):unsupported op mod(float, number)), ` + |
| 163 | `me2: _|_((2 mod 1.0):unsupported op mod(number, float))}`, |
| 164 | }, { |
| 165 | desc: "booleans", |
| 166 | in: ` |
| 167 | t: true |
| 168 | t: !false |
| 169 | f: false |
| 170 | f: !t |
| 171 | e: true |
| 172 | e: !true |
| 173 | `, |
| 174 | out: "<0>{t: true, f: false, e: _|_(true:failed to unify: true != false)}", |
| 175 | }, { |
| 176 | desc: "boolean arithmetic", |
| 177 | in: ` |
| 178 | a: true && true |
| 179 | b: true || false |
| 180 | c: false == true |
| 181 | d: false != true |
| 182 | e: true & true |
| 183 | f: true & false |
| 184 | `, |
| 185 | out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:failed to unify: true != false)}", |
| 186 | }, { |
| 187 | desc: "basic type", |
| 188 | in: ` |
| 189 | a: 1 & int |
| 190 | b: number & 1 |
| 191 | c: 1.0 |
| 192 | c: float |
| 193 | d: int & float // _|_ |
| 194 | e: "4" & string |
| 195 | f: true |
| 196 | f: bool |
| 197 | `, |
| 198 | out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):unsupported op &((int)*, (float)*)), e: "4", f: true}`, |
| 199 | }, { |
| 200 | desc: "escaping", |
| 201 | |
| 202 | in: ` |
| 203 | a: "foo\nbar", |
| 204 | b: a, |
| 205 | |
| 206 | // TODO: mimic http://exploringjs.com/es6/ch_template-literals.html#sec_introduction-template-literals |
| 207 | `, |
| 208 | out: `<0>{a: "foo\nbar", b: "foo\nbar"}`, |
| 209 | // out: `<0>{a: "foo\nbar", b: <0>.a}`, |
| 210 | }, { |
| 211 | desc: "reference", |
| 212 | in: ` |
| 213 | a: b |
| 214 | b: 2 |
| 215 | d: { |
| 216 | d: 3 |
| 217 | e: d |
| 218 | } |
| 219 | e: { |
| 220 | e: { |
| 221 | v: 1 |
| 222 | } |
| 223 | f: { |
| 224 | v: e.v |
| 225 | } |
| 226 | } |
| 227 | `, |
| 228 | out: "<0>{a: 2, b: 2, d: <1>{d: 3, e: 3}, e: <2>{e: <3>{v: 1}, f: <4>{v: 1}}}", |
| 229 | }, { |
| 230 | desc: "lists", |
| 231 | in: ` |
| 232 | list: [1,2,3] |
| 233 | index: [1,2,3][1] |
| 234 | unify: [1,2,3] & [_,2,3] |
| 235 | e: [] & 4 |
| 236 | e2: [3]["d"] |
| 237 | e3: [3][-1] |
| 238 | e4: [1, 2, ...4..5] & [1, 2, 4, 8] |
| 239 | e5: [1, 2, 4, 8] & [1, 2, ...4..5] |
| 240 | `, |
| 241 | out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, number)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: _|_(((4..5) & 8):value 8 not in range (4..5)), e5: _|_(((4..5) & 8):value 8 not in range (4..5))}`, |
| 242 | }, { |
| 243 | desc: "selecting", |
| 244 | in: ` |
| 245 | obj: {a: 1, b: 2} |
| 246 | index: {a: 1, b: 2}["b"] |
| 247 | mulidx: {a: 1, b: {a:1, b: 3}}["b"]["b"] |
| 248 | e: {a: 1}[4] |
| 249 | f: {a: 1}.b |
| 250 | g: {a: 1}["b"] |
| 251 | h: [3].b |
| 252 | `, |
| 253 | out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type number)), f: _|_(<2>{a: 1}.b:undefined field "b"), g: _|_(<3>{a: 1}["b"]:undefined field "b"), h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`, |
| 254 | }, { |
| 255 | desc: "obj unify", |
| 256 | in: ` |
| 257 | o1: {a: 1 } & { b: 2} // {a:1,b:2} |
| 258 | o2: {a: 1, b:2 } & { b: 2} // {a:1,b:2} |
| 259 | o3: {a: 1 } & { a:1, b: 2} // {a:1,b:2} |
| 260 | o4: {a: 1 } & { b: 2} // {a:1,b:2} |
| 261 | o4: {a: 1, b:2 } & { b: 2} |
| 262 | o4: {a: 1 } & { a:1, b: 2} |
| 263 | e: 1 // 1 & {a:3} |
| 264 | e: {a:3} |
| 265 | `, |
| 266 | out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):unsupported op &(number, struct))}", |
| 267 | }, { |
| 268 | desc: "disjunctions", |
| 269 | in: ` |
| 270 | o1: 1 | 2 | 3 |
| 271 | o2: (1 | 2 | 3) & 1 |
| 272 | o3: 2 & (1 | 2 | 3) |
| 273 | o4: (1 | 2 | 3) & (1 | 2 | 3) |
| 274 | o5: (1 | 2 | 3) & (3 | 2 | 1) |
| 275 | o6: (1 | 2 | 3) & (3 | 1 | 2) |
| 276 | o7: (1 | 2 | 3) & (2 | 3) |
| 277 | o8: (1 | 2 | 3) & (3 | 2) |
| 278 | o9: (2 | 3) & (1 | 2 | 3) |
| 279 | o10: (3 | 2) & (1 | 2 | 3) |
| 280 | |
| 281 | // All errors are treated the same as per the unification model. |
| 282 | i1: [1, 2][3] | "c" |
| 283 | `, |
| 284 | out: `<0>{o1: (1 | 2 | 3), o2: 1, o3: 2, o4: (1 | 2 | 3), o5: (1! | 2! | 3!), o6: (1! | 2! | 3!), o7: (2 | 3), o8: (2! | 3!), o9: (2 | 3), o10: (3! | 2!), i1: "c"}`, |
| 285 | }, { |
| 286 | desc: "lambda", |
| 287 | in: ` |
| 288 | o1(A:1, B:2) -> { a: A, b: B } |
| 289 | oe() -> { a: 1, b: 2 } |
| 290 | l1: (A:1, B:2) -> { a: A, b: B } |
| 291 | c1: ((A:int, B:int) -> {a:A, b:B})(1, 2) |
| 292 | `, |
| 293 | // TODO(P1): don't let values refer to themselves. |
| 294 | out: "<0>{o1: <1>(A: 1, B: 2)-><2>{a: <1>.A, b: <1>.B}, oe: <3>()-><4>{a: 1, b: 2}, l1: <5>(A: 1, B: 2)-><6>{a: <5>.A, b: <5>.B}, c1: <7>{a: 1, b: 2}}", |
| 295 | }, { |
| 296 | desc: "types", |
| 297 | in: ` |
| 298 | i: int |
| 299 | j: int & 3 |
| 300 | s: string |
| 301 | t: "s" & string |
| 302 | e: int & string |
| 303 | e2: 1 & string |
| 304 | b: !int |
| 305 | p: +true |
| 306 | m: -false |
| 307 | `, |
| 308 | out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):unsupported op &((int)*, (string)*)), e2: _|_((1 & string):unsupported op &(number, (string)*)), b: _|_(!int:unary '!' requires bool value, found (int)*), p: _|_(+true:unary '+' requires numeric value, found bool), m: _|_(-false:unary '-' requires numeric value, found bool)}`, |
| 309 | }, { |
| 310 | desc: "comparisson", |
| 311 | in: ` |
| 312 | lss: 1 < 2 |
| 313 | leq: 1 <= 1.0 |
| 314 | leq: 2.0 <= 3 |
| 315 | eql: 1 == 1.0 |
| 316 | neq: 1.0 == 1 |
| 317 | gtr: !(2 > 3) |
| 318 | geq: 2.0 >= 2 |
| 319 | seq: "a" + "b" == "ab" |
| 320 | err: 2 == "s" |
| 321 | `, |
| 322 | out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):unsupported op ==(number, string))}`, |
| 323 | }, { |
| 324 | desc: "null", |
| 325 | in: ` |
| 326 | eql: null == null |
| 327 | neq: null != null |
| 328 | unf: null & null |
| 329 | |
| 330 | // errors |
| 331 | eqe1: null == 1 |
| 332 | eqe2: 1 == null |
| 333 | nee1: "s" != null |
| 334 | call: null() |
| 335 | `, |
| 336 | out: `<0>{eql: true, neq: false, unf: null, eqe1: _|_((null == 1):unsupported op ==(null, number)), eqe2: _|_((1 == null):unsupported op ==(number, null)), nee1: _|_(("s" != null):unsupported op !=(string, null)), call: _|_(null:cannot call non-function null (type null))}`, |
| 337 | }, { |
| 338 | desc: "self-reference cycles", |
| 339 | in: ` |
| 340 | a: b - 100 |
| 341 | b: a + 100 |
| 342 | |
| 343 | c: [c[1], c[0]] |
| 344 | `, |
| 345 | out: `<0>{a: _|_(cycle detected), b: _|_(cycle detected), c: _|_(cycle detected)}`, |
| 346 | // }, { |
| 347 | // desc: "resolved self-reference cycles", |
| 348 | // in: ` |
| 349 | // a: b - 100 |
| 350 | // b: a + 100 |
| 351 | // b: 200 |
| 352 | |
| 353 | // c: [c[1], a] // TODO: should be allowed |
| 354 | |
| 355 | // s1: s2 & {a: 1} |
| 356 | // s2: s3 & {b: 2} |
| 357 | // s3: s1 & {c: 3} |
| 358 | // `, |
| 359 | // out: `<0>{a: 100, b: 200, c: _|_(cycle detected)}`, |
| 360 | }, { |
| 361 | desc: "delayed constraint failure", |
| 362 | in: ` |
| 363 | a: b - 100 |
| 364 | b: a + 110 |
| 365 | b: 200 |
| 366 | `, |
| 367 | out: `<0>{a: 100, b: 200} |
| 368 | _|_(((<1>.a + 110) & 200):constraint violated: _|_((210 & 200):cannot unify numbers 210 and 200))`, |
| 369 | // TODO: find a way to mark error in data. |
| 370 | }} |
| 371 | rewriteHelper(t, testCases, evalPartial) |
| 372 | } |
| 373 | |
| 374 | func TestChooseFirst(t *testing.T) { |
| 375 | testCases := []testCase{{ |
| 376 | desc: "pick first", |
| 377 | in: ` |
| 378 | a: 5 | "a" | true |
| 379 | b c: { |
| 380 | a: 2 |
| 381 | } | { |
| 382 | a : 3 |
| 383 | } |
| 384 | `, |
| 385 | out: "<0>{a: 5, b: <1>{c: <2>{a: 2}}}", |
| 386 | }, { |
| 387 | desc: "simple disambiguation conflict", |
| 388 | in: ` |
| 389 | a: "a" | "b" |
| 390 | b: "b" | "a" |
| 391 | c: a & b |
| 392 | `, |
| 393 | out: `<0>{a: "a", b: "b", c: _|_(("a"! | "b"!):ambiguous disjunction)}`, |
| 394 | }, { |
| 395 | desc: "disambiguation non-conflict", |
| 396 | in: ` |
| 397 | a: "a" | ("b" | "c") |
| 398 | b: ("a" | "b") | "c" |
| 399 | c: a & b |
| 400 | `, |
| 401 | out: `<0>{a: "a", b: "a", c: "a"}`, |
| 402 | }} |
| 403 | rewriteHelper(t, testCases, evalFull) |
| 404 | } |
| 405 | |
| 406 | func TestResolve(t *testing.T) { |
| 407 | testCases := []testCase{{ |
| 408 | in: ` |
| 409 | a: b.c.d |
| 410 | b c: { d: 3 } |
| 411 | c: { c: d.d, } |
| 412 | d: { d: 2 } |
| 413 | `, |
| 414 | out: "<0>{a: 3, b: <1>{c: <2>{d: 3}}, c: <3>{c: 2}, d: <4>{d: 2}}", |
| 415 | }, { |
| 416 | in: ` |
| 417 | a: _ |
| 418 | b: a |
| 419 | a: { d: 1, d: _ } |
| 420 | b: _ |
| 421 | `, |
| 422 | out: `<0>{a: <1>{d: 1}, b: <2>{d: 1}}`, |
| 423 | }, { |
| 424 | desc: "JSON", |
| 425 | in: ` |
| 426 | "a": 3 |
| 427 | b: a |
| 428 | o: { "a\nb": 2 } // TODO: use $ for root? |
| 429 | c: o["a\nb"] |
| 430 | `, |
| 431 | out: `<0>{a: 3, b: 3, o: <1>{"a\nb": 2}, c: 2}`, |
| 432 | }, { |
| 433 | desc: "arithmetic", |
| 434 | in: ` |
| 435 | v1: 1.0T/2.0 // |
| 436 | v2: 2.0 == 2 |
| 437 | i1: 1 |
| 438 | v5: 2.0 / i1 // TODO: should probably fail |
| 439 | e1: 2.0 % 3 |
| 440 | e2: int & 4.0/2.0 |
| 441 | `, |
| 442 | out: `<0>{v1: 5e+11, v2: true, i1: 1, v5: 2, e1: _|_((2.0 % 3):unsupported op %(float, number)), e2: _|_((int & 2):unsupported op &((int)*, float))}`, |
| 443 | // }, { |
| 444 | // desc: "null coalescing", |
| 445 | // in: ` |
| 446 | // a: null |
| 447 | // b: a.x |
| 448 | // c: a["x"] |
| 449 | // `, |
| 450 | // out: ``, |
| 451 | }, { |
| 452 | desc: "call", |
| 453 | in: ` |
| 454 | a: { a: (P, Q) -> {p:P, q:Q} } |
| 455 | b: a // reference different nodes |
| 456 | c: a.a(1, 2) |
| 457 | `, |
| 458 | out: "<0>{a: <1>{a: <2>(P: _, Q: _)-><3>{p: <2>.P, q: <2>.Q}}, b: <4>{a: <2>(P: _, Q: _)-><3>{p: <2>.P, q: <2>.Q}}, c: <5>{p: 1, q: 2}}", |
| 459 | }, { |
| 460 | desc: "call of lambda", |
| 461 | in: ` |
| 462 | a(P, Q) -> {p:P, q:Q} |
| 463 | a(P, Q) -> {p:P, q:Q} |
| 464 | ai(P, Q) -> {p:Q, q:P} |
| 465 | b: a(1,2) |
| 466 | c: (a | b)(1) |
| 467 | d: ([] | (a) -> 3)(2) |
| 468 | e1: a(1) |
| 469 | `, |
| 470 | out: "<1>{a: <2>(P: _, Q: _)->(<3>{p: <2>.P, q: <2>.Q} & <4>{p: <2>.P, q: <2>.Q}), ai: <5>(P: _, Q: _)-><6>{p: <5>.Q, q: <5>.P}, b: <7>{p: 1, q: 2}, c: _|_((<8>.a | <8>.b) (1):number of arguments does not match (2 vs 1)), d: _|_(([] | <0>(a: _)->3):cannot call non-function [] (type list)), e1: _|_(<8>.a (1):number of arguments does not match (2 vs 1))}", |
| 471 | }, { |
| 472 | desc: "reference across tuples and back", |
| 473 | // Tests that it is okay to partially evaluate structs. |
| 474 | in: ` |
| 475 | a: { c: b.e, d: b.f } |
| 476 | b: { e: 3, f: a.c } |
| 477 | `, |
| 478 | out: "<0>{a: <1>{c: 3, d: 3}, b: <2>{e: 3, f: 3}}", |
| 479 | }, { |
| 480 | desc: "index", |
| 481 | in: ` |
| 482 | a: [2][0] |
| 483 | b: {foo:"bar"}["foo"] |
| 484 | c: (l|{"3":3})["3"] |
| 485 | d: ([]|[1])[0] |
| 486 | l: [] |
| 487 | e1: [2][""] |
| 488 | e2: 2[2] |
| 489 | e3: [][true] |
| 490 | e4: [1,2,3][3] |
| 491 | e5: [1,2,3][-1] |
| 492 | e6: ([]|{})[1] |
| 493 | `, |
| 494 | out: `<0>{a: 2, b: "bar", c: _|_("3":invalid list index "3" (type string)), l: [], d: _|_([]:index 0 out of bounds), e1: _|_("":invalid list index "" (type string)), e2: _|_(2:invalid operation: 2[2] (type number does not support indexing)), e3: _|_(true:invalid list index true (type bool)), e4: _|_([1,2,3]:index 3 out of bounds), e5: _|_(-1:invalid list index -1 (index must be non-negative)), e6: _|_([]:index 1 out of bounds)}`, |
| 495 | }, { |
| 496 | desc: "string index", |
| 497 | in: ` |
| 498 | a0: "abc"[0] |
| 499 | a1: "abc"[1] |
| 500 | a2: "abc"[2] |
| 501 | a3: "abc"[3] |
| 502 | a4: "abc"[-1] |
| 503 | |
| 504 | b: "zoëven"[2] |
| 505 | `, |
| 506 | out: `<0>{a0: "a", a1: "b", a2: "c", a3: _|_("abc":index 3 out of bounds), a4: _|_(-1:invalid string index -1 (index must be non-negative)), b: "ë"}`, |
| 507 | }, { |
| 508 | desc: "disjunctions of lists", |
| 509 | in: ` |
| 510 | l: [ int, int ] | [ string, string ] |
| 511 | |
| 512 | l1: [ "a", "b" ] |
| 513 | l2: l & [ "c", "d" ] |
| 514 | `, |
| 515 | out: `<0>{l: ([int,int] | [string,string]), l1: ["a","b"], l2: ["c","d"]}`, |
| 516 | }, { |
| 517 | desc: "slice", |
| 518 | in: ` |
| 519 | a: [2][0:0] |
| 520 | b: [0][1:1] |
| 521 | e1: [][1:1] |
| 522 | e2: [0][-1:0] |
| 523 | e3: [0][1:0] |
| 524 | e4: [0][1:2] |
| 525 | e5: 4[1:2] |
| 526 | e6: [2]["":] |
| 527 | e7: [2][:"9"] |
| 528 | |
| 529 | `, |
| 530 | out: `<0>{a: [], b: [], e1: _|_(1:slice bounds out of range), e2: _|_([0]:negative slice index), e3: _|_([0]:invalid slice index: 1 > 0), e4: _|_(2:slice bounds out of range), e5: _|_(4:cannot slice 4 (type number)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`, |
| 531 | }, { |
| 532 | desc: "string slice", |
| 533 | in: ` |
| 534 | a0: ""[0:0] |
| 535 | a1: ""[:] |
| 536 | a2: ""[0:] |
| 537 | a3: ""[:0] |
| 538 | b0: "abc"[0:0] |
| 539 | b1: "abc"[0:1] |
| 540 | b2: "abc"[0:2] |
| 541 | b3: "abc"[0:3] |
| 542 | b4: "abc"[3:3] |
| 543 | b5: "abc"[1:] |
| 544 | b6: "abc"[:2] |
| 545 | |
| 546 | // TODO: supported extended graphemes, instead of just runes. |
| 547 | u: "Spaß"[3:4] |
| 548 | `, |
| 549 | out: `<0>{a0: "", a1: "", a2: "", a3: "", b0: "", b1: "a", b2: "ab", b3: "abc", b4: "", b5: "bc", b6: "ab", u: "ß"}`, |
| 550 | }, { |
| 551 | desc: "list types", |
| 552 | in: ` |
| 553 | l0: 3*[int] |
| 554 | l0: [1, 2, 3] |
| 555 | l1:(0..5)*[string] |
| 556 | l1: ["a", "b"] |
| 557 | l2: (0..5)*[{ a: int }] |
| 558 | l2: [{a: 1}, {a: 2, b: 3}] |
| 559 | l3: (0..10)*[int] |
| 560 | l3: [1, 2, 3, ...] |
| 561 | |
| 562 | s1: ((0..6)*[int])[2:3] // TODO: simplify 1*[int] to [int] |
| 563 | s2: [0,2,3][1:2] |
| 564 | |
| 565 | i1: ((0..6)*[int])[2] |
| 566 | i2: [0,2,3][2] |
| 567 | |
| 568 | t0: [...{a: 8}] |
| 569 | t0: [{}] |
| 570 | |
| 571 | e0: (2..5)*[{}] |
| 572 | e0: [{}] |
| 573 | |
| 574 | e1: 0.._*[...int] |
| 575 | `, |
| 576 | out: `<0>{l0: [1,2,3], l1: ["a","b"], l2: [<1>{a: 1},<2>{a: 2, b: 3}], l3: (3..10)*[int]([1,2,3, ...int]), s1: 1*[int], s2: [2], i1: int, i2: 3, t0: [<3>{a: 8}], e0: _|_(((2..5)*[<4>{}] & [<5>{}]):incompatible list lengths: value 1 not in range (2..5)), e1: [, ...int]}`, |
| 577 | }, { |
| 578 | desc: "list arithmetic", |
| 579 | in: ` |
| 580 | l0: 3*[1, 2, 3] |
| 581 | l1: 0*[1, 2, 3] |
| 582 | l2: 10*[] |
| 583 | l3: (0..2)*[] |
| 584 | l4: (0..2)*[int] |
| 585 | l5: (0..2)*(int*[int]) |
| 586 | l6: 3*((3..4)*[int]) |
| 587 | `, |
| 588 | out: `<0>{l0: [1,2,3,1,2,3,1,2,3], l1: [], l2: [], l3: [], l4: (0..2)*[int], l5: (0..2)*[int], l6: (9..12)*[int]}`, |
| 589 | }, { |
| 590 | desc: "correct error messages", |
| 591 | // Tests that it is okay to partially evaluate structs. |
| 592 | in: ` |
| 593 | a: "a" & 1 |
| 594 | `, |
| 595 | out: `<0>{a: _|_(("a" & 1):unsupported op &(string, number))}`, |
| 596 | }, { |
| 597 | desc: "structs", |
| 598 | in: ` |
| 599 | a: t & { c: 5 } // {c:5,d:15} |
| 600 | b: ti & { c: 7 } // {c:7,d:21} |
| 601 | t: { c: number, d: c * 3 } // {c:number,d:number*3} |
| 602 | ti: t & { c: int } |
| 603 | `, |
| 604 | out: `<0>{a: <1>{c: 5, d: 15}, t: <2>{c: number, d: (<3>.c * 3)}, b: <4>{c: 7, d: 21}, ti: <5>{c: int, d: (<6>.c * 3)}}`, |
| 605 | }, { |
| 606 | desc: "reference to root", |
| 607 | in: ` |
| 608 | a: { b: int } |
| 609 | c: a & { |
| 610 | b: 100 |
| 611 | d: a.b + 3 // do not resolve as c != a. |
| 612 | |
| 613 | // TODO(crash) |
| 614 | // e: int; e < 100 // where clause can be different. |
| 615 | } |
| 616 | x: { |
| 617 | b: int |
| 618 | c: b + 5 |
| 619 | } |
| 620 | y: x & { |
| 621 | b: 100 |
| 622 | // c should resolve to 105 |
| 623 | } |
| 624 | v: { |
| 625 | b: int |
| 626 | c: v.b + 5 // reference starting from copied node. |
| 627 | } |
| 628 | w: v & { b: 100 } |
| 629 | wp: v & { b: 100 } |
| 630 | `, |
| 631 | out: `<0>{a: <1>{b: int}, c: <2>{b: 100, d: (<3>.a.b + 3)}, x: <4>{b: int, c: (<5>.b + 5)}, y: <6>{b: 100, c: 105}, v: <7>{b: int, c: (<8>.b + 5)}, w: <9>{b: 100, c: 105}, wp: <10>{b: 100, c: 105}}`, |
| 632 | }, { |
| 633 | desc: "references from template to concrete", |
| 634 | in: ` |
| 635 | res: [t] |
| 636 | t <X>: { |
| 637 | a: c + b.str |
| 638 | b str: string |
| 639 | c: "X" |
| 640 | } |
| 641 | t x: { b str: "DDDD" } |
| 642 | `, |
| 643 | out: `<0>{res: [<1>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], t: <7>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`, |
| 644 | }, { |
| 645 | desc: "interpolation", |
| 646 | in: ` |
| 647 | a: "\(4)" |
| 648 | b: "one \(a) two \( a + c )" |
| 649 | c: "one" |
| 650 | d: "\(r)" |
| 651 | u: "\(_)" |
| 652 | r: _ |
| 653 | e: "\([])"`, |
| 654 | out: `<0>{a: "4", b: "one 4 two 4one", c: "one", d: ""+<1>.r+"", r: _, u: ""+_+"", e: _|_([]:expression in interpolation must evaluate to a number kind or string (found list))}`, |
| 655 | }, { |
| 656 | desc: "diamond-shaped constraints", |
| 657 | in: ` |
| 658 | S: { |
| 659 | A: { |
| 660 | a: 1, |
| 661 | }, |
| 662 | B: A & { |
| 663 | b: 2, |
| 664 | } |
| 665 | }, |
| 666 | T: S & { // S == { A: { a:1 }, B: { a:1, b:2 } } |
| 667 | A: { |
| 668 | c: 3, |
| 669 | }, |
| 670 | B: { // S.B & A |
| 671 | d: 4, // Combines constraints S.A, S.B, T.A, and T.B |
| 672 | } |
| 673 | }`, |
| 674 | out: "<0>{S: <1>{A: <2>{a: 1}, B: <3>{a: 1, b: 2}}, T: <4>{A: <5>{a: 1, c: 3}, B: <6>{a: 1, b: 2, c: 3, d: 4}}}", |
| 675 | }, { |
| 676 | desc: "field templates", |
| 677 | in: ` |
| 678 | a: { |
| 679 | <name>: int |
| 680 | k: 1 |
| 681 | } |
| 682 | b: { |
| 683 | <x>: { x: 0, y: 1 | int } |
| 684 | v: {} |
| 685 | w: { x: 0 } |
| 686 | } |
| 687 | b: { <y>: {} } // TODO: allow different name |
| 688 | c: { |
| 689 | <Name>: { name: Name, y: 1 } |
| 690 | foo: {} |
| 691 | bar: _ |
| 692 | } |
| 693 | `, |
| 694 | out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: (1 | int)}, w: <8>{x: 0, y: (1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`, |
| 695 | }, { |
| 696 | desc: "simple ranges", |
| 697 | in: ` |
| 698 | a: 1..2 |
| 699 | c: "a".."b" |
| 700 | d: (2+3)..(4+5) // 5..9 |
| 701 | |
| 702 | s1: 1..1 // 1 |
| 703 | s2: 1..2..3 // simplify (1..2)..3 to 1..3 |
| 704 | s3: (1..10)..5 // This is okay! |
| 705 | s4: 5..(1..10) // This is okay! |
| 706 | s5: (0..(5..6))..(1..10) |
| 707 | `, |
| 708 | out: `<0>{a: (1..2), c: ("a".."b"), d: (5..9), s1: 1, s2: (1..3), s3: (1..5), s4: (5..10), s5: (0..10)}`, |
| 709 | }, { |
| 710 | desc: "range unification", |
| 711 | in: ` |
| 712 | // with concrete values |
| 713 | a1: 1..5 & 3 |
| 714 | a2: 1..5 & 1 |
| 715 | a3: 1..5 & 5 |
| 716 | a4: 1..5 & 6 |
| 717 | a5: 1..5 & 0 |
| 718 | |
| 719 | a6: 3 & 1..5 |
| 720 | a7: 1 & 1..5 |
| 721 | a8: 5 & 1..5 |
| 722 | a9: 6 & 1..5 |
| 723 | a10: 0 & 1..5 |
| 724 | |
| 725 | // with ranges |
| 726 | b1: 1..5 & 1..5 |
| 727 | b2: 1..5 & 1..1 |
| 728 | b3: 1..5 & 5..5 |
| 729 | b4: 1..5 & 2..3 |
| 730 | b5: 1..5 & 3..9 |
| 731 | b6: 1..5 & 5..9 |
| 732 | b7: 1..5 & 6..9 |
| 733 | |
| 734 | b8: 1..5 & 1..5 |
| 735 | b9: 1..1 & 1..5 |
| 736 | b10: 5..5 & 1..5 |
| 737 | b11: 2..3 & 1..5 |
| 738 | b12: 3..9 & 1..5 |
| 739 | b13: 5..9 & 1..5 |
| 740 | b14: 6..9 & 1..5 |
| 741 | |
| 742 | // ranges with more general types |
| 743 | c1: int & 1..5 |
| 744 | c2: 1..5 & int |
| 745 | c3: string & 1..5 |
| 746 | c4: 1..5 & string |
| 747 | |
| 748 | // other types |
| 749 | s1: "d" .. "z" & "e" |
| 750 | s2: "d" .. "z" & "ee" |
| 751 | |
| 752 | n1: number & 1..2 |
| 753 | n2: int & 1.1 .. 1.3 |
| 754 | n3: 1.0..3.0 & 2 |
| 755 | n4: 0.0..0.1 & 0.09999 |
| 756 | n5: 1..5 & 2.5 |
| 757 | `, |
| 758 | out: `<0>{a1: 3, a2: 1, a3: 5, a4: _|_(((1..5) & 6):value 6 not in range (1..5)), a5: _|_(((1..5) & 0):value 0 not in range (1..5)), a6: 3, a7: 1, a8: 5, a9: _|_(((1..5) & 6):value 6 not in range (1..5)), a10: _|_(((1..5) & 0):value 0 not in range (1..5)), b1: (1..5), b2: 1, b3: 5, b4: (2..3), b5: (3..5), b6: 5, b7: _|_(((1..5) & (6..9)):non-overlapping ranges (1..5) and (6..9)), b8: (1..5), b9: 1, b10: 5, b11: (2..3), b12: (3..5), b13: 5, b14: _|_(((6..9) & (1..5)):non-overlapping ranges (6..9) and (1..5)), c1: (1..5), c2: (1..5), c3: _|_((string & (1..5)):unsupported op &((string)*, (number)*)), c4: _|_(((1..5) & string):unsupported op &((number)*, (string)*)), s1: "e", s2: "ee", n1: (1..2), n2: _|_((int & (1.1..1.3)):unsupported op &((int)*, (float)*)), n3: 2, n4: 0.09999, n5: 2.5}`, |
| 759 | }, { |
| 760 | desc: "range arithmetic", |
| 761 | in: ` |
| 762 | r0: (1..2) * (4..5) |
| 763 | r1: (1..2) * (-1..2) |
| 764 | r2: (1.0..2.0) * (-0.5..1.0) |
| 765 | r3: (1..2) + (4..5) |
| 766 | |
| 767 | i0: (1..2) * 2 |
| 768 | i1: (2..3) * -2 |
| 769 | i2: (1..2) * 2 |
| 770 | i3: (2..3) * -2 |
| 771 | |
| 772 | t0: int * (1..2) // TODO: should be int |
| 773 | t1: (1..2) * int |
| 774 | t2: (1..2) * (0..int) |
| 775 | t3: (1..int) * (0..2) |
| 776 | t4: (1..int) * (-1..2) |
| 777 | t5: _ * (1..2) // TODO: should be int |
| 778 | |
| 779 | s0: (1..2) - (3..5) |
| 780 | s1: (1..2) - 1 |
| 781 | |
| 782 | str0: ("ab".."cd") + "ef" |
| 783 | str1: ("ab".."cd") + ("ef".."gh") |
| 784 | str2: ("ab".."cd") + string |
| 785 | |
| 786 | `, |
| 787 | out: `<0>{r0: (4..10), r1: (-2..4), r2: (-1.00..2.00), r3: (5..7), i0: (2..4), i1: (-6..-4), i2: (2..4), i3: (-6..-4), t0: (int * (1..2)), t1: int, t2: (0..int), t3: (0..int), t4: int, t5: _|_((_ * (1..2)):binary operation on non-ground top value), s0: (-4..-1), s1: (0..1), str0: ("abef".."cdef"), str1: ("abef".."cdgh"), str2: ("ab".."cd")}`, |
| 788 | }, { |
| 789 | desc: "predefined ranges", |
| 790 | in: ` |
| 791 | k1: int8 |
| 792 | k1: 44 |
| 793 | |
| 794 | k2: int64 |
| 795 | k2: -8_000_000_000 |
| 796 | |
| 797 | e1: int16 |
| 798 | e1: 100_000 |
| 799 | `, |
| 800 | out: `<0>{k1: 44, k2: -8000000000, ` + |
| 801 | `e1: _|_(((-32768..32767) & 100000):value 100000 not in range (-32768..32767))}`, |
| 802 | // TODO(P3): if two fields are evaluating to the same field, their |
| 803 | // values could be bound. |
| 804 | // nodes: use a shared node |
| 805 | // other: change to where clause |
| 806 | // unknown: change to where clause. |
| 807 | // in: ` |
| 808 | // a: b |
| 809 | // b: a |
| 810 | // a: { d: 1 } |
| 811 | // `, |
| 812 | // out: `<0>{a: {d:1}, b: {d:1}}`, |
| 813 | |
| 814 | // TODO(P2): circular references in expressions can be resolved when |
| 815 | // unified with complete values. |
| 816 | // in: ` |
| 817 | // a: 20 |
| 818 | // a: b + 10 // 20 & b+10 ==> 20; where a == b+10 |
| 819 | // b: a - 10 // 10-10 = 10 ==> 20 == 10+10 |
| 820 | // ` |
| 821 | // out: `<0>{a_0:20, b_1:10}` |
| 822 | }} |
| 823 | rewriteHelper(t, testCases, evalPartial) |
| 824 | } |
| 825 | |
| 826 | func TestFullEval(t *testing.T) { |
| 827 | testCases := []testCase{{ |
| 828 | desc: "detect conflicting value", |
| 829 | in: ` |
| 830 | a: 8000.9 |
| 831 | a: 7080 | int`, |
| 832 | out: `<0>{a: _|_(empty disjunction after evaluation)}`, |
| 833 | }, { |
| 834 | desc: "resolve all disjunctions", |
| 835 | in: ` |
| 836 | service <Name>: { |
| 837 | name: Name | string |
| 838 | port: 7080 | int |
| 839 | } |
| 840 | service foo: _ |
| 841 | service bar: { port: 8000 } |
| 842 | service baz: { name: "foobar" } |
| 843 | `, |
| 844 | out: `<0>{service: <1>{<>: <2>(Name: string)-><3>{name: (<2>.Name | string), port: (7080 | int)}, foo: <4>{name: "foo", port: 7080}, bar: <5>{name: "bar", port: 8000}, baz: <6>{name: "foobar", port: 7080}}}`, |
| 845 | }, { |
| 846 | desc: "field templates", |
| 847 | in: ` |
| 848 | a: { |
| 849 | <name>: int |
| 850 | k: 1 |
| 851 | } |
| 852 | b: { |
| 853 | <x>: { x: 0, y: 1 | int } |
| 854 | v: {} |
| 855 | w: { y: 0 } |
| 856 | } |
| 857 | b: { <y>: {} } // TODO: allow different name |
| 858 | c: { |
| 859 | <Name>: { name: Name, y: 1 } |
| 860 | foo: {} |
| 861 | bar: _ |
| 862 | } |
| 863 | `, |
| 864 | out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`, |
| 865 | }, { |
| 866 | desc: "field comprehension", |
| 867 | in: ` |
| 868 | a: { "\(k)": v for k, v in b if k < "d" if v > b.a } |
| 869 | b: { |
| 870 | a: 1 |
| 871 | b: 2 |
| 872 | c: 3 |
| 873 | d: 4 |
| 874 | } |
| 875 | c: { |
| 876 | "\(k)": v <- |
| 877 | for k, v in b |
| 878 | if k < "d" |
| 879 | if v > b.a |
| 880 | } |
| 881 | // TODO: Propagate error: |
| 882 | // e: { "\(k)": v for k, v in b if k < "d" if v > b.a } |
| 883 | `, |
| 884 | out: `<0>{a: <1>{b: 2, c: 3}, b: <2>{a: 1, b: 2, c: 3, d: 4}, c: <3>{b: 2, c: 3}}`, |
| 885 | }, { |
| 886 | desc: "nested templates in one field", |
| 887 | in: ` |
| 888 | a <A> b <B>: { |
| 889 | name: A |
| 890 | kind: B |
| 891 | } |
| 892 | a "A" b "B": _ |
| 893 | a "C" b "D": _ |
| 894 | a "E" b "F": { c: "bar" } |
| 895 | `, |
| 896 | out: `<0>{a: <1>{<>: <2>(A: string)-><3>{b: <4>{<>: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, A: <7>{b: <8>{<>: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, B: <12>{name: "A", kind: "B"}}}, C: <13>{b: <14>{<>: <15>(B: string)-><16>{name: <17>.A, kind: <15>.B}, D: <18>{name: "C", kind: "D"}}}, E: <19>{b: <20>{<>: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, F: <24>{name: "E", kind: "F", c: "bar"}}}}}`, |
| 897 | }, { |
| 898 | desc: "template unification within one struct", |
| 899 | in: ` |
| 900 | a: { |
| 901 | <A>: { name: A } |
| 902 | <A>: { kind: A } |
| 903 | } |
| 904 | a "A": _ |
| 905 | a "C": _ |
| 906 | a "E": { c: "bar" } |
| 907 | `, |
| 908 | out: `<0>{a: <1>{<>: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` + |
| 909 | `A: <5>{name: "A", kind: "A"}, ` + |
| 910 | `C: <6>{name: "C", kind: "C"}, ` + |
| 911 | `E: <7>{name: "E", kind: "E", c: "bar"}}}`, |
| 912 | }, { |
| 913 | desc: "field comprehensions with multiple keys", |
| 914 | in: ` |
| 915 | a "\(x.a)" b "\(x.b)": x for x in [ |
| 916 | {a: "A", b: "B" }, |
| 917 | {a: "C", b: "D" }, |
| 918 | {a: "E", b: "F" }, |
| 919 | ] |
| 920 | |
| 921 | "\(x.a)" "\(x.b)": x for x in [ |
| 922 | {a: "A", b: "B" }, |
| 923 | {a: "C", b: "D" }, |
| 924 | {a: "E", b: "F" }, |
| 925 | ]`, |
| 926 | out: `<0>{a: <1>{` + |
| 927 | `A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` + |
| 928 | `C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` + |
| 929 | `E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` + |
| 930 | `A: <11>{B: <12>{a: "A", b: "B"}}, ` + |
| 931 | `C: <13>{D: <14>{a: "C", b: "D"}}, ` + |
| 932 | `E: <15>{F: <16>{a: "E", b: "F"}}}`, |
| 933 | }, { |
| 934 | desc: "field comprehensions with templates", |
| 935 | in: ` |
| 936 | num: 1 |
| 937 | a: { |
| 938 | <A> <B>: { |
| 939 | name: A |
| 940 | kind: B |
| 941 | } if num < 5 |
| 942 | |
| 943 | } |
| 944 | a b c d: "bar" |
| 945 | `, |
| 946 | out: `<0>{num: 1, a: <1>{<>: <2>(A: string)-><3>{<>: <4>(B: string)-><5>{name: <2>.A, kind: <4>.B}, }, ` + |
| 947 | `b: <6>{<>: <7>(B: string)-><8>{name: <9>.A, kind: <7>.B}, ` + |
| 948 | `c: <10>{name: "b", kind: "c", ` + |
| 949 | `d: "bar"}}}}`, |
| 950 | }, { |
| 951 | desc: "disjunctions of lists", |
| 952 | in: ` |
| 953 | l: [ int, int ] | [ string, string ] |
| 954 | |
| 955 | l1: [ "a", "b" ] |
| 956 | l2: l & [ "c", "d" ] |
| 957 | `, |
| 958 | out: `<0>{l: [int,int], l1: ["a","b"], l2: ["c","d"]}`, |
| 959 | }, { |
| 960 | desc: "list comprehension", |
| 961 | in: ` |
| 962 | // a: [ k for k: v in b if k < "d" if v > b.a ] // TODO test error using common iso colon |
| 963 | a: [ k for k, v in b if k < "d" if v > b.a ] |
| 964 | b: { |
| 965 | a: 1 |
| 966 | b: 2 |
| 967 | c: 3 |
| 968 | d: 4 |
| 969 | } |
| 970 | c: [ x for _, x in b for _, y in b if x < y ] |
| 971 | d: [ x for x, _ in a ] |
| 972 | `, |
| 973 | out: `<0>{a: ["b","c"], b: <1>{a: 1, b: 2, c: 3, d: 4}, c: [1,1,1,2,2,3], d: [0,1]}`, |
| 974 | }, { |
| 975 | desc: "struct comprehension with template", |
| 976 | in: ` |
| 977 | result: [ v for _, v in service ] |
| 978 | |
| 979 | service <Name>: { |
| 980 | type: "service" |
| 981 | name: Name | string |
| 982 | port: 7080 | int |
| 983 | } |
| 984 | service foo: {} |
| 985 | service bar: { port: 8000 } |
| 986 | service baz: { name: "foobar" } |
| 987 | `, |
| 988 | out: `<0>{result: [` + |
| 989 | `<1>{type: "service", name: "foo", port: 7080},` + |
| 990 | `<2>{type: "service", name: "bar", port: 8000},` + |
| 991 | `<3>{type: "service", name: "foobar", port: 7080}], ` + |
| 992 | |
| 993 | `service: <4>{` + |
| 994 | `<>: <5>(Name: string)-><6>{type: "service", name: (<5>.Name | string), port: (7080 | int)}, ` + |
| 995 | `foo: <7>{type: "service", name: "foo", port: 7080}, ` + |
| 996 | `bar: <8>{type: "service", name: "bar", port: 8000}, ` + |
| 997 | `baz: <9>{type: "service", name: "foobar", port: 7080}}}`, |
| 998 | }, { |
| 999 | desc: "resolutions in struct comprehension keys", |
| 1000 | in: ` |
Marcel van Lohuizen | 76b92b5 | 2018-12-16 10:47:03 +0100 | [diff] [blame^] | 1001 | a: { "\(b + ".")": "a" for _, b in ["c"] } |
Marcel van Lohuizen | 17157ea | 2018-12-11 10:41:10 +0100 | [diff] [blame] | 1002 | `, |
| 1003 | out: `<0>{a: <1>{c.: "a"}}`, |
| 1004 | }, { |
| 1005 | desc: "recursive evaluation within list", |
| 1006 | in: ` |
| 1007 | l: [a] |
| 1008 | a: b & { c: "t" } |
| 1009 | b: { |
| 1010 | d: c |
| 1011 | c: string |
| 1012 | } |
| 1013 | l1: [a1] |
| 1014 | a1: b1 & { c: "t" } |
| 1015 | b1: { |
| 1016 | d: "s" + c |
| 1017 | c: string |
| 1018 | } |
| 1019 | `, |
| 1020 | out: `<0>{l: [<1>{c: "t", d: "t"}], a: <2>{c: "t", d: "t"}, b: <3>{c: string, d: string}, l1: [<4>{c: "t", d: "st"}], a1: <5>{c: "t", d: "st"}, b1: <6>{c: string, d: _|_(("s" + string):unsupported op +(string, (string)*))}}`, |
| 1021 | }, { |
| 1022 | desc: "ips", |
| 1023 | in: ` |
| 1024 | IP: 4*[ 0..255 ] |
| 1025 | |
| 1026 | Private: |
| 1027 | [ 192, 168, 0..255, 0..255 ] | |
| 1028 | [ 10, 0..255, 0..255, 0..255] | |
| 1029 | [ 172, 16..32, 0..255, 0..255 ] |
| 1030 | |
| 1031 | Inst: Private & [ _, 10, ... ] |
| 1032 | |
| 1033 | MyIP: Inst & [_, _, 10, 10 ] |
| 1034 | `, |
| 1035 | out: `<0>{IP: 4*[(0..255)], Private: [192,168,(0..255),(0..255)], Inst: [10,10,(0..255),(0..255)], MyIP: [10,10,10,10]}`, |
| 1036 | }, { |
| 1037 | desc: "complex interaction of groundness", |
| 1038 | in: ` |
| 1039 | res: [ y & { d: "b" } for x in a for y in x ] |
| 1040 | res: [ a.b.c & { d: "b" } ] |
| 1041 | |
| 1042 | a b <C>: { d: string, s: "a" + d } |
| 1043 | a b c d: string |
| 1044 | `, |
| 1045 | // TODO(perf): unification should catch shared node. |
| 1046 | out: `<0>{res: [<1>{d: "b", s: "ab"}], a: <2>{b: <3>{<>: <4>(C: string)-><5>{d: string, s: ("a" + <5>.d)}, c: <6>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}}}`, |
| 1047 | }, { |
| 1048 | desc: "complex groundness 2", |
| 1049 | in: ` |
| 1050 | r1: f1 & { y: "c" } |
| 1051 | |
| 1052 | f1: { y: string, res: a.b.c & { d: y } } |
| 1053 | |
| 1054 | a b c: { d: string, s: "a" + d } |
| 1055 | a b <C>: { d: string, s: "a" + d } |
| 1056 | a b c d: string |
| 1057 | `, |
| 1058 | out: `<0>{r1: <1>{y: "c", res: <2>{d: "c", s: "ac"}}, f1: <3>{y: string, res: <4>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}, a: <5>{b: <6>{<>: <7>(C: string)-><8>{d: string, s: ("a" + <8>.d)}, c: <9>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}}}`, |
| 1059 | }, { |
| 1060 | desc: "references from template to concrete", |
| 1061 | in: ` |
| 1062 | res: [t] |
| 1063 | t <X>: { |
| 1064 | a: c + b.str |
| 1065 | b str: string |
| 1066 | c: "X" |
| 1067 | } |
| 1068 | t x: { b str: "DDDD" } |
| 1069 | `, |
| 1070 | out: `<0>{res: [<1>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], ` + |
| 1071 | `t: <7>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`, |
| 1072 | }} |
| 1073 | rewriteHelper(t, testCases, evalFull) |
| 1074 | } |