-
Notifications
You must be signed in to change notification settings - Fork 12
/
lineage.cue
211 lines (191 loc) · 6.92 KB
/
lineage.cue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package thema
import (
"list"
)
// A Lineage is the top-level container in thema, holding the complete
// evolutionary history of a particular kind of object: every schema that has
// ever existed for that object, and the lenses that allow translating between
// those schema versions.
#Lineage: {
// joinSchema governs the shape of schema that may be expressed in a
// lineage. It is the least upper bound, or join, of the acceptable schema
// value space; the schemas defined in this lineage must be instances of the
// joinSchema.
//
// In the base case, the joinSchema is unconstrained/top - any value may be
// used as a schema.
//
// A lineage's joinSchema may never change as the lineage evolves.
//
// TODO should it be an open struct rather than top?
// TODO can this be a def? should it?
joinSchema: _
// The name of the thing being schematized in this lineage.
name: string
// TODO(must) https://github.com/cue-lang/cue/issues/943
// name: must(isconcrete(name), "all lineages must have a name")
// A Sequence is a non-empty ordered list of schemas, with the property that
// every schema in the sequence is backwards compatible with (subsumes) its
// predecessors.
// #Sequence: [...joinSchema]
#Sequence: [...joinSchema] & list.MinItems(1)
// This exists because constraining with list.MinItems(1) isn't able to
// tell the evaluator that it is always safe to reference #Sequence[0],
// resulting in lots of garbage errors.
//
// Unfortunately, this allows empty lineage declarations by making the first
// schema an actual joinSchema, which we do not want to be valid text for
// authors to write.
//
// TODO figure out how to express the constraint without blowing up our Go logic
#Sequence: [joinSchema, ...joinSchema]
#Lens: {
// The last schema in the previous sequence; logical predecessor
ancestor: joinSchema
// The first schema in this sequence; logical successor
descendant: joinSchema
forward: {
to: descendant
from: ancestor
rel: descendant
lacunas: [...#Lacuna]
translated: to & rel
}
reverse: {
to: ancestor
from: descendant
rel: ancestor
lacunas: [...#Lacuna]
translated: to & rel
}
}
// seqs is the list of sequences of schema that comprise the overall
// lineage, along with the lenses that allow translation back and forth
// across sequences.
seqs: [
{
schemas: #Sequence
},
...{
schemas: #Sequence
lens: #Lens
}
]
// Constrain that ancestor and descendant for each defined lens are the
// final and initial schemas in the predecessor seq and the seq containing
// the lens, respectively.
//
// FIXME figure out how to actually do this correctly
// if len(seqs) > 1 {
// seqs: [for seqv, seq in S {
// if seqv == 0 { {} }
// if seqv != 0 {
// lens: ancestor: S[seqv-1].schemas[len(S[seqv-1].schemas)-1]
// lens: descendant: seq.schemas[0]
// }
// }]
// }
// TODO check subsumption (backwards compat) of each schema with its successor natively in CUE
}
_#vSch: {
v: #SyntacticVersion
sch: _
}
// Helper that extracts SyntacticVersion of latest schema.
//
// Internal only, because writing programs where the text expresses an intent to
// float across backwards-incompatible changes is exactly the antipattern thema
// is trying to get away from.
//
// TODO functionize
_latest: {
lin: #Lineage
out: #SyntacticVersion & [len(lin.seqs)-1, len(lin.seqs[len(lin.seqs)-1].schemas)-1]
}
// Helper that flattens all schema into a single list, putting their
// version in an adjacent property.
//
// TODO functionize
_all: {
lin: #Lineage
out: [..._#vSch] & list.FlattenN([for seqv, seq in lin.seqs {
[for schv, seqsch in seq.schemas {
v: [seqv, schv]
sch: seqsch
}]
}], 1)
}
// Helper that constructs a one-dimensional list of all the schema versions that
// exist in a lineage.
_allv: {
lin: #Lineage
out: [...#SyntacticVersion] & list.FlattenN(
[for seqv, seq in lin.seqs {
[for schv, _ in seq.schemas { [seqv, schv] }]
}], 1)
}
// Get a single schema version from the lineage.
#Pick: {
lin: #Lineage
// The schema version to pick. Either:
//
// * An exact #SyntacticVersion: [1, 0]
// * Just the sequence number: [1]
//
// The latter form will select the latest schema within the given
// sequence.
v: #SyntacticVersion | [int & >= 0]
v: [<len(lin.seqs), <len(lin.seqs[v[0]].schemas)] | [<len(lin.seqs)]
// TODO(must) https://github.com/cue-lang/cue/issues/943
// must(isconcrete(v[0]), "must specify a concrete sequence number")
let _v = #SyntacticVersion & [
v[0],
if len(v) == 2 { v[1] },
if len(v) == 1 { len(lin.seqs[v[0].schemas]) - 1 },
]
out: lin.seqs[_v[0]].schemas[_v[1]]
// TODO ^ apply object headers, etc.
}
// SyntacticVersion is an ordered pair of non negative integers. It represents
// the version of a schema within a lineage, or the version of an instance that
// is valid with respect to a schema having the same version.
//
// Most version numbering systems leave it to the author to assign a version
// number. In Thema, a schema's version is a property of the position of the
// schema within the lineage's list of sequences, which in turn is governed by
// Thema's constraints on backwards compatibility and lens completeness. A
// SyntacticVersion ordered pair is a coordinate system, giving first the index
// of the sequence within the lineage, and second the index of the schema within
// that sequence.
//
// In a Turing-incomplete language like CUE, schema/sequence backwards
// compatibility are properties that can be reliably checked by the CUE's
// evaluator. Relating version numbers to these checkable properties turns Thema
// versions into an encoding of those properties - hence the name,
// "SyntacticVersion".
#SyntacticVersion: [int & >= 0, int & >= 0]
// TODO functionize
_cmpSV: {
l: #SyntacticVersion
r: #SyntacticVersion
out: -1 | 0 | 1
out: {
if l[0] < r[0] { -1 }
if l[0] > r[0] { 1 }
if l[0] == r[0] && l[1] < r[1] { -1 }
if l[0] == r[0] && l[1] > r[1] { 1 }
if l == r { 0 }
}
}
// TODO functionize
_flatidx: {
lin: #Lineage
v: #SyntacticVersion
let inlin = lin
// TODO this constraint should be fine to express, but uncommenting it seems
// to blow up Go programs when they call in to unrelated pseudofuncs with
// complaints about an incomplete v
// _has: (_allv & { lin: inlin }).out & list.Contains(v)
let all = (_all & { lin: inlin }).out
out: {for i, sch in all if sch.v == v { i }}
}