@@ -6,6 +6,7 @@ use miette::{LabeledSpan, miette};
66use pyo3:: exceptions:: { PyFileNotFoundError , PyTypeError , PyValueError } ;
77use pyo3:: prelude:: * ;
88use pyo3:: types:: { PyDate , PyDict , PyInt , PyList , PyString } ;
9+ use std:: collections:: { HashMap , HashSet } ;
910use std:: fs;
1011use std:: path:: PathBuf ;
1112use unic_langid:: LanguageIdentifier ;
@@ -210,6 +211,111 @@ impl FormatError {
210211 }
211212}
212213
214+ /// Extract all variable references from a pattern
215+ fn extract_variable_references ( pattern : & fluent_syntax:: ast:: Pattern < & str > ) -> HashSet < String > {
216+ let mut vars = HashSet :: new ( ) ;
217+ collect_vars_from_pattern ( pattern, & mut vars) ;
218+ vars
219+ }
220+
221+ fn collect_vars_from_pattern (
222+ pattern : & fluent_syntax:: ast:: Pattern < & str > ,
223+ vars : & mut HashSet < String > ,
224+ ) {
225+ use fluent_syntax:: ast;
226+ for element in & pattern. elements {
227+ if let ast:: PatternElement :: Placeable { expression } = element {
228+ collect_vars_from_expression ( expression, vars) ;
229+ }
230+ }
231+ }
232+
233+ fn collect_vars_from_expression (
234+ expr : & fluent_syntax:: ast:: Expression < & str > ,
235+ vars : & mut HashSet < String > ,
236+ ) {
237+ use fluent_syntax:: ast;
238+ match expr {
239+ ast:: Expression :: Inline ( inline) => match inline {
240+ ast:: InlineExpression :: VariableReference { id } => {
241+ vars. insert ( id. name . to_string ( ) ) ;
242+ }
243+ ast:: InlineExpression :: FunctionReference { arguments, .. } => {
244+ // Check positional args
245+ for arg in & arguments. positional {
246+ collect_vars_from_expression ( & ast:: Expression :: Inline ( arg. clone ( ) ) , vars) ;
247+ }
248+ // Check named args
249+ for arg in & arguments. named {
250+ collect_vars_from_expression ( & ast:: Expression :: Inline ( arg. value . clone ( ) ) , vars) ;
251+ }
252+ }
253+ ast:: InlineExpression :: TermReference { arguments, .. } => {
254+ if let Some ( args) = arguments {
255+ // Check positional args
256+ for arg in & args. positional {
257+ collect_vars_from_expression ( & ast:: Expression :: Inline ( arg. clone ( ) ) , vars) ;
258+ }
259+ // Check named args
260+ for arg in & args. named {
261+ collect_vars_from_expression (
262+ & ast:: Expression :: Inline ( arg. value . clone ( ) ) ,
263+ vars,
264+ ) ;
265+ }
266+ }
267+ }
268+ _ => { }
269+ } ,
270+ ast:: Expression :: Select { selector, variants } => {
271+ // Check selector expression
272+ collect_vars_from_expression ( & ast:: Expression :: Inline ( ( * selector) . clone ( ) ) , vars) ;
273+
274+ // Check all variant values
275+ for variant in variants {
276+ collect_vars_from_pattern ( & variant. value , vars) ;
277+ }
278+ }
279+ }
280+ }
281+
282+ fn collect_references ( pattern : & Option < fluent_syntax:: ast:: Pattern < & str > > , refs : & mut Vec < String > ) {
283+ use fluent_syntax:: ast;
284+
285+ if let Some ( pattern) = pattern {
286+ for element in & pattern. elements {
287+ if let ast:: PatternElement :: Placeable { expression } = element {
288+ collect_expression_references ( expression, refs) ;
289+ }
290+ }
291+ }
292+ }
293+
294+ fn collect_expression_references (
295+ expression : & fluent_syntax:: ast:: Expression < & str > ,
296+ refs : & mut Vec < String > ,
297+ ) {
298+ use fluent_syntax:: ast;
299+
300+ match expression {
301+ ast:: Expression :: Inline ( inline) => match inline {
302+ ast:: InlineExpression :: MessageReference { id, .. } => {
303+ refs. push ( id. name . to_string ( ) ) ;
304+ }
305+ ast:: InlineExpression :: TermReference { id, .. } => {
306+ refs. push ( format ! ( "-{}" , id. name) ) ;
307+ }
308+ _ => { }
309+ } ,
310+ ast:: Expression :: Select { selector, variants } => {
311+ collect_expression_references ( & ast:: Expression :: Inline ( ( * selector) . clone ( ) ) , refs) ;
312+ for variant in variants {
313+ collect_references ( & Some ( variant. value . clone ( ) ) , refs) ;
314+ }
315+ }
316+ }
317+ }
318+
213319#[ pymodule]
214320mod rustfluent {
215321 use super :: * ;
0 commit comments