macroonz_compiler/recipe/account/
informed.rs1use super::admit::{informed_codecs, informed_members, informed_relations, informed_vocabularies};
4use super::collisions::ensure_standard_names;
5use super::contracts::{
6 ensure_evidence_targets, ensure_projection_contracts, ensure_transition_account,
7};
8use super::restore::{restore_projection_references, restore_relation_references};
9use super::settle::missing_vocabulary;
10use super::{
11 EffectiveProjection, EvidenceTarget, PROJECTION_LIMIT, ProjectionDisposition,
12 ProjectionStanding, Recipe, RecipeCodec, RecipeError, RecipeEvidence, RecipeIssue,
13 RecipeMember, RecipeParts, RecipeRelation, RecipeRole, VOCABULARY_LIMIT,
14};
15use crate::bounded::KeyedRoster;
16use crate::recipe::evidence_position;
17use crate::support::SupportName;
18use crate::token::{GeneratedTree, SpanHandle};
19
20impl RecipeMember {
21 pub(in crate::recipe) fn authored(
22 spelling: String,
23 name: crate::token::GeneratedToken,
24 at: SpanHandle,
25 ) -> Self {
26 Self { spelling, name, at }
27 }
28
29 #[must_use]
31 pub fn spelling(&self) -> &str {
32 self.spelling.as_str()
33 }
34
35 #[must_use]
37 pub const fn name_token(&self) -> &crate::token::GeneratedToken {
38 &self.name
39 }
40
41 #[must_use]
43 pub(in crate::recipe) const fn at(&self) -> SpanHandle {
44 self.at
45 }
46}
47
48impl EvidenceTarget {
49 pub(in crate::recipe) const fn named(vocabulary: String) -> Self {
50 Self { vocabulary }
51 }
52
53 #[must_use]
55 pub fn name(&self) -> &str {
56 self.vocabulary.as_str()
57 }
58}
59
60impl RecipeCodec {
61 pub(in crate::recipe) fn informed(
62 name: String,
63 content: crate::codec::CodecContent,
64 at: SpanHandle,
65 refusal_at: SpanHandle,
66 direction_at: SpanHandle,
67 ) -> Self {
68 Self {
69 name,
70 content,
71 at,
72 refusal_at,
73 direction_at,
74 }
75 }
76
77 #[must_use]
79 pub fn name(&self) -> &str {
80 self.name.as_str()
81 }
82
83 #[must_use]
85 pub const fn content(&self) -> &crate::codec::CodecContent {
86 &self.content
87 }
88
89 pub(in crate::recipe) const fn at(&self) -> SpanHandle {
90 self.at
91 }
92
93 pub(in crate::recipe) const fn refusal_at(&self) -> SpanHandle {
94 self.refusal_at
95 }
96
97 pub(in crate::recipe) const fn direction_at(&self) -> SpanHandle {
98 self.direction_at
99 }
100}
101
102impl super::RecipeVocabulary {
103 pub(super) fn informed(
104 name: String,
105 name_token: crate::token::GeneratedToken,
106 members: Vec<RecipeMember>,
107 at: SpanHandle,
108 ) -> Result<Self, RecipeError> {
109 let members = informed_members(name.as_str(), members, at)?;
110 Ok(Self {
111 name,
112 name_token,
113 members,
114 at,
115 })
116 }
117
118 #[must_use]
120 pub fn name(&self) -> &str {
121 self.name.as_str()
122 }
123
124 #[must_use]
126 pub const fn name_token(&self) -> &crate::token::GeneratedToken {
127 &self.name_token
128 }
129
130 #[must_use]
132 pub const fn members(&self) -> &KeyedRoster<RecipeMember, String, VOCABULARY_LIMIT> {
133 &self.members
134 }
135}
136
137impl RecipeEvidence {
138 pub(in crate::recipe) const fn captured(
139 role: RecipeRole,
140 target: Option<EvidenceTarget>,
141 body: crate::token::CapturedInput,
142 at: SpanHandle,
143 ) -> Self {
144 Self {
145 role,
146 target,
147 body,
148 at,
149 }
150 }
151
152 #[must_use]
154 pub const fn role(&self) -> RecipeRole {
155 self.role
156 }
157
158 #[must_use]
160 pub const fn target(&self) -> Option<&EvidenceTarget> {
161 self.target.as_ref()
162 }
163
164 #[must_use]
166 pub const fn body(&self) -> &crate::token::CapturedInput {
167 &self.body
168 }
169
170 pub(crate) const fn at(&self) -> SpanHandle {
171 self.at
172 }
173}
174
175impl Recipe {
176 pub(in crate::recipe) fn informed(offered: RecipeParts) -> Result<Self, RecipeError> {
177 let RecipeParts {
178 module_name,
179 module_name_token,
180 module_head,
181 authored_body,
182 authored_declaration,
183 module_body_at,
184 vocabularies,
185 relations,
186 transition_relation,
187 codecs,
188 mut projections,
189 evidence,
190 support,
191 } = offered;
192 let vocabularies = informed_vocabularies(vocabularies)?;
193 let relations = informed_relations(
194 relations,
195 vocabularies.as_ref(),
196 transition_relation.as_deref(),
197 )?;
198 let codecs = informed_codecs(codecs)?;
199 ensure_evidence_targets(&evidence, vocabularies.as_ref())?;
200 ensure_transition_account(
201 transition_relation.as_deref(),
202 relations.as_ref(),
203 vocabularies.as_ref(),
204 )?;
205 resolve_typestate_subject(&mut projections, vocabularies.as_ref())?;
206 ensure_projection_contracts(
207 &projections,
208 codecs.as_ref(),
209 relations.as_ref(),
210 transition_relation.as_deref(),
211 )?;
212 ensure_standard_names(
213 &projections,
214 vocabularies.as_ref(),
215 relations.as_ref(),
216 transition_relation.as_deref(),
217 )?;
218 Ok(Self {
219 module_name,
220 module_name_token,
221 module_head,
222 authored_body,
223 authored_declaration,
224 module_body_at,
225 vocabularies,
226 relations,
227 transition_relation,
228 codecs,
229 projections,
230 evidence,
231 support,
232 })
233 }
234
235 #[must_use]
237 pub fn module_name(&self) -> &str {
238 self.module_name.as_str()
239 }
240
241 #[must_use]
243 pub const fn module_name_token(&self) -> &crate::token::GeneratedToken {
244 &self.module_name_token
245 }
246
247 pub(in crate::recipe) const fn module_head(&self) -> &GeneratedTree {
248 &self.module_head
249 }
250
251 pub(in crate::recipe) const fn authored_body(&self) -> &GeneratedTree {
252 &self.authored_body
253 }
254
255 pub(in crate::recipe) fn restore_authored_references(
257 &self,
258 tree: &GeneratedTree,
259 ) -> GeneratedTree {
260 let mut restored = tree.clone();
261 for relation in self.relations() {
262 restored = restore_relation_references(&restored, relation);
263 }
264 for role in RecipeRole::ALL.iter().copied() {
265 let Some(effective) = self.effective(role) else {
266 continue;
267 };
268 restored = restore_projection_references(&restored, effective);
269 }
270 restored.restored_references_from(&self.authored_declaration)
271 }
272
273 pub(in crate::recipe) const fn module_body_at(&self) -> Option<SpanHandle> {
274 self.module_body_at
275 }
276
277 pub fn vocabularies(&self) -> impl Iterator<Item = &super::RecipeVocabulary> {
279 self.vocabularies.iter().flat_map(KeyedRoster::members)
280 }
281
282 #[must_use]
284 pub fn vocabulary(&self, name: &str) -> Option<&super::RecipeVocabulary> {
285 self.vocabularies
286 .as_ref()
287 .and_then(|vocabularies| vocabularies.get(name))
288 }
289
290 pub fn relations(&self) -> impl Iterator<Item = &RecipeRelation> {
292 self.relations.iter().flat_map(KeyedRoster::members)
293 }
294
295 #[must_use]
297 pub fn relation(&self, name: &str) -> Option<&RecipeRelation> {
298 self.relations
299 .as_ref()
300 .and_then(|relations| relations.get(name))
301 }
302
303 #[must_use]
305 pub fn transition_relation(&self) -> Option<&RecipeRelation> {
306 self.transition_relation
307 .as_deref()
308 .and_then(|name| self.relation(name))
309 }
310
311 pub fn codecs(&self) -> impl Iterator<Item = &RecipeCodec> {
313 self.codecs.iter().flat_map(KeyedRoster::members)
314 }
315
316 #[must_use]
318 pub fn codec(&self, name: &str) -> Option<&RecipeCodec> {
319 self.codecs.as_ref().and_then(|codecs| codecs.get(name))
320 }
321
322 pub(in crate::recipe) fn transition_account(
323 &self,
324 ) -> Option<(
325 &super::RecipeVocabulary,
326 &super::RecipeVocabulary,
327 &RecipeRelation,
328 )> {
329 let relation = self.transition_relation()?;
330 let left = self.vocabulary(relation.left_vocabulary())?;
331 let right = self.vocabulary(relation.right_vocabulary())?;
332 Some((left, right, relation))
333 }
334
335 #[must_use]
337 pub(in crate::recipe) fn standing(&self, role: RecipeRole) -> &ProjectionStanding {
338 role.standing(&self.projections)
339 }
340
341 #[must_use]
343 pub fn projection_disposition(&self, role: RecipeRole) -> ProjectionDisposition {
344 match self.standing(role) {
345 ProjectionStanding::Generated(_) => ProjectionDisposition::Generated,
346 ProjectionStanding::NotRequested => ProjectionDisposition::NotRequested,
347 ProjectionStanding::FeatureUnavailable => ProjectionDisposition::FeatureUnavailable,
348 ProjectionStanding::TargetUnavailable => ProjectionDisposition::TargetUnavailable,
349 }
350 }
351
352 #[must_use]
354 pub fn effective(&self, role: RecipeRole) -> Option<&EffectiveProjection> {
355 match self.standing(role) {
356 ProjectionStanding::Generated(effective) => Some(effective),
357 ProjectionStanding::NotRequested
358 | ProjectionStanding::FeatureUnavailable
359 | ProjectionStanding::TargetUnavailable => None,
360 }
361 }
362
363 pub(in crate::recipe) fn selected_roles(&self) -> impl Iterator<Item = RecipeRole> + '_ {
365 RecipeRole::ALL
366 .iter()
367 .copied()
368 .filter(|role| matches!(self.standing(*role), ProjectionStanding::Generated(_)))
369 }
370
371 #[must_use]
373 pub(crate) const fn support(&self) -> Option<&SupportName> {
374 self.support.as_ref()
375 }
376
377 #[must_use]
379 pub fn evidence(&self, role: RecipeRole) -> Option<&RecipeEvidence> {
380 let position = evidence_position(role)?;
381 self.evidence.get(position).and_then(Option::as_ref)
382 }
383
384 pub(crate) fn baked_type_names(&self) -> Vec<String> {
385 let mut names = self
386 .effective(RecipeRole::RelationTables)
387 .into_iter()
388 .flat_map(EffectiveProjection::relation_tables)
389 .map(|table| table.relation().to_owned())
390 .collect::<Vec<_>>();
391 if self.effective(RecipeRole::Codec).is_some() {
392 names.extend(
393 self.codecs()
394 .filter(|codec| codec.content().direction.reads())
395 .map(|codec| codec.content().shape.refusal().to_owned()),
396 );
397 }
398 if self.effective(RecipeRole::Dispatch).is_some() {
399 names.push("TransitionRefusal".to_owned());
400 }
401 if self.effective(RecipeRole::Typestate).is_some() {
402 names.push("typestate".to_owned());
403 }
404 names
405 }
406}
407
408fn resolve_typestate_subject(
409 projections: &mut [ProjectionStanding; PROJECTION_LIMIT],
410 vocabularies: Option<&KeyedRoster<super::RecipeVocabulary, String, VOCABULARY_LIMIT>>,
411) -> Result<(), RecipeError> {
412 let ProjectionStanding::Generated(effective) = RecipeRole::Typestate.standing_mut(projections)
413 else {
414 return Ok(());
415 };
416 let subject = effective.subject().map(str::to_owned).or_else(|| {
417 let mut candidates = vocabularies.into_iter().flat_map(KeyedRoster::members);
418 let only = candidates.next()?;
419 candidates.next().is_none().then(|| only.name().to_owned())
420 });
421 let Some(subject) = subject else {
422 return Err(RecipeError::at(
423 RecipeIssue::ProjectionSubjectRequired {
424 role: RecipeRole::Typestate,
425 expected: "one named vocabulary when several are declared",
426 },
427 None,
428 ));
429 };
430 if vocabularies
431 .and_then(|vocabularies| vocabularies.get(subject.as_str()))
432 .is_none()
433 {
434 return Err(missing_vocabulary(subject.as_str(), None));
435 }
436 effective.select_subject(subject);
437 Ok(())
438}