graphcal_compiler/syntax/
desugar.rs1#[cold]
37#[track_caller]
38#[inline(never)]
39#[expect(
40 clippy::panic,
41 reason = "indicates a broken invariant — multi-decls must be desugared before this pass"
42)]
43pub fn unreachable_post_desugar() -> ! {
44 panic!(
45 "DeclKind::Sugar should have been removed by syntax::desugar::desugar_multi_decls_in_file"
46 )
47}
48
49use crate::syntax::ast::{
50 ConstNodeDecl, DeclKind, Declaration, Expr, ExprKind, File, MapEntry, MapEntryIndex,
51 MapEntryKey, MultiDecl, MultiHeaderCell, MultiSlotColumnSpan, MultiSlotKind, NodeDecl,
52 ParamDecl, TableIndexSpec,
53};
54use crate::syntax::names::{IndexName, IndexVariantName};
55use crate::syntax::non_empty::NonEmpty;
56use crate::syntax::phase::{Desugared, Raw};
57use crate::syntax::span::Spanned;
58
59fn multi_entry_keys(
60 mut prefix: Vec<MapEntryKey>,
61 row: MapEntryKey,
62 extra: Option<MapEntryKey>,
63) -> NonEmpty<MapEntryKey> {
64 if prefix.is_empty() {
65 let rest = extra.into_iter().collect();
66 NonEmpty::new(row, rest)
67 } else {
68 let first = prefix.remove(0);
69 prefix.push(row);
70 prefix.extend(extra);
71 NonEmpty::new(first, prefix)
72 }
73}
74
75#[must_use]
85pub fn desugar_multi_decls_in_file(file: File<Raw>) -> File<Desugared> {
86 file.into()
87}
88
89#[derive(Debug)]
95pub enum ExpandedSlotDecl {
96 Param(ParamDecl, crate::syntax::span::Span),
97 Node(NodeDecl, crate::syntax::span::Span),
98 ConstNode(ConstNodeDecl, crate::syntax::span::Span),
99}
100
101impl ExpandedSlotDecl {
102 #[must_use]
105 pub fn into_declaration(self) -> Declaration {
106 let (kind, span) = match self {
107 Self::Param(p, span) => (DeclKind::Param(p), span),
108 Self::Node(n, span) => (DeclKind::Node(n), span),
109 Self::ConstNode(c, span) => (DeclKind::ConstNode(c), span),
110 };
111 Declaration {
112 attributes: vec![],
113 kind,
114 span,
115 }
116 }
117}
118
119#[must_use]
121#[expect(
122 clippy::too_many_lines,
123 reason = "single cohesive routine for multi-decl expansion"
124)]
125pub fn expand_multi_decl(multi: &MultiDecl) -> Vec<ExpandedSlotDecl> {
126 let row_index_spec = multi.shared_axes.row_axis().clone();
127 let slice_axis_specs: &[TableIndexSpec] = multi.shared_axes.slice_axes();
128
129 let row_index_name = match &row_index_spec {
130 TableIndexSpec::Named(s) => Spanned::new(MapEntryIndex::Named(s.value.clone()), s.span),
131 TableIndexSpec::NatRange(n, sp) => Spanned::new(MapEntryIndex::NatRange(*n), *sp),
132 };
133
134 let mut out: Vec<ExpandedSlotDecl> = Vec::with_capacity(multi.slots.len());
135 for (slot_idx, slot) in multi.slots.iter().enumerate() {
136 let mut slot_entries: Vec<MapEntry> = Vec::new();
137 let mut slot_indexes: Vec<TableIndexSpec> = slice_axis_specs.to_vec();
138 slot_indexes.push(row_index_spec.clone());
139 let mut extra_axis_name: Option<Spanned<IndexName>> = None;
140
141 for slice in &multi.slices {
142 let col_span = &slice.column_layout[slot_idx];
143 match col_span {
144 MultiSlotColumnSpan::Single(col_idx) => {
145 for row in &slice.rows {
146 let row_key = MapEntryKey {
147 index: row_index_name.clone(),
148 variant: row.label.clone(),
149 };
150 slot_entries.push(MapEntry {
151 keys: multi_entry_keys(slice.prefix_keys.clone(), row_key, None),
152 value: row.values[*col_idx].clone(),
153 });
154 }
155 }
156 MultiSlotColumnSpan::Range {
157 start,
158 end,
159 extra_axis,
160 } => {
161 if extra_axis_name.is_none() {
162 extra_axis_name = Some(extra_axis.clone());
163 }
164 let extra_index_name = Spanned::new(
165 MapEntryIndex::Named(extra_axis.value.clone().into()),
166 extra_axis.span,
167 );
168 let col_variants: Vec<Spanned<IndexVariantName>> = slice.header_cells
169 [*start..*end]
170 .iter()
171 .filter_map(|c| match c {
172 MultiHeaderCell::Variant { variant, .. } => Some(variant.clone()),
173 MultiHeaderCell::Underscore { .. } => None,
174 })
175 .collect();
176 for row in &slice.rows {
177 for (local_col, col_variant) in col_variants.iter().enumerate() {
178 let global_col = start + local_col;
179 let row_key = MapEntryKey {
180 index: row_index_name.clone(),
181 variant: row.label.clone(),
182 };
183 let extra_key = MapEntryKey {
184 index: extra_index_name.clone(),
185 variant: col_variant.clone(),
186 };
187 slot_entries.push(MapEntry {
188 keys: multi_entry_keys(
189 slice.prefix_keys.clone(),
190 row_key,
191 Some(extra_key),
192 ),
193 value: row.values[global_col].clone(),
194 });
195 }
196 }
197 }
198 }
199 }
200
201 if let Some(extra) = extra_axis_name {
202 slot_indexes.push(TableIndexSpec::Named(Spanned::new(
203 extra.value.into(),
204 extra.span,
205 )));
206 }
207
208 let table_expr = Expr::new(
209 ExprKind::Sugar(crate::syntax::ast::RawExprSugar::TableLiteral {
210 indexes: slot_indexes,
211 entries: slot_entries,
212 }),
213 multi.table_expr_span,
214 );
215
216 let decl_span = slot.header_span.merge(multi.span);
219
220 out.push(match slot.kind {
221 MultiSlotKind::Param => ExpandedSlotDecl::Param(
222 ParamDecl {
223 name: slot.name.clone(),
224 type_ann: slot.type_ann.clone(),
225 value: Some(table_expr),
226 },
227 decl_span,
228 ),
229 MultiSlotKind::Node => ExpandedSlotDecl::Node(
230 NodeDecl {
231 visibility: slot.visibility,
232 name: slot.name.clone(),
233 type_ann: slot.type_ann.clone(),
234 value: table_expr,
235 },
236 decl_span,
237 ),
238 MultiSlotKind::ConstNode => ExpandedSlotDecl::ConstNode(
239 ConstNodeDecl {
240 visibility: slot.visibility,
241 name: slot.name.clone(),
242 type_ann: slot.type_ann.clone(),
243 value: table_expr,
244 },
245 decl_span,
246 ),
247 });
248 }
249
250 out
251}