Skip to content

Commit 276d940

Browse files
committed
internal/core/compile: add matchIf builtin
This primitive will make it significantly easier to implement JSON Schema's `if`, `then`, `else` keywords. It follows a discussion with Marcel where it became clear that implementing these keywords with comprehensions would be tricky, and that a builtin along the lines of `matchN` would be at least a reasonable interim solution. I've left the testing deliberately light for now until we've decided that this is actually the correct approach. The implementation is largely boilerplated from that of `matchN`. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Id74e40369bf16c7a3d011545890f0a47505b26cb Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200942 Reviewed-by: Daniel Martí <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 7a3260c commit 276d940

File tree

3 files changed

+322
-0
lines changed

3 files changed

+322
-0
lines changed

cue/testdata/builtins/matchif.txtar

+282
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
-- in.cue --
2+
regularFields: {
3+
[_]: matchIf({x!: >2}, {y!: 5}, {y!: 1})
4+
ok1: {x: 10, y: 5}
5+
ok2: {x: 11, y: 5}
6+
ok3: {x: 2, y: 1}
7+
ok4: {x: 1, y: 1}
8+
err1: {x: 10, y: 6}
9+
err2: {x: 11, y: 6}
10+
err3: {x: 2, y: 5}
11+
err4: {x: 1, y: 2}
12+
}
13+
-- out/eval/stats --
14+
Leaks: 24
15+
Freed: 74
16+
Reused: 69
17+
Allocs: 29
18+
Retain: 24
19+
20+
Unifications: 98
21+
Conjuncts: 154
22+
Disjuncts: 98
23+
-- diff/-out/evalalpha<==>+out/eval --
24+
diff old new
25+
--- old
26+
+++ new
27+
@@ -2,22 +2,18 @@
28+
regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
29+
./in.cue:2:7
30+
./in.cue:2:30
31+
- ./in.cue:7:8
32+
./in.cue:7:19
33+
regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
34+
./in.cue:2:7
35+
./in.cue:2:30
36+
- ./in.cue:8:8
37+
./in.cue:8:19
38+
regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
39+
./in.cue:2:7
40+
./in.cue:2:39
41+
- ./in.cue:9:8
42+
./in.cue:9:18
43+
regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
44+
./in.cue:2:7
45+
./in.cue:2:39
46+
- ./in.cue:10:8
47+
./in.cue:10:18
48+
49+
Result:
50+
@@ -45,7 +41,6 @@
51+
// [eval] regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
52+
// ./in.cue:2:7
53+
// ./in.cue:2:30
54+
- // ./in.cue:7:8
55+
// ./in.cue:7:19
56+
x: (int){ 10 }
57+
y: (int){ 6 }
58+
@@ -54,7 +49,6 @@
59+
// [eval] regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
60+
// ./in.cue:2:7
61+
// ./in.cue:2:30
62+
- // ./in.cue:8:8
63+
// ./in.cue:8:19
64+
x: (int){ 11 }
65+
y: (int){ 6 }
66+
@@ -63,7 +57,6 @@
67+
// [eval] regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
68+
// ./in.cue:2:7
69+
// ./in.cue:2:39
70+
- // ./in.cue:9:8
71+
// ./in.cue:9:18
72+
x: (int){ 2 }
73+
y: (int){ 5 }
74+
@@ -72,7 +65,6 @@
75+
// [eval] regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
76+
// ./in.cue:2:7
77+
// ./in.cue:2:39
78+
- // ./in.cue:10:8
79+
// ./in.cue:10:18
80+
x: (int){ 1 }
81+
y: (int){ 2 }
82+
-- out/eval --
83+
Errors:
84+
regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
85+
./in.cue:2:7
86+
./in.cue:2:30
87+
./in.cue:7:8
88+
./in.cue:7:19
89+
regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
90+
./in.cue:2:7
91+
./in.cue:2:30
92+
./in.cue:8:8
93+
./in.cue:8:19
94+
regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
95+
./in.cue:2:7
96+
./in.cue:2:39
97+
./in.cue:9:8
98+
./in.cue:9:18
99+
regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
100+
./in.cue:2:7
101+
./in.cue:2:39
102+
./in.cue:10:8
103+
./in.cue:10:18
104+
105+
Result:
106+
(_|_){
107+
// [eval]
108+
regularFields: (_|_){
109+
// [eval]
110+
ok1: (struct){
111+
x: (int){ 10 }
112+
y: (int){ 5 }
113+
}
114+
ok2: (struct){
115+
x: (int){ 11 }
116+
y: (int){ 5 }
117+
}
118+
ok3: (struct){
119+
x: (int){ 2 }
120+
y: (int){ 1 }
121+
}
122+
ok4: (struct){
123+
x: (int){ 1 }
124+
y: (int){ 1 }
125+
}
126+
err1: (_|_){
127+
// [eval] regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
128+
// ./in.cue:2:7
129+
// ./in.cue:2:30
130+
// ./in.cue:7:8
131+
// ./in.cue:7:19
132+
x: (int){ 10 }
133+
y: (int){ 6 }
134+
}
135+
err2: (_|_){
136+
// [eval] regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
137+
// ./in.cue:2:7
138+
// ./in.cue:2:30
139+
// ./in.cue:8:8
140+
// ./in.cue:8:19
141+
x: (int){ 11 }
142+
y: (int){ 6 }
143+
}
144+
err3: (_|_){
145+
// [eval] regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
146+
// ./in.cue:2:7
147+
// ./in.cue:2:39
148+
// ./in.cue:9:8
149+
// ./in.cue:9:18
150+
x: (int){ 2 }
151+
y: (int){ 5 }
152+
}
153+
err4: (_|_){
154+
// [eval] regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
155+
// ./in.cue:2:7
156+
// ./in.cue:2:39
157+
// ./in.cue:10:8
158+
// ./in.cue:10:18
159+
x: (int){ 1 }
160+
y: (int){ 2 }
161+
}
162+
}
163+
}
164+
-- out/evalalpha --
165+
Errors:
166+
regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
167+
./in.cue:2:7
168+
./in.cue:2:30
169+
./in.cue:7:19
170+
regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
171+
./in.cue:2:7
172+
./in.cue:2:30
173+
./in.cue:8:19
174+
regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
175+
./in.cue:2:7
176+
./in.cue:2:39
177+
./in.cue:9:18
178+
regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
179+
./in.cue:2:7
180+
./in.cue:2:39
181+
./in.cue:10:18
182+
183+
Result:
184+
(_|_){
185+
// [eval]
186+
regularFields: (_|_){
187+
// [eval]
188+
ok1: (struct){
189+
x: (int){ 10 }
190+
y: (int){ 5 }
191+
}
192+
ok2: (struct){
193+
x: (int){ 11 }
194+
y: (int){ 5 }
195+
}
196+
ok3: (struct){
197+
x: (int){ 2 }
198+
y: (int){ 1 }
199+
}
200+
ok4: (struct){
201+
x: (int){ 1 }
202+
y: (int){ 1 }
203+
}
204+
err1: (_|_){
205+
// [eval] regularFields.err1: invalid value {x:10,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
206+
// ./in.cue:2:7
207+
// ./in.cue:2:30
208+
// ./in.cue:7:19
209+
x: (int){ 10 }
210+
y: (int){ 6 }
211+
}
212+
err2: (_|_){
213+
// [eval] regularFields.err2: invalid value {x:11,y:6} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 5 and 6:
214+
// ./in.cue:2:7
215+
// ./in.cue:2:30
216+
// ./in.cue:8:19
217+
x: (int){ 11 }
218+
y: (int){ 6 }
219+
}
220+
err3: (_|_){
221+
// [eval] regularFields.err3: invalid value {x:2,y:5} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 5:
222+
// ./in.cue:2:7
223+
// ./in.cue:2:39
224+
// ./in.cue:9:18
225+
x: (int){ 2 }
226+
y: (int){ 5 }
227+
}
228+
err4: (_|_){
229+
// [eval] regularFields.err4: invalid value {x:1,y:2} (does not satisfy matchIf({x!:>2}, {y!:5}, {y!:1})): conflicting values 1 and 2:
230+
// ./in.cue:2:7
231+
// ./in.cue:2:39
232+
// ./in.cue:10:18
233+
x: (int){ 1 }
234+
y: (int){ 2 }
235+
}
236+
}
237+
}
238+
-- out/compile --
239+
--- in.cue
240+
{
241+
regularFields: {
242+
[_]: matchIf({
243+
x!: >2
244+
}, {
245+
y!: 5
246+
}, {
247+
y!: 1
248+
})
249+
ok1: {
250+
x: 10
251+
y: 5
252+
}
253+
ok2: {
254+
x: 11
255+
y: 5
256+
}
257+
ok3: {
258+
x: 2
259+
y: 1
260+
}
261+
ok4: {
262+
x: 1
263+
y: 1
264+
}
265+
err1: {
266+
x: 10
267+
y: 6
268+
}
269+
err2: {
270+
x: 11
271+
y: 6
272+
}
273+
err3: {
274+
x: 2
275+
y: 5
276+
}
277+
err4: {
278+
x: 1
279+
y: 2
280+
}
281+
}
282+
}

internal/core/compile/predeclared.go

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ func predeclared(n *ast.Ident) adt.Expr {
4444
return lenBuiltin
4545
case "close", "__close":
4646
return closeBuiltin
47+
case "matchIf", "__matchIf":
48+
return matchIfBuiltin
4749
case "matchN", "__matchN":
4850
return matchNBuiltin
4951
case "and", "__and":

internal/core/compile/validator.go

+38
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,44 @@ var matchNBuiltin = &adt.Builtin{
6868
},
6969
}
7070

71+
// matchIf is a validator that checks that if the first argument unifies with
72+
// self, the second argument also unifies with self, otherwise the third
73+
// argument unifies with self.
74+
// The same finalization heuristics are applied to self as are applied
75+
// in matchN.
76+
var matchIfBuiltin = &adt.Builtin{
77+
Name: "matchIf",
78+
Params: []adt.Param{topParam, topParam, topParam, topParam},
79+
Result: adt.BoolKind,
80+
NonConcrete: true,
81+
Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
82+
if !c.IsValidator {
83+
return c.NewErrf("matchIf is a validator and should not be used as a function")
84+
}
85+
86+
self := finalizeSelf(c, args[0])
87+
if err := bottom(c, self); err != nil {
88+
return &adt.Bool{B: false}
89+
}
90+
ifSchema, thenSchema, elseSchema := args[1], args[2], args[3]
91+
v := unifyValidator(c, self, ifSchema)
92+
var chosenSchema adt.Value
93+
if err := validate.Validate(c, v, finalCfg); err == nil {
94+
chosenSchema = thenSchema
95+
} else {
96+
chosenSchema = elseSchema
97+
}
98+
v = unifyValidator(c, self, chosenSchema)
99+
err := validate.Validate(c, v, finalCfg)
100+
if err == nil {
101+
return &adt.Bool{B: true}
102+
}
103+
// TODO should we also include in the error something about the fact that
104+
// the if condition passed or failed?
105+
return err
106+
},
107+
}
108+
71109
var finalCfg = &validate.Config{Final: true}
72110

73111
// finalizeSelf ensures a value is fully evaluated and then strips it of any

0 commit comments

Comments
 (0)