Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
@@ -0,0 +1,101 @@
use fluent_syntax::ast::InlineExpression;
use std::error::Error;
/// Maps an [`InlineExpression`] into the kind of reference, with owned strings
/// that identify the expression. This makes it so that the [`InlineExpression`] can
/// be used to generate an error string.
#[derive(Clone, Debug, Eq, PartialEq)]
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!(),
}
}
}
/// 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.
#[derive(Clone, Debug, Eq, PartialEq)]
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 {}
@@ -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<'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
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 { selector, .. } => selector.write_error(w),
}
}
}
@@ -0,0 +1,196 @@
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<'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
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.into_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<'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<FluentResource>,
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()
}
}
}
}
+52
View File
@@ -0,0 +1,52 @@
//! 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.
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;
/// 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>
where
R: Borrow<FluentResource>,
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,
R: Borrow<FluentResource>,
M: MemoizerKind;
/// Writes error information to `W`. This can be used to add FTL errors inline
/// to a message.
fn write_error<W>(&self, _w: &mut W) -> fmt::Result
where
W: fmt::Write;
}
@@ -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<'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<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<'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<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()
}
}
+140
View File
@@ -0,0 +1,140 @@
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> {
/// The current `FluentBundle` instance.
pub bundle: &'bundle FluentBundle<R, M>,
/// The current arguments passed by the developer.
pub(super) args: Option<&'args FluentArgs<'args>>,
/// Local args
pub(super) local_args: Option<FluentArgs<'bundle>>,
/// 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.
traveled: smallvec::SmallVec<[&'ast ast::Pattern<&'bundle str>; 2]>,
/// Track errors accumulated during resolving.
pub errors: Option<&'errors mut Vec<FluentError>>,
/// Makes the resolver bail.
pub dirty: bool,
}
impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R, M> {
pub fn new(
bundle: &'bundle FluentBundle<R, M>,
args: Option<&'args FluentArgs>,
errors: Option<&'errors mut Vec<FluentError>>,
) -> Self {
Scope {
bundle,
args,
local_args: None,
placeables: 0,
traveled: 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: &'ast ast::Pattern<&'bundle str>,
exp: &'ast ast::Expression<&'bundle str>,
) -> fmt::Result
where
R: Borrow<FluentResource>,
W: fmt::Write,
M: MemoizerKind,
{
if self.traveled.is_empty() {
self.traveled.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: &'ast ast::Pattern<&'bundle str>,
exp: &'ast ast::InlineExpression<&'bundle str>,
) -> fmt::Result
where
R: Borrow<FluentResource>,
W: fmt::Write,
M: MemoizerKind,
{
if self.traveled.contains(&pattern) {
self.add_error(ResolverError::Cyclic);
w.write_char('{')?;
exp.write_error(w)?;
w.write_char('}')
} else {
self.traveled.push(pattern);
let result = pattern.write(w, self);
self.traveled.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<&'ast ast::CallArguments<&'bundle str>>,
) -> (Vec<FluentValue<'bundle>>, FluentArgs<'bundle>)
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())
}
}
}