use super::{ConcurrencyCaptureError, ConcurrencyDeclaration, ExplorationRow};
use crate::descriptor::{CaptureCause, DirectBinding, Grammar};
use crate::token::{
CapturedDelimiter, CapturedInput, CapturedTokenTree, SpanHandle, rendered_identifier,
rust_keyword,
};
pub fn declared(
body: &CapturedInput,
grammar: Grammar,
) -> Result<ConcurrencyDeclaration, ConcurrencyCaptureError> {
let groups = comma_groups(grammar, body.trees())?;
let mut harness: Option<DirectBinding> = None;
let mut module: Option<String> = None;
let mut namespace: Option<String> = None;
let mut rows: Vec<ExplorationRow> = Vec::new();
for group in &groups {
match group.as_slice() {
[key, ..] if key.word() == Some("harness") => {
binding_once(grammar, group, &mut harness)?;
}
[key, ..] if key.word() == Some("module") => {
assigned_once(grammar, group, &mut module, assigned_ident)?;
}
[key, ..] if key.word() == Some("namespace") => {
assigned_once(grammar, group, &mut namespace, assigned_text)?;
}
[name, row] if name.word().is_some() && row.group().is_some() => {
let read = row_of(grammar, name, row)?;
if rows.iter().any(|held| held.name() == read.name()) {
return Err(refused(grammar, CaptureCause::ChoiceDoubled, name.span()));
}
rows.push(read);
}
_unread => {
return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group)));
}
}
}
let Some(harness) = harness else {
return Err(refused(
grammar,
CaptureCause::ClauseAbsent,
SpanHandle::at(0),
));
};
let Some(module) = module else {
return Err(refused(
grammar,
CaptureCause::ClauseAbsent,
SpanHandle::at(0),
));
};
let Some(namespace) = namespace else {
return Err(refused(
grammar,
CaptureCause::ClauseAbsent,
SpanHandle::at(0),
));
};
if rows.is_empty() {
return Err(refused(
grammar,
CaptureCause::NothingChosen,
SpanHandle::at(0),
));
}
Ok(ConcurrencyDeclaration::read(
harness, module, namespace, rows,
))
}
const fn refused(grammar: Grammar, cause: CaptureCause, at: SpanHandle) -> ConcurrencyCaptureError {
ConcurrencyCaptureError::grammar_refused(grammar, cause, at)
}
fn comma_groups(
grammar: Grammar,
trees: &[CapturedTokenTree],
) -> Result<Vec<Vec<&CapturedTokenTree>>, ConcurrencyCaptureError> {
let mut groups: Vec<Vec<&CapturedTokenTree>> = Vec::new();
let mut group: Vec<&CapturedTokenTree> = 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 binding_once(
grammar: Grammar,
group: &[&CapturedTokenTree],
seat: &mut Option<DirectBinding>,
) -> Result<(), ConcurrencyCaptureError> {
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)| ConcurrencyCaptureError::binding_refused(grammar, issue, at))?;
*seat = Some(binding);
Ok(())
}
fn assigned_once(
grammar: Grammar,
group: &[&CapturedTokenTree],
seat: &mut Option<String>,
read: fn(Grammar, &[&CapturedTokenTree]) -> Result<String, ConcurrencyCaptureError>,
) -> Result<(), ConcurrencyCaptureError> {
if seat.is_some() {
return Err(refused(
grammar,
CaptureCause::ClauseDoubled,
opening(group),
));
}
*seat = Some(read(grammar, group)?);
Ok(())
}
fn assigned_ident(
grammar: Grammar,
group: &[&CapturedTokenTree],
) -> Result<String, ConcurrencyCaptureError> {
let [value] = value_of(group) else {
return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group)));
};
let word = value
.word()
.ok_or_else(|| refused(grammar, CaptureCause::ClauseUnread, value.span()))?;
if !rendered_identifier(word) {
return Err(refused(grammar, CaptureCause::ClauseUnread, value.span()));
}
if rust_keyword(word) {
return Err(refused(grammar, CaptureCause::NameReserved, value.span()));
}
Ok(word.to_owned())
}
fn assigned_text(
grammar: Grammar,
group: &[&CapturedTokenTree],
) -> Result<String, ConcurrencyCaptureError> {
let [value] = value_of(group) else {
return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group)));
};
value
.text()
.map(str::to_owned)
.ok_or_else(|| refused(grammar, CaptureCause::ClauseUnread, value.span()))
}
fn assigned_number<Number: core::str::FromStr>(
grammar: Grammar,
group: &[&CapturedTokenTree],
) -> Result<Number, ConcurrencyCaptureError> {
let [value] = value_of(group) else {
return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group)));
};
let digits = value
.number()
.ok_or_else(|| refused(grammar, CaptureCause::ClauseUnread, value.span()))?;
digits
.parse::<Number>()
.map_err(|_beyond| refused(grammar, CaptureCause::NumberBeyondSeat, value.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 row_of(
grammar: Grammar,
name: &CapturedTokenTree,
row: &CapturedTokenTree,
) -> Result<ExplorationRow, ConcurrencyCaptureError> {
let Some((CapturedDelimiter::Brace, members)) = row.group() else {
return Err(refused(grammar, CaptureCause::RowUnread, row.span()));
};
let mut population: Option<String> = None;
let mut interleavings: Option<u32> = None;
let mut samples: Option<u32> = None;
let mut seed: Option<u64> = None;
for group in &comma_groups(grammar, members)? {
match group.first().and_then(|tree| tree.word()) {
Some("population") => assigned_once(grammar, group, &mut population, assigned_text)?,
Some("interleavings") => number_once(grammar, group, &mut interleavings)?,
Some("samples") => number_once(grammar, group, &mut samples)?,
Some("seed") => number_once(grammar, group, &mut seed)?,
Some(_) => {
return Err(refused(
grammar,
CaptureCause::ClauseUndeclared,
opening(group),
));
}
None => return Err(refused(grammar, CaptureCause::ClauseUnread, opening(group))),
}
}
let (Some(population), Some(interleavings), Some(samples), Some(seed)) =
(population, interleavings, samples, seed)
else {
return Err(refused(grammar, CaptureCause::ClauseAbsent, name.span()));
};
let Some(spelling) = name.word() else {
return Err(refused(grammar, CaptureCause::RowUnread, name.span()));
};
if !rendered_identifier(spelling) {
return Err(refused(grammar, CaptureCause::RowUnread, name.span()));
}
if rust_keyword(spelling) {
return Err(refused(grammar, CaptureCause::NameReserved, name.span()));
}
Ok(ExplorationRow::declared(
spelling.to_owned(),
population,
interleavings,
samples,
seed,
))
}
fn number_once<Number: core::str::FromStr>(
grammar: Grammar,
group: &[&CapturedTokenTree],
seat: &mut Option<Number>,
) -> Result<(), ConcurrencyCaptureError> {
if seat.is_some() {
return Err(refused(
grammar,
CaptureCause::ClauseDoubled,
opening(group),
));
}
*seat = Some(assigned_number(grammar, group)?);
Ok(())
}