use ltk_meta::PropertyKind;
use crate::span::Span;
use crate::{
ast::{
builder::Builder,
diagnostics::{Diagnostic as D, ListLike, MaybeSpanDiag, TypeMismatch},
Value,
},
cst::{Child, Cst, Kind, Node},
RitoType, Spanned,
};
struct ListIter<'a, 'b, 'c> {
ctx: &'a mut Builder<'b>,
children: std::slice::Iter<'c, Child>,
cst: &'c Cst,
span: Span,
type_span: Option<Span>,
count: u8,
}
impl<'c> ListIter<'_, '_, 'c> {
fn next_value(&mut self, expected: PropertyKind) -> Option<Result<Value, MaybeSpanDiag>> {
for child in self.children.by_ref() {
let Some(node) = child.tree(self.cst) else {
continue;
};
if node.kind == Kind::Comment {
continue;
}
self.count += 1;
self.span = node.span;
return Some(
self.ctx
.resolve_numeric(node, expected, self.type_span)
.map_err(MaybeSpanDiag::from),
);
}
None
}
fn expect_next(
&mut self,
expected_kind: PropertyKind,
expected: ListLike,
) -> Result<Value, MaybeSpanDiag> {
match self.next_value(expected_kind) {
Some(v) => v,
None => Err(D::NotEnoughItems {
span: self.span,
got: self.count,
expected,
}
.into()),
}
}
fn read_floats<const N: usize>(
&mut self,
expected: ListLike,
) -> Result<[f32; N], MaybeSpanDiag> {
let mut out = [0.0f32; N];
for slot in &mut out {
*slot = match self.expect_next(PropertyKind::F32, expected)? {
Value::F32(v) => v.value,
other => {
return Err(D::from(TypeMismatch {
span: other.span(),
expected: RitoType::simple(PropertyKind::F32).into(),
expected_span: self.type_span,
got: other.kind().map(RitoType::simple).into(),
})
.into())
}
};
}
Ok(out)
}
fn read_u8s<const N: usize>(&mut self, expected: ListLike) -> Result<[u8; N], MaybeSpanDiag> {
let mut out = [0u8; N];
for slot in &mut out {
*slot = match self.expect_next(PropertyKind::U8, expected)? {
Value::U8(v) => v.value,
other => {
return Err(D::from(TypeMismatch {
span: other.span(),
expected: RitoType::simple(PropertyKind::U8).into(),
expected_span: self.type_span,
got: other.kind().map(RitoType::simple).into(),
})
.into())
}
};
}
Ok(out)
}
}
impl<'a> Builder<'a> {
pub(crate) fn resolve_numeric(
&mut self,
node: &Node,
expected: PropertyKind,
hint_span: Option<Span>,
) -> Result<Value, D> {
self.resolve_value(node, Some(RitoType::simple(expected)), hint_span)
}
pub(crate) fn resolve_listlike_fallable(
&mut self,
block: &Node,
kind: PropertyKind,
type_span: Option<Span>,
) -> Result<Value, MaybeSpanDiag> {
let cst = self.cst();
let span = block.span;
let mut items = ListIter {
ctx: self,
children: block.children.get(cst).iter(),
cst,
span,
type_span,
count: 0,
};
let value = match kind {
PropertyKind::Vector2 => {
let [x, y] = items.read_floats::<2>(ListLike::Vec2)?;
Value::Vector2(Spanned::new(span, [x, y].into()))
}
PropertyKind::Vector3 => {
let [x, y, z] = items.read_floats::<3>(ListLike::Vec3)?;
Value::Vector3(Spanned::new(span, [x, y, z].into()))
}
PropertyKind::Vector4 => {
let [x, y, z, w] = items.read_floats::<4>(ListLike::Vec4)?;
Value::Vector4(Spanned::new(span, [x, y, z, w].into()))
}
PropertyKind::Color => {
let [r, g, b, a] = items.read_u8s::<4>(ListLike::Color)?;
Value::Color(Spanned::new(span, ltk_primitives::Color { r, g, b, a }))
}
PropertyKind::Matrix44 => {
let x_axis = items.read_floats::<4>(ListLike::Mat44)?;
let y_axis = items.read_floats::<4>(ListLike::Mat44)?;
let z_axis = items.read_floats::<4>(ListLike::Mat44)?;
let w_axis = items.read_floats::<4>(ListLike::Mat44)?;
let mat = glam::Mat4::from_cols(
x_axis.into(),
y_axis.into(),
z_axis.into(),
w_axis.into(),
)
.transpose();
Value::Matrix44(Spanned::new(span, mat))
}
_ => unreachable!("resolve_listlike called with a non-listlike kind"),
};
let expected = match kind {
PropertyKind::Vector2 => ListLike::Vec2,
PropertyKind::Vector3 => ListLike::Vec3,
PropertyKind::Vector4 => ListLike::Vec4,
PropertyKind::Color => ListLike::Color,
PropertyKind::Matrix44 => ListLike::Mat44,
_ => unreachable!(),
};
if let Some(extra) = items.next_value(PropertyKind::F32) {
let extra = extra?;
let count = 1 + items.children.count();
return Err(D::TooManyItems {
span: extra.span(),
extra: count as _,
expected,
}
.into());
}
Ok(value)
}
pub(super) fn resolve_listlike(
&mut self,
block: &Node,
kind: PropertyKind,
type_span: Option<Span>,
) -> Value {
match self.resolve_listlike_fallable(block, kind, type_span) {
Ok(value) => value,
Err(e) => {
self.push(e.fallback(block.span));
Value::Unresolved {
kind,
span: block.span,
}
}
}
}
}