feature.bend
bendvy-ecs-core@0.1.0.4 · 173 lines · 36 definitions
1import Base
2import bendvy-ecs-core@0.1.0.4/schema_fragments.bend as SF
3import bendvy-ecs-core@0.1.0.4/schedule_provision.bend as P
4
5
6
7
8
9type Tree<S:Data> is Data:
10 Empty{}
11 Feature{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
12 Sequence{left:Tree<S>,right:Tree<S>}
13
14
15type Error is Data:
16 DuplicateFeature{name:String}
17 MissingDependency{name:String}
18 SchemaConflict{error:SF.Error}
19
20type Validation is Data:
21 Valid{}
22 Invalid{error:Error}
23
24
25type Composition<S:Data,Owners:Type,Output:Type> is Type:
26 Built{output:Output}
27 Rejected{error:Error,owners:Owners}
28
29
30def names(~S:Data,tree:Tree<S>) -> List<&2,String>:
31 match tree:
32 case Empty{}: []
33 case Feature{name,_,_}: [name]
34 case Sequence{left,right}: List.append(&2,String,names(~S,left),names(~S,right))
35
36
37def entries(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
38 match tree:
39 case Empty{}: []
40 case Feature{_,entries,_}: entries
41 case Sequence{left,right}: List.append(&2,SF.Entry<S>,entries(~S,left),entries(~S,right))
42
43
44def visibility(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
45 match tree:
46 case Empty{}: []
47 case Feature{_,own,dependencies}: List.append(&2,SF.Entry<S>,own,visibility(~S,dependencies))
48 case Sequence{left,right}: List.append(&2,SF.Entry<S>,visibility(~S,left),visibility(~S,right))
49def contains(items:List<&2,String>,+name:String) -> Bool:
50 match items:
51 case []: False{}
52 case head <> rest: Bool.or(String.eq(head,name),contains(rest,name))
53def repeated(present:Bool,name:String,next:Unit -> Validation) -> Validation:
54 match present:
55 case True{}: Invalid{DuplicateFeature{name}}
56 case False{}: next(Unit{})
57def duplicates(items:List<&2,String>,+seen:List<&2,String>) -> Validation:
58 match items:
59 case []: Valid{}
60 case +name <> rest: repeated(contains(seen,name),name,_ => duplicates(rest,name <> seen))
61def missing(present:Bool,name:String,next:Unit -> Validation) -> Validation:
62 match present:
63 case False{}: Invalid{MissingDependency{name}}
64 case True{}: next(Unit{})
65def required(items:List<&2,String>,+selected:List<&2,String>) -> Validation:
66 match items:
67 case []: Valid{}
68 case +name <> rest: missing(contains(selected,name),name,_ => required(rest,selected))
69def continue_validation(result:Validation,next:Unit -> Validation) -> Validation:
70 match result:
71 case Invalid{error}: Invalid{error}
72 case Valid{}: next(Unit{})
73def dependencies(~S:Data,tree:Tree<S>,+selected:List<&2,String>) -> Validation:
74 match tree:
75 case Empty{}: Valid{}
76 case Feature{_,_,deps}: required(names(~S,deps),selected)
77 case Sequence{left,right}: continue_validation(dependencies(~S,left,selected),_ => dependencies(~S,right,selected))
78def schema_validation(result:SF.Validation) -> Validation:
79 match result:
80 case SF.Valid{}: Valid{}
81 case SF.Invalid{error}: Invalid{SchemaConflict{error}}
82
83
84
85def validate(~S:Data,+tree:Tree<S>) -> Validation:
86 continue_validation(duplicates(names(~S,tree),[]),_ => continue_validation(dependencies(~S,tree,names(~S,tree)),_ => schema_validation(SF.validate(~S,entries(~S,tree)))))
87def compose_checked(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,result:Validation,owners:Owners) -> Composition<S,Owners,Output>:
88 match result:
89 case Invalid{error}: Rejected{error,owners}
90 case Valid{}: Built{build(owners)}
91
92
93
94
95def compose(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,tree:Tree<S>,owners:Owners) -> Composition<S,Owners,Output>:
96 compose_checked(~S,~Owners,~Output,~build,validate(~S,tree),owners)
97
98
99
100
101
102type Pack<Head:Type,Tail:Type> is Type:
103 Pack{head:Head,tail:Tail}
104
105
106type Output<S:Data,Extra:Type> is Type:
107 Output{extra:Extra,bootstrap:P.Plan<S>,update:P.Plan<S>}
108def combine(~S:Data,~Ctx:Type,~HE:Type,~TE:Type,observed:Ctx & Output<S,TE>,head:Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
109 match observed head:
110 case (ctx,Output{te,tb,tu}) Output{he,hb,hu}: (ctx,Output{Pack{he,te},P.Sequence{hb,tb},P.Sequence{hu,tu}})
111def build_head_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~tail:Ctx -> TO -> Ctx & Output<S,TE>,owners:TO,observed:Ctx & Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
112 match observed:
113 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output)
114def build_head(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~head:Ctx -> HO -> Ctx & Output<S,HE>,~tail:Ctx -> TO -> Ctx & Output<S,TE>,ctx:Ctx,owners:Pack<HO,TO>) -> Ctx & Output<S,Pack<HE,TE>>:
115 match owners:
116 case Pack{ho,to}: build_head_done(~S,~Ctx,~HO,~TO,~HE,~TE,~tail,to,head(ctx,ho))
117def build_empty(~S:Data,~Ctx:Type,ctx:Ctx,owner:Unit) -> Ctx & Output<S,Unit>:
118 (ctx,Output{owner,P.Empty{},P.Empty{}})
119
120
121
122
123
124type Definition<S:Data> is Data:
125 Definition{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
126
127
128
129type Feature<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
130 FeatureOwner{definition:Definition<S>,owners:Owners,build:Ctx -> Owners -> Ctx & Output<S,Extra>}
131
132
133type Recipe<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
134 Recipe{selected:Tree<S>,owners:Owners,build:Ctx -> Owners -> Ctx & Output<S,Extra>}
135
136
137
138type RecipeResult<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
139 Completed{ctx:Ctx,output:Output<S,Extra>}
140 Refused{error:Error,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>}
141def definition_tree(~S:Data,definition:Definition<S>) -> Tree<S>:
142 match definition:
143 case Definition{name,entries,dependencies}: Feature{name,entries,dependencies}
144def callbacks_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,tail:Ctx -> TO -> Ctx & Output<S,TE>,owners:TO,observed:Ctx & Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
145 match observed:
146 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output)
147def callbacks(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Ctx -> HO -> Ctx & Output<S,HE>,tail:Ctx -> TO -> Ctx & Output<S,TE>,ctx:Ctx,owners:Pack<HO,TO>) -> Ctx & Output<S,Pack<HE,TE>>:
148 match owners:
149 case Pack{ho,to}: callbacks_done(~S,~Ctx,~HO,~TO,~HE,~TE,tail,to,head(ctx,ho))
150
151
152
153def prepend(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Feature<S,Ctx,HO,HE>,tail:Recipe<S,Ctx,TO,TE>) -> Recipe<S,Ctx,Pack<HO,TO>,Pack<HE,TE>>:
154 match head tail:
155 case FeatureOwner{definition,ho,head} Recipe{tree,to,tail}: Recipe{Sequence{definition_tree(~S,definition),tree},Pack{ho,to},ctx => owners => callbacks(~S,~Ctx,~HO,~TO,~HE,~TE,head,tail,ctx,owners)}
156
157
158def recipe_empty(~S:Data,~Ctx:Type) -> Recipe<S,Ctx,Unit,Unit>:
159 Recipe{Empty{},Unit{},ctx => owners => build_empty(~S,~Ctx,ctx,owners)}
160def recipe_completed(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,observed:Ctx & Output<S,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
161 match observed:
162 case (ctx,output): Completed{ctx,output}
163def recipe_checked(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,result:Validation,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
164 match result recipe:
165 case Invalid{error} recipe: Refused{error,ctx,recipe}
166 case Valid{} Recipe{_,owners,build}: recipe_completed(~S,~Ctx,~Owners,~Extra,build(ctx,owners))
167
168
169
170
171def run_recipe(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
172 match recipe:
173 case Recipe{+tree,owners,build}: recipe_checked(~S,~Ctx,~Owners,~Extra,validate(~S,tree),ctx,Recipe{tree,owners,build})