summaryrefslogtreecommitdiffstats
path: root/vendor/fluent-bundle/src/resolver
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/fluent-bundle/src/resolver
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/fluent-bundle/src/resolver')
-rw-r--r--vendor/fluent-bundle/src/resolver/errors.rs96
-rw-r--r--vendor/fluent-bundle/src/resolver/expression.rs66
-rw-r--r--vendor/fluent-bundle/src/resolver/inline_expression.rs181
-rw-r--r--vendor/fluent-bundle/src/resolver/mod.rs42
-rw-r--r--vendor/fluent-bundle/src/resolver/pattern.rs108
-rw-r--r--vendor/fluent-bundle/src/resolver/scope.rs141
6 files changed, 634 insertions, 0 deletions
diff --git a/vendor/fluent-bundle/src/resolver/errors.rs b/vendor/fluent-bundle/src/resolver/errors.rs
new file mode 100644
index 000000000..831d8474a
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/errors.rs
@@ -0,0 +1,96 @@
+use fluent_syntax::ast::InlineExpression;
+use std::error::Error;
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum ReferenceKind {
+ Function {
+ id: String,
+ },
+ Message {
+ id: String,
+ attribute: Option<String>,
+ },
+ Term {
+ id: String,
+ attribute: Option<String>,
+ },
+ Variable {
+ id: String,
+ },
+}
+
+impl<T> From<&InlineExpression<T>> for ReferenceKind
+where
+ T: ToString,
+{
+ fn from(exp: &InlineExpression<T>) -> Self {
+ match exp {
+ InlineExpression::FunctionReference { id, .. } => Self::Function {
+ id: id.name.to_string(),
+ },
+ InlineExpression::MessageReference { id, attribute } => Self::Message {
+ id: id.name.to_string(),
+ attribute: attribute.as_ref().map(|i| i.name.to_string()),
+ },
+ InlineExpression::TermReference { id, attribute, .. } => Self::Term {
+ id: id.name.to_string(),
+ attribute: attribute.as_ref().map(|i| i.name.to_string()),
+ },
+ InlineExpression::VariableReference { id, .. } => Self::Variable {
+ id: id.name.to_string(),
+ },
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum ResolverError {
+ Reference(ReferenceKind),
+ NoValue(String),
+ MissingDefault,
+ Cyclic,
+ TooManyPlaceables,
+}
+
+impl std::fmt::Display for ResolverError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Reference(exp) => match exp {
+ ReferenceKind::Function { id } => write!(f, "Unknown function: {}()", id),
+ ReferenceKind::Message {
+ id,
+ attribute: None,
+ } => write!(f, "Unknown message: {}", id),
+ ReferenceKind::Message {
+ id,
+ attribute: Some(attribute),
+ } => write!(f, "Unknown attribute: {}.{}", id, attribute),
+ ReferenceKind::Term {
+ id,
+ attribute: None,
+ } => write!(f, "Unknown term: -{}", id),
+ ReferenceKind::Term {
+ id,
+ attribute: Some(attribute),
+ } => write!(f, "Unknown attribute: -{}.{}", id, attribute),
+ ReferenceKind::Variable { id } => write!(f, "Unknown variable: ${}", id),
+ },
+ Self::NoValue(id) => write!(f, "No value: {}", id),
+ Self::MissingDefault => f.write_str("No default"),
+ Self::Cyclic => f.write_str("Cyclical dependency detected"),
+ Self::TooManyPlaceables => f.write_str("Too many placeables"),
+ }
+ }
+}
+
+impl<T> From<&InlineExpression<T>> for ResolverError
+where
+ T: ToString,
+{
+ fn from(exp: &InlineExpression<T>) -> Self {
+ Self::Reference(exp.into())
+ }
+}
+
+impl Error for ResolverError {}
diff --git a/vendor/fluent-bundle/src/resolver/expression.rs b/vendor/fluent-bundle/src/resolver/expression.rs
new file mode 100644
index 000000000..d0d02decd
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/expression.rs
@@ -0,0 +1,66 @@
+use super::scope::Scope;
+use super::WriteValue;
+
+use std::borrow::Borrow;
+use std::fmt;
+
+use fluent_syntax::ast;
+
+use crate::memoizer::MemoizerKind;
+use crate::resolver::{ResolveValue, ResolverError};
+use crate::resource::FluentResource;
+use crate::types::FluentValue;
+
+impl<'p> WriteValue for ast::Expression<&'p str> {
+ fn write<'scope, 'errors, W, R, M>(
+ &'scope self,
+ w: &mut W,
+ scope: &mut Scope<'scope, 'errors, R, M>,
+ ) -> fmt::Result
+ where
+ W: fmt::Write,
+ R: Borrow<FluentResource>,
+ M: MemoizerKind,
+ {
+ match self {
+ Self::Inline(exp) => exp.write(w, scope),
+ Self::Select { selector, variants } => {
+ let selector = selector.resolve(scope);
+ match selector {
+ FluentValue::String(_) | FluentValue::Number(_) => {
+ for variant in variants {
+ let key = match variant.key {
+ ast::VariantKey::Identifier { name } => name.into(),
+ ast::VariantKey::NumberLiteral { value } => {
+ FluentValue::try_number(value)
+ }
+ };
+ if key.matches(&selector, scope) {
+ return variant.value.write(w, scope);
+ }
+ }
+ }
+ _ => {}
+ }
+
+ for variant in variants {
+ if variant.default {
+ return variant.value.write(w, scope);
+ }
+ }
+ scope.add_error(ResolverError::MissingDefault);
+ Ok(())
+ }
+ }
+ }
+
+ fn write_error<W>(&self, w: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ match self {
+ Self::Inline(exp) => exp.write_error(w),
+ Self::Select { .. } => unreachable!(),
+ }
+ }
+}
diff --git a/vendor/fluent-bundle/src/resolver/inline_expression.rs b/vendor/fluent-bundle/src/resolver/inline_expression.rs
new file mode 100644
index 000000000..b9e89b6e8
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/inline_expression.rs
@@ -0,0 +1,181 @@
+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 crate::entry::GetEntry;
+use crate::memoizer::MemoizerKind;
+use crate::resource::FluentResource;
+use crate::types::FluentValue;
+
+impl<'p> WriteValue for ast::InlineExpression<&'p str> {
+ fn write<'scope, 'errors, W, R, M>(
+ &'scope self,
+ w: &mut W,
+ scope: &mut Scope<'scope, 'errors, R, M>,
+ ) -> fmt::Result
+ where
+ W: fmt::Write,
+ R: Borrow<FluentResource>,
+ M: MemoizerKind,
+ {
+ match self {
+ Self::StringLiteral { value } => unescape_unicode(w, value),
+ Self::MessageReference { id, attribute } => {
+ if let Some(msg) = scope.bundle.get_entry_message(id.name) {
+ if let Some(attr) = attribute {
+ msg.attributes
+ .iter()
+ .find_map(|a| {
+ if a.id.name == attr.name {
+ Some(scope.track(w, &a.value, self))
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| scope.write_ref_error(w, self))
+ } else {
+ msg.value
+ .as_ref()
+ .map(|value| scope.track(w, value, self))
+ .unwrap_or_else(|| {
+ scope.add_error(ResolverError::NoValue(id.name.to_string()));
+ w.write_char('{')?;
+ self.write_error(w)?;
+ w.write_char('}')
+ })
+ }
+ } else {
+ scope.write_ref_error(w, self)
+ }
+ }
+ Self::NumberLiteral { value } => FluentValue::try_number(*value).write(w, scope),
+ Self::TermReference {
+ id,
+ attribute,
+ arguments,
+ } => {
+ let (_, resolved_named_args) = scope.get_arguments(arguments.as_ref());
+
+ scope.local_args = Some(resolved_named_args);
+ let result = scope
+ .bundle
+ .get_entry_term(id.name)
+ .and_then(|term| {
+ if let Some(attr) = attribute {
+ term.attributes.iter().find_map(|a| {
+ if a.id.name == attr.name {
+ Some(scope.track(w, &a.value, self))
+ } else {
+ None
+ }
+ })
+ } else {
+ Some(scope.track(w, &term.value, self))
+ }
+ })
+ .unwrap_or_else(|| scope.write_ref_error(w, self));
+ scope.local_args = None;
+ result
+ }
+ 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);
+ if let FluentValue::Error = result {
+ self.write_error(w)
+ } else {
+ w.write_str(&result.as_string(scope))
+ }
+ } else {
+ scope.write_ref_error(w, self)
+ }
+ }
+ 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());
+ }
+ w.write_char('{')?;
+ self.write_error(w)?;
+ w.write_char('}')
+ }
+ }
+ Self::Placeable { expression } => expression.write(w, scope),
+ }
+ }
+
+ fn write_error<W>(&self, w: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ match self {
+ Self::MessageReference {
+ id,
+ attribute: Some(attribute),
+ } => write!(w, "{}.{}", id.name, attribute.name),
+ Self::MessageReference {
+ id,
+ attribute: None,
+ } => w.write_str(id.name),
+ Self::TermReference {
+ id,
+ attribute: Some(attribute),
+ ..
+ } => write!(w, "-{}.{}", id.name, attribute.name),
+ Self::TermReference {
+ id,
+ attribute: None,
+ ..
+ } => write!(w, "-{}", id.name),
+ Self::FunctionReference { id, .. } => write!(w, "{}()", id.name),
+ Self::VariableReference { id } => write!(w, "${}", id.name),
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<'p> ResolveValue for ast::InlineExpression<&'p str> {
+ fn resolve<'source, 'errors, R, M>(
+ &'source self,
+ scope: &mut Scope<'source, 'errors, R, M>,
+ ) -> FluentValue<'source>
+ where
+ R: Borrow<FluentResource>,
+ M: MemoizerKind,
+ {
+ match self {
+ Self::StringLiteral { value } => unescape_unicode_to_string(value).into(),
+ Self::NumberLiteral { value } => FluentValue::try_number(*value),
+ 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.clone()
+ } else {
+ if scope.local_args.is_none() {
+ scope.add_error(self.into());
+ }
+ FluentValue::Error
+ }
+ }
+ _ => {
+ let mut result = String::new();
+ self.write(&mut result, scope).expect("Failed to write");
+ result.into()
+ }
+ }
+ }
+}
diff --git a/vendor/fluent-bundle/src/resolver/mod.rs b/vendor/fluent-bundle/src/resolver/mod.rs
new file mode 100644
index 000000000..f137bcc91
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/mod.rs
@@ -0,0 +1,42 @@
+pub mod errors;
+mod expression;
+mod inline_expression;
+mod pattern;
+mod scope;
+
+pub use errors::ResolverError;
+pub use scope::Scope;
+
+use std::borrow::Borrow;
+use std::fmt;
+
+use crate::memoizer::MemoizerKind;
+use crate::resource::FluentResource;
+use crate::types::FluentValue;
+
+// Converts an AST node to a `FluentValue`.
+pub(crate) trait ResolveValue {
+ fn resolve<'source, 'errors, R, M>(
+ &'source self,
+ scope: &mut Scope<'source, 'errors, R, M>,
+ ) -> FluentValue<'source>
+ where
+ R: Borrow<FluentResource>,
+ M: MemoizerKind;
+}
+
+pub(crate) trait WriteValue {
+ fn write<'source, 'errors, W, R, M>(
+ &'source self,
+ w: &mut W,
+ scope: &mut Scope<'source, 'errors, R, M>,
+ ) -> fmt::Result
+ where
+ W: fmt::Write,
+ R: Borrow<FluentResource>,
+ M: MemoizerKind;
+
+ fn write_error<W>(&self, _w: &mut W) -> fmt::Result
+ where
+ W: fmt::Write;
+}
diff --git a/vendor/fluent-bundle/src/resolver/pattern.rs b/vendor/fluent-bundle/src/resolver/pattern.rs
new file mode 100644
index 000000000..4e01d4ca4
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/pattern.rs
@@ -0,0 +1,108 @@
+use super::scope::Scope;
+use super::{ResolverError, WriteValue};
+
+use std::borrow::Borrow;
+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<'p> WriteValue for ast::Pattern<&'p str> {
+ fn write<'scope, 'errors, W, R, M>(
+ &'scope self,
+ w: &mut W,
+ scope: &mut Scope<'scope, 'errors, R, M>,
+ ) -> fmt::Result
+ where
+ W: fmt::Write,
+ R: Borrow<FluentResource>,
+ M: MemoizerKind,
+ {
+ let len = self.elements.len();
+
+ for elem in &self.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)?;
+ }
+ }
+ 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}')?;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn write_error<W>(&self, _w: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ unreachable!()
+ }
+}
+
+impl<'p> ResolveValue for ast::Pattern<&'p str> {
+ fn resolve<'source, 'errors, R, M>(
+ &'source self,
+ scope: &mut Scope<'source, 'errors, R, M>,
+ ) -> FluentValue<'source>
+ where
+ R: Borrow<FluentResource>,
+ M: MemoizerKind,
+ {
+ let len = self.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());
+ }
+ }
+
+ let mut result = String::new();
+ self.write(&mut result, scope)
+ .expect("Failed to write to a string.");
+ result.into()
+ }
+}
diff --git a/vendor/fluent-bundle/src/resolver/scope.rs b/vendor/fluent-bundle/src/resolver/scope.rs
new file mode 100644
index 000000000..004701137
--- /dev/null
+++ b/vendor/fluent-bundle/src/resolver/scope.rs
@@ -0,0 +1,141 @@
+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<R, M>,
+ /// The current arguments passed by the developer.
+ pub(super) args: Option<&'scope FluentArgs<'scope>>,
+ /// Local args
+ pub(super) local_args: Option<FluentArgs<'scope>>,
+ /// 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<FluentError>>,
+ /// Makes the resolver bail.
+ pub dirty: bool,
+}
+
+impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
+ pub fn new(
+ bundle: &'scope FluentBundle<R, M>,
+ args: Option<&'scope FluentArgs>,
+ errors: Option<&'errors mut Vec<FluentError>>,
+ ) -> 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<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,
+ {
+ 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<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,
+ {
+ 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<W>(
+ &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<FluentValue<'scope>>, FluentArgs<'scope>)
+ where
+ R: Borrow<FluentResource>,
+ 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())
+ }
+ }
+}