feature.bend
bendvy-ecs-core@0.1.0.3 · 180 lines · 40 definitions
1import Base
2import bendvy-ecs-core@0.1.0.3/schema_fragments.bend as SF
3import bendvy-ecs-core@0.1.0.3/execution_composer_layout.bend as Layout
4import bendvy-ecs-core@0.1.0.3/execution_composer_schema.bend as Schema
5import bendvy-ecs-core@0.1.0.3/execution_registry.bend as Registry
6import bendvy-ecs-core@0.1.0.3/dependency_registration.bend as Reg
7import bendvy-ecs-core@0.1.0.3/dependency_executor.bend as Exec
8import bendvy-ecs-core@0.1.0.3/dependency_composer_program.bend as Scoped
9import bendvy-ecs-core@0.1.0.3/dependency_composer.bend as Compose
10import bendvy-ecs-core@0.1.0.3/dependency_plan.bend as Plan
11
12
13
14
15
16type Tree<S:Data> is Data:
17 Empty{}
18 Feature{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
19 Sequence{left:Tree<S>,right:Tree<S>}
20
21
22type Error is Data:
23 DuplicateFeature{name:String}
24 MissingDependency{name:String}
25 SchemaConflict{error:SF.Error}
26
27type Validation is Data:
28 Valid{}
29 Invalid{error:Error}
30
31
32type Composition<S:Data,Owners:Type,Output:Type> is Type:
33 Built{output:Output}
34 Rejected{error:Error,owners:Owners}
35
36
37def names(~S:Data,tree:Tree<S>) -> List<&2,String>:
38 match tree:
39 case Empty{}: []
40 case Feature{name,_,_}: [name]
41 case Sequence{left,right}: List.append(&2,String,names(~S,left),names(~S,right))
42
43
44def entries(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
45 match tree:
46 case Empty{}: []
47 case Feature{_,entries,_}: entries
48 case Sequence{left,right}: List.append(&2,SF.Entry<S>,entries(~S,left),entries(~S,right))
49
50
51def visibility(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
52 match tree:
53 case Empty{}: []
54 case Feature{_,own,dependencies}: List.append(&2,SF.Entry<S>,own,visibility(~S,dependencies))
55 case Sequence{left,right}: List.append(&2,SF.Entry<S>,visibility(~S,left),visibility(~S,right))
56def contains(items:List<&2,String>,+name:String) -> Bool:
57 match items:
58 case []: False{}
59 case head <> rest: Bool.or(String.eq(head,name),contains(rest,name))
60def repeated(present:Bool,name:String,next:Unit -> Validation) -> Validation:
61 match present:
62 case True{}: Invalid{DuplicateFeature{name}}
63 case False{}: next(Unit{})
64def duplicates(items:List<&2,String>,+seen:List<&2,String>) -> Validation:
65 match items:
66 case []: Valid{}
67 case +name <> rest: repeated(contains(seen,name),name,_ => duplicates(rest,name <> seen))
68def missing(present:Bool,name:String,next:Unit -> Validation) -> Validation:
69 match present:
70 case False{}: Invalid{MissingDependency{name}}
71 case True{}: next(Unit{})
72def required(items:List<&2,String>,+selected:List<&2,String>) -> Validation:
73 match items:
74 case []: Valid{}
75 case +name <> rest: missing(contains(selected,name),name,_ => required(rest,selected))
76def continue_validation(result:Validation,next:Unit -> Validation) -> Validation:
77 match result:
78 case Invalid{error}: Invalid{error}
79 case Valid{}: next(Unit{})
80def dependencies(~S:Data,tree:Tree<S>,+selected:List<&2,String>) -> Validation:
81 match tree:
82 case Empty{}: Valid{}
83 case Feature{_,_,deps}: required(names(~S,deps),selected)
84 case Sequence{left,right}: continue_validation(dependencies(~S,left,selected),_ => dependencies(~S,right,selected))
85def schema_validation(result:SF.Validation) -> Validation:
86 match result:
87 case SF.Valid{}: Valid{}
88 case SF.Invalid{error}: Invalid{SchemaConflict{error}}
89
90
91
92def validate(~S:Data,+tree:Tree<S>) -> Validation:
93 continue_validation(duplicates(names(~S,tree),[]),_ => continue_validation(dependencies(~S,tree,names(~S,tree)),_ => schema_validation(SF.validate(~S,entries(~S,tree)))))
94def compose_checked(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,result:Validation,owners:Owners) -> Composition<S,Owners,Output>:
95 match result:
96 case Invalid{error}: Rejected{error,owners}
97 case Valid{}: Built{build(owners)}
98
99
100
101
102def compose(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,tree:Tree<S>,owners:Owners) -> Composition<S,Owners,Output>:
103 compose_checked(~S,~Owners,~Output,~build,validate(~S,tree),owners)
104
105
106
107type Pack<Head:Type,Tail:Type> is Type:
108 Pack{head:Head,tail:Tail}
109
110type Definition<S:Data> is Data:
111 Definition{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
112def definition_tree(~S:Data,definition:Definition<S>) -> Tree<S>:
113 match definition:
114 case Definition{name,entries,dependencies}:Feature{name,entries,dependencies}
115
116
117
118type Assembly<-S:Data,-ActualRegistry:Type> is Type:
119 Assembly{selected:Tree<S>,facts:Registry.Facts<ActualRegistry>,scope:Scoped.Scope}
120type AssemblyError is Data:
121 InvalidFeatures{error:Error}
122 InvalidComposition{error:Compose.Error}
123type Lowered<-ActualRegistry:Type> is Type:
124 Lowered{facts:Registry.Facts<ActualRegistry>,edges:List<&2,Plan.Edge>,invalid:Maybe<&2,AssemblyError>}
125def needs_key(nodes:List<&2,Plan.Node<String>>,+key:String) -> Bool:
126 match nodes:
127 case []:False{}
128 case Plan.Node{_,needs} <> rest:Bool.or(contains(needs,key),needs_key(rest,key))
129def selected_entry(~S:Data,selected:Bool,entry:SF.Entry<S>,rest:List<&2,SF.Entry<S>>) -> List<&2,SF.Entry<S>>:
130 match selected:
131 case True{}:entry <> rest
132 case False{}:rest
133def own_entries(~S:Data,entries:List<&2,SF.Entry<S>>,+nodes:List<&2,Plan.Node<String>>) -> List<&2,SF.Entry<S>>:
134 match entries:
135 case []:[]
136 case +entry <> rest:selected_entry(~S,needs_key(nodes,Compose.entry_key(~S,entry)),entry,own_entries(~S,rest,nodes))
137def from_facts(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation<Error,Out>),name:String,dependencies:Tree<S>,facts:Registry.Facts<Reg.Registry<World,Nodes,Error,Out,dispatch>>) -> Assembly<S,Reg.Registry<World,Nodes,Error,Out,dispatch>>:
138 match facts:
139 case Registry.Facts{Reg.Registry{nodes,+planNodes},deferred}:Assembly{Feature{name,own_entries(~S,Schema.inventory(~S,~components,~resources),planNodes),dependencies},Registry.Facts{Reg.Registry{nodes,planNodes},deferred},Scoped.Scope{Scoped.ids(planNodes),[]}}
140def scope_members(scope:Scoped.Scope) -> List<&2,U32>:
141 match scope:
142 case Scoped.Scope{members,_}:members
143def pair(~S:Data,~World:Type,~Left:Type,~Right:Type,~Error:Data,~Out:Data,~leftDispatch:Left -> World -> U32 -> Left & (World & Exec.Invocation<Error,Out>),~rightDispatch:Right -> World -> U32 -> Right & (World & Exec.Invocation<Error,Out>),left:Assembly<S,Reg.Registry<World,Left,Error,Out,leftDispatch>>,right:Assembly<S,Reg.Registry<World,Right,Error,Out,rightDispatch>>) -> Assembly<S,Reg.Registry<World,Reg.Product<Left,Right>,Error,Out,Reg.paired_dispatch(~World,~Left,~Right,~Error,~Out,~leftDispatch,~rightDispatch)>>:
144 match left right:
145 case Assembly{left,lf,+ls} Assembly{right,rf,+rs}:Assembly{Sequence{left,right},Registry.paired(~World,~Left,~Right,~Error,~Out,~leftDispatch,~rightDispatch,lf,rf),Scoped.Scope{List.append(&2,U32,scope_members(ls),scope_members(rs)),[ls,rs]}}
146def feature_error(validation:Validation) -> Maybe<&2,AssemblyError>:
147 match validation:
148 case Valid{}:None{}
149 case Invalid{error}:Some{InvalidFeatures{error}}
150def composition_error(error:Maybe<&2,Compose.Error>) -> Maybe<&2,AssemblyError>:
151 match error:
152 case None{}:None{}
153 case Some{error}:Some{InvalidComposition{error}}
154def first_error(first:Maybe<&2,AssemblyError>,second:Maybe<&2,AssemblyError>) -> Maybe<&2,AssemblyError>:
155 match first:
156 case Some{error}:Some{error}
157 case None{}:second
158def nesting_error(error:Maybe<&2,Scoped.NestError>) -> Maybe<&2,Compose.Error>:
159 match error:
160 case None{}:None{}
161 case Some{error}:Some{Compose.Nesting{error}}
162def lower_nested(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation<Error,Out>),selected:Tree<S>,facts:Registry.Facts<Reg.Registry<World,Nodes,Error,Out,dispatch>>,nested:Scoped.Nesting) -> Lowered<Reg.Registry<World,Nodes,Error,Out,dispatch>>:
163 match facts nested:
164 case Registry.Facts{Reg.Registry{nodes,+planNodes},deferred} Scoped.Nesting{edges,invalid}:Lowered{Registry.Facts{Reg.Registry{nodes,planNodes},deferred},edges,first_error(feature_error(validate(~S,selected)),composition_error(Compose.first_error(nesting_error(invalid),Compose.first_error(Compose.schema_error(Schema.validate(~S,~components,~resources)),Compose.schedule_error(Compose.missing(~S,planNodes,Schema.inventory(~S,~components,~resources)))))))}
165def lower(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation<Error,Out>),groups:List<&2,Scoped.Nested>,assembly:Assembly<S,Reg.Registry<World,Nodes,Error,Out,dispatch>>) -> Lowered<Reg.Registry<World,Nodes,Error,Out,dispatch>>:
166 match assembly:
167 case Assembly{selected,facts,scope}:lower_nested(~S,~components,~resources,~World,~Nodes,~Error,~Out,~dispatch,selected,facts,Scoped.nested(groups,scope))
168
169type Application<-Root:Type,-Provisions:Type> is Type:
170 Application{root:Root,provisions:Provisions,invalid:Maybe<&2,AssemblyError>}
171type Report<Out:Data> is Data:
172 Executed{report:Out}
173 Refused{error:AssemblyError}
174def returned(-Root:Type,-Provisions:Type,-Out:Data,provisions:Provisions,result:Root & Out) -> Application<Root,Provisions> & Report<Out>:
175 match result:
176 case (root,report):(Application{root,provisions,None{}},Executed{report})
177def run(-Root:Type,-Provisions:Type,-Out:Data,invoke:Root -> Root & Out,application:Application<Root,Provisions>) -> Application<Root,Provisions> & Report<Out>:
178 match application:
179 case Application{root,provisions,Some{+error}}:(Application{root,provisions,Some{error}},Refused{error})
180 case Application{root,provisions,None{}}:returned(Root,Provisions,Out,provisions,invoke(root))