Skip to main content

macroonz_compiler/explanation/
encode.rs

1//! The canonical bytes one answered seat is, one coverage issue is, and one complete view's identity is derived over.
2//!
3//! Every member is a typed value: a question's roster position, an answer's own discriminant, an identity at full width, or a typed roster written behind its count.
4//! **No human prose enters.** A rendered line is composed from an answer at the moment it is asked for, so a preimage carrying one would commit to a rendering rather than to what was answered, and would rename every explanation in the tree the day a sentence was reworded.
5//! The one seat where prose sits beside a typed value is a repair, and only the repair's CITATION is written: the sentence is that citation's own projection.
6
7use super::{ExplanationIssue, RelatedDisposition, UniversalAnswer};
8use crate::identity::{encode_bytes, encode_length};
9use crate::kind::{Answer, Disposition, Question};
10
11/// Appends one disposition's canonical bytes: the row's discriminant, then the material that row carries.
12fn disposition_into(disposition: Disposition, into: &mut Vec<u8>) {
13    match disposition {
14        Disposition::Generated { unit } => {
15            into.push(0);
16            encode_bytes(unit.as_bytes(), into);
17        }
18        Disposition::NotApplicable { because } => {
19            into.push(1);
20            encode_bytes(&because.citation_bytes(), into);
21        }
22        Disposition::NotRequested { because } => {
23            into.push(2);
24            encode_bytes(&because.citation_bytes(), into);
25        }
26        Disposition::UnavailableUnderProfile { profile, because } => {
27            into.push(3);
28            profile.encode_into(into);
29            encode_bytes(&because.citation_bytes(), into);
30        }
31    }
32}
33
34/// Appends one accounted kind's canonical bytes: its declared name, then what happened to it.
35fn related_into(related: &RelatedDisposition, into: &mut Vec<u8>) {
36    encode_bytes(related.kind.as_bytes(), into);
37    disposition_into(related.disposition, into);
38}
39
40/// The typed material one universal answer carries, written through each value's own declared spelling.
41///
42/// Exhaustive on purpose: an arm added to [`UniversalAnswer`] stops compiling HERE until somebody says what of it an explanation's identity commits to, so no answer can be admitted and left out of the preimage.
43pub(super) fn answer_material(answer: &UniversalAnswer, into: &mut Vec<u8>) {
44    match answer {
45        UniversalAnswer::Kind { name } => encode_bytes(name.as_bytes(), into),
46        UniversalAnswer::Owner { owner } => encode_bytes(&owner.citation_bytes(), into),
47        UniversalAnswer::CausingDeclarations {
48            commitment,
49            dependencies,
50        } => {
51            encode_bytes(commitment.as_bytes(), into);
52            encode_length(dependencies.len(), into);
53            for dependency in dependencies.iter() {
54                encode_bytes(dependency.as_bytes(), into);
55            }
56        }
57        UniversalAnswer::Profile { profile } => profile.encode_into(into),
58        UniversalAnswer::OutputAndDigest { outputs } => {
59            encode_length(outputs.len(), into);
60            for row in outputs.iter() {
61                row.output.encode_into(into);
62                encode_bytes(row.digest.as_bytes(), into);
63            }
64        }
65        UniversalAnswer::Assumptions { assumptions } => {
66            encode_length(assumptions.len(), into);
67            for assumption in assumptions.iter() {
68                encode_bytes(&assumption.citation_bytes(), into);
69            }
70        }
71        UniversalAnswer::Invalidators { triggers } => {
72            encode_length(triggers.count(), into);
73            for trigger in triggers.iter() {
74                trigger.encode_into(into);
75            }
76        }
77        UniversalAnswer::RelatedDispositions { related } => {
78            encode_length(related.len(), into);
79            for accounted in related.iter() {
80                related_into(accounted, into);
81            }
82        }
83        UniversalAnswer::Repairs { repairs } => {
84            encode_length(repairs.len(), into);
85            for repair in repairs.iter() {
86                encode_bytes(&repair.declared_by.citation_bytes(), into);
87            }
88        }
89    }
90}
91
92/// Appends one roster's answered seats: the count, then each seat as its question's roster position in two big-endian bytes followed by the answer's own canonical bytes.
93///
94/// The question rides ahead of the answer because the question is what was asked.
95/// Each roster is written behind its own count, so the split between the universal seats and the kind's is framed rather than inferred — a universal seat and a declared seat may share a position, and one run of seats could otherwise be cut at another boundary.
96pub(super) fn seats_into<A: Answer>(answers: &[A], into: &mut Vec<u8>) {
97    encode_length(answers.len(), into);
98    for answer in answers {
99        into.extend_from_slice(&answer.question().slot().to_be_bytes());
100        answer.encode_into(into);
101    }
102}
103
104impl ExplanationIssue {
105    /// This issue's canonical bytes on their own, for the related identity a diagnostic derives over it.
106    #[must_use]
107    pub fn canonical_bytes(&self) -> Vec<u8> {
108        let mut bytes = Vec::new();
109        self.encode_into(&mut bytes);
110        bytes
111    }
112
113    /// Appends this issue's canonical bytes: the row's position in the declared roster, then the typed material that row carries, framed.
114    ///
115    /// Two rows carrying one question encode differently, because the row's position rides ahead of the material.
116    pub fn encode_into(&self, into: &mut Vec<u8>) {
117        into.push(self.slot());
118        let mut material = Vec::new();
119        self.material_into(&mut material);
120        encode_bytes(&material, into);
121    }
122
123    /// The typed material one issue carries.
124    fn material_into(&self, into: &mut Vec<u8>) {
125        match self {
126            Self::UniversalUnanswered { question } | Self::UniversalAnsweredTwice { question } => {
127                into.extend_from_slice(&question.slot().to_be_bytes());
128            }
129            Self::DeclaredUnanswered { question, slot }
130            | Self::DeclaredAnsweredTwice { question, slot } => {
131                encode_bytes(question.as_bytes(), into);
132                into.extend_from_slice(&slot.to_be_bytes());
133            }
134            Self::QuestionOutsideRoster { question } => encode_bytes(question.as_bytes(), into),
135            Self::SeatBoundExceeded { bound, observed } => {
136                into.extend_from_slice(&bound.to_be_bytes());
137                into.extend_from_slice(&observed.to_be_bytes());
138            }
139            Self::OutputsBesideTheProof {
140                expected,
141                observed,
142                diverges,
143            } => {
144                into.extend_from_slice(&expected.to_be_bytes());
145                into.extend_from_slice(&observed.to_be_bytes());
146                into.extend_from_slice(&diverges.to_be_bytes());
147            }
148        }
149    }
150}