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> { |
30 | 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 | |
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. |
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 | |
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 | |
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 | |
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 | |