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<'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
29impl<'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