use super::{
Address, Declaration, FactMapping, FamilySlug, MutationCaptureError, Permission, Policy,
};
use crate::descriptor::clause::{
Clause, assigned, declaration_clauses, identifier, named_reference, named_value,
};
use crate::descriptor::{
CaptureCause, DeclarationError, Grammar, ModuleName, Name, SupportName, TypeName,
};
use crate::token::{CapturedDelimiter, CapturedTokenTree, SpanHandle};
const MODULE: &str = "module";
const REFUSAL: &str = "refusal";
const SUPPORT: &str = "support";
const FAMILY: &str = "family";
const POINT: &str = "point";
const FACT: &str = "fact";
const MAP: &str = "map";
const PERMIT: &str = "permit";
const DECLARABLE: [&str; 6] = [MODULE, REFUSAL, SUPPORT, FAMILY, POINT, FACT];
pub fn captured(
body: &[&CapturedTokenTree],
at: SpanHandle,
grammar: Grammar,
) -> Result<Declaration, MutationCaptureError> {
let clauses = declaration_clauses(grammar, body, &DECLARABLE, nested_clause, refused)?;
let address = Address {
module: ModuleName::declared(identifier(grammar, &clauses, MODULE, at, refused)?)
.map_err(|refusal| carried(grammar, refusal, at))?,
support: optional_support(grammar, &clauses)?,
refusal: TypeName::declared(identifier(grammar, &clauses, REFUSAL, at, refused)?)
.map_err(|refusal| carried(grammar, refusal, at))?,
};
let family = named_reference(grammar, &clauses, FAMILY, at, refused, carried)?;
let point = named_reference(grammar, &clauses, POINT, at, refused, carried)?;
let fact = named_reference(grammar, &clauses, FACT, at, refused, carried)?;
let mut mappings: Vec<FactMapping> = Vec::new();
let mut permissions: Vec<Permission> = Vec::new();
for clause in &clauses {
let Some(nested) = clause.nested_value() else {
continue;
};
match nested {
NestedClause::Mapping {
fact: mapped,
claim,
} => mappings.push(FactMapping {
fact: mapped.clone(),
claim: claim.clone(),
}),
NestedClause::Permission {
claim,
families,
at: site,
} => permissions.push(
Permission::permitted(claim.clone(), families.clone())
.map_err(|refusal| carried(grammar, refusal, *site))?,
),
}
}
let policy = Policy::declared(family, mappings, permissions)
.map_err(|refusal| carried(grammar, refusal, at))?;
Ok(Declaration::captured(address, policy, point, fact))
}
const fn refused(grammar: Grammar, cause: CaptureCause, at: SpanHandle) -> MutationCaptureError {
MutationCaptureError::grammar_refused(grammar, cause, at)
}
const fn carried(
grammar: Grammar,
refusal: DeclarationError,
at: SpanHandle,
) -> MutationCaptureError {
MutationCaptureError::vocabulary_refused(grammar, refusal, at)
}
enum NestedClause {
Mapping {
fact: Name,
claim: Name,
},
Permission {
claim: Name,
families: Vec<FamilySlug>,
at: SpanHandle,
},
}
fn nested_clause(
grammar: Grammar,
group: &[&CapturedTokenTree],
) -> Result<Option<NestedClause>, MutationCaptureError> {
let Some((head, rest)) = group.split_first() else {
return Ok(None);
};
match head.word() {
Some(MAP) => mapping(grammar, rest, head.span()).map(Some),
Some(PERMIT) => permission(grammar, rest, head.span()).map(Some),
Some(key) if !DECLARABLE.contains(&key) => Err(refused(
grammar,
CaptureCause::ClauseUndeclared,
head.span(),
)),
Some(_) | None => Ok(None),
}
}
fn mapping(
grammar: Grammar,
rest: &[&CapturedTokenTree],
at: SpanHandle,
) -> Result<NestedClause, MutationCaptureError> {
let [
fact_word,
fact_arguments,
assigned_by,
claim_word,
claim_arguments,
] = rest
else {
return Err(refused(grammar, CaptureCause::MappingUnread, at));
};
if assigned_by.punct() != Some('=') {
return Err(refused(
grammar,
CaptureCause::MappingUnread,
assigned_by.span(),
));
}
Ok(NestedClause::Mapping {
fact: named_value(
grammar,
&[*fact_word, *fact_arguments],
at,
refused,
carried,
)?,
claim: named_value(
grammar,
&[*claim_word, *claim_arguments],
at,
refused,
carried,
)?,
})
}
fn permission(
grammar: Grammar,
rest: &[&CapturedTokenTree],
at: SpanHandle,
) -> Result<NestedClause, MutationCaptureError> {
let [word, arguments, assigned_by, bracketed] = rest else {
return Err(refused(grammar, CaptureCause::PermissionUnread, at));
};
if assigned_by.punct() != Some('=') {
return Err(refused(
grammar,
CaptureCause::PermissionUnread,
assigned_by.span(),
));
}
let claim = named_value(grammar, &[*word, *arguments], at, refused, carried)?;
let Some((CapturedDelimiter::Bracket, inner)) = bracketed.group() else {
return Err(refused(
grammar,
CaptureCause::PermissionUnread,
bracketed.span(),
));
};
let mut families: Vec<FamilySlug> = Vec::new();
let mut group: Vec<&CapturedTokenTree> = Vec::new();
for tree in inner {
if tree.punct() == Some(',') {
families.push(separated_family(grammar, &group, tree.span())?);
group.clear();
} else {
group.push(tree);
}
}
if let Some(last) = trailing_family(grammar, &group)? {
families.push(last);
}
Ok(NestedClause::Permission {
claim,
families,
at,
})
}
fn separated_family(
grammar: Grammar,
group: &[&CapturedTokenTree],
at: SpanHandle,
) -> Result<FamilySlug, MutationCaptureError> {
match group {
[] => Err(refused(grammar, CaptureCause::SeparatorDangling, at)),
[only] => family(grammar, only),
[first, ..] => Err(refused(
grammar,
CaptureCause::PermissionUnread,
first.span(),
)),
}
}
fn trailing_family(
grammar: Grammar,
group: &[&CapturedTokenTree],
) -> Result<Option<FamilySlug>, MutationCaptureError> {
match group {
[] => Ok(None),
[only] => family(grammar, only).map(Some),
[first, ..] => Err(refused(
grammar,
CaptureCause::PermissionUnread,
first.span(),
)),
}
}
fn family(grammar: Grammar, tree: &CapturedTokenTree) -> Result<FamilySlug, MutationCaptureError> {
let Some(slug) = tree.text() else {
return Err(refused(
grammar,
CaptureCause::PermissionUnread,
tree.span(),
));
};
FamilySlug::declared(slug).map_err(|refusal| carried(grammar, refusal, tree.span()))
}
fn optional_support(
grammar: Grammar,
clauses: &[Clause<'_, NestedClause>],
) -> Result<Option<SupportName>, MutationCaptureError> {
let Some((value, at)) = assigned(clauses, SUPPORT) else {
return Ok(None);
};
let [only] = value else {
return Err(refused(grammar, CaptureCause::ClauseUnread, at));
};
let spelling = only
.word()
.ok_or_else(|| refused(grammar, CaptureCause::ClauseUnread, only.span()))?;
SupportName::declared(spelling)
.map(Some)
.map_err(|refusal| carried(grammar, refusal, only.span()))
}