1use crate::bundle::FluentBundle;
2use crate::memoizer::MemoizerKind;
3use crate::resolver::{ResolveValue, ResolverError, WriteValue};
4use crate::types::FluentValue;
5use crate::{FluentArgs, FluentError, FluentResource};
6use fluent_syntax::ast;
7use std::borrow::Borrow;
8use std::fmt;
9
10/// State for a single `ResolveValue::to_value` call.
11pub struct Scope<'bundle, 'ast, 'args, 'errors, R, M> {
12 /// The current `FluentBundle` instance.
13 pub bundle: &'bundle FluentBundle<R, M>,
14 /// The current arguments passed by the developer.
15 pub(super) args: Option<&'args FluentArgs<'args>>,
16 /// Local args
17 pub(super) local_args: Option<FluentArgs<'bundle>>,
18 /// The running count of resolved placeables. Used to detect the Billion
19 /// Laughs and Quadratic Blowup attacks.
20 pub(super) placeables: u8,
21 /// Tracks hashes to prevent infinite recursion.
22 travelled: smallvec::SmallVec<[&'ast ast::Pattern<&'bundle str>; 2]>,
23 /// Track errors accumulated during resolving.
24 pub errors: Option<&'errors mut Vec<FluentError>>,
25 /// Makes the resolver bail.
26 pub dirty: bool,
27}
28
29impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R, M> {
30 pub fn new(
31 bundle: &'bundle FluentBundle<R, M>,
32 args: Option<&'args FluentArgs>,
33 errors: Option<&'errors mut Vec<FluentError>>,
34 ) -> Self {
35 Scope {
36 bundle,
37 args,
38 local_args: None,
39 placeables: 0,
40 travelled: Default::default(),
41 errors,
42 dirty: false,
43 }
44 }
45
46 pub fn add_error(&mut self, error: ResolverError) {
47 if let Some(errors) = self.errors.as_mut() {
48 errors.push(error.into());
49 }
50 }
51
52 /// This method allows us to lazily add Pattern on the stack, only if the
53 /// Pattern::resolve has been called on an empty stack.
54 ///
55 /// This is the case when pattern is called from Bundle and it allows us to fast-path
56 /// simple resolutions, and only use the stack for placeables.
57 pub fn maybe_track<W>(
58 &mut self,
59 w: &mut W,
60 pattern: &'ast ast::Pattern<&'bundle str>,
61 exp: &'ast ast::Expression<&'bundle str>,
62 ) -> fmt::Result
63 where
64 R: Borrow<FluentResource>,
65 W: fmt::Write,
66 M: MemoizerKind,
67 {
68 if self.travelled.is_empty() {
69 self.travelled.push(pattern);
70 }
71 exp.write(w, self)?;
72 if self.dirty {
73 w.write_char('{')?;
74 exp.write_error(w)?;
75 w.write_char('}')
76 } else {
77 Ok(())
78 }
79 }
80
81 pub fn track<W>(
82 &mut self,
83 w: &mut W,
84 pattern: &'ast ast::Pattern<&'bundle str>,
85 exp: &'ast ast::InlineExpression<&'bundle str>,
86 ) -> fmt::Result
87 where
88 R: Borrow<FluentResource>,
89 W: fmt::Write,
90 M: MemoizerKind,
91 {
92 if self.travelled.contains(&pattern) {
93 self.add_error(ResolverError::Cyclic);
94 w.write_char('{')?;
95 exp.write_error(w)?;
96 w.write_char('}')
97 } else {
98 self.travelled.push(pattern);
99 let result = pattern.write(w, self);
100 self.travelled.pop();
101 result
102 }
103 }
104
105 pub fn write_ref_error<W>(
106 &mut self,
107 w: &mut W,
108 exp: &ast::InlineExpression<&str>,
109 ) -> fmt::Result
110 where
111 W: fmt::Write,
112 {
113 self.add_error(exp.into());
114 w.write_char('{')?;
115 exp.write_error(w)?;
116 w.write_char('}')
117 }
118
119 pub fn get_arguments(
120 &mut self,
121 arguments: Option<&'ast ast::CallArguments<&'bundle str>>,
122 ) -> (Vec<FluentValue<'bundle>>, FluentArgs<'bundle>)
123 where
124 R: Borrow<FluentResource>,
125 M: MemoizerKind,
126 {
127 if let Some(ast::CallArguments { positional, named }) = arguments {
128 let positional = positional.iter().map(|expr| expr.resolve(self)).collect();
129
130 let named = named
131 .iter()
132 .map(|arg| (arg.name.name, arg.value.resolve(self)))
133 .collect();
134
135 (positional, named)
136 } else {
137 (Vec::new(), FluentArgs::new())
138 }
139 }
140}
141