use crate::bundle::FluentBundle; use crate::memoizer::MemoizerKind; use crate::resolver::{ResolveValue, ResolverError, WriteValue}; use crate::types::FluentValue; use crate::{FluentArgs, FluentError, FluentResource}; use fluent_syntax::ast; use std::borrow::Borrow; use std::fmt; /// State for a single `ResolveValue::to_value` call. pub struct Scope<'scope, 'errors, R, M> { /// The current `FluentBundle` instance. pub bundle: &'scope FluentBundle, /// The current arguments passed by the developer. pub(super) args: Option<&'scope FluentArgs<'scope>>, /// Local args pub(super) local_args: Option>, /// The running count of resolved placeables. Used to detect the Billion /// Laughs and Quadratic Blowup attacks. pub(super) placeables: u8, /// Tracks hashes to prevent infinite recursion. travelled: smallvec::SmallVec<[&'scope ast::Pattern<&'scope str>; 2]>, /// Track errors accumulated during resolving. pub errors: Option<&'errors mut Vec>, /// Makes the resolver bail. pub dirty: bool, } impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> { pub fn new( bundle: &'scope FluentBundle, args: Option<&'scope FluentArgs>, errors: Option<&'errors mut Vec>, ) -> Self { Scope { bundle, args, local_args: None, placeables: 0, travelled: Default::default(), errors, dirty: false, } } pub fn add_error(&mut self, error: ResolverError) { if let Some(errors) = self.errors.as_mut() { errors.push(error.into()); } } // This method allows us to lazily add Pattern on the stack, // only if the Pattern::resolve has been called on an empty stack. // // This is the case when pattern is called from Bundle and it // allows us to fast-path simple resolutions, and only use the stack // for placeables. pub fn maybe_track( &mut self, w: &mut W, pattern: &'scope ast::Pattern<&str>, exp: &'scope ast::Expression<&str>, ) -> fmt::Result where R: Borrow, W: fmt::Write, M: MemoizerKind, { if self.travelled.is_empty() { self.travelled.push(pattern); } exp.write(w, self)?; if self.dirty { w.write_char('{')?; exp.write_error(w)?; w.write_char('}') } else { Ok(()) } } pub fn track( &mut self, w: &mut W, pattern: &'scope ast::Pattern<&str>, exp: &ast::InlineExpression<&str>, ) -> fmt::Result where R: Borrow, W: fmt::Write, M: MemoizerKind, { if self.travelled.contains(&pattern) { self.add_error(ResolverError::Cyclic); w.write_char('{')?; exp.write_error(w)?; w.write_char('}') } else { self.travelled.push(pattern); let result = pattern.write(w, self); self.travelled.pop(); result } } pub fn write_ref_error( &mut self, w: &mut W, exp: &ast::InlineExpression<&str>, ) -> fmt::Result where W: fmt::Write, { self.add_error(exp.into()); w.write_char('{')?; exp.write_error(w)?; w.write_char('}') } pub fn get_arguments( &mut self, arguments: Option<&'scope ast::CallArguments<&'scope str>>, ) -> (Vec>, FluentArgs<'scope>) where R: Borrow, M: MemoizerKind, { if let Some(ast::CallArguments { positional, named }) = arguments { let positional = positional.iter().map(|expr| expr.resolve(self)).collect(); let named = named .iter() .map(|arg| (arg.name.name, arg.value.resolve(self))) .collect(); (positional, named) } else { (Vec::new(), FluentArgs::new()) } } }