Skip to main content

macroonz_compiler/codec/
type_guard.rs

1//! The codec home's invariant nucleus: every road that reaches a private field, and the alphabet they all read a spelling through.
2//!
3//! Declared inside `types.rs` as its own child, which is what makes this home's walls structural.
4//! A shape is seated by one road, and that road refuses an empty roster, a doubled spelling, and a spelling the decode road has already taken — so a codec whose decode road could not refuse, or whose bindings would shadow one another, is a value nobody can write rather than a state a reader has to notice.
5
6use super::super::bank::RESERVED_BINDINGS;
7use super::{
8    AssemblyPosture, CODEC_ISSUE_LIMIT, Cardinality, CodecAssembly, CodecError, CodecIssue,
9    CodecMember, CodecMemberShape, CodecShape, CodecTypePath, ModuleSpelling, PathRooting,
10};
11use crate::bounded::{Capped, Capping, NonEmpty, NonEmptyError, Overflow};
12use std::collections::BTreeSet;
13
14impl CodecTypePath {
15    /// One type path, rooted as the caller stated and spelled from the segments it named.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`CodecIssue::SegmentNotAnIdentifier`] where a segment cannot name a rendered item — the root is typed as the rooting, so every segment is an item step, refused outside the alphabet or on the keyword roster — [`CodecIssue::PathSegmentsAbsent`] where no segment was supplied, and [`CodecIssue::PathSegmentsUnbounded`] where the segments outgrow the declared magnitude.
20    /// The checks are dependent and in that order, so exactly one cause is true of any refused path.
21    pub fn spelled(rooting: PathRooting, segments: Vec<String>) -> Result<Self, CodecError> {
22        for segment in &segments {
23            if !rendered_name(segment) {
24                return Err(CodecError::of(CodecIssue::SegmentNotAnIdentifier {
25                    segment: segment.clone(),
26                }));
27            }
28        }
29        let admitted = NonEmpty::new(segments).map_err(|refused| match refused {
30            NonEmptyError::Empty(_) => CodecError::of(CodecIssue::PathSegmentsAbsent),
31            NonEmptyError::Overflow(overflow) => {
32                let (bound, observed) = counted(overflow);
33                CodecError::of(CodecIssue::PathSegmentsUnbounded { bound, observed })
34            }
35        })?;
36        Ok(Self {
37            rooting,
38            segments: admitted,
39        })
40    }
41
42    /// Where this path is rooted.
43    #[must_use]
44    pub const fn rooting(&self) -> PathRooting {
45        self.rooting
46    }
47
48    /// The segments, from the root inward; structurally at least one.
49    pub fn segments(&self) -> impl Iterator<Item = &str> {
50        self.segments.iter().map(String::as_str)
51    }
52
53    /// How many segments the path carries; structurally at least one.
54    #[must_use]
55    pub fn count(&self) -> usize {
56        self.segments.count()
57    }
58}
59
60impl ModuleSpelling {
61    /// One published module's spelling.
62    ///
63    /// # Errors
64    ///
65    /// Returns [`CodecIssue::ModuleSpellingNotAnIdentifier`] where the spelling cannot name a rendered item: not one Rust identifier, or a keyword the language already took.
66    pub fn spelled(spelling: &str) -> Result<Self, CodecError> {
67        if rendered_name(spelling) {
68            Ok(Self {
69                spelling: spelling.to_owned(),
70            })
71        } else {
72            Err(CodecError::of(CodecIssue::ModuleSpellingNotAnIdentifier {
73                spelling: spelling.to_owned(),
74            }))
75        }
76    }
77
78    /// The declared spelling.
79    #[must_use]
80    pub fn spelling(&self) -> &str {
81        self.spelling.as_str()
82    }
83}
84
85impl CodecMember {
86    /// Declare one member of a shape.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`CodecIssue::MemberSpellingAbsent`] where the member states no spelling, and [`CodecIssue::MemberSpellingNotAnIdentifier`] where the spelling cannot name a rendered item — not one Rust identifier, or a keyword the language already took.
91    /// The two are dependent — there is no alphabet to read until there are characters — so exactly one is ever established.
92    pub fn declared(
93        spelling: &str,
94        held_as: CodecTypePath,
95        shape: CodecMemberShape,
96        cardinality: Cardinality,
97    ) -> Result<Self, CodecError> {
98        if spelling.is_empty() {
99            return Err(CodecError::of(CodecIssue::MemberSpellingAbsent));
100        }
101        if !rendered_name(spelling) {
102            return Err(CodecError::of(CodecIssue::MemberSpellingNotAnIdentifier {
103                spelling: spelling.to_owned(),
104            }));
105        }
106        Ok(Self {
107            spelling: spelling.to_owned(),
108            held_as,
109            shape,
110            cardinality,
111        })
112    }
113
114    /// What the owner calls this member.
115    #[must_use]
116    pub fn spelling(&self) -> &str {
117        self.spelling.as_str()
118    }
119
120    /// The type ONE OCCURRENCE of this member is held at, never the collection or the option a cardinality wraps it in.
121    #[must_use]
122    pub const fn held_as(&self) -> &CodecTypePath {
123        &self.held_as
124    }
125
126    /// How this member is written.
127    #[must_use]
128    pub const fn shape(&self) -> CodecMemberShape {
129        self.shape
130    }
131
132    /// How many of this member there are.
133    #[must_use]
134    pub const fn cardinality(&self) -> Cardinality {
135        self.cardinality
136    }
137}
138
139impl CodecAssembly {
140    /// The assembly road, under the posture the caller stated.
141    ///
142    /// # Errors
143    ///
144    /// Returns [`CodecIssue::AssemblyRoadAbsent`] where the road states no spelling, and [`CodecIssue::AssemblyRoadNotAnIdentifier`] where the spelling cannot name a rendered item — not one Rust identifier, or a keyword the language already took.
145    pub fn stated(road: &str, posture: AssemblyPosture) -> Result<Self, CodecError> {
146        if road.is_empty() {
147            return Err(CodecError::of(CodecIssue::AssemblyRoadAbsent));
148        }
149        if !rendered_name(road) {
150            return Err(CodecError::of(CodecIssue::AssemblyRoadNotAnIdentifier {
151                spelling: road.to_owned(),
152            }));
153        }
154        Ok(Self {
155            road: road.to_owned(),
156            posture,
157        })
158    }
159
160    /// The associated road the decode surface calls.
161    #[must_use]
162    pub fn road(&self) -> &str {
163        self.road.as_str()
164    }
165
166    /// The posture that road stands under.
167    #[must_use]
168    pub const fn posture(&self) -> &AssemblyPosture {
169        &self.posture
170    }
171}
172
173impl CodecShape {
174    /// Declare one complete shape.
175    ///
176    /// # Errors
177    ///
178    /// Returns [`CodecIssue::RefusalSpellingNotAnIdentifier`] where the rendered refusal's spelling cannot name a rendered item — not one Rust identifier, or a keyword the language already took — then whatever the pass over the offered members established — [`CodecIssue::MemberSpellingDoubled`] and [`CodecIssue::MemberShadowsBinding`], which co-establish — then [`CodecIssue::MembersAbsent`] where no member was supplied and [`CodecIssue::MembersUnbounded`] where the members outgrow the declared magnitude.
179    pub fn declared(
180        owner: CodecTypePath,
181        refusal: &str,
182        assembly: CodecAssembly,
183        members: Vec<CodecMember>,
184    ) -> Result<Self, CodecError> {
185        if !rendered_name(refusal) {
186            return Err(CodecError::of(CodecIssue::RefusalSpellingNotAnIdentifier {
187                spelling: refusal.to_owned(),
188            }));
189        }
190        if let Some((first, rest)) = established(member_issues(&members)) {
191            return Err(CodecError::over(first, rest));
192        }
193        let admitted = NonEmpty::new(members).map_err(|refused| match refused {
194            NonEmptyError::Empty(_) => CodecError::of(CodecIssue::MembersAbsent),
195            NonEmptyError::Overflow(overflow) => {
196                let (bound, observed) = counted(overflow);
197                CodecError::of(CodecIssue::MembersUnbounded { bound, observed })
198            }
199        })?;
200        Ok(Self {
201            owner,
202            refusal: refusal.to_owned(),
203            assembly,
204            members: admitted,
205        })
206    }
207
208    /// The type the codec is written for.
209    #[must_use]
210    pub const fn owner(&self) -> &CodecTypePath {
211        &self.owner
212    }
213
214    /// The spelling the rendered decode refusal is declared under.
215    #[must_use]
216    pub fn refusal(&self) -> &str {
217        self.refusal.as_str()
218    }
219
220    /// The road the decoded members are assembled by.
221    #[must_use]
222    pub const fn assembly(&self) -> &CodecAssembly {
223        &self.assembly
224    }
225
226    /// The members, in the order the shape declares them.
227    ///
228    /// # Ordering
229    ///
230    /// This order IS meaning: it is the order the encode road writes and the decode road reads, so the same members supplied in another order are a different byte string for the same value — which is exactly what a canonical encoding may not have two of.
231    pub fn members(&self) -> impl Iterator<Item = &CodecMember> {
232        self.members.iter()
233    }
234
235    /// How many members the shape declares; structurally at least one.
236    #[must_use]
237    pub fn count(&self) -> usize {
238        self.members.count()
239    }
240}
241
242impl CodecError {
243    /// The refusal one established issue makes.
244    pub fn of(issue: CodecIssue) -> Self {
245        Self {
246            body: Capped::all(NonEmpty::one(issue)),
247        }
248    }
249
250    /// The refusal a pass whose checks co-establish makes.
251    ///
252    /// The caller arrives holding every issue its pass established, so the posture the body writes is about the REPORT and never about the pass: where the issues fit it carries all of them, and where they do not it carries what fits and counts the rest.
253    pub fn over(first: CodecIssue, rest: Vec<CodecIssue>) -> Self {
254        Self {
255            body: Capped::first_n(first, rest.into_iter()),
256        }
257    }
258
259    /// The first issue the pass established, which every refusal has.
260    #[must_use]
261    pub fn first_issue(&self) -> &CodecIssue {
262        self.body.items().first()
263    }
264
265    /// Every issue this refusal carries, in the order the pass established them; structurally at least one.
266    #[must_use]
267    pub fn issues(&self) -> &NonEmpty<CodecIssue, CODEC_ISSUE_LIMIT> {
268        self.body.items()
269    }
270
271    /// Whether this refusal carries every issue its pass established.
272    #[must_use]
273    pub const fn capping(&self) -> Capping {
274        self.body.capping()
275    }
276}
277
278pub use crate::token::{rendered_identifier, rendered_name};
279
280/// The pass over one shape's offered members: what their spellings say about each other and about the locals the decode road declares for itself.
281///
282/// Every member is asked and every collision is reported, because a caller repairing a shape one member per attempt is a caller this home failed.
283/// A spelling doubled three times establishes one issue, not two: the fact is that the spelling is shared, and it is stated once.
284fn member_issues(members: &[CodecMember]) -> Vec<CodecIssue> {
285    let mut issues: Vec<CodecIssue> = Vec::new();
286    let mut seen: BTreeSet<&str> = BTreeSet::new();
287    let mut reported: BTreeSet<&str> = BTreeSet::new();
288    for member in members {
289        let standing = member_standing(member, &mut seen, &mut reported);
290        if let Some(spelling) = standing.doubled {
291            issues.push(CodecIssue::MemberSpellingDoubled {
292                spelling: spelling.to_owned(),
293            });
294        }
295        if let Some((spelling, binding)) = standing.shadowed {
296            issues.push(CodecIssue::MemberShadowsBinding {
297                spelling: spelling.to_owned(),
298                binding,
299            });
300        }
301    }
302    issues
303}
304
305/// What one offered member's spelling establishes after the duplicate and reserved-binding passes have both read it.
306struct MemberStanding<'member> {
307    doubled: Option<&'member str>,
308    shadowed: Option<(&'member str, &'static str)>,
309}
310
311/// Read one member through both spelling indexes before the issue pass decides what to report.
312fn member_standing<'member>(
313    member: &'member CodecMember,
314    seen: &mut BTreeSet<&'member str>,
315    reported: &mut BTreeSet<&'member str>,
316) -> MemberStanding<'member> {
317    let spelling = member.spelling();
318    let doubled = (!seen.insert(spelling) && reported.insert(spelling)).then_some(spelling);
319    let shadowed = RESERVED_BINDINGS
320        .into_iter()
321        .find(|binding| spelling == *binding)
322        .map(|binding| (spelling, binding));
323    MemberStanding { doubled, shadowed }
324}
325
326/// One pass's established issues as the pair a refusal is built from, or nothing where the pass established none.
327fn established(issues: Vec<CodecIssue>) -> Option<(CodecIssue, Vec<CodecIssue>)> {
328    let mut walk = issues.into_iter();
329    let first = walk.next()?;
330    Some((first, walk.collect()))
331}
332
333/// The two counts an overflow already carries, at the width a refusal states them.
334fn counted(overflow: Overflow) -> (u64, u64) {
335    (
336        u64::try_from(overflow.capacity).unwrap_or(u64::MAX),
337        u64::try_from(overflow.offered).unwrap_or(u64::MAX),
338    )
339}