use super::{SHADOW_ROSTER, ShadowCaptureError, ShadowRow, Shadows};
use crate::descriptor::{CaptureCause, DirectBinding, Grammar};
use crate::token::{CapturedDelimiter, CapturedInput, CapturedTokenTree, SpanHandle};
pub fn chosen(body: &CapturedInput, grammar: Grammar) -> Result<Shadows, ShadowCaptureError> {
let groups = comma_groups(grammar, body.trees())?;
let mut loom: Option<DirectBinding> = None;
let mut rows: Option<Vec<ShadowRow>> = None;
for group in &groups {
match group.first().and_then(|tree| tree.word()) {
Some("loom") => binding_once(grammar, group, &mut loom)?,
Some("names") => names_once(grammar, group, &mut rows)?,
Some(_) => {
return Err(refused(
grammar,
CaptureCause::ClauseUndeclared,
opening(group),
));
}
None => return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group))),
}
}
let Some(loom) = loom else {
return Err(refused(
grammar,
CaptureCause::ClauseAbsent,
SpanHandle::at(0),
));
};
let Some(rows) = rows else {
return Err(refused(
grammar,
CaptureCause::ClauseAbsent,
SpanHandle::at(0),
));
};
Ok(Shadows::declared(loom, rows))
}
fn comma_groups(
grammar: Grammar,
trees: &[CapturedTokenTree],
) -> Result<Vec<Vec<&CapturedTokenTree>>, ShadowCaptureError> {
let mut groups = Vec::new();
let mut group = Vec::new();
for tree in trees {
if tree.punct() == Some(',') {
if group.is_empty() {
return Err(refused(
grammar,
CaptureCause::SeparatorDangling,
tree.span(),
));
}
groups.push(core::mem::take(&mut group));
} else {
group.push(tree);
}
}
if !group.is_empty() {
groups.push(group);
}
Ok(groups)
}
fn opening(group: &[&CapturedTokenTree]) -> SpanHandle {
group.first().map_or(SpanHandle::at(0), |tree| tree.span())
}
fn value_of<'group, 'trees>(
group: &'group [&'trees CapturedTokenTree],
) -> &'group [&'trees CapturedTokenTree] {
match group {
[_key, assigned_by, value @ ..] if assigned_by.punct() == Some('=') => value,
_malformed => &[],
}
}
fn binding_once(
grammar: Grammar,
group: &[&CapturedTokenTree],
seat: &mut Option<DirectBinding>,
) -> Result<(), ShadowCaptureError> {
if seat.is_some() {
return Err(refused(
grammar,
CaptureCause::ClauseDoubled,
opening(group),
));
}
let binding = crate::descriptor::binding::direct_binding(value_of(group))
.map_err(|(issue, at)| ShadowCaptureError::binding_refused(grammar, issue, at))?;
*seat = Some(binding);
Ok(())
}
fn names_once(
grammar: Grammar,
group: &[&CapturedTokenTree],
seat: &mut Option<Vec<ShadowRow>>,
) -> Result<(), ShadowCaptureError> {
if seat.is_some() {
return Err(refused(
grammar,
CaptureCause::ClauseDoubled,
opening(group),
));
}
let [roster] = value_of(group) else {
return Err(refused(grammar, CaptureCause::RosterUnread, opening(group)));
};
let Some((CapturedDelimiter::Bracket, members)) = roster.group() else {
return Err(refused(grammar, CaptureCause::RosterUnread, roster.span()));
};
let mut rows = Vec::new();
let mut expecting_name = true;
for tree in members {
if expecting_name {
let Some(word) = tree.word() else {
return Err(refused(grammar, CaptureCause::ChoiceUnread, tree.span()));
};
let Some(row) = SHADOW_ROSTER.iter().find(|row| row.name() == word) else {
return Err(refused(grammar, CaptureCause::NameUnshadowed, tree.span()));
};
if rows.iter().any(|held: &ShadowRow| held.name() == word) {
return Err(refused(grammar, CaptureCause::ChoiceDoubled, tree.span()));
}
rows.push(*row);
expecting_name = false;
} else if tree.punct() == Some(',') {
expecting_name = true;
} else {
return Err(refused(grammar, CaptureCause::ChoiceUnread, tree.span()));
}
}
if rows.is_empty() {
return Err(refused(grammar, CaptureCause::NothingChosen, roster.span()));
}
*seat = Some(rows);
Ok(())
}
const fn refused(grammar: Grammar, cause: CaptureCause, at: SpanHandle) -> ShadowCaptureError {
ShadowCaptureError::grammar_refused(grammar, cause, at)
}