1use super::{
2 Annotation, Ast, AstAlloc, MergePriority, TraverseAlloc, TraverseControl, TraverseOrder,
3};
4
5use crate::{
6 identifier::{Ident, LocIdent},
7 position::TermPos,
8};
9
10use indexmap::IndexMap;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum FieldPathElem<'ast> {
17 Ident(LocIdent),
19 Expr(Ast<'ast>),
25}
26
27impl<'ast> FieldPathElem<'ast> {
28 pub fn expr(expr: Ast<'ast>) -> Self {
31 if let Some(id) = expr.node.try_str_chunk_as_static_str() {
32 FieldPathElem::Ident(LocIdent::from(id).with_pos(expr.pos))
33 } else {
34 FieldPathElem::Expr(expr)
35 }
36 }
37
38 pub fn pos(&self) -> TermPos {
40 match self {
41 FieldPathElem::Ident(ident) => ident.pos,
42 FieldPathElem::Expr(expr) => expr.pos,
43 }
44 }
45
46 pub fn single_ident_path(
48 alloc: &'ast AstAlloc,
49 ident: LocIdent,
50 ) -> &'ast [FieldPathElem<'ast>] {
51 alloc.alloc_singleton(FieldPathElem::Ident(ident))
52 }
53
54 pub fn single_expr_path(alloc: &'ast AstAlloc, expr: Ast<'ast>) -> &'ast [FieldPathElem<'ast>] {
56 alloc.alloc_singleton(FieldPathElem::Expr(expr))
57 }
58
59 pub fn try_as_ident(&self) -> Option<LocIdent> {
65 if let FieldPathElem::Ident(ident) = self {
66 Some(*ident)
67 } else {
68 None
69 }
70 }
71
72 pub fn try_as_dyn_expr(&self) -> Option<&Ast<'ast>> {
75 if let FieldPathElem::Expr(expr) = self {
76 Some(expr)
77 } else {
78 None
79 }
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct FieldDef<'ast> {
87 pub path: &'ast [FieldPathElem<'ast>],
94 pub metadata: FieldMetadata<'ast>,
96 pub value: Option<Ast<'ast>>,
97 pub pos: TermPos,
99}
100
101impl FieldDef<'_> {
102 pub fn path_as_ident(&self) -> Option<LocIdent> {
105 if let [elem] = self.path {
106 elem.try_as_ident()
107 } else {
108 None
109 }
110 }
111
112 pub fn name_as_ident(&self) -> Option<LocIdent> {
115 self.path.last().expect("empty field path").try_as_ident()
116 }
117
118 pub fn root_as_ident(&self) -> Option<LocIdent> {
121 self.path.first().expect("empty field path").try_as_ident()
122 }
123}
124
125#[derive(Debug, PartialEq, Eq, Clone, Default)]
127pub struct FieldMetadata<'ast> {
128 pub doc: Option<&'ast str>,
130 pub annotation: Annotation<'ast>,
132 pub opt: bool,
134 pub not_exported: bool,
136 pub priority: MergePriority,
138}
139
140impl FieldMetadata<'_> {
141 pub fn new() -> Self {
142 Default::default()
143 }
144
145 pub fn is_empty(&self) -> bool {
146 self.doc.is_none()
147 && self.annotation.is_empty()
148 && !self.opt
149 && !self.not_exported
150 && matches!(self.priority, MergePriority::Neutral)
151 }
152}
153
154impl<'ast> From<Annotation<'ast>> for FieldMetadata<'ast> {
155 fn from(annotation: Annotation<'ast>) -> Self {
156 FieldMetadata {
157 annotation,
158 ..Default::default()
159 }
160 }
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
165pub struct Include<'ast> {
166 pub ident: LocIdent,
168 pub metadata: FieldMetadata<'ast>,
170}
171
172#[derive(Clone, Debug, Default, PartialEq, Eq)]
174pub struct Record<'ast> {
175 pub includes: &'ast [Include<'ast>],
177 pub field_defs: &'ast [FieldDef<'ast>],
179 pub open: bool,
181}
182
183impl<'ast> Record<'ast> {
184 pub fn empty() -> Self {
186 Default::default()
187 }
188
189 pub fn open(self) -> Self {
191 Record { open: true, ..self }
192 }
193
194 pub fn has_static_structure(&self) -> bool {
197 self.field_defs
198 .iter()
199 .all(|field| field.path.iter().any(|elem| elem.try_as_ident().is_some()))
200 }
201
202 pub fn toplvl_dyn_fields(&self) -> Vec<&Ast<'ast>> {
204 self.field_defs
205 .iter()
206 .filter_map(|field| field.path.first()?.try_as_dyn_expr())
207 .collect()
208 }
209
210 pub fn defs_of(&self, ident: Ident) -> impl Iterator<Item = &'ast FieldDef<'ast>> {
213 self.field_defs.iter().filter(move |field| {
214 field
215 .path
216 .first()
217 .and_then(FieldPathElem::try_as_ident)
218 .is_some_and(|i| i.ident() == ident)
219 })
220 }
221
222 pub fn group_by_field_id(&self) -> IndexMap<Ident, Vec<&FieldDef<'ast>>> {
226 let mut map = IndexMap::new();
227
228 for (id, field) in self.field_defs.iter().filter_map(|field| {
229 field
230 .path
231 .first()
232 .and_then(FieldPathElem::try_as_ident)
233 .map(|i| (i, field))
234 }) {
235 map.entry(id.ident()).or_insert_with(Vec::new).push(field);
236 }
237
238 map
239 }
240}
241
242impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for FieldDef<'ast> {
243 fn traverse<F, E>(
244 self,
245 alloc: &'ast AstAlloc,
246 f: &mut F,
247 order: TraverseOrder,
248 ) -> Result<Self, E>
249 where
250 F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
251 {
252 let path: Result<Vec<_>, E> = self
253 .path
254 .iter()
255 .map(|elem| match elem {
256 FieldPathElem::Ident(ident) => Ok(FieldPathElem::Ident(*ident)),
257 FieldPathElem::Expr(expr) => expr
258 .clone()
259 .traverse(alloc, f, order)
260 .map(FieldPathElem::Expr),
261 })
262 .collect();
263
264 let metadata = FieldMetadata {
265 annotation: self.metadata.annotation.traverse(alloc, f, order)?,
266 ..self.metadata
267 };
268
269 let value = self
270 .value
271 .map(|v| v.traverse(alloc, f, order))
272 .transpose()?;
273
274 Ok(FieldDef {
275 path: alloc.alloc_many(path?),
276 metadata,
277 value,
278 pos: self.pos,
279 })
280 }
281
282 fn traverse_ref<S, U>(
283 &'ast self,
284 f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
285 scope: &S,
286 ) -> Option<U> {
287 self.path
288 .iter()
289 .find_map(|elem| match elem {
290 FieldPathElem::Ident(_) => None,
291 FieldPathElem::Expr(expr) => expr.traverse_ref(f, scope),
292 })
293 .or_else(|| self.metadata.annotation.traverse_ref(f, scope))
294 .or_else(|| self.value.as_ref().and_then(|v| v.traverse_ref(f, scope)))
295 }
296}