use std::cell::OnceCell;
use super::{Error, MessageContext};
use crate::macro_impl::derive::ReferenceKind;
use fixed_decimal::{Decimal, FloatPrecision};
use fluent_syntax::ast::{Expression, InlineExpression, PatternElement, VariantKey};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use miette::SourceSpan;
use quote::{format_ident, quote};
use syn::parse_quote;
pub fn message_body(message_context: MessageContext) -> Result<syn::Expr, Error> {
let mut write_expressions: Vec<syn::Expr> =
Vec::with_capacity(message_context.pattern.elements.len());
for element in &message_context.pattern.elements {
match element {
PatternElement::TextElement { value, span: _ } => {
let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
write_expressions.push(parse_quote!(writer.write_all(#byte_string_literal)?));
}
PatternElement::Placeable {
expression,
span: _,
} => {
write_expressions.push(match expression {
Expression::Select { selector, variants, span: _ } => {
let match_target = inline_expression(selector, message_context)?;
let default_arm = OnceCell::new();
let mut additional_arms = Vec::with_capacity(variants.len());
for variant in variants {
let variant_pattern: syn::Pat = match &variant.key {
VariantKey::Identifier { name } => {
let ident_pascal_case =
format_ident!("{}", name.to_pascal_case());
parse_quote!(::fluent_embed::icu_plurals::PluralCategory::#ident_pascal_case)
}
VariantKey::NumberLiteral { .. } => todo!(),
};
let variant_context = MessageContext {
pattern: &variant.value,
..message_context
};
let variant_body = message_body(variant_context)?;
if variant.default {
default_arm
.set(quote!(#variant_pattern | _ => #variant_body))
.expect("Multiple default patterns for match statement.");
} else {
additional_arms.push(quote!(#variant_pattern => #variant_body));
}
}
let default_arm = default_arm.get().unwrap();
parse_quote! {
match plural_rules.category_for(*#match_target) {
#(#additional_arms,)*
#default_arm,
}
}
}
Expression::Inline(expression, _span) => {
let expression = inline_expression(expression, message_context)?;
parse_quote!(#expression.message_for_locale(writer, locale)?)
}
});
}
}
}
Ok(parse_quote! {
{
#(#write_expressions;)*
}
})
}
fn inline_expression(
expression: &InlineExpression<String>,
message_context: MessageContext,
) -> Result<syn::Expr, Error> {
Ok(match expression {
InlineExpression::StringLiteral { value, span: _ } => {
let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
parse_quote!(#byte_string_literal)
}
InlineExpression::NumberLiteral { value, span: _ } => {
let parsed_value: f64 = value.parse().unwrap();
assert!(Decimal::try_from_f64(parsed_value, FloatPrecision::RoundTrip).is_ok());
let float_literal = proc_macro2::Literal::f64_suffixed(parsed_value);
parse_quote!(::fluent_embed::Decimal::try_from_f64(#float_literal, ::fluent_embed::FloatPrecision::RoundTrip).unwrap())
}
InlineExpression::VariableReference { id, span } => {
let fluent_name = &id.name;
let rust_name = id.name.to_snake_case();
let ident = if let Some(variable) = message_context
.derive_context
.valid_references
.get(&rust_name)
{
format_ident!("{variable}")
} else {
let source_code = message_context.source.named_source.clone();
return Err(Error::InvalidReference {
fluent_name: fluent_name.clone(),
rust_name,
source_code,
span: SourceSpan::from(span.0.to_owned()),
valid_references: format!(
"the following references are valid:\n{}",
message_context
.derive_context
.valid_references
.iter()
.map(|field| format!("- ${}", field.to_lower_camel_case()))
.collect::<Vec<String>>()
.join("\n")
),
});
};
match message_context.derive_context.reference_kind {
ReferenceKind::EnumField => parse_quote!(#ident),
ReferenceKind::StructField => parse_quote!(self.#ident),
}
}
_ => {
dbg!(expression);
todo!()
}
})
}