diff --git a/fluent-bundle/src/bundle.rs b/fluent-bundle/src/bundle.rs index d198004d..6c2510f5 100644 --- a/fluent-bundle/src/bundle.rs +++ b/fluent-bundle/src/bundle.rs @@ -21,7 +21,8 @@ use crate::entry::GetEntry; use crate::errors::{EntryKind, FluentError}; use crate::memoizer::MemoizerKind; use crate::message::FluentMessage; -use crate::resolver::{ResolveValue, Scope, WriteValue}; +use crate::resolver::pattern::{resolve_pattern, write_pattern}; +use crate::resolver::Scope; use crate::resource::FluentResource; use crate::types::FluentValue; @@ -450,7 +451,7 @@ impl FluentBundle { M: MemoizerKind, { let mut scope = Scope::new(self, args, Some(errors)); - pattern.write(w, &mut scope) + write_pattern(pattern, w, &mut scope) } /// Formats a pattern which comes from a `FluentMessage`. @@ -493,7 +494,7 @@ impl FluentBundle { M: MemoizerKind, { let mut scope = Scope::new(self, args, Some(errors)); - let value = pattern.resolve(&mut scope); + let value = resolve_pattern(pattern, &mut scope); value.into_string(&scope) } diff --git a/fluent-bundle/src/resolver/errors.rs b/fluent-bundle/src/resolver/errors.rs index 7606faba..ad042c5f 100644 --- a/fluent-bundle/src/resolver/errors.rs +++ b/fluent-bundle/src/resolver/errors.rs @@ -48,7 +48,7 @@ where } /// Errors generated during the process of resolving a fluent message into a string. -/// This process takes place in the `write` method of the `WriteValue` trait. +/// This process takes place in the `write_or_resolve` method of the `WriteOrResolve` trait. #[derive(Debug, PartialEq, Eq, Clone)] pub enum ResolverError { Reference(ReferenceKind), diff --git a/fluent-bundle/src/resolver/expression.rs b/fluent-bundle/src/resolver/expression.rs index ce030e4c..63dc50a8 100644 --- a/fluent-bundle/src/resolver/expression.rs +++ b/fluent-bundle/src/resolver/expression.rs @@ -1,5 +1,5 @@ use super::scope::Scope; -use super::WriteValue; +use super::{ResolveContext, WriteOrResolve, WriteOrResolveContext}; use std::borrow::Borrow; use std::fmt; @@ -7,25 +7,25 @@ use std::fmt; use fluent_syntax::ast; use crate::memoizer::MemoizerKind; -use crate::resolver::{ResolveValue, ResolverError}; +use crate::resolver::ResolverError; use crate::resource::FluentResource; use crate::types::FluentValue; -impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> { - fn write<'ast, 'args, 'errors, W, R, M>( - &'ast self, - w: &mut W, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> fmt::Result +impl<'bundle> WriteOrResolve<'bundle> for ast::Expression<&'bundle str> { + fn write_or_resolve<'other, R, M, T>( + &'bundle self, + scope: &mut Scope<'bundle, 'other, R, M>, + context: &mut T, + ) -> T::Result where - W: fmt::Write, R: Borrow, M: MemoizerKind, + T: WriteOrResolveContext<'bundle>, { match self { - Self::Inline(exp) => exp.write(w, scope), + Self::Inline(exp) => exp.write_or_resolve(scope, context), Self::Select { selector, variants } => { - let selector = selector.resolve(scope); + let selector = selector.write_or_resolve(scope, &mut ResolveContext); match selector { FluentValue::String(_) | FluentValue::Number(_) => { for variant in variants { @@ -36,7 +36,7 @@ impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> { } }; if key.matches(&selector, scope) { - return variant.value.write(w, scope); + return context.resolve_pattern(scope, &variant.value); } } } @@ -45,11 +45,11 @@ impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> { for variant in variants { if variant.default { - return variant.value.write(w, scope); + return context.resolve_pattern(scope, &variant.value); } } scope.add_error(ResolverError::MissingDefault); - Ok(()) + context.error(self, true) } } } diff --git a/fluent-bundle/src/resolver/inline_expression.rs b/fluent-bundle/src/resolver/inline_expression.rs index 3f8c8d4f..7bdf825c 100644 --- a/fluent-bundle/src/resolver/inline_expression.rs +++ b/fluent-bundle/src/resolver/inline_expression.rs @@ -1,30 +1,27 @@ use super::scope::Scope; -use super::{ResolveValue, ResolverError, WriteValue}; - -use std::borrow::Borrow; -use std::fmt; - -use fluent_syntax::ast; -use fluent_syntax::unicode::{unescape_unicode, unescape_unicode_to_string}; +use super::{ResolverError, WriteOrResolve, WriteOrResolveContext}; use crate::entry::GetEntry; use crate::memoizer::MemoizerKind; use crate::resource::FluentResource; use crate::types::FluentValue; +use fluent_syntax::ast; +use std::borrow::{Borrow, Cow}; +use std::fmt; -impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> { - fn write<'ast, 'args, 'errors, W, R, M>( - &'ast self, - w: &mut W, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> fmt::Result +impl<'bundle> WriteOrResolve<'bundle> for ast::InlineExpression<&'bundle str> { + fn write_or_resolve<'other, R, M, T>( + &'bundle self, + scope: &mut Scope<'bundle, 'other, R, M>, + context: &mut T, + ) -> T::Result where - W: fmt::Write, R: Borrow, M: MemoizerKind, + T: WriteOrResolveContext<'bundle>, { match self { - Self::StringLiteral { value } => unescape_unicode(w, value), + Self::StringLiteral { value } => context.unescape(value), Self::MessageReference { id, attribute } => { if let Some(msg) = scope.bundle.get_entry_message(id.name) { if let Some(attr) = attribute { @@ -32,28 +29,32 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> { .iter() .find_map(|a| { if a.id.name == attr.name { - Some(scope.track(w, &a.value, self)) + Some(scope.track(context, &a.value, self)) } else { None } }) - .unwrap_or_else(|| scope.write_ref_error(w, self)) + .unwrap_or_else(|| { + scope.add_error(self.into()); + context.error(self, true) + }) } else { msg.value .as_ref() - .map(|value| scope.track(w, value, self)) + .map(|value| scope.track(context, value, self)) .unwrap_or_else(|| { scope.add_error(ResolverError::NoValue(id.name.to_string())); - w.write_char('{')?; - self.write_error(w)?; - w.write_char('}') + context.error(self, true) }) } } else { - scope.write_ref_error(w, self) + scope.add_error(self.into()); + context.error(self, true) } } - Self::NumberLiteral { value } => FluentValue::try_number(value).write(w, scope), + Self::NumberLiteral { value } => { + context.value(scope, Cow::Owned(FluentValue::try_number(value))) + } Self::TermReference { id, attribute, @@ -69,16 +70,19 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> { if let Some(attr) = attribute { term.attributes.iter().find_map(|a| { if a.id.name == attr.name { - Some(scope.track(w, &a.value, self)) + Some(scope.track(context, &a.value, self)) } else { None } }) } else { - Some(scope.track(w, &term.value, self)) + Some(scope.track(context, &term.value, self)) } }) - .unwrap_or_else(|| scope.write_ref_error(w, self)); + .unwrap_or_else(|| { + scope.add_error(self.into()); + context.error(self, true) + }); scope.local_args = None; result } @@ -90,30 +94,31 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> { if let Some(func) = func { let result = func(resolved_positional_args.as_slice(), &resolved_named_args); - if let FluentValue::Error = result { - self.write_error(w) + if matches!(result, FluentValue::Error) { + context.error(self, false) } else { - w.write_str(&result.into_string(scope)) + context.value(scope, Cow::Owned(result)) } } else { - scope.write_ref_error(w, self) + scope.add_error(self.into()); + context.error(self, true) } } Self::VariableReference { id } => { - let args = scope.local_args.as_ref().or(scope.args); - - if let Some(arg) = args.and_then(|args| args.get(id.name)) { - arg.write(w, scope) - } else { - if scope.local_args.is_none() { - scope.add_error(self.into()); + if let Some(local_args) = &scope.local_args { + if let Some(arg) = local_args.get(id.name) { + return context.value(scope, Cow::Borrowed(arg)); } - w.write_char('{')?; - self.write_error(w)?; - w.write_char('}') + } else if let Some(arg) = scope.args.and_then(|args| args.get(id.name)) { + return context.value(scope, Cow::Owned(arg.into_owned())); + } + + if scope.local_args.is_none() { + scope.add_error(self.into()); } + context.error(self, true) } - Self::Placeable { expression } => expression.write(w, scope), + Self::Placeable { expression } => expression.write_or_resolve(scope, context), } } @@ -146,51 +151,3 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> { } } } - -impl<'bundle> ResolveValue<'bundle> for ast::InlineExpression<&'bundle str> { - fn resolve<'ast, 'args, 'errors, R, M>( - &'ast self, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> FluentValue<'bundle> - where - R: Borrow, - M: MemoizerKind, - { - match self { - Self::StringLiteral { value } => unescape_unicode_to_string(value).into(), - Self::NumberLiteral { value } => FluentValue::try_number(value), - Self::VariableReference { id } => { - if let Some(local_args) = &scope.local_args { - if let Some(arg) = local_args.get(id.name) { - return arg.clone(); - } - } else if let Some(arg) = scope.args.and_then(|args| args.get(id.name)) { - return arg.into_owned(); - } - - if scope.local_args.is_none() { - scope.add_error(self.into()); - } - FluentValue::Error - } - Self::FunctionReference { id, arguments } => { - let (resolved_positional_args, resolved_named_args) = - scope.get_arguments(Some(arguments)); - - let func = scope.bundle.get_entry_function(id.name); - - if let Some(func) = func { - let result = func(resolved_positional_args.as_slice(), &resolved_named_args); - result - } else { - FluentValue::Error - } - } - _ => { - let mut result = String::new(); - self.write(&mut result, scope).expect("Failed to write"); - result.into() - } - } - } -} diff --git a/fluent-bundle/src/resolver/mod.rs b/fluent-bundle/src/resolver/mod.rs index cbab7e84..d53f2537 100644 --- a/fluent-bundle/src/resolver/mod.rs +++ b/fluent-bundle/src/resolver/mod.rs @@ -1,52 +1,157 @@ //! The `resolver` module contains the definitions and implementations for the internal -//! `ResolveValue` and `WriteValue` traits. The former converts AST nodes to a -//! [`FluentValue`], and the latter converts them to a string that is written to an -//! implementor of the [`std::fmt::Write`] trait. +//! `WriteOrResolve` and `WriteOrResolveContext` traits. +//! There is an implementation that resolves AST nodes to a [`FluentValue`], and one +//! that writes to an implementor of the [`std::fmt::Write`] trait. pub mod errors; mod expression; mod inline_expression; -mod pattern; +pub(crate) mod pattern; mod scope; pub use errors::ResolverError; +use fluent_syntax::ast; +use fluent_syntax::unicode::{unescape_unicode, unescape_unicode_to_string}; pub use scope::Scope; -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow}; use std::fmt; use crate::memoizer::MemoizerKind; use crate::resource::FluentResource; use crate::types::FluentValue; -/// Resolves an AST node to a [`FluentValue`]. -pub(crate) trait ResolveValue<'bundle> { - /// Resolves an AST node to a [`FluentValue`]. - fn resolve<'ast, 'args, 'errors, R, M>( - &'ast self, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> FluentValue<'bundle> +use self::pattern::{resolve_pattern, write_pattern}; + +pub trait WriteOrResolveContext<'bundle> { + type Result; + + fn unescape(&mut self, s: &'bundle str) -> Self::Result; + fn value<'other, R, M>( + &mut self, + scope: &Scope<'bundle, 'other, R, M>, + value: Cow>, + ) -> Self::Result + where + R: Borrow, + M: MemoizerKind; + + fn error>(&mut self, exp: &E, is_ref: bool) -> Self::Result; + fn resolve_pattern<'other, R, M>( + &mut self, + scope: &mut Scope<'bundle, 'other, R, M>, + pattern: &'bundle ast::Pattern<&'bundle str>, + ) -> Self::Result where R: Borrow, M: MemoizerKind; } /// Resolves an AST node to a string that is written to source `W`. -pub(crate) trait WriteValue<'bundle> { - /// Resolves an AST node to a string that is written to source `W`. - fn write<'ast, 'args, 'errors, W, R, M>( - &'ast self, - w: &mut W, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> fmt::Result - where - W: fmt::Write, +impl<'bundle, W> WriteOrResolveContext<'bundle> for W +where + W: fmt::Write, +{ + type Result = fmt::Result; + + fn unescape(&mut self, s: &'bundle str) -> Self::Result { + unescape_unicode(self, s) + } + + fn value<'other, R, M>( + &mut self, + scope: &Scope<'bundle, 'other, R, M>, + value: Cow>, + ) -> Self::Result + where R: Borrow, - M: MemoizerKind; + M: MemoizerKind, + { + assert!(!matches!(value.as_ref(), FluentValue::Error)); + let s = match value { + Cow::Borrowed(value) => value.as_string(scope), + Cow::Owned(value) => value.into_string(scope), + }; + self.write_str(&s) + } + + fn error>(&mut self, exp: &E, is_ref: bool) -> Self::Result { + if is_ref { + self.write_char('{')?; + } + exp.write_error(self)?; + if is_ref { + self.write_char('}')?; + } + Ok(()) + } + + fn resolve_pattern<'other, R, M>( + &mut self, + scope: &mut Scope<'bundle, 'other, R, M>, + pattern: &'bundle ast::Pattern<&'bundle str>, + ) -> Self::Result + where + R: Borrow, + M: MemoizerKind, + { + write_pattern(pattern, self, scope) + } +} + +/// Resolves an AST node to a [`FluentValue`]. +struct ResolveContext; + +impl<'bundle> WriteOrResolveContext<'bundle> for ResolveContext { + type Result = FluentValue<'bundle>; + + fn unescape(&mut self, s: &'bundle str) -> Self::Result { + unescape_unicode_to_string(s).into() + } + + fn value<'other, R, M>( + &mut self, + _scope: &Scope<'bundle, 'other, R, M>, + value: Cow>, + ) -> Self::Result + where + R: Borrow, + M: MemoizerKind, + { + value.into_owned() + } + + fn error>(&mut self, _exp: &E, _is_ref: bool) -> Self::Result { + FluentValue::Error + } + + fn resolve_pattern<'other, R, M>( + &mut self, + scope: &mut Scope<'bundle, 'other, R, M>, + pattern: &'bundle ast::Pattern<&'bundle str>, + ) -> Self::Result + where + R: Borrow, + M: MemoizerKind, + { + resolve_pattern(pattern, scope) + } +} + +pub trait WriteOrResolve<'bundle> { + fn write_or_resolve<'other, R, M, T>( + &'bundle self, + scope: &mut Scope<'bundle, 'other, R, M>, + context: &mut T, + ) -> T::Result + where + R: Borrow, + M: MemoizerKind, + T: WriteOrResolveContext<'bundle>; /// Writes error information to `W`. This can be used to add FTL errors inline /// to a message. - fn write_error(&self, _w: &mut W) -> fmt::Result + fn write_error(&self, w: &mut W) -> fmt::Result where W: fmt::Write; } diff --git a/fluent-bundle/src/resolver/pattern.rs b/fluent-bundle/src/resolver/pattern.rs index e20bfcde..1cb32031 100644 --- a/fluent-bundle/src/resolver/pattern.rs +++ b/fluent-bundle/src/resolver/pattern.rs @@ -1,5 +1,5 @@ use super::scope::Scope; -use super::{ResolverError, WriteValue}; +use super::ResolverError; use std::borrow::Borrow; use std::fmt; @@ -7,102 +7,85 @@ use std::fmt; use fluent_syntax::ast; use crate::memoizer::MemoizerKind; -use crate::resolver::ResolveValue; use crate::resource::FluentResource; use crate::types::FluentValue; const MAX_PLACEABLES: u8 = 100; -impl<'bundle> WriteValue<'bundle> for ast::Pattern<&'bundle str> { - fn write<'ast, 'args, 'errors, W, R, M>( - &'ast self, - w: &mut W, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> fmt::Result - where - W: fmt::Write, - R: Borrow, - M: MemoizerKind, - { - let len = self.elements.len(); +pub fn write_pattern<'bundle, W, R, M>( + pattern: &'bundle ast::Pattern<&'bundle str>, + w: &mut W, + scope: &mut Scope<'bundle, '_, R, M>, +) -> fmt::Result +where + W: fmt::Write, + R: Borrow, + M: MemoizerKind, +{ + let len = pattern.elements.len(); - for elem in &self.elements { - if scope.dirty { - return Ok(()); - } + for elem in &pattern.elements { + if scope.dirty { + return Ok(()); + } - match elem { - ast::PatternElement::TextElement { value } => { - if let Some(ref transform) = scope.bundle.transform { - w.write_str(&transform(value))?; - } else { - w.write_str(value)?; - } + match elem { + ast::PatternElement::TextElement { value } => { + if let Some(ref transform) = scope.bundle.transform { + w.write_str(&transform(value))?; + } else { + w.write_str(value)?; + } + } + ast::PatternElement::Placeable { ref expression } => { + scope.placeables += 1; + if scope.placeables > MAX_PLACEABLES { + scope.dirty = true; + scope.add_error(ResolverError::TooManyPlaceables); + return Ok(()); } - ast::PatternElement::Placeable { ref expression } => { - scope.placeables += 1; - if scope.placeables > MAX_PLACEABLES { - scope.dirty = true; - scope.add_error(ResolverError::TooManyPlaceables); - return Ok(()); - } - let needs_isolation = scope.bundle.use_isolating - && len > 1 - && !matches!( - expression, - ast::Expression::Inline(ast::InlineExpression::MessageReference { .. },) - | ast::Expression::Inline( - ast::InlineExpression::TermReference { .. }, - ) - | ast::Expression::Inline( - ast::InlineExpression::StringLiteral { .. }, - ) - ); - if needs_isolation { - w.write_char('\u{2068}')?; - } - scope.maybe_track(w, self, expression)?; - if needs_isolation { - w.write_char('\u{2069}')?; - } + let needs_isolation = scope.bundle.use_isolating + && len > 1 + && !matches!( + expression, + ast::Expression::Inline(ast::InlineExpression::MessageReference { .. },) + | ast::Expression::Inline(ast::InlineExpression::TermReference { .. },) + | ast::Expression::Inline(ast::InlineExpression::StringLiteral { .. },) + ); + if needs_isolation { + w.write_char('\u{2068}')?; + } + scope.maybe_track(w, pattern, expression)?; + if needs_isolation { + w.write_char('\u{2069}')?; } } } - Ok(()) - } - - fn write_error(&self, _w: &mut W) -> fmt::Result - where - W: fmt::Write, - { - unreachable!() } + Ok(()) } -impl<'bundle> ResolveValue<'bundle> for ast::Pattern<&'bundle str> { - fn resolve<'ast, 'args, 'errors, R, M>( - &'ast self, - scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>, - ) -> FluentValue<'bundle> - where - R: Borrow, - M: MemoizerKind, - { - let len = self.elements.len(); +pub fn resolve_pattern<'bundle, R, M>( + pattern: &'bundle ast::Pattern<&'bundle str>, + scope: &mut Scope<'bundle, '_, R, M>, +) -> FluentValue<'bundle> +where + R: Borrow, + M: MemoizerKind, +{ + let len = pattern.elements.len(); - if len == 1 { - if let ast::PatternElement::TextElement { value } = self.elements[0] { - return scope - .bundle - .transform - .map_or_else(|| value.into(), |transform| transform(value).into()); - } + if len == 1 { + if let ast::PatternElement::TextElement { value } = pattern.elements[0] { + return scope + .bundle + .transform + .map_or_else(|| value.into(), |transform| transform(value).into()); } - - let mut result = String::new(); - self.write(&mut result, scope) - .expect("Failed to write to a string."); - result.into() } + + let mut result = String::new(); + write_pattern(pattern, &mut result, scope).expect("Failed to write to a string."); + result.into() } diff --git a/fluent-bundle/src/resolver/scope.rs b/fluent-bundle/src/resolver/scope.rs index 1ddff1a4..f12fddae 100644 --- a/fluent-bundle/src/resolver/scope.rs +++ b/fluent-bundle/src/resolver/scope.rs @@ -1,36 +1,36 @@ 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<'bundle, 'ast, 'args, 'errors, R, M> { +use super::{ResolveContext, ResolverError, WriteOrResolve, WriteOrResolveContext}; + +/// State for a single `WriteOrResolve::write_or_resolve` call. +pub struct Scope<'bundle, 'other, R, M> { /// The current `FluentBundle` instance. pub bundle: &'bundle FluentBundle, /// The current arguments passed by the developer. - pub(super) args: Option<&'args FluentArgs<'args>>, + pub(super) args: Option<&'other FluentArgs<'other>>, /// 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<[&'ast ast::Pattern<&'bundle str>; 2]>, + travelled: smallvec::SmallVec<[&'bundle ast::Pattern<&'bundle str>; 2]>, /// Track errors accumulated during resolving. - pub errors: Option<&'errors mut Vec>, + pub errors: Option<&'other mut Vec>, /// Makes the resolver bail. pub dirty: bool, } -impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R, M> { +impl<'bundle, 'other, R, M> Scope<'bundle, 'other, R, M> { pub fn new( bundle: &'bundle FluentBundle, - args: Option<&'args FluentArgs>, - errors: Option<&'errors mut Vec>, + args: Option<&'other FluentArgs>, + errors: Option<&'other mut Vec>, ) -> Self { Scope { bundle, @@ -54,82 +54,72 @@ impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R /// /// 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( + pub fn maybe_track( &mut self, - w: &mut W, - pattern: &'ast ast::Pattern<&'bundle str>, - exp: &'ast ast::Expression<&'bundle str>, - ) -> fmt::Result + context: &mut T, + pattern: &'bundle ast::Pattern<&'bundle str>, + exp: &'bundle ast::Expression<&'bundle str>, + ) -> T::Result where R: Borrow, - W: fmt::Write, M: MemoizerKind, + T: WriteOrResolveContext<'bundle>, { if self.travelled.is_empty() { self.travelled.push(pattern); } - exp.write(w, self)?; + let res = exp.write_or_resolve(self, context); if self.dirty { - w.write_char('{')?; - exp.write_error(w)?; - w.write_char('}') + context.error(exp, true) } else { - Ok(()) + res } } - pub fn track( + pub fn track( &mut self, - w: &mut W, - pattern: &'ast ast::Pattern<&'bundle str>, - exp: &'ast ast::InlineExpression<&'bundle str>, - ) -> fmt::Result + context: &mut T, + pattern: &'bundle ast::Pattern<&'bundle str>, + exp: &'bundle ast::InlineExpression<&'bundle str>, + ) -> T::Result where R: Borrow, - W: fmt::Write, M: MemoizerKind, + T: WriteOrResolveContext<'bundle>, { if self.travelled.contains(&pattern) { self.add_error(ResolverError::Cyclic); - w.write_char('{')?; - exp.write_error(w)?; - w.write_char('}') + context.error(exp, true) } else { self.travelled.push(pattern); - let result = pattern.write(w, self); + let result = context.resolve_pattern(self, pattern); 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<&'ast ast::CallArguments<&'bundle str>>, + arguments: Option<&'bundle ast::CallArguments<&'bundle str>>, ) -> (Vec>, FluentArgs<'bundle>) where R: Borrow, M: MemoizerKind, { if let Some(ast::CallArguments { positional, named }) = arguments { - let positional = positional.iter().map(|expr| expr.resolve(self)).collect(); + let positional = positional + .iter() + .map(|expr| expr.write_or_resolve(self, &mut ResolveContext)) + .collect(); let named = named .iter() - .map(|arg| (arg.name.name, arg.value.resolve(self))) + .map(|arg| { + ( + arg.name.name, + arg.value.write_or_resolve(self, &mut ResolveContext), + ) + }) .collect(); (positional, named) diff --git a/fluent-syntax/benches/parser.rs b/fluent-syntax/benches/parser.rs index 2397044d..71fe96e5 100644 --- a/fluent-syntax/benches/parser.rs +++ b/fluent-syntax/benches/parser.rs @@ -18,7 +18,7 @@ fn get_resources(tests: &[&'static str]) -> HashMap<&'static str, String> { let path = format!("./benches/{}", test); ftl_strings.insert(*test, read_file(&path).expect("Couldn't load file")); } - return ftl_strings; + ftl_strings } fn get_ctxs(tests: &[&'static str]) -> HashMap<&'static str, Vec> { diff --git a/fluent-syntax/tests/parser_fixtures.rs b/fluent-syntax/tests/parser_fixtures.rs index eb8b9d1f..a067d38e 100644 --- a/fluent-syntax/tests/parser_fixtures.rs +++ b/fluent-syntax/tests/parser_fixtures.rs @@ -27,7 +27,7 @@ fn parse_fixtures_compare() { let reference_path = path.replace(".ftl", ".json"); let reference_file = read_file(&reference_path, true).unwrap(); - let ftl_file = read_file(&path, false).unwrap(); + let ftl_file = read_file(path, false).unwrap(); println!("Parsing: {:#?}", path); let target_ast = match parse(ftl_file) { @@ -72,7 +72,7 @@ fn parse_bench_fixtures() { file_name.replace(".ftl", ".json") ); let reference_file = read_file(&reference_path, true).unwrap(); - let ftl_file = read_file(&path, false).unwrap(); + let ftl_file = read_file(path, false).unwrap(); println!("Parsing: {:#?}", path); let target_ast = match parse(ftl_file) { @@ -106,7 +106,7 @@ fn parse_bench_fixtures() { file_name.replace(".ftl", ".json") ); let reference_file = read_file(&reference_path, true).unwrap(); - let ftl_file = read_file(&path, false).unwrap(); + let ftl_file = read_file(path, false).unwrap(); println!("Parsing: {:#?}", path); let target_ast = match parse(ftl_file.clone()) {