Skip to content

Commit 5615698

Browse files
Resolve more pattern types into &str references
1 parent 2d7b1fa commit 5615698

File tree

4 files changed

+173
-10
lines changed

4 files changed

+173
-10
lines changed

fluent-bundle/src/resolver/expression.rs

+42
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

+62-4
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

+26-6
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

+43
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)