fluent_bundle/resolver/
scope.rs

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    traveled: 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            traveled: 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.traveled.is_empty() {
69            self.traveled.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.traveled.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.traveled.push(pattern);
99            let result = pattern.write(w, self);
100            self.traveled.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}