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