Skip to content

Commit a599792

Browse files
JasperDeSutteralerque
authored andcommitted
feat(fluent-bundle): Resolve more pattern types into &str references
1 parent 78978eb commit a599792

File tree

20 files changed

+217
-55
lines changed

20 files changed

+217
-55
lines changed

fluent-bundle/examples/custom_formatter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ key-var-with-arg = Here is a variable formatted with an argument { NUMBER($num,
3636
.expect("Failed to add FTL resources to the bundle.");
3737
bundle
3838
.add_function("NUMBER", |positional, named| {
39-
match positional.get(0) {
39+
match positional.first() {
4040
Some(FluentValue::Number(n)) => {
4141
let mut num = n.clone();
4242
// This allows us to merge the arguments provided

fluent-bundle/examples/custom_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ key-date = Today is { DATETIME($epoch, dateStyle: "long", timeStyle: "short") }
165165
.expect("Failed to add FTL resources to the bundle.");
166166

167167
bundle
168-
.add_function("DATETIME", |positional, named| match positional.get(0) {
168+
.add_function("DATETIME", |positional, named| match positional.first() {
169169
Some(FluentValue::Number(n)) => {
170170
let epoch = n.value as usize;
171171
let options = named.into();

fluent-bundle/examples/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn main() {
2222
// Test for a function that accepts unnamed positional arguments
2323
bundle
2424
.add_function("MEANING_OF_LIFE", |args, _named_args| {
25-
if let Some(arg0) = args.get(0) {
25+
if let Some(arg0) = args.first() {
2626
if *arg0 == 42.into() {
2727
return "The answer to life, the universe, and everything".into();
2828
}

fluent-bundle/examples/simple-app.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ fn main() {
9696
Some(&default_locale),
9797
NegotiationStrategy::Filtering,
9898
);
99-
let current_locale = resolved_locales
100-
.get(0)
99+
let current_locale = resolved_locales.first()
101100
.cloned()
102101
.expect("At least one locale should match.");
103102

fluent-bundle/src/bundle.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,10 @@ impl<R, M> FluentBundle<R, M> {
482482
///
483483
/// assert_eq!(result, "Hello World!");
484484
/// ```
485-
pub fn format_pattern<'bundle, 'args>(
485+
pub fn format_pattern<'bundle>(
486486
&'bundle self,
487487
pattern: &'bundle ast::Pattern<&'bundle str>,
488-
args: Option<&'args FluentArgs>,
488+
args: Option<&FluentArgs>,
489489
errors: &mut Vec<FluentError>,
490490
) -> Cow<'bundle, str>
491491
where
@@ -577,7 +577,7 @@ impl<R> FluentBundle<R, IntlLangMemoizer> {
577577
///
578578
/// This will panic if no formatters can be found for the locales.
579579
pub fn new(locales: Vec<LanguageIdentifier>) -> Self {
580-
let first_locale = locales.get(0).cloned().unwrap_or_default();
580+
let first_locale = locales.first().cloned().unwrap_or_default();
581581
Self {
582582
locales,
583583
resources: vec![],

fluent-bundle/src/concurrent.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl<R> FluentBundle<R> {
3030
/// FluentBundle::new_concurrent(vec![langid_en]);
3131
/// ```
3232
pub fn new_concurrent(locales: Vec<LanguageIdentifier>) -> Self {
33-
let first_locale = locales.get(0).cloned().unwrap_or_default();
33+
let first_locale = locales.first().cloned().unwrap_or_default();
3434
Self {
3535
locales,
3636
resources: vec![],

fluent-bundle/src/resolver/expression.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,45 @@ impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> {
6464
}
6565
}
6666
}
67+
68+
impl<'bundle> ResolveValue<'bundle> for ast::Expression<&'bundle str> {
69+
fn resolve<'ast, 'args, 'errors, R, M>(
70+
&'ast self,
71+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
72+
) -> FluentValue<'bundle>
73+
where
74+
R: Borrow<FluentResource>,
75+
M: MemoizerKind,
76+
{
77+
match self {
78+
Self::Inline(exp) => exp.resolve(scope),
79+
Self::Select { selector, variants } => {
80+
let selector = selector.resolve(scope);
81+
match selector {
82+
FluentValue::String(_) | FluentValue::Number(_) => {
83+
for variant in variants {
84+
let key = match variant.key {
85+
ast::VariantKey::Identifier { name } => name.into(),
86+
ast::VariantKey::NumberLiteral { value } => {
87+
FluentValue::try_number(value)
88+
}
89+
};
90+
if key.matches(&selector, scope) {
91+
return variant.value.resolve(scope);
92+
}
93+
}
94+
}
95+
_ => {}
96+
}
97+
98+
for variant in variants {
99+
if variant.default {
100+
return variant.value.resolve(scope);
101+
}
102+
}
103+
scope.add_error(ResolverError::MissingDefault);
104+
FluentValue::Error
105+
}
106+
}
107+
}
108+
}

fluent-bundle/src/resolver/inline_expression.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,72 @@ impl<'bundle> ResolveValue<'bundle> for ast::InlineExpression<&'bundle str> {
183183
let result = func(resolved_positional_args.as_slice(), &resolved_named_args);
184184
result
185185
} else {
186+
scope.add_error(self.into());
187+
FluentValue::Error
188+
}
189+
}
190+
ast::InlineExpression::MessageReference { id, attribute } => {
191+
if let Some(msg) = scope.bundle.get_entry_message(id.name) {
192+
if let Some(attr) = attribute {
193+
msg.attributes
194+
.iter()
195+
.find_map(|a| {
196+
if a.id.name == attr.name {
197+
Some(scope.track_resolve(&a.value))
198+
} else {
199+
None
200+
}
201+
})
202+
.unwrap_or_else(|| {
203+
scope.add_error(self.into());
204+
FluentValue::Error
205+
})
206+
} else {
207+
msg.value
208+
.as_ref()
209+
.map(|value| scope.track_resolve(value))
210+
.unwrap_or_else(|| {
211+
scope.add_error(ResolverError::NoValue(id.name.to_string()));
212+
FluentValue::Error
213+
})
214+
}
215+
} else {
216+
scope.add_error(self.into());
186217
FluentValue::Error
187218
}
188219
}
189-
_ => {
190-
let mut result = String::new();
191-
self.write(&mut result, scope).expect("Failed to write");
192-
result.into()
220+
ast::InlineExpression::TermReference {
221+
id,
222+
attribute,
223+
arguments,
224+
} => {
225+
let (_, resolved_named_args) = scope.get_arguments(arguments.as_ref());
226+
227+
scope.local_args = Some(resolved_named_args);
228+
let result = scope
229+
.bundle
230+
.get_entry_term(id.name)
231+
.and_then(|term| {
232+
if let Some(attr) = attribute {
233+
term.attributes.iter().find_map(|a| {
234+
if a.id.name == attr.name {
235+
Some(scope.track_resolve(&a.value))
236+
} else {
237+
None
238+
}
239+
})
240+
} else {
241+
Some(scope.track_resolve(&term.value))
242+
}
243+
})
244+
.unwrap_or_else(|| {
245+
scope.add_error(self.into());
246+
FluentValue::Error
247+
});
248+
scope.local_args = None;
249+
result
193250
}
251+
ast::InlineExpression::Placeable { expression } => expression.resolve(scope),
194252
}
195253
}
196254
}

fluent-bundle/src/resolver/pattern.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,33 @@ impl<'bundle> ResolveValue<'bundle> for ast::Pattern<&'bundle str> {
9191
{
9292
let len = self.elements.len();
9393

94+
// more than 1 element means concatenation, which is more efficient to write to a String
95+
// 1 element often means just a message reference that can be passed back as a Cow::Borrowed
9496
if len == 1 {
95-
if let ast::PatternElement::TextElement { value } = self.elements[0] {
96-
return scope
97-
.bundle
98-
.transform
99-
.map_or_else(|| value.into(), |transform| transform(value).into());
100-
}
97+
match &self.elements[0] {
98+
&ast::PatternElement::TextElement { value } => {
99+
return scope
100+
.bundle
101+
.transform
102+
.map_or_else(|| value.into(), |transform| transform(value).into());
103+
}
104+
ast::PatternElement::Placeable { expression } => {
105+
let before = scope.placeables;
106+
scope.placeables += 1;
107+
108+
let res = scope.maybe_track_resolve(self, expression);
109+
if !matches!(res, FluentValue::Error) {
110+
return res;
111+
}
112+
113+
// when hitting an error, reset scope state and format using writer below to write error information
114+
scope.placeables = before;
115+
scope.dirty = false;
116+
if let Some(err) = &mut scope.errors {
117+
err.pop();
118+
}
119+
}
120+
};
101121
}
102122

103123
let mut result = String::new();

fluent-bundle/src/resolver/scope.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,49 @@ impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R
102102
}
103103
}
104104

105+
/// Similar to [`Scope::maybe_track`], but resolves to a value
106+
/// instead of writing to a Writer instance.
107+
pub fn maybe_track_resolve(
108+
&mut self,
109+
pattern: &'ast ast::Pattern<&'bundle str>,
110+
exp: &'ast ast::Expression<&'bundle str>,
111+
) -> FluentValue<'bundle>
112+
where
113+
R: Borrow<FluentResource>,
114+
M: MemoizerKind,
115+
{
116+
if self.travelled.is_empty() {
117+
self.travelled.push(pattern);
118+
}
119+
let res = exp.resolve(self);
120+
if self.dirty {
121+
FluentValue::Error
122+
} else {
123+
res
124+
}
125+
}
126+
127+
/// Similar to [`Scope::track`], but resolves to a value
128+
/// instead of writing to a Writer instance.
129+
pub fn track_resolve(
130+
&mut self,
131+
pattern: &'ast ast::Pattern<&'bundle str>,
132+
) -> FluentValue<'bundle>
133+
where
134+
R: Borrow<FluentResource>,
135+
M: MemoizerKind,
136+
{
137+
if self.travelled.contains(&pattern) {
138+
self.add_error(ResolverError::Cyclic);
139+
FluentValue::Error
140+
} else {
141+
self.travelled.push(pattern);
142+
let result = pattern.resolve(self);
143+
self.travelled.pop();
144+
result
145+
}
146+
}
147+
105148
pub fn write_ref_error<W>(
106149
&mut self,
107150
w: &mut W,

0 commit comments

Comments
 (0)