use super::{
Annotation, Ast, AstAlloc, MergePriority, TraverseAlloc, TraverseControl, TraverseOrder,
};
use crate::{
identifier::{Ident, LocIdent},
position::TermPos,
};
use indexmap::IndexMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldPathElem<'ast> {
Ident(LocIdent),
Expr(Ast<'ast>),
}
impl<'ast> FieldPathElem<'ast> {
pub fn expr(expr: Ast<'ast>) -> Self {
if let Some(id) = expr.node.try_str_chunk_as_static_str() {
FieldPathElem::Ident(LocIdent::from(id).with_pos(expr.pos))
} else {
FieldPathElem::Expr(expr)
}
}
pub fn pos(&self) -> TermPos {
match self {
FieldPathElem::Ident(ident) => ident.pos,
FieldPathElem::Expr(expr) => expr.pos,
}
}
pub fn single_ident_path(
alloc: &'ast AstAlloc,
ident: LocIdent,
) -> &'ast [FieldPathElem<'ast>] {
alloc.alloc_singleton(FieldPathElem::Ident(ident))
}
pub fn single_expr_path(alloc: &'ast AstAlloc, expr: Ast<'ast>) -> &'ast [FieldPathElem<'ast>] {
alloc.alloc_singleton(FieldPathElem::Expr(expr))
}
pub fn try_as_ident(&self) -> Option<LocIdent> {
if let FieldPathElem::Ident(ident) = self {
Some(*ident)
} else {
None
}
}
pub fn try_as_dyn_expr(&self) -> Option<&Ast<'ast>> {
if let FieldPathElem::Expr(expr) = self {
Some(expr)
} else {
None
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldDef<'ast> {
pub path: &'ast [FieldPathElem<'ast>],
pub metadata: FieldMetadata<'ast>,
pub value: Option<Ast<'ast>>,
pub pos: TermPos,
}
impl FieldDef<'_> {
pub fn path_as_ident(&self) -> Option<LocIdent> {
if let [elem] = self.path {
elem.try_as_ident()
} else {
None
}
}
pub fn name_as_ident(&self) -> Option<LocIdent> {
self.path.last().expect("empty field path").try_as_ident()
}
pub fn root_as_ident(&self) -> Option<LocIdent> {
self.path.first().expect("empty field path").try_as_ident()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct FieldMetadata<'ast> {
pub doc: Option<&'ast str>,
pub annotation: Annotation<'ast>,
pub opt: bool,
pub not_exported: bool,
pub priority: MergePriority,
}
impl FieldMetadata<'_> {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.doc.is_none()
&& self.annotation.is_empty()
&& !self.opt
&& !self.not_exported
&& matches!(self.priority, MergePriority::Neutral)
}
}
impl<'ast> From<Annotation<'ast>> for FieldMetadata<'ast> {
fn from(annotation: Annotation<'ast>) -> Self {
FieldMetadata {
annotation,
..Default::default()
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Include<'ast> {
pub ident: LocIdent,
pub metadata: FieldMetadata<'ast>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Record<'ast> {
pub includes: &'ast [Include<'ast>],
pub field_defs: &'ast [FieldDef<'ast>],
pub open: bool,
}
impl<'ast> Record<'ast> {
pub fn empty() -> Self {
Default::default()
}
pub fn open(self) -> Self {
Record { open: true, ..self }
}
pub fn has_static_structure(&self) -> bool {
self.field_defs
.iter()
.all(|field| field.path.iter().any(|elem| elem.try_as_ident().is_some()))
}
pub fn toplvl_dyn_fields(&self) -> Vec<&Ast<'ast>> {
self.field_defs
.iter()
.filter_map(|field| field.path.first()?.try_as_dyn_expr())
.collect()
}
pub fn defs_of(&self, ident: Ident) -> impl Iterator<Item = &'ast FieldDef<'ast>> {
self.field_defs.iter().filter(move |field| {
field
.path
.first()
.and_then(FieldPathElem::try_as_ident)
.is_some_and(|i| i.ident() == ident)
})
}
pub fn group_by_field_id(&self) -> IndexMap<Ident, Vec<&FieldDef<'ast>>> {
let mut map = IndexMap::new();
for (id, field) in self.field_defs.iter().filter_map(|field| {
field
.path
.first()
.and_then(FieldPathElem::try_as_ident)
.map(|i| (i, field))
}) {
map.entry(id.ident()).or_insert_with(Vec::new).push(field);
}
map
}
}
impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for FieldDef<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Self, E>
where
F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
{
let path: Result<Vec<_>, E> = self
.path
.iter()
.map(|elem| match elem {
FieldPathElem::Ident(ident) => Ok(FieldPathElem::Ident(*ident)),
FieldPathElem::Expr(expr) => expr
.clone()
.traverse(alloc, f, order)
.map(FieldPathElem::Expr),
})
.collect();
let metadata = FieldMetadata {
annotation: self.metadata.annotation.traverse(alloc, f, order)?,
..self.metadata
};
let value = self
.value
.map(|v| v.traverse(alloc, f, order))
.transpose()?;
Ok(FieldDef {
path: alloc.alloc_many(path?),
metadata,
value,
pos: self.pos,
})
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
scope: &S,
) -> Option<U> {
self.path
.iter()
.find_map(|elem| match elem {
FieldPathElem::Ident(_) => None,
FieldPathElem::Expr(expr) => expr.traverse_ref(f, scope),
})
.or_else(|| self.metadata.annotation.traverse_ref(f, scope))
.or_else(|| self.value.as_ref().and_then(|v| v.traverse_ref(f, scope)))
}
}