1use super::settle::{missing_vocabulary, referenced_refusal, settle_relation_requirements};
4use super::{
5 RELATION_ROW_LIMIT, RecipeError, RecipeIssue, RecipeRelation, RecipeRelationParts,
6 RecipeRelationPayload, RecipeRelationPayloadKind, RecipeRelationRequirements,
7 RecipeRelationRow, RecipeTransitionEffect, RecipeVocabulary, RelationLowering,
8 VOCABULARY_LIMIT,
9};
10use crate::bounded::{Bounded, KeyedRoster};
11use crate::relation::{EmptyPosture, KeyedRosterRows, RepetitionPosture};
12use crate::token::{GeneratedTree, SpanHandle};
13
14impl RecipeRelationPayload {
15 #[must_use]
17 pub const fn kind(&self) -> RecipeRelationPayloadKind {
18 match self {
19 Self::Unlabeled => RecipeRelationPayloadKind::Unlabeled,
20 Self::Path(_) => RecipeRelationPayloadKind::Path,
21 Self::ExactRust(_) => RecipeRelationPayloadKind::ExactRust,
22 Self::Transition { .. } => RecipeRelationPayloadKind::Transition,
23 }
24 }
25
26 pub(in crate::recipe) fn transition(
27 target: String,
28 target_name: crate::token::GeneratedToken,
29 effect: GeneratedTree,
30 ) -> Self {
31 Self::Transition {
32 target,
33 target_name,
34 effect: RecipeTransitionEffect::Path(effect),
35 }
36 }
37
38 pub(in crate::recipe) fn transition_exact(
39 target: String,
40 target_name: crate::token::GeneratedToken,
41 target_binding: crate::token::GeneratedToken,
42 body: GeneratedTree,
43 ) -> Self {
44 Self::Transition {
45 target,
46 target_name,
47 effect: RecipeTransitionEffect::ExactRust {
48 target_binding,
49 body,
50 },
51 }
52 }
53
54 pub(in crate::recipe) const fn transition_parts(
55 &self,
56 ) -> Option<(&str, &crate::token::GeneratedToken, &RecipeTransitionEffect)> {
57 match self {
58 Self::Transition {
59 target,
60 target_name,
61 effect,
62 } => Some((target.as_str(), target_name, effect)),
63 Self::Unlabeled | Self::Path(_) | Self::ExactRust(_) => None,
64 }
65 }
66}
67
68impl RecipeRelationRow {
69 pub(in crate::recipe) fn authored(
70 left: (String, crate::token::GeneratedToken, SpanHandle),
71 right: (String, crate::token::GeneratedToken, SpanHandle),
72 payload: RecipeRelationPayload,
73 payload_at: SpanHandle,
74 effect_binding_at: Option<SpanHandle>,
75 ) -> Self {
76 Self {
77 left: left.0,
78 left_name: left.1,
79 left_at: left.2,
80 right: right.0,
81 right_name: right.1,
82 right_at: right.2,
83 payload,
84 payload_at,
85 effect_binding_at,
86 }
87 }
88
89 #[must_use]
91 pub fn left(&self) -> &str {
92 self.left.as_str()
93 }
94
95 #[must_use]
97 pub const fn left_name_token(&self) -> &crate::token::GeneratedToken {
98 &self.left_name
99 }
100
101 #[must_use]
103 pub fn right(&self) -> &str {
104 self.right.as_str()
105 }
106
107 #[must_use]
109 pub const fn right_name_token(&self) -> &crate::token::GeneratedToken {
110 &self.right_name
111 }
112
113 #[must_use]
115 pub const fn payload(&self) -> &RecipeRelationPayload {
116 &self.payload
117 }
118
119 pub(in crate::recipe) const fn effect_binding_at(&self) -> Option<SpanHandle> {
120 self.effect_binding_at
121 }
122}
123
124impl RecipeRelationRequirements {
125 pub(in crate::recipe) const fn unspecified() -> Self {
126 Self {
127 empty: None,
128 repetition: None,
129 membership: None,
130 completeness: None,
131 density: None,
132 absence: None,
133 self_relation: None,
134 cycle: None,
135 }
136 }
137
138 pub(in crate::recipe) const fn transitions(absence: crate::relation::AbsencePosture) -> Self {
139 Self {
140 empty: Some(EmptyPosture::Refusal),
141 repetition: Some(RepetitionPosture::Refusal),
142 membership: None,
143 completeness: None,
144 density: None,
145 absence: Some(absence),
146 self_relation: None,
147 cycle: None,
148 }
149 }
150
151 pub(in crate::recipe) fn with_empty(mut self, posture: EmptyPosture) -> Option<Self> {
152 (self.empty.is_none()).then(|| {
153 self.empty = Some(posture);
154 self
155 })
156 }
157
158 pub(in crate::recipe) fn with_repetition(mut self, posture: RepetitionPosture) -> Option<Self> {
159 (self.repetition.is_none()).then(|| {
160 self.repetition = Some(posture);
161 self
162 })
163 }
164
165 pub(in crate::recipe) fn with_membership(
166 mut self,
167 left: crate::relation::MembershipPosture,
168 right: crate::relation::MembershipPosture,
169 ) -> Option<Self> {
170 self.membership.is_none().then(|| {
171 self.membership = Some([left, right]);
172 self
173 })
174 }
175
176 pub(in crate::recipe) fn with_completeness(
177 mut self,
178 left: crate::relation::CompletenessPosture,
179 right: crate::relation::CompletenessPosture,
180 ) -> Option<Self> {
181 self.completeness.is_none().then(|| {
182 self.completeness = Some([left, right]);
183 self
184 })
185 }
186
187 pub(in crate::recipe) fn with_density(
188 mut self,
189 posture: crate::relation::DensityPosture,
190 ) -> Option<Self> {
191 (self.density.is_none()).then(|| {
192 self.density = Some(posture);
193 self
194 })
195 }
196
197 pub(in crate::recipe) fn with_absence(
198 mut self,
199 posture: crate::relation::AbsencePosture,
200 ) -> Option<Self> {
201 (self.absence.is_none()).then(|| {
202 self.absence = Some(posture);
203 self
204 })
205 }
206
207 pub(in crate::recipe) fn with_self_relation(
208 mut self,
209 posture: crate::relation::SelfRelationPosture,
210 ) -> Option<Self> {
211 (self.self_relation.is_none()).then(|| {
212 self.self_relation = Some(posture);
213 self
214 })
215 }
216
217 pub(in crate::recipe) fn with_cycle(
218 mut self,
219 posture: crate::relation::CyclePosture,
220 ) -> Option<Self> {
221 (self.cycle.is_none()).then(|| {
222 self.cycle = Some(posture);
223 self
224 })
225 }
226
227 #[must_use]
229 pub const fn empty(&self) -> Option<EmptyPosture> {
230 self.empty
231 }
232
233 #[must_use]
235 pub const fn repetition(&self) -> Option<RepetitionPosture> {
236 self.repetition
237 }
238
239 #[must_use]
241 pub const fn left_membership(&self) -> Option<crate::relation::MembershipPosture> {
242 match self.membership {
243 Some([left, _]) => Some(left),
244 None => None,
245 }
246 }
247
248 #[must_use]
250 pub const fn right_membership(&self) -> Option<crate::relation::MembershipPosture> {
251 match self.membership {
252 Some([_, right]) => Some(right),
253 None => None,
254 }
255 }
256
257 #[must_use]
259 pub const fn left_completeness(&self) -> Option<crate::relation::CompletenessPosture> {
260 match self.completeness {
261 Some([left, _]) => Some(left),
262 None => None,
263 }
264 }
265
266 #[must_use]
268 pub const fn right_completeness(&self) -> Option<crate::relation::CompletenessPosture> {
269 match self.completeness {
270 Some([_, right]) => Some(right),
271 None => None,
272 }
273 }
274
275 #[must_use]
277 pub const fn density(&self) -> Option<crate::relation::DensityPosture> {
278 self.density
279 }
280
281 #[must_use]
283 pub const fn absence(&self) -> Option<crate::relation::AbsencePosture> {
284 self.absence
285 }
286
287 #[must_use]
289 pub const fn self_relation(&self) -> Option<crate::relation::SelfRelationPosture> {
290 self.self_relation
291 }
292
293 #[must_use]
295 pub const fn cycle(&self) -> Option<crate::relation::CyclePosture> {
296 self.cycle
297 }
298}
299
300impl RecipeRelation {
301 pub(super) fn informed(
302 parts: RecipeRelationParts,
303 vocabularies: &KeyedRoster<RecipeVocabulary, String, VOCABULARY_LIMIT>,
304 lowering: RelationLowering,
305 ) -> Result<Self, RecipeError> {
306 let RecipeRelationParts {
307 name,
308 name_token,
309 name_at,
310 left_vocabulary,
311 left_vocabulary_at,
312 right_vocabulary,
313 right_vocabulary_at,
314 rows,
315 requirements,
316 } = parts;
317 let at = rows.first().map(|row| row.left_at);
318 let left = vocabularies.get(left_vocabulary.as_str()).ok_or_else(|| {
319 missing_vocabulary(left_vocabulary.as_str(), Some(left_vocabulary_at))
320 })?;
321 let right = vocabularies.get(right_vocabulary.as_str()).ok_or_else(|| {
322 missing_vocabulary(right_vocabulary.as_str(), Some(right_vocabulary_at))
323 })?;
324 let informed = KeyedRosterRows::referenced(
325 left.members(),
326 right.members(),
327 rows.clone(),
328 |row| row.left.clone(),
329 |row| row.right.clone(),
330 )
331 .map_err(|refusal| referenced_refusal(left.name(), right.name(), &rows, refusal))?;
332 let payload_kind = rows
333 .first()
334 .map_or(RecipeRelationPayloadKind::Unlabeled, |row| {
335 row.payload.kind()
336 });
337 if let Some(row) = rows.iter().find(|row| row.payload.kind() != payload_kind) {
338 return Err(RecipeError::at(
339 RecipeIssue::RelationPayloadShapeMismatch {
340 relation: name,
341 expected: payload_kind,
342 observed: row.payload.kind(),
343 },
344 Some(row.payload_at),
345 ));
346 }
347 if requirements.repetition == Some(RepetitionPosture::Refusal)
348 && let Err(repeated) = informed.clone().distinct()
349 {
350 let Some(pair) = repeated.iter().next() else {
351 return Err(RecipeError::at(RecipeIssue::FragmentNotGenerated, at));
352 };
353 let Some(repeated_position) = pair.repeated_positions().iter().next().copied() else {
354 return Err(RecipeError::at(RecipeIssue::FragmentNotGenerated, at));
355 };
356 let Some(row) = rows.get(repeated_position) else {
357 return Err(RecipeError::at(RecipeIssue::FragmentNotGenerated, at));
358 };
359 let issue = if lowering == RelationLowering::Transition {
360 RecipeIssue::DuplicateTransition {
361 state: row.left.clone(),
362 event: row.right.clone(),
363 }
364 } else {
365 RecipeIssue::DuplicateRelationRow {
366 relation: name.clone(),
367 left: row.left.clone(),
368 right: row.right.clone(),
369 }
370 };
371 return Err(RecipeError::at(issue, Some(row.left_at)));
372 }
373 settle_relation_requirements(name.as_str(), &informed, requirements, at)?;
374 let overflow_at = rows.get(RELATION_ROW_LIMIT).map(|row| row.left_at);
375 let rows = Bounded::new(rows).map_err(|overflow| {
376 RecipeError::at(
377 RecipeIssue::Grammar(crate::token::CaptureReadIssue::SequenceUnbounded {
378 limit: overflow.capacity,
379 }),
380 overflow_at.or(at),
381 )
382 })?;
383 Ok(Self {
384 name,
385 name_token,
386 name_at,
387 left_vocabulary,
388 right_vocabulary,
389 rows,
390 payload_kind,
391 requirements,
392 })
393 }
394
395 #[must_use]
397 pub fn name(&self) -> &str {
398 self.name.as_str()
399 }
400
401 #[must_use]
403 pub const fn name_token(&self) -> &crate::token::GeneratedToken {
404 &self.name_token
405 }
406
407 #[must_use]
409 pub fn left_vocabulary(&self) -> &str {
410 self.left_vocabulary.as_str()
411 }
412
413 #[must_use]
415 pub fn right_vocabulary(&self) -> &str {
416 self.right_vocabulary.as_str()
417 }
418
419 pub fn rows(&self) -> impl Iterator<Item = &RecipeRelationRow> {
421 self.rows.iter()
422 }
423
424 #[must_use]
426 pub fn row_count(&self) -> usize {
427 self.rows.len()
428 }
429
430 #[must_use]
432 pub const fn payload_kind(&self) -> RecipeRelationPayloadKind {
433 self.payload_kind
434 }
435
436 #[must_use]
438 pub const fn requirements(&self) -> &RecipeRelationRequirements {
439 &self.requirements
440 }
441}