1use std::{
2 collections::{BTreeMap, BTreeSet},
3 rc::Rc,
4};
5
6use convert_case::{Case, Casing};
7use fluent_static_value::{Number, Value};
8use fluent_syntax::ast;
9use intl_pluralrules::PluralCategory;
10use proc_macro2::{Ident, Literal, TokenStream as TokenStream2};
11use quote::{format_ident, quote};
12use unic_langid::LanguageIdentifier;
13
14use crate::{
15 ast::{Node, Visitor},
16 function::FunctionCallGenerator,
17 types::{FluentId, FluentMessage, FluentVariable, PublicFluentId},
18 Error,
19};
20
21#[derive(Debug, Clone)]
22enum ExpressionContext {
23 Inline,
24 Selector {
25 plural_rules: bool,
26 },
27 TermArguments {
28 term: FluentMessage,
29 },
30 FunctionCall {
31 positional_args: Ident,
32 named_args: Ident,
33 },
34}
35
36pub struct LanguageBuilder {
37 pending_fns: Vec<FluentMessage>,
38 expression_contexts: Vec<ExpressionContext>,
39 fn_call_generator: Rc<dyn FunctionCallGenerator>,
40
41 #[allow(dead_code)]
42 pub language_id: LanguageIdentifier,
43 pub prefix: String,
44 pub registered_fns: BTreeMap<FluentId, FluentMessage>,
45 pub registered_message_fns: BTreeMap<PublicFluentId, FluentMessage>,
46}
47
48impl LanguageBuilder {
49 pub fn new(
50 language_id: &LanguageIdentifier,
51 fn_call_generator: Rc<dyn FunctionCallGenerator>,
52 ) -> Self {
53 Self {
54 language_id: language_id.clone(),
55 fn_call_generator,
56 prefix: language_id.to_string().to_case(Case::Snake),
57 pending_fns: Vec::new(),
58 registered_fns: BTreeMap::new(),
59 registered_message_fns: BTreeMap::new(),
60 expression_contexts: Vec::new(),
61 }
62 }
63
64 fn push_message<S: ToString>(&mut self, message: &ast::Message<S>) {
65 let id = &message.id;
66 self.pending_fns.push(FluentMessage::new(
67 id,
68 self.make_fn_ident(&message.id, None),
69 false,
70 ));
71 }
72
73 fn push_term<S: ToString>(&mut self, term: &ast::Term<S>) {
74 self.pending_fns.push(FluentMessage::new(
75 &term.id,
76 self.make_fn_ident(&term.id, None),
77 true,
78 ));
79 }
80
81 fn push_attribute<S: ToString>(&mut self, attribute: &ast::Attribute<S>) -> Result<(), Error> {
82 if let Some(parent) = self.pending_fns.last() {
83 let id = parent.id().join(&attribute.id);
84 let fn_ident = self.make_fn_ident(parent.id().clone(), Some(attribute.into()));
85 self.pending_fns
86 .push(FluentMessage::new(id, fn_ident, parent.is_private()));
87 Ok(())
88 } else {
89 Err(Error::UnexpectedContextState)
90 }
91 }
92
93 fn generate_message_code(&self, msg: &FluentMessage, body: TokenStream2) -> TokenStream2 {
94 let fn_ident = msg.fn_ident();
95 let var_idents: BTreeSet<Ident> = msg.vars().into_iter().map(|v| v.var_ident).collect();
96
97 let fn_generics = if var_idents.is_empty() {
98 quote! {
99 <W: ::std::fmt::Write>
100 }
101 } else {
102 quote! {
103 <'a, W: ::std::fmt::Write>
104 }
105 };
106
107 quote! {
108 #[inline]
109 fn #fn_ident #fn_generics(
110 &self,
111 out: &mut W,
112 #(#var_idents: ::fluent_static::value::Value<'a>),*
113 ) -> ::std::fmt::Result {
114 #body
115 Ok(())
116 }
117 }
118 }
119
120 fn register_pending_fn(&mut self, body: TokenStream2) -> Result<TokenStream2, Error> {
121 let f = self
122 .pending_fns
123 .pop()
124 .ok_or(Error::UnexpectedContextState)?;
125
126 let result = self.generate_message_code(&f, body);
127
128 if self.registered_fns.insert(f.id(), f.clone()).is_some() {
129 Err(Error::DuplicateEntryId(f.id().to_string()))
130 } else {
131 if !f.is_private() {
132 self.registered_message_fns.insert(f.public_id(), f.clone());
133 }
134 Ok(result)
135 }
136 }
137
138 fn append_var<S: ToString>(&mut self, id: &ast::Identifier<S>) -> Result<Ident, Error> {
139 if let Some(item) = self.pending_fns.last_mut() {
140 let var_name = id.name.to_string();
141 let var_ident = format_ident!("{}", var_name.to_case(Case::Snake));
142 let var = FluentVariable::new(var_name, var_ident.clone());
143 item.add_var(var);
144 Ok(var_ident)
145 } else {
146 Err(Error::UnexpectedContextState)
147 }
148 }
149
150 fn current_context(&self) -> Result<&FluentMessage, Error> {
151 self.pending_fns.last().ok_or(Error::UnexpectedContextState)
152 }
153
154 fn make_fn_ident<I: Into<FluentId>>(&self, id: I, attribute: Option<I>) -> Ident {
155 let id = id.into().as_ref().to_case(Case::Snake);
156 if let Some(attribute) = attribute {
157 let attr_id = attribute.into().as_ref().to_case(Case::Snake);
158 format_ident!("{}_{}_{}", self.prefix, id, attr_id,)
159 } else {
160 format_ident!("{}_{}", self.prefix, id)
161 }
162 }
163
164 fn enter_expr_context(&mut self, ctx: ExpressionContext) {
165 self.expression_contexts.push(ctx);
166 }
167
168 fn leave_expr_context(&mut self) -> Result<ExpressionContext, Error> {
169 self.expression_contexts
170 .pop()
171 .ok_or(Error::UnexpectedContextState)
172 }
173
174 fn current_expr_context(&self) -> &ExpressionContext {
175 self.expression_contexts
176 .last()
177 .unwrap_or(&ExpressionContext::Inline)
178 }
179
180 fn expr_context_depth(&self) -> usize {
181 self.expression_contexts.len()
182 }
183
184 fn find_entry<S: ToString>(
185 &self,
186 id: &ast::Identifier<S>,
187 attribute: Option<&ast::Identifier<S>>,
188 ) -> (FluentId, Option<FluentMessage>) {
189 let msg_id = if let Some(attribute_id) = attribute {
190 FluentId::from(id).join(attribute_id)
191 } else {
192 FluentId::from(id)
193 };
194 (msg_id.clone(), self.registered_fns.get(&msg_id).cloned())
195 }
196}
197
198impl<S: ToString> Visitor<S> for LanguageBuilder {
199 type Output = Result<TokenStream2, Error>;
200
201 fn visit_resource(&mut self, resource: &ast::Resource<S>) -> Self::Output {
202 resource
203 .body
204 .iter()
205 .try_fold(TokenStream2::new(), |mut result, entry| {
206 let tokens = entry.accept(self)?;
207 result.extend(tokens);
208 Ok(result)
209 })
210 }
211
212 fn visit_entry(&mut self, entry: &ast::Entry<S>) -> Self::Output {
213 match entry {
214 ast::Entry::Message(message) => message.accept(self),
215 ast::Entry::Term(term) => term.accept(self),
216 _ => Ok(TokenStream2::new()),
217 }
218 }
219
220 fn visit_message(&mut self, message: &ast::Message<S>) -> Self::Output {
221 self.push_message(message);
222 let body = message
223 .value
224 .as_ref()
225 .map(|pattern| pattern.accept(self))
226 .unwrap_or_else(|| Ok(TokenStream2::new()))?;
227
228 let attribute_fns = message
229 .attributes
230 .iter()
231 .map(|attribute| attribute.accept(self))
232 .collect::<Result<Vec<TokenStream2>, Error>>()?;
233
234 let message_fn = self.register_pending_fn(body)?;
235
236 Ok(quote! {
237 #message_fn
238 #(#attribute_fns)*
239 })
240 }
241
242 fn visit_term(&mut self, term: &ast::Term<S>) -> Self::Output {
243 self.push_term(term);
244 let body = term.value.accept(self)?;
245
246 let attribute_fns = term
247 .attributes
248 .iter()
249 .map(|attribute| attribute.accept(self))
250 .collect::<Result<Vec<TokenStream2>, Error>>()?;
251
252 let term_fn = self.register_pending_fn(body)?;
253
254 Ok(quote! {
255 #term_fn
256 #(#attribute_fns)*
257 })
258 }
259
260 fn visit_pattern(&mut self, pattern: &ast::Pattern<S>) -> Self::Output {
261 let elements: Vec<TokenStream2> = pattern
262 .elements
263 .iter()
264 .map(|element| element.accept(self))
265 .collect::<Result<Vec<TokenStream2>, Error>>()?;
266 Ok(quote! {
267 #(#elements)*
268 })
269 }
270
271 fn visit_text_element(&mut self, value: &S) -> Self::Output {
272 let text = Literal::string(value.to_string().as_str());
273 Ok(quote! {
274 out.write_str(#text)?;
275 })
276 }
277
278 fn visit_attribute(&mut self, attribute: &ast::Attribute<S>) -> Self::Output {
279 self.push_attribute(attribute)?;
280 let body = attribute.value.accept(self)?;
281 self.register_pending_fn(body)
282 }
283
284 fn visit_variant(
285 &mut self,
286 variant_key: &ast::VariantKey<S>,
287 pattern: &ast::Pattern<S>,
288 is_default: bool,
289 ) -> Self::Output {
290 let match_key = if is_default {
291 quote! {
292 _
293 }
294 } else {
295 match variant_key {
296 ast::VariantKey::Identifier { name } => {
297 let name = name.to_string();
298 let lit = Literal::string(&name);
299 if get_plural_category(variant_key).is_some() {
300 let category_ident = format_ident!("{}", &name.to_uppercase());
301 quote! {
302 (Some(::std::borrow::Cow::Borrowed(#lit)), _, _) | (_, _, Some(::fluent_static::intl_pluralrules::PluralCategory::#category_ident))
303 }
304 } else {
305 quote! {
306 (Some(::std::borrow::Cow::Borrowed(#lit)), None, None)
307 }
308 }
309 }
310 ast::VariantKey::NumberLiteral { value } => {
311 let number = mk_number(value)?;
312 quote! {
313 (None, Some(n), _) if n == #number
314 }
315 }
316 }
317 };
318
319 let body = pattern.accept(self)?;
320
321 Ok(quote! {
322 #match_key => {
323 #body
324 }
325 })
326 }
327
328 fn visit_comment(&mut self, _comment: &ast::Comment<S>) -> Self::Output {
329 todo!()
330 }
331
332 fn visit_call_arguments(&mut self, arguments: &ast::CallArguments<S>) -> Self::Output {
333 match self.current_expr_context() {
334 ExpressionContext::TermArguments { term } => {
335 let term = term.to_owned();
336 let vars = term.vars();
337 let vars_by_name: BTreeMap<&str, &Ident> = vars
338 .iter()
339 .map(|var| (var.var_name.as_str(), &var.var_ident))
340 .collect();
341 let mut sorted_args: BTreeMap<&Ident, TokenStream2> = BTreeMap::new();
342 for named_arg in arguments.named.iter() {
343 let name = named_arg.name.name.to_string();
344 if let Some(ident) = vars_by_name.get(name.as_str()) {
345 let tokens = named_arg.accept(self)?;
346 sorted_args.insert(ident, tokens);
347 } else {
348 let term_id = term.id().to_string();
349 return Err(Error::UndeclaredTermArgument {
350 term_id,
351 arg_name: name,
352 });
353 };
354 }
355 let args: Vec<TokenStream2> = sorted_args.into_values().collect();
356 Ok(quote! {
357 #(#args),*
358 })
359 }
360 ExpressionContext::Inline => Err(Error::UnexpectedContextState),
361 ExpressionContext::Selector { .. } => Err(Error::UnexpectedContextState),
362 ExpressionContext::FunctionCall {
363 positional_args,
364 named_args,
365 ..
366 } => {
367 let positional_args_ident = positional_args.clone();
368 let named_args_ident = named_args.clone();
369
370 let positional: Vec<TokenStream2> = arguments
371 .positional
372 .iter()
373 .map(|expr| expr.accept(self))
374 .collect::<Result<Vec<TokenStream2>, Error>>()?;
375
376 let named = arguments
377 .named
378 .iter()
379 .map(|arg| {
380 arg.value.accept(self).map(|val| {
381 let name = Literal::string(&arg.name.name.to_string());
382 quote! {
383 (#name, #val)
384 }
385 })
386 })
387 .collect::<Result<Vec<TokenStream2>, Error>>()?;
388 Ok(quote! {
389 let #positional_args_ident = [ #(#positional),* ];
390 let #named_args_ident = [ #(#named),* ];
391 })
392 }
393 }
394 }
395
396 fn visit_named_argument(&mut self, argument: &ast::NamedArgument<S>) -> Self::Output {
397 argument.value.accept(self)
398 }
399
400 fn visit_string_literal(&mut self, value: &S) -> Self::Output {
401 match self.current_expr_context() {
402 ExpressionContext::Inline => {
403 let literal = Literal::string(&value.to_string());
404 Ok(quote! {
405 out.write_str(#literal)?;
406 })
407 }
408 ExpressionContext::Selector { .. } => Err(Error::UnsupportedFeature {
409 feature: "Usage of string literal as a selector".to_string(),
410 id: self.current_context()?.id().to_string(),
411 }),
412 ExpressionContext::TermArguments { .. } | ExpressionContext::FunctionCall { .. } => {
413 let lit = Literal::string(&value.to_string());
414 Ok(quote! {
415 ::fluent_static::value::Value::from(#lit)
416 })
417 }
418 }
419 }
420
421 fn visit_number_literal(&mut self, value: &S) -> Self::Output {
422 match self.current_expr_context() {
423 ExpressionContext::Inline => {
424 let value = value.to_string();
425 let literal = Literal::string(&value);
426 Ok(quote! {
427 out.write_str(#literal)?;
428 })
429 }
430 ExpressionContext::Selector { .. } => Err(Error::UnsupportedFeature {
431 feature: "Usage of number literal as a selector".to_string(),
432 id: self.current_context()?.id().to_string(),
433 }),
434 ExpressionContext::TermArguments { .. } | ExpressionContext::FunctionCall { .. } => {
435 let number = mk_number(value)?;
436 Ok(quote! {
437 ::fluent_static::value::Value::Number { value: #number, format: None }
438 })
439 }
440 }
441 }
442
443 fn visit_function_reference(
444 &mut self,
445 id: &ast::Identifier<S>,
446 arguments: &ast::CallArguments<S>,
447 ) -> Self::Output {
448 let function_id = id.name.to_string();
449 let index = self.expr_context_depth() + 1;
450 let positional_args = format_ident!("positional_args_{}", index);
451 let named_args = format_ident!("named_args_{}", index);
452
453 let fn_call = if let Some(fn_call) =
454 self.fn_call_generator
455 .generate(&function_id, &positional_args, &named_args)
456 {
457 fn_call
458 } else {
459 return Err(Error::UnimplementedFunction {
460 entry_id: self.current_context()?.id().to_string(),
461 function_id: function_id.clone(),
462 });
463 };
464
465 self.enter_expr_context(ExpressionContext::FunctionCall {
466 positional_args,
467 named_args,
468 });
469
470 let args = arguments.accept(self)?;
471 self.leave_expr_context()?;
472
473 match self.current_expr_context() {
474 ExpressionContext::Inline => Ok(quote! {
475 {
476 #args
477 self._write_(&#fn_call, out)?;
478 };
479 }),
480 ExpressionContext::Selector { plural_rules } => {
481 let has_plural_rules = *plural_rules;
482 let number_expr = if has_plural_rules {
483 quote! {
484 {
485 let plural_category = self.language.plural_rules_cardinal().select(n.as_f64()).ok();
486 (None, Some(n), plural_category)
487 }
488 }
489 } else {
490 quote! {
491 (None, Some(n), None)
492 }
493 };
494 Ok(quote! {
495 {
496 #args
497
498 let fn_result = #fn_call;
499
500 match fn_result {
501 ::fluent_static::value::Value::String(s) => (Some(s), None, None),
502 ::fluent_static::value::Value::Number { value: n, .. } => #number_expr,
503 _ => (None, None, None)
504 }
505 }
506 })
507 }
508 ExpressionContext::TermArguments { .. } | ExpressionContext::FunctionCall { .. } => {
509 Ok(quote! {
510 {
511 #args
512 #fn_call
513 }
514 })
515 }
516 }
517 }
518
519 fn visit_message_reference(
520 &mut self,
521 id: &ast::Identifier<S>,
522 attribute: Option<&ast::Identifier<S>>,
523 ) -> Self::Output {
524 match self.current_expr_context() {
525 ExpressionContext::Inline => {
526 let (msg_id, msg) = self.find_entry(id, attribute);
527 if let Some(msg) = msg {
528 let fn_ident = msg.fn_ident();
529 Ok(quote! {
530 self.#fn_ident(out)?;
531 })
532 } else {
533 let entry_id = self.current_context()?.id().to_string();
534 let reference_id = msg_id.to_string();
535 Err(Error::UndeclaredMessageReference {
536 entry_id,
537 reference_id,
538 })
539 }
540 }
541 ExpressionContext::Selector { .. } => Err(Error::UnsupportedFeature {
542 feature: "Usage of message reference as selector".to_string(),
543 id: self.current_context()?.id().to_string(),
544 }),
545 ExpressionContext::TermArguments { .. } | ExpressionContext::FunctionCall { .. } => {
546 let (msg_id, msg) = self.find_entry(id, attribute);
547 if let Some(msg) = msg {
548 let fn_ident = msg.fn_ident();
549 Ok(quote! {
550 {
551 let mut out = String::new();
552 self.#fn_ident(&mut out)?;
553 ::fluent_static::value::Value::from(out)
554 }
555 })
556 } else {
557 let entry_id = self.current_context()?.id().to_string();
558 let reference_id = msg_id.to_string();
559 Err(Error::UndeclaredMessageReference {
560 entry_id,
561 reference_id,
562 })
563 }
564 }
565 }
566 }
567
568 fn visit_term_reference(
569 &mut self,
570 id: &ast::Identifier<S>,
571 attribute: Option<&ast::Identifier<S>>,
572 arguments: Option<&ast::CallArguments<S>>,
573 ) -> Self::Output {
574 match self.current_expr_context() {
575 ExpressionContext::Inline => {
576 let (term_id, term) = self.find_entry(id, attribute);
577 if let Some(term) = term.as_ref() {
578 let fn_ident = term.fn_ident();
579 let args = if let Some(args) = arguments.as_ref() {
580 self.enter_expr_context(ExpressionContext::TermArguments {
581 term: term.clone(),
582 });
583 let result = args.accept(self);
584 self.leave_expr_context()?;
585 result?
586 } else {
587 quote! {}
588 };
589 Ok(quote! {
590 self.#fn_ident(out, #args)?;
591 })
592 } else {
593 let entry_id = self.current_context()?.id().to_string();
594 let reference_id = term_id.to_string();
595 Err(Error::UndeclaredTermReference {
596 entry_id,
597 reference_id,
598 })
599 }
600 }
601 ExpressionContext::Selector { .. } => Err(Error::UnsupportedFeature {
602 feature: "Usage of term reference as selector".to_string(),
603 id: self.current_context()?.id().to_string(),
604 }),
605 ExpressionContext::TermArguments { .. } | ExpressionContext::FunctionCall { .. } => {
606 let (term_id, term) = self.find_entry(id, attribute);
607 if let Some(term) = term.as_ref() {
608 let fn_ident = term.fn_ident();
609 let args = if let Some(args) = arguments.as_ref() {
610 self.enter_expr_context(ExpressionContext::TermArguments {
611 term: term.clone(),
612 });
613 let result = args.accept(self);
614 self.leave_expr_context()?;
615 result?
616 } else {
617 quote! {}
618 };
619 Ok(quote! {
620 {
621 let mut out = String::new();
622 self.#fn_ident(&mut out, #args)?;
623 ::fluent_static::value::Value::from(out)
624 }
625 })
626 } else {
627 let entry_id = self.current_context()?.id().to_string();
628 let reference_id = term_id.to_string();
629 Err(Error::UndeclaredTermReference {
630 entry_id,
631 reference_id,
632 })
633 }
634 }
635 }
636 }
637
638 fn visit_variable_reference(&mut self, id: &ast::Identifier<S>) -> Self::Output {
639 match self.current_expr_context() {
640 ExpressionContext::Inline => {
641 let var_ident = self.append_var(id)?;
642 Ok(quote! {
643 self._write_(&#var_ident, out)?;
644 })
645 }
646 ExpressionContext::Selector { plural_rules } => {
647 let has_plural_rules = *plural_rules;
648 let var_ident = self.append_var(id)?;
649 let number_expr = if has_plural_rules {
650 quote! {
651 {
652 let plural_category = self.language.plural_rules_cardinal().select(n.as_f64()).ok();
653 (None, Some(n.clone()), plural_category)
654 }
655 }
656 } else {
657 quote! {
658 (None, Some(n.clone()), None::<::fluent_static::intl_pluralrules::PluralCategory>)
659 }
660 };
661 Ok(quote! {
662 {
663 match &#var_ident {
664 ::fluent_static::value::Value::String(s) => (Some(s.clone()), None, None),
665 ::fluent_static::value::Value::Number { value: n, .. } => #number_expr,
666 _ => (None, None, None)
667 }
668 }
669 })
670 }
671 ExpressionContext::TermArguments { .. } => Err(Error::UnsupportedFeature {
672 feature: "Usage of variable reference as a term argument".to_string(),
673 id: self.current_context()?.id().to_string(),
674 }),
675 ExpressionContext::FunctionCall { .. } => {
676 let var_ident = self.append_var(id)?;
677 Ok(quote! {
679 #var_ident
680 })
681 }
682 }
683 }
684
685 fn visit_select_expression<'a, I>(
686 &mut self,
687 selector: &'a ast::InlineExpression<S>,
688 variants: I,
689 ) -> Self::Output
690 where
691 I: Iterator<Item = &'a ast::Variant<S>>,
692 {
693 let mut variants: Vec<&ast::Variant<S>> = variants.collect();
694 variants.sort_by_key(|variant| variant.default);
696
697 let default_variants: Vec<&&ast::Variant<S>> =
698 variants.iter().filter(|variant| variant.default).collect();
699
700 if default_variants.len() != 1 {
702 let msg_id = self.current_context()?.id().to_string();
703 Err(Error::InvalidSelectorDefaultVariant { message_id: msg_id })
704 } else {
705 let plural_rules = variants
708 .iter()
709 .find(|variant| get_plural_category(&variant.key).is_some())
710 .is_some();
711
712 self.enter_expr_context(ExpressionContext::Selector { plural_rules });
713 let selector_expr = selector.accept(self)?;
714 self.leave_expr_context()?;
715 let selector_variants = variants
716 .iter()
717 .map(|variant| variant.accept(self))
718 .collect::<Result<Vec<TokenStream2>, Error>>()?;
719
720 Ok(quote! {
723 match #selector_expr {
724 #(#selector_variants),*
725 }
726 })
727 }
728 }
729}
730
731fn get_plural_category<S: ToString>(key: &ast::VariantKey<S>) -> Option<PluralCategory> {
732 if let ast::VariantKey::Identifier { name } = key {
733 match name.to_string().as_str() {
734 "zero" => Some(PluralCategory::ZERO),
735 "one" => Some(PluralCategory::ONE),
736 "two" => Some(PluralCategory::TWO),
737 "few" => Some(PluralCategory::FEW),
738 "many" => Some(PluralCategory::MANY),
739 "other" => Some(PluralCategory::OTHER),
740 _ => None,
741 }
742 } else {
743 None
744 }
745}
746
747fn mk_number<S: ToString>(value: &S) -> Result<TokenStream2, Error> {
748 let value = value.to_string();
749 match Value::try_number(&value) {
750 Value::Number { value, .. } => match value {
751 Number::I64(n) => Ok(quote! {
752 ::fluent_static::value::Number::I64(#n)
753 }),
754 Number::U64(n) => Ok(quote! {
755 ::fluent_static::value::Number::U64(#n)
756 }),
757 Number::I128(n) => Ok(quote! {
758 ::fluent_static::value::Number::I128(#n)
759 }),
760 Number::U128(n) => Ok(quote! {
761 ::fluent_static::value::Number::U128(#n)
762 }),
763 Number::F64(n) => Ok(quote! {
764 ::fluent_static::value::Number::F64(#n)
765 }),
766 },
767 _ => Err(Error::InvalidLiteral(value)),
768 }
769}