use mf2_locale_data::LocaleNeeds;
use mf2_locale_data::number::{NumberNeeds, Selection};
use mf2_model::{Declaration, Expression, FunctionRef, Message, OptionValue, SelectMessage};
use crate::config::LocaleDataConfig;
use crate::features::Features;
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug, Default)]
pub struct Slice {
#[cfg(feature = "icu-blob")]
pub dates: mf2_locale_data::icu_blob::DateNeeds,
pub needs: LocaleNeeds,
pub dynamic_currency: bool,
pub dynamic_unit: bool,
pub formats_numbers: bool,
pub unannotated: bool,
}
pub fn of(messages: &[&Message<'_>], config: &LocaleDataConfig, features: &Features) -> Slice {
let mut slice = Slice::default();
let mut numbers = NumberNeeds::default();
for message in messages {
numbers.add_message(message);
let (cardinal, ordinal) = plural_kinds(message);
slice.needs.cardinal |= cardinal;
slice.needs.ordinal |= ordinal;
scan_dynamic(message, &mut slice);
#[cfg(feature = "icu-blob")]
if features.datetime_icu() {
slice.dates.add_message(message);
}
}
slice.formats_numbers = numbers.symbols;
if features.fn_number() {
if let Some(currency) = &mut numbers.currency {
currency.codes = config.currencies.with_used(¤cy.codes);
}
if let Some(unit) = &mut numbers.unit {
unit.ids = config.units.with_used(&unit.ids);
}
slice.needs.numbers = numbers;
} else {
slice.needs.numbers = NumberNeeds::default();
}
slice
}
fn plural_kinds(message: &Message<'_>) -> (bool, bool) {
match message {
Message::Select(select) => selector_kinds(select),
_ => (false, false),
}
}
pub(crate) fn selector_kinds(select: &SelectMessage<'_>) -> (bool, bool) {
let mut cardinal = false;
let mut ordinal = false;
for selector in &select.selectors {
match selector_function(select, &selector.name, 0) {
Some(f) if f.name == "string" => {}
Some(f) => match f.options.get("select") {
None => cardinal = true,
Some(OptionValue::Literal(l)) => match l.value.as_ref() {
"exact" => {}
"ordinal" => ordinal = true,
_ => cardinal = true,
},
Some(_) => {
cardinal = true;
ordinal = true;
}
},
None => {}
}
}
(cardinal, ordinal)
}
pub fn selector_function<'m>(
select: &'m SelectMessage<'_>,
name: &str,
depth: u32,
) -> Option<&'m FunctionRef<'m>> {
declared_function(&select.declarations, name, depth)
}
pub fn declared_function<'m>(
declarations: &'m [Declaration<'m>],
name: &str,
depth: u32,
) -> Option<&'m FunctionRef<'m>> {
if depth > 16 {
return None;
}
let mut found = None;
for declaration in declarations {
if declaration.name() == name {
found = Some(declaration);
}
}
match found? {
Declaration::Input(input) => input.value.function.as_ref(),
Declaration::Local(local) => match local.value.function() {
Some(function) => Some(function),
None => match &local.value {
Expression::Variable(v) => {
declared_function(declarations, v.arg.name.as_ref(), depth + 1)
}
_ => None,
},
},
_ => None,
}
}
fn scan_dynamic(message: &Message<'_>, slice: &mut Slice) {
let mut note = |function: Option<&FunctionRef<'_>>| {
let Some(function) = function else { return };
match function.name.as_ref() {
"currency" => {
if !matches!(
function.options.get("currency"),
Some(OptionValue::Literal(_))
) {
slice.dynamic_currency = true;
}
}
"unit" if !matches!(function.options.get("unit"), Some(OptionValue::Literal(_))) => {
slice.dynamic_unit = true;
}
_ => {}
}
};
for declaration in message.declarations() {
match declaration {
Declaration::Input(input) => note(input.value.function.as_ref()),
Declaration::Local(local) => note(local.value.function()),
_ => {}
}
}
for pattern in patterns(message) {
for part in pattern {
if let mf2_model::PatternPart::Expression(e) = part {
note(e.function());
if let (None, Expression::Variable(v)) = (e.function(), e) {
let declared =
declared_function(message.declarations(), v.arg.name.as_ref(), 0);
if declared.is_none() {
slice.unannotated = true;
}
}
}
}
}
}
fn patterns<'m>(message: &'m Message<'_>) -> Vec<&'m mf2_model::Pattern<'m>> {
match message {
Message::Pattern(p) => vec![&p.pattern],
Message::Select(s) => s.variants.iter().map(|v| &v.value).collect(),
_ => Vec::new(),
}
}
pub fn is_all(selection: &Selection) -> bool {
matches!(selection, Selection::All)
}
#[cfg(feature = "icu-blob")]
pub fn icu_entry(
locale: &str,
slice: &Slice,
) -> Result<Option<(u32, Vec<u8>)>, mf2_locale_data::Error> {
use mf2_locale_data::icu_blob::{IcuBlobSpec, icu_blob};
if slice.dates.is_empty() {
return Ok(None);
}
let blob = icu_blob(locale, &IcuBlobSpec::every_variant(slice.dates.clone()))?;
Ok(Some((mf2_catalog::format::locale_key::ICU_BLOB, blob)))
}