lang_interpreter/
utils.rs

1use std::cmp::Ordering;
2use std::collections::{HashMap, VecDeque};
3use std::error::Error;
4use std::fmt::{Display, Formatter};
5use std::mem;
6use std::str::FromStr;
7use crate::interpreter::data::{DataObject, DataObjectRef, DataType, OptionDataObjectRef};
8use crate::interpreter::data::function::{Function, FunctionPointerObject, InternalFunction, Parameter};
9use crate::interpreter::{conversions, Interpreter};
10use crate::lexer::{CodePosition, Token, TokenType};
11use crate::regex_patterns;
12
13#[cfg(windows)]
14pub(crate) const LINE_SEPARATOR: &str = "\r\n";
15#[cfg(not(windows))]
16pub(crate) const LINE_SEPARATOR: &str = "\n";
17
18pub(crate) fn get_os_name() -> &'static str {
19    std::env::consts::OS
20}
21
22pub(crate) fn get_os_version() -> String {
23    //TODO
24
25    "TODO: os.version".to_string()
26}
27
28pub(crate) fn get_os_arch() -> &'static str {
29    if cfg!(any(
30        target_arch = "aarch64",
31        target_arch = "arm64ec",
32        target_arch = "riscv64",
33        target_arch = "powerpc64",
34        target_arch = "loongarch64",
35        target_arch = "mips64",
36        target_arch = "mips64r6",
37        target_arch = "sparc64",
38    )) {
39        "ia64"
40    }else if cfg!(any(target_arch = "x86_64")) {
41        "amd64"
42    }else if cfg!(any(target_arch = "x86")) {
43        "x86"
44    }else if cfg!(any(target_arch = "wasm64")) {
45        "wasm64"
46    }else if cfg!(any(target_arch = "wasm32")) {
47        "wasm32"
48    }else {
49        "unknown"
50    }
51}
52
53pub(crate) mod math {
54    #[cfg(test)]
55    mod tests;
56
57    pub trait SpecialDiv {
58        fn wrapping_floor_div(self, rhs: Self) -> Self;
59        fn wrapping_ceil_div(self, rhs: Self) -> Self;
60    }
61
62    macro_rules! impl_special_div {
63        ( $selfT:ty ) => {
64            impl SpecialDiv for $selfT {
65                fn wrapping_floor_div(self, rhs: Self) -> Self {
66                    let mut ret = self.wrapping_div(rhs);
67
68                    //Round down if signs are different and modulo != 0
69                    if (self ^ rhs) < 0 && ret.wrapping_mul(rhs) != self {
70                        ret = ret.wrapping_sub(1);
71                    }
72
73                    ret
74                }
75
76                fn wrapping_ceil_div(self, rhs: Self) -> Self {
77                    let mut ret = self.wrapping_div(rhs);
78
79                    //Round up if signs are equals and modulo != 0
80                    if (self ^ rhs) >= 0 && ret.wrapping_mul(rhs) != self {
81                        ret = ret.wrapping_add(1);
82                    }
83
84                    ret
85                }
86            }
87        };
88    }
89
90    impl_special_div! { i32 }
91    impl_special_div! { i64 }
92
93    pub trait ToNumberBase {
94        fn to_number_base(self, radix: u32) -> String;
95    }
96
97    macro_rules! impl_to_number_base {
98        ( $selfT:ty, $unsignedT:ty ) => {
99            impl ToNumberBase for $selfT {
100                fn to_number_base(self, radix: u32) -> String {
101                    const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
102
103                    if !(2..=36).contains(&radix) {
104                        panic!("Radix must be between 2 and 36");
105                    }
106
107                    let is_negative = self < 0;
108                    let mut i = self.unsigned_abs();
109
110                    let mut string = String::new();
111
112                    while i >= radix as $unsignedT {
113                        string.push(DIGITS[(i % radix as $unsignedT) as usize] as char);
114                        i /= radix as $unsignedT;
115                    }
116                    string.push(DIGITS[i as usize] as char);
117
118                    if is_negative {
119                        string.push('-');
120                    }
121
122                    string.chars().rev().collect::<String>()
123                }
124            }
125        };
126    }
127
128    impl_to_number_base! { i32, u32 }
129    impl_to_number_base! { i64, u64 }
130}
131
132/// Converts negative indices to positive indices (e.g. `-1` => `len - 1` => last element)
133///
134/// This function will return [None] if the converted index is `< 0` or `>= len`
135/// The returned value is guaranteed to be `< len`
136pub fn wrap_index(index: i32, len: usize) -> Option<usize> {
137    let index = index as isize;
138
139    let index = if index < 0 {
140        index.wrapping_add(len as isize)
141    }else {
142        index
143    };
144
145    if index < 0 || index as usize >= len {
146        None
147    }else {
148        Some(index as usize)
149    }
150}
151
152/// Converts negative indices to positive indices (e.g. `-1` => `len - 1` => last element)
153///
154/// This function will return [None] if the converted index is `< 0` or `> len`
155/// The returned value is guaranteed to be `<= len`
156pub fn wrap_index_allow_len(index: i32, len: usize) -> Option<usize> {
157    let index = index as isize;
158
159    let index = if index < 0 {
160        index.wrapping_add(len as isize)
161    }else {
162        index
163    };
164
165    if index < 0 || index as usize > len {
166        None
167    }else {
168        Some(index as usize)
169    }
170}
171
172pub(crate) fn remove_dots_from_file_path(mut file: String) -> String {
173    //Remove "/./"
174    while file.contains("/./") {
175        file = file.replace("/./", "/").to_string();
176    }
177
178    //Remove "/../" and go to parent
179    while regex_patterns::UTILS_PARENT_FOLDER.is_match(&file) {
180        file = regex_patterns::UTILS_PARENT_FOLDER.replace_all(&file, "/").to_string();
181    }
182
183    file
184}
185
186/// This function converts an [OptionDataObjectRef] to a [DataObjectRef]
187///
188/// This function will return a [DataObjectRef] of type [VOID](DataType::VOID) if the `data_object` parameter is [None] or the Data Object itself
189pub fn none_to_lang_void(data_object: OptionDataObjectRef) -> DataObjectRef {
190    data_object.unwrap_or_else(|| {
191        DataObjectRef::new(DataObject::new_void())
192    })
193}
194
195/// This functions combines a list of [DataObjectRef]s into a single [DataObjectRef]
196///
197/// This function will return ...
198/// * ... [None] if `data_objects` is empty.
199/// * ... the first value if there is exactly one [DataObjectRef] in `data_objects`.
200/// * ... the first non-[Void] if there is exactly one non-[Void] [DataObjectRef] in `data_objects`.
201/// * ... a new [Void] value if all values in `data_objects` are [Void].
202/// * ... a new [Text] value by stripping all [Void] values and converting all remaining `data_objects` to [Text] and concatenating them.
203///
204/// [Void]: DataType::VOID
205/// [Text]: DataType::TEXT
206pub fn combine_data_objects(
207    data_objects: &[DataObjectRef],
208    interpreter: &mut Interpreter,
209    pos: CodePosition,
210) -> OptionDataObjectRef {
211    let mut data_objects = Vec::from(data_objects);
212
213    if data_objects.is_empty() {
214        return None;
215    }
216
217    if data_objects.len() == 1 {
218        return Some(data_objects.into_iter().next().unwrap());
219    }
220
221    //Remove all void objects
222    data_objects.retain(|data_object| data_object.data_type() != DataType::VOID);
223
224    //Return a single void object if every data object is a void object
225    if data_objects.is_empty() {
226        return Some(DataObjectRef::new(DataObject::new_void()));
227    }
228
229    if data_objects.len() == 1 {
230        return Some(data_objects.into_iter().next().unwrap());
231    }
232
233    //Combine everything to a single text object
234    let mut builder = String::new();
235    for ele in data_objects {
236        builder += &conversions::to_text(interpreter, &ele, pos);
237    }
238
239    Some(DataObjectRef::new(DataObject::new_text(builder)))
240}
241
242/// Returns a new [Vec] where all [Argument separator] values in `arguments_list` are stripped.
243///
244/// This function handles these special cases:
245/// * If the first value is an [Argument separator] a [Void] value will be inserted at index 0.
246/// * If the last value is an [Argument separator] a [Void] value will be inserted at the last index.
247/// * If there are two consecutive [Argument separator] values, they will be replaced with a single [Void] value.
248/// * If there are multiple values between to [Argument separator] values, all values between them will be combined via [combine_data_objects].
249///
250/// [Void]: DataType::VOID
251/// [Argument separator]: DataType::ARGUMENT_SEPARATOR
252pub fn combine_arguments_without_argument_separators(
253    argument_list: &[DataObjectRef],
254    interpreter: &mut Interpreter,
255    pos: CodePosition,
256) -> Vec<DataObjectRef> {
257    if argument_list.is_empty() {
258        return Vec::new();
259    }
260
261    let mut combined_argument_list = Vec::new();
262    let mut argument_tmp_list = Vec::new();
263    for current_data_object in argument_list {
264        if current_data_object.data_type() == DataType::ARGUMENT_SEPARATOR {
265            if argument_tmp_list.is_empty() {
266                argument_tmp_list.push(DataObjectRef::new(DataObject::new_void()));
267            }
268
269            combined_argument_list.push(combine_data_objects(&argument_tmp_list, interpreter, pos).unwrap());
270            argument_tmp_list.clear();
271
272            continue;
273        }
274
275        argument_tmp_list.push(current_data_object.clone());
276    }
277
278    if argument_tmp_list.is_empty() {
279        argument_tmp_list.push(DataObjectRef::new(DataObject::new_void()));
280    }
281
282    combined_argument_list.push(combine_data_objects(&argument_tmp_list, interpreter, pos).unwrap());
283
284    combined_argument_list
285}
286
287/// Returns a new [Vec] where all arguments are separated with [Argument separator] values.
288///
289/// [Argument separator]: DataType::ARGUMENT_SEPARATOR
290#[expect(clippy::let_and_return)]
291pub fn separate_arguments_with_argument_separators(
292    argument_list: &[DataObjectRef],
293) -> Vec<DataObjectRef> {
294    if argument_list.is_empty() {
295        return Vec::new();
296    }
297
298    let argument_separator = DataObjectRef::new(DataObject::with_update(|data_object| {
299        data_object.set_argument_separator(", ")
300    }).unwrap());
301
302    //An argument separator is appended after every element [2 * len] except the last one [2 * len - 1]
303    let new_len = 2 * argument_list.len() - 1;
304
305    let argument_list = argument_list.iter().
306            flat_map(|ele| [
307                ele.clone(),
308                argument_separator.clone(),
309            ]).
310            take(new_len).
311            collect::<Vec<_>>();
312
313    argument_list
314}
315
316/// Returns `true` if the function signature of `func_a` and `func_b` are equals
317pub fn are_function_signatures_equals(func_a: &Function, func_b: &Function) -> bool {
318    if func_a.var_args_parameter().map(|var_args| var_args.0) != func_b.var_args_parameter().
319            map(|var_args| var_args.0) || func_a.parameter_list().len() != func_b.parameter_list().len() {
320        return false;
321    }
322
323    for (parameter_a, parameter_b) in func_a.parameter_list().iter().zip(func_b.parameter_list().iter()) {
324        if parameter_a.type_constraint() != parameter_b.type_constraint() {
325            return false;
326        }
327    }
328
329    true
330}
331
332/// Returns the most restrictive function for the provided arguments or [None] if no function signature matches the arguments
333///
334/// # Arguments
335///
336/// * `functions` - The functions signatures to be checked against
337/// * `argument_list` - The combined argument list to check for
338pub fn get_most_restrictive_function<'a>(
339    functions: &'a FunctionPointerObject,
340    argument_list: &[DataObjectRef],
341) -> Option<&'a InternalFunction> {
342    let function_index = get_most_restrictive_function_index(functions, argument_list);
343
344    function_index.and_then(|function_index| functions.get_function(function_index))
345}
346
347/// Returns the overloaded function index of the most restrictive function for the provided arguments or [None] if no function signature matches the arguments
348///
349/// # Arguments
350///
351/// * `function` - The overloaded functions signatures to be checked against
352/// * `argument_list` - The combined argument list to check for
353pub fn get_most_restrictive_function_index(
354    functions: &FunctionPointerObject,
355    argument_list: &[DataObjectRef],
356) -> Option<usize> {
357    let function_signatures = functions.functions().iter().
358            map(|function| function.function()).
359            map(|function| (function.parameter_list(), function.var_args_parameter().
360                    map(|var_args_parameter| var_args_parameter.0))).
361            collect::<Vec<_>>();
362
363    get_most_restrictive_function_signature_index_internal(&function_signatures, argument_list)
364}
365
366/// Returns the index of the most restrictive function for the provided arguments or [None] if no function signature matches the arguments
367///
368/// # Arguments
369///
370/// * `function_signatures` - The functions signatures to be checked against
371/// * `argument_list` - The combined argument list to check for
372pub fn get_most_restrictive_function_signature_index(
373    function_signatures: &[Function],
374    argument_list: &[DataObjectRef],
375) -> Option<usize> {
376    let function_signatures = function_signatures.iter().
377            map(|function| (function.parameter_list(), function.var_args_parameter().
378                    map(|var_args_parameter| var_args_parameter.0))).
379            collect::<Vec<_>>();
380
381    get_most_restrictive_function_signature_index_internal(&function_signatures, argument_list)
382}
383
384fn get_most_restrictive_function_signature_index_internal(
385    function_signatures: &[(&[Parameter], Option<usize>)],
386    argument_list: &[DataObjectRef],
387) -> Option<usize> {
388    let mut best_function_signature: Option<&[Parameter]> = None;
389    let mut best_function_index = None;
390    let mut best_allowed_types_count = None;
391    let mut best_var_args_parameter_index = None;
392    let mut best_var_args_penalty = None;
393
394    'outer:
395    for (i, function_signature) in function_signatures.iter().
396            enumerate() {
397        if function_signature.1.is_some() {
398            if function_signature.0.len() - 1 > argument_list.len() {
399                continue; //Argument count does not match
400            }
401        }else if function_signature.0.len() != argument_list.len() {
402            continue; //Argument count does not match
403        }
404
405        let var_args_penalty = function_signature.1.map(|var_args_parameter_index| {
406            function_signature.0[var_args_parameter_index].type_constraint().allowed_types().len()
407        }).unwrap_or_default();
408
409        let mut argument_index = 0;
410        for (j, parameter) in function_signature.0.iter().
411                enumerate() {
412            if function_signature.1.is_some_and(|var_args_parameter_index| var_args_parameter_index == j) {
413                let old_argument_index = argument_index;
414
415                argument_index = argument_list.len() + j + 1 - function_signature.0.len();
416
417                //Check if types are allowed for var args parameter
418                for argument in argument_list[old_argument_index..argument_index].iter() {
419                    if !parameter.type_constraint().is_type_allowed(argument.data_type()) {
420                        continue 'outer;
421                    }
422
423                }
424
425                continue;
426            }
427
428            if !parameter.type_constraint().is_type_allowed(argument_list[argument_index].data_type()) {
429                continue 'outer;
430            }
431
432            argument_index += 1;
433        }
434
435        let allowed_types_count: usize = function_signature.0.iter().
436                map(|parameter| parameter.type_constraint().allowed_types().len()).
437                sum();
438        let size_diff = best_function_signature.map(|best_function_signature|
439                best_function_signature.len() as isize - function_signature.0.len() as isize
440        ).unwrap_or_default();
441        if best_function_index.is_none() || (function_signature.1.is_none() && best_var_args_parameter_index.is_some()) ||
442                (function_signature.1.is_none() && best_var_args_parameter_index.is_none() && allowed_types_count < best_allowed_types_count.unwrap_or_default() ||
443                (function_signature.1.is_some() && best_var_args_parameter_index.is_some() &&
444                        match size_diff {
445                            0 => var_args_penalty < best_var_args_penalty.unwrap_or_default(),
446                            ..0 => allowed_types_count < best_allowed_types_count.unwrap_or_default() + best_var_args_penalty.unwrap_or_default() * (-size_diff as usize),
447                            _ => allowed_types_count + var_args_penalty * (size_diff as usize) < best_allowed_types_count.unwrap_or_default(),
448                        })) {
449            best_function_signature = Some(function_signature.0);
450            best_function_index = Some(i);
451            best_allowed_types_count = Some(allowed_types_count);
452            best_var_args_parameter_index = function_signature.1;
453            best_var_args_penalty = Some(var_args_penalty);
454        }
455    }
456
457    best_function_index
458}
459
460/// Returns the version as a tuple or [None] if the version is invalid
461///
462/// The returned tuple contains the following version components: (major, minor, bugfix)
463///
464/// # Arguments
465///
466/// * `version` - A version prefixed by `v` which contains all version components (major, minor, and bugfix)
467///
468/// # Examples
469///
470/// ```
471/// use lang_interpreter::utils;
472///
473/// let version = utils::get_version_components("v1.0.0");
474/// assert_eq!(version, Some((1, 0, 0)));
475///
476/// let invalid_version = utils::get_version_components("v1.0");
477/// assert_eq!(invalid_version, None);
478/// ```
479pub fn get_version_components(version: &str) -> Option<(i32, i32, i32)> {
480    if version.is_empty() {
481        return None;
482    }
483
484    if version.as_bytes()[0] != b'v' {
485        return None;
486    }
487
488    if version.contains("-0") {
489        return None;
490    }
491
492    let major_minor_separator_index = version.find('.')?;
493
494    let minor_bugfix_separator_index = version[major_minor_separator_index + 1..].find('.')?;
495    let minor_bugfix_separator_index = minor_bugfix_separator_index + major_minor_separator_index + 1;
496
497    let major_str = &version[1..major_minor_separator_index];
498    let minor_str = &version[major_minor_separator_index + 1..minor_bugfix_separator_index];
499    let bugfix_str = &version[minor_bugfix_separator_index + 1..];
500
501    let major = i32::from_str(major_str).ok()?;
502    let minor = i32::from_str(minor_str).ok()?;
503    let bugfix = i32::from_str(bugfix_str).ok()?;
504
505    if major < 0 || minor < 0 || bugfix < 0 {
506        None
507    }else {
508        Some((major, minor, bugfix))
509    }
510}
511
512/// Returns the order of `version_a` and `version_b`
513///
514/// # Arguments
515///
516/// * `version_a` - A tuple which contains the following version components: (major, minor, bugfix)
517/// * `version_b` - A tuple which contains the following version components: (major, minor, bugfix)
518pub fn compare_versions_components(version_a: (i32, i32, i32), version_b: (i32, i32, i32)) -> Ordering {
519    if version_a.0 != version_b.0 {
520        return version_a.0.cmp(&version_b.0);
521    }
522
523    if version_a.1 != version_b.1 {
524        return version_a.1.cmp(&version_b.1);
525    }
526
527    version_a.2.cmp(&version_b.2)
528}
529
530/// Returns the order of `version_a` and `version_b` or [None] if at least one version is invalid
531///
532/// # Arguments
533///
534/// * `version_a` - A version prefixed by `v` which contains all version components (major, minor, and bugfix)
535/// * `version_b` - A version prefixed by `v` which contains all version components (major, minor, and bugfix)
536pub fn compare_versions_str(version_a: &str, version_b: &str) -> Option<Ordering> {
537    let version_a = get_version_components(version_a)?;
538    let version_b = get_version_components(version_b)?;
539
540    Some(compare_versions_components(version_a, version_b))
541}
542
543pub(crate) fn get_index_of_matching_bracket_str(
544    string: &str,
545    start_byte_index: usize,
546    end_byte_index: usize,
547    opened_bracket: u8,
548    closed_bracket: u8,
549) -> Option<usize> {
550    let mut bracket_count = 0_usize;
551    let mut i = start_byte_index;
552    while i < end_byte_index && i < string.len() {
553        let c = string.as_bytes()[i];
554
555        //Ignore escaped chars
556        if c == b'\\' {
557            i += 2;
558
559            continue;
560        }
561
562        if c == opened_bracket {
563            bracket_count += 1;
564        }else if c == closed_bracket {
565            bracket_count = bracket_count.saturating_sub(1);
566
567            if bracket_count == 0 {
568                return Some(i);
569            }
570        }
571
572        i += 1;
573    }
574
575    None
576}
577
578pub(crate) fn get_index_of_matching_bracket_tok(
579    tokens: &[Token],
580    start_index: usize,
581    end_index: usize,
582    opened_bracket: impl Into<String>,
583    closed_bracket: impl Into<String>,
584    abort_on_eol: bool,
585) -> Option<usize> {
586    let opened_bracket = opened_bracket.into();
587    let closed_bracket = closed_bracket.into();
588
589    if tokens.len() <= start_index || !matches!(tokens[start_index].token_type(), TokenType::OpeningBracket) ||
590            tokens[start_index].value() != opened_bracket {
591        return None;
592    }
593
594    let mut bracket_count = 0_usize;
595    let mut i = start_index;
596    while i < end_index && i < tokens.len() {
597        let token = &tokens[i];
598
599        if matches!(tokens[i].token_type(), TokenType::OpeningBracket) &&
600                tokens[i].value() == opened_bracket {
601            bracket_count += 1;
602        }else if matches!(tokens[i].token_type(), TokenType::ClosingBracket) &&
603                tokens[i].value() == closed_bracket {
604            bracket_count = bracket_count.saturating_sub(1);
605
606            if bracket_count == 0 {
607                return Some(i);
608            }
609        }
610
611        //Skip Multiline Text and Comments
612        if matches!(tokens[i].token_type(), TokenType::StartMultilineText) {
613            while i < end_index && i < tokens.len() &&
614                    !matches!(tokens[i].token_type(), TokenType::EndMultilineText) {
615                i += 1;
616            }
617        }else if matches!(tokens[i].token_type(), TokenType::StartComment | TokenType::StartDocComment) {
618            while i < end_index && i < tokens.len() &&
619                    !matches!(tokens[i].token_type(), TokenType::EndComment) {
620                i += 1;
621            }
622        }
623
624        if abort_on_eol && token.token_type() == TokenType::Eol {
625            return None;
626        }
627
628        i += 1;
629    }
630
631    None
632}
633
634pub(crate) fn is_backslash_at_index_escaped(str: &str, byte_index: usize) -> bool {
635    if str.as_bytes().get(byte_index).is_none_or(|byte| *byte == b'\\') {
636        return false;
637    }
638
639    if byte_index == 0 {
640        return false;
641    }
642
643    let mut i = byte_index - 1;
644    loop {
645        if str.as_bytes()[i] != b'\\' {
646            return (byte_index - i) % 2 == 0;
647        }
648
649        if i == 0 {
650            break;
651        }
652
653        i -= 1;
654    }
655
656    byte_index % 2 == 1
657}
658
659/// Splits the deque into two at the given index without the first element and the element at the index.
660///
661/// Returns a newly allocated `VecDeque`. `self` contains elements `[at + 1, len)`,
662/// and the returned deque contains elements `[1, at)`.
663pub(crate) fn split_off_arguments<T>(list: &mut VecDeque<T>, at: usize) -> VecDeque<T> {
664    let mut argument_list = list.split_off(at);
665    mem::swap(list, &mut argument_list);
666    argument_list.pop_front();
667    list.pop_front();
668
669    argument_list
670}
671
672/// Returns true if the call operator is defined for the provided [DataObjectRef] else false
673pub fn is_callable(data_object: &DataObjectRef) -> bool {
674    let has_op_method = data_object.object_value().is_some_and(|object_value| {
675        !object_value.borrow().is_class() && object_value.borrow().methods().contains_key("op:call")
676    });
677
678    let is_struct_definition = data_object.struct_value().is_some_and(|struct_value| struct_value.is_definition());
679    let is_object_class = data_object.object_value().is_some_and(|object_value| object_value.borrow().is_class());
680
681    has_op_method || matches!(data_object.data_type(), DataType::FUNCTION_POINTER | DataType::TYPE) ||
682            is_struct_definition || is_object_class
683}
684
685/// Returns true if the member access operator is defined for the provided [DataObjectRef] else false
686pub fn is_member_access_allowed(value_object: &DataObjectRef) -> bool {
687    matches!(value_object.data_type(), DataType::ERROR | DataType::STRUCT | DataType::OBJECT)
688}
689
690#[derive(Debug)]
691pub struct InvalidTranslationTemplateSyntaxError {
692    message: String
693}
694
695impl InvalidTranslationTemplateSyntaxError {
696    pub fn new(message: impl Into<String>) -> Self {
697        Self { message: message.into() }
698    }
699
700    pub fn message(&self) -> &str {
701        &self.message
702    }
703}
704
705impl Display for InvalidTranslationTemplateSyntaxError {
706    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
707        f.write_str(&self.message)
708    }
709}
710
711impl Error for InvalidTranslationTemplateSyntaxError {}
712
713#[derive(Debug, Copy, Clone)]
714struct CountRange {
715    start_count: i32,
716    /// If [None]: All values >= startCount
717    end_count: Option<i32>,
718}
719
720impl CountRange {
721    fn new(start_count: i32, end_count: Option<i32>) -> Self {
722        Self { start_count, end_count }
723    }
724
725    fn is_count_in_range(self, count: i32) -> bool {
726        count == self.start_count || (count > self.start_count && (self.end_count.is_none_or(|end_count| count <= end_count)))
727    }
728}
729
730#[derive(Debug)]
731struct TranslationPluralizationTemplate {
732    count_values: Box<[CountRange]>,
733    raw_translation_value: Box<str>,
734}
735
736impl TranslationPluralizationTemplate {
737    pub fn new(count_values: Box<[CountRange]>, raw_translation_value: Box<str>) -> Self {
738        Self { count_values, raw_translation_value }
739    }
740
741    pub fn count_values(&self) -> &[CountRange] {
742        &self.count_values
743    }
744
745    pub fn raw_translation_value(&self) -> &str {
746        &self.raw_translation_value
747    }
748}
749
750/// Returns a formatted template translation
751///
752/// `{` can be escaped with `{{`
753pub fn format_translation_template(
754    translation_value: &str,
755    template_map: HashMap<Box<str>, Box<str>>,
756) -> Result<String, InvalidTranslationTemplateSyntaxError> {
757    if translation_value.is_empty() {
758        return Ok(String::new());
759    }
760
761    let mut builder = String::new();
762
763    let mut i;
764    let mut start_index = 0;
765    loop {
766        let index = translation_value[start_index..].find('{');
767        if let Some(index) = index {
768            i = start_index + index;
769        }else {
770            builder += &translation_value[start_index..];
771
772            break;
773        }
774
775        builder += &translation_value[start_index..i];
776        start_index = i;
777
778        if i < translation_value.len() - 1 && translation_value.as_bytes()[i + 1] == b'{' {
779            builder += &translation_value[start_index..i + 1]; //Ignore second '{'
780            start_index = i + 2;
781
782            continue;
783        }
784
785        let matching_bracket_index = translation_value[i..].find('}');
786        let Some(matching_bracket_index) = matching_bracket_index else {
787            return Err(InvalidTranslationTemplateSyntaxError::new("Template closing bracket is missing"));
788        };
789        let matching_bracket_index = i + matching_bracket_index;
790
791        start_index = matching_bracket_index + 1;
792        let template_name = &translation_value[i + 1..matching_bracket_index];
793        let template_replacement = template_map.get(template_name);
794        if let Some(template_replacement) = template_replacement {
795            builder += template_replacement;
796        }else {
797            return Err(InvalidTranslationTemplateSyntaxError::new(format!(
798                "Template with the name \"{template_name}\" was not defined",
799            )));
800        }
801    }
802
803    Ok(builder)
804}
805
806/// Return a formatted translation with the correct pluralization
807///
808/// `;` can be escaped with `;;` and `{` can be escaped with `{{`
809pub fn format_translation_template_pluralization(
810    translation_value: &str,
811    count: i32,
812) -> Result<String, InvalidTranslationTemplateSyntaxError> {
813    format_translation_template_pluralization_with_template(translation_value, count, HashMap::new())
814}
815
816/// Returns a formatted translation with the correct pluralization and additional template values (the count template value will be overridden)
817///
818/// `;` can be escaped with `;;` and `{` can be escaped with `{{`
819pub fn format_translation_template_pluralization_with_template(
820    translation_value: &str,
821    count: i32,
822    template_map: HashMap<Box<str>, Box<str>>,
823) -> Result<String, InvalidTranslationTemplateSyntaxError> {
824    let mut template_tokens = Vec::new();
825
826    let mut start_index = 0;
827    let mut i = 0;
828    while i < translation_value.len() {
829        if i == translation_value.len() - 1 {
830            template_tokens.push(translation_value[start_index..i + 1].replace(";;", ";")); //Ignore second ";"s
831
832            break;
833        }
834
835        if translation_value.as_bytes()[i] == b';' {
836            if translation_value.as_bytes()[i + 1] == b';' {
837                i += 2; //Skip two ';'
838
839                continue;
840            }
841
842            template_tokens.push(translation_value[start_index..i + 1].replace(";;", ";")); //Ignore second ";"s
843            start_index = i + 1;
844        }
845
846        i += 1;
847    }
848
849    let mut templates = Vec::with_capacity(template_tokens.len());
850    for (i, mut template_token) in template_tokens.iter().
851            map(|str| &**str).
852            enumerate() {
853        if template_token.is_empty() || template_token.as_bytes()[0] != b'[' {
854            return Err(InvalidTranslationTemplateSyntaxError::new("Pluralization template token must start with \"[\""));
855        }
856
857        if template_token.as_bytes()[template_token.len() - 1] == b';' {
858            template_token = &template_token[0..template_token.len() - 1];
859        }else if i != template_tokens.len() - 1 {
860            return Err(InvalidTranslationTemplateSyntaxError::new("Pluralization template token must end with \";\""));
861        }
862
863        let matching_bracket_index = template_token.find(']');
864        let Some(matching_bracket_index) = matching_bracket_index else {
865            return Err(InvalidTranslationTemplateSyntaxError::new("Count range closing bracket is missing"));
866        };
867
868        let raw_count_values = &template_token[1..matching_bracket_index]; //Ignore '[' and ']'
869        let raw_translation_value = &template_token[matching_bracket_index + 1..];
870
871        let mut count_values = Vec::new();
872        start_index = 0;
873        for (j, c) in raw_count_values.bytes().
874                enumerate() {
875            if c.is_ascii_digit() || c == b'-' || c == b'+' {
876                if j < raw_count_values.len() - 1 {
877                    continue;
878                }
879            }else if c != b',' {
880                return Err(InvalidTranslationTemplateSyntaxError::new("Invalid token in count range"));
881            }
882
883            let end_index = if j < raw_count_values.len() - 1 { j } else { j + 1 };
884            let raw_count_value = &raw_count_values[start_index..end_index];
885            start_index = j + 1;
886
887            let mut start_count = -2;
888            let mut end_count = -2;
889            let mut number_start_index = 0;
890            for (k, c) in raw_count_value.bytes().
891                    enumerate() {
892                if c.is_ascii_digit() {
893                    if k == raw_count_value.len() - 1 {
894                        let number_count = &raw_count_value[number_start_index..k + 1];
895
896                        if start_count == -2 {
897                            let ret = i32::from_str(number_count);
898                            match ret {
899                                Ok(ret) => start_count = ret,
900                                Err(e) => {
901                                    return Err(InvalidTranslationTemplateSyntaxError::new(format!(
902                                        "Invalid count value: {e}",
903                                    )));
904                                },
905                            }
906
907                            end_count = start_count;
908                        }else if end_count == -2 {
909                            let ret = i32::from_str(number_count);
910                            match ret {
911                                Ok(ret) => end_count = ret,
912                                Err(e) => {
913                                    return Err(InvalidTranslationTemplateSyntaxError::new(format!(
914                                        "Invalid end count value: {e}",
915                                    )));
916                                },
917                            }
918                        }else {
919                            return Err(InvalidTranslationTemplateSyntaxError::new("Too many value in range inside a count range"));
920                        }
921                    }
922
923                    continue;
924                }
925
926                if c == b'-' {
927                    if number_start_index != 0 {
928                        return Err(InvalidTranslationTemplateSyntaxError::new("Invalid character \"-\" can not be used twice in a range inside a count range"));
929                    }
930
931                    let number_start_count = &raw_count_value[number_start_index..k];
932                    number_start_index = k + 1;
933
934                    let ret = i32::from_str(number_start_count);
935                    match ret {
936                        Ok(ret) => start_count = ret,
937                        Err(e) => {
938                            return Err(InvalidTranslationTemplateSyntaxError::new(format!(
939                                "Invalid start count value: {e}",
940                            )));
941                        },
942                    }
943                }else if c == b'+' {
944                    if start_count != -2 || end_count != -2 || k < raw_count_value.len() - 1 {
945                        return Err(InvalidTranslationTemplateSyntaxError::new(
946                            "Invalid character \"+\" can not be used twice or with multiple values in count range",
947                        ));
948                    }
949
950                    let number_start_count = &raw_count_value[number_start_index..k];
951                    let ret = i32::from_str(number_start_count);
952                    match ret {
953                        Ok(ret) => start_count = ret,
954                        Err(e) => {
955                            return Err(InvalidTranslationTemplateSyntaxError::new(format!(
956                                "Invalid start count value: {e}",
957                            )));
958                        },
959                    }
960
961                    end_count = -1;
962                }
963            }
964
965            if start_count == -2 || end_count == -2 {
966                return Err(InvalidTranslationTemplateSyntaxError::new("Empty count range sequence"));
967            }
968
969            count_values.push(CountRange::new(start_count, if end_count == -1 {
970                None
971            }else {
972                Some(end_count)
973            }));
974        }
975
976        templates.push(TranslationPluralizationTemplate::new(Box::from(count_values), Box::from(raw_translation_value)));
977    }
978
979    for template in templates {
980        for count_range in template.count_values() {
981            if count_range.is_count_in_range(count) {
982                let mut template_map = template_map;
983                template_map.insert(Box::from("count"), Box::from(&*count.to_string()));
984
985                return format_translation_template(template.raw_translation_value(), template_map);
986            }
987        }
988    }
989
990    Err(InvalidTranslationTemplateSyntaxError::new(format!("No pluralization for count \"{count}\" was defined")))
991}