use fluent_syntax::ast::{
Expression, InlineExpression, Message, Pattern, PatternElement, Variant, VariantKey,
};
use heck::{ToPascalCase, ToSnakeCase};
use quote::{format_ident, quote};
use syn::parse_quote;
pub(crate) fn pattern(pattern: &Pattern<String>) -> syn::Expr {
let mut format_body = String::new();
let mut format_arguments = Vec::new();
for element in &pattern.elements {
match element {
PatternElement::TextElement { value } => format_body.push_str(value),
PatternElement::Placeable { expression } => {
let expression = placeable_expression(&expression);
format_body.push_str("{}");
format_arguments.push(quote!(#expression));
}
}
}
let format_body_literal = proc_macro2::Literal::string(format_body.to_string().as_str());
parse_quote!(format!(#format_body_literal, #(#format_arguments),*))
}
fn placeable_expression(expression: &Expression<String>) -> syn::Expr {
match expression {
Expression::Select { selector, variants } => {
let target = inline_expression(selector);
let arms: Vec<syn::Arm> = variants.iter().map(variant).collect();
parse_quote! {
match plural_rules.category_for(*#target) {
#(#arms),*
}
}
}
Expression::Inline(expression) => inline_expression(expression),
}
}
fn inline_expression(expression: &InlineExpression<String>) -> syn::Expr {
match expression {
InlineExpression::StringLiteral { value } => {
let string_literal = proc_macro2::Literal::string(value);
parse_quote!(#string_literal)
}
InlineExpression::NumberLiteral { value } => {
// FIXME: i128 is "good enough" for now but an incorrect representation!
// e.g. decimals not supported
let parsed_value = i128::from_str_radix(value, 10).unwrap();
let number_literal = proc_macro2::Literal::i128_unsuffixed(parsed_value);
parse_quote!(#number_literal)
}
InlineExpression::VariableReference { id } => {
let ident = format_ident!("{}", id.name.to_snake_case());
parse_quote!(#ident)
}
_ => {
dbg!(expression);
todo!()
}
}
}
fn variant(variant: &Variant<String>) -> syn::Arm {
let base_pattern = variant_key(&variant.key);
let body = pattern(&variant.value);
// Default patterns match anything else
// TODO: this can potentially generate unreachable patterns,
// should be replaced with a more sophisticated implementation
let pattern = if variant.default {
parse_quote!(#base_pattern | _)
} else {
base_pattern
};
parse_quote!(#pattern => #body)
}
fn variant_key(variant_key: &VariantKey<String>) -> syn::Pat {
match variant_key {
VariantKey::Identifier { name } => {
let ident = format_ident!("{}", name.to_pascal_case());
parse_quote!(::icu_plurals::PluralCategory::#ident)
}
VariantKey::NumberLiteral { value } => todo!(),
}
}
pub(crate) fn message(message: &Message<String>) -> syn::Expr {
if let Some(value) = message.value.as_ref() {
pattern(value)
} else {
parse_quote!(())
}
}