1 use crate::bundle::FluentBundle;
2 use crate::memoizer::MemoizerKind;
3 use crate::resolver::{ResolveValue, ResolverError, WriteValue};
4 use crate::types::FluentValue;
5 use crate::{FluentArgs, FluentError, FluentResource};
6 use fluent_syntax::ast;
7 use std::borrow::Borrow;
8 use std::fmt;
9 
10 /// State for a single `ResolveValue::to_value` call.
11 pub struct Scope<'scope, 'errors, R, M> {
12     /// The current `FluentBundle` instance.
13     pub bundle: &'scope FluentBundle<R, M>,
14     /// The current arguments passed by the developer.
15     pub(super) args: Option<&'scope FluentArgs<'scope>>,
16     /// Local args
17     pub(super) local_args: Option<FluentArgs<'scope>>,
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<[&'scope ast::Pattern<&'scope 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 
29 impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
new( bundle: &'scope FluentBundle<R, M>, args: Option<&'scope FluentArgs>, errors: Option<&'errors mut Vec<FluentError>>, ) -> Self30     pub fn new(
31         bundle: &'scope FluentBundle<R, M>,
32         args: Option<&'scope 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 
add_error(&mut self, error: ResolverError)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,
53     // only if the Pattern::resolve has been called on an empty stack.
54     //
55     // This is the case when pattern is called from Bundle and it
56     // allows us to fast-path simple resolutions, and only use the stack
57     // for placeables.
maybe_track<W>( &mut self, w: &mut W, pattern: &'scope ast::Pattern<&str>, exp: &'scope ast::Expression<&str>, ) -> fmt::Result where R: Borrow<FluentResource>, W: fmt::Write, M: MemoizerKind,58     pub fn maybe_track<W>(
59         &mut self,
60         w: &mut W,
61         pattern: &'scope ast::Pattern<&str>,
62         exp: &'scope ast::Expression<&str>,
63     ) -> fmt::Result
64     where
65         R: Borrow<FluentResource>,
66         W: fmt::Write,
67         M: MemoizerKind,
68     {
69         if self.travelled.is_empty() {
70             self.travelled.push(pattern);
71         }
72         exp.write(w, self)?;
73         if self.dirty {
74             w.write_char('{')?;
75             exp.write_error(w)?;
76             w.write_char('}')
77         } else {
78             Ok(())
79         }
80     }
81 
track<W>( &mut self, w: &mut W, pattern: &'scope ast::Pattern<&str>, exp: &ast::InlineExpression<&str>, ) -> fmt::Result where R: Borrow<FluentResource>, W: fmt::Write, M: MemoizerKind,82     pub fn track<W>(
83         &mut self,
84         w: &mut W,
85         pattern: &'scope ast::Pattern<&str>,
86         exp: &ast::InlineExpression<&str>,
87     ) -> fmt::Result
88     where
89         R: Borrow<FluentResource>,
90         W: fmt::Write,
91         M: MemoizerKind,
92     {
93         if self.travelled.contains(&pattern) {
94             self.add_error(ResolverError::Cyclic);
95             w.write_char('{')?;
96             exp.write_error(w)?;
97             w.write_char('}')
98         } else {
99             self.travelled.push(pattern);
100             let result = pattern.write(w, self);
101             self.travelled.pop();
102             result
103         }
104     }
105 
write_ref_error<W>( &mut self, w: &mut W, exp: &ast::InlineExpression<&str>, ) -> fmt::Result where W: fmt::Write,106     pub fn write_ref_error<W>(
107         &mut self,
108         w: &mut W,
109         exp: &ast::InlineExpression<&str>,
110     ) -> fmt::Result
111     where
112         W: fmt::Write,
113     {
114         self.add_error(exp.into());
115         w.write_char('{')?;
116         exp.write_error(w)?;
117         w.write_char('}')
118     }
119 
get_arguments( &mut self, arguments: Option<&'scope ast::CallArguments<&'scope str>>, ) -> (Vec<FluentValue<'scope>>, FluentArgs<'scope>) where R: Borrow<FluentResource>, M: MemoizerKind,120     pub fn get_arguments(
121         &mut self,
122         arguments: Option<&'scope ast::CallArguments<&'scope str>>,
123     ) -> (Vec<FluentValue<'scope>>, FluentArgs<'scope>)
124     where
125         R: Borrow<FluentResource>,
126         M: MemoizerKind,
127     {
128         if let Some(ast::CallArguments { positional, named }) = arguments {
129             let positional = positional.iter().map(|expr| expr.resolve(self)).collect();
130 
131             let named = named
132                 .iter()
133                 .map(|arg| (arg.name.name, arg.value.resolve(self)))
134                 .collect();
135 
136             (positional, named)
137         } else {
138             (Vec::new(), FluentArgs::new())
139         }
140     }
141 }
142