use fluent_syntax::ast;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ref {
pub name: String,
pub kind: RefKind,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum RefKind {
Variable,
Term,
}
pub fn find_refs(pattern: &ast::Pattern<&str>) -> Vec<Ref> {
let mut refs = Vec::new();
collect_refs_pattern(pattern, &mut refs);
refs
}
fn collect_refs_pattern(pattern: &ast::Pattern<&str>, refs: &mut Vec<Ref>) {
for element in &pattern.elements {
if let ast::PatternElement::Placeable { expression } = element {
collect_refs_expr(expression, refs);
}
}
}
fn collect_refs_expr(expression: &ast::Expression<&str>, refs: &mut Vec<Ref>) {
match expression {
ast::Expression::Inline(inline) => collect_refs_inline(inline, refs),
ast::Expression::Select { selector, variants } => {
collect_refs_inline(selector, refs);
for variant in variants {
collect_refs_pattern(&variant.value, refs);
}
}
}
}
fn collect_refs_inline(inline: &ast::InlineExpression<&str>, refs: &mut Vec<Ref>) {
match inline {
ast::InlineExpression::VariableReference { id } => refs.push(Ref {
name: id.name.to_owned(),
kind: RefKind::Variable,
}),
ast::InlineExpression::TermReference { id, arguments, .. } => {
refs.push(Ref {
name: id.name.to_owned(),
kind: RefKind::Term,
});
if let Some(arguments) = arguments {
collect_refs_call_arguments(arguments, refs);
}
}
ast::InlineExpression::FunctionReference { arguments, .. } => {
collect_refs_call_arguments(arguments, refs)
}
ast::InlineExpression::Placeable { expression } => collect_refs_expr(expression, refs),
ast::InlineExpression::StringLiteral { .. }
| ast::InlineExpression::NumberLiteral { .. }
| ast::InlineExpression::MessageReference { .. } => {}
}
}
fn collect_refs_call_arguments(arguments: &ast::CallArguments<&str>, refs: &mut Vec<Ref>) {
for arg in &arguments.positional {
collect_refs_inline(arg, refs);
}
for named in &arguments.named {
collect_refs_inline(&named.value, refs);
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum RefsIncompat {
UnknownVariable { variable: String },
ElementMismatch {
expected: Vec<(String, RefKind)>,
found: Vec<(String, RefKind)>,
},
}
pub fn check_refs(
vars: &[&str],
elements: &[(&str, RefKind)],
refs: &[Ref],
) -> Result<(), RefsIncompat> {
if !elements.is_empty() {
let element_names: Vec<&str> = elements.iter().map(|(n, _)| *n).collect();
let found: Vec<(String, RefKind)> = refs
.iter()
.filter(|r| element_names.contains(&r.name.as_str()))
.map(|r| (r.name.clone(), r.kind))
.collect();
let expected: Vec<(String, RefKind)> =
elements.iter().map(|(n, k)| (n.to_string(), *k)).collect();
if expected != found {
return Err(RefsIncompat::ElementMismatch { expected, found });
}
for r in refs {
if r.kind == RefKind::Variable
&& !element_names.contains(&r.name.as_str())
&& !vars.contains(&r.name.as_str())
{
return Err(RefsIncompat::UnknownVariable {
variable: r.name.clone(),
});
}
}
return Ok(());
}
for r in refs {
if r.kind == RefKind::Variable && !vars.contains(&r.name.as_str()) {
return Err(RefsIncompat::UnknownVariable {
variable: r.name.clone(),
});
}
}
Ok(())
}