synthphonia/text/formatting/
float.rs

1use std::cmp::{max, min};
2
3use regex::Regex;
4
5use crate::forward::enumeration::Enumerator1;
6use crate::utils::F64;
7use crate::value::{ConstValue, Value};
8use crate::{ impl_name, impl_op1, parser::config::Config};
9
10use crate::galloc::{AllocForExactSizeIter, AllocForStr};
11
12use super::FormattingOp;
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct FormatFloat{
15    cost: usize,
16    padding: (usize, usize),
17    min_size : (usize, usize),
18}
19
20impl FormatFloat {
21    pub fn from_config(config: &Config) -> Self {
22        Self{
23            cost: config.get_usize("cost").unwrap_or(1),
24            padding: (config.get_usize("left").unwrap_or(0), config.get_usize("right").unwrap_or(0)),
25            min_size: (0, 0)
26        }
27    }
28    pub fn format_single(&self, value: F64) -> String {
29        let value = *value;
30        let value_int = if value >= 0.0 { value.floor() } else { value.ceil() };
31        let left = if self.padding.0 > 0 {
32            format!("{:0left$}", value_int, left= self.padding.0)
33        } else { format!("{}", value_int) };
34
35        if let Some(mut right) = format!("{}", value).split_once('.').map(|x| x.1.to_string()) {
36            while right.len() < self.padding.1 {
37                right.push('0');
38            }
39            left + "." + &right
40        } else if self.padding.1 > 0 {
41            left + "." + &"0".repeat(self.padding.1)
42        } else { left }
43    }
44    pub fn get_format(input: &str) -> Self {
45        let endzero = input.ends_with("0") && input.contains(".");
46        let startzero = input.starts_with("+0") || input.starts_with("-0") || input.starts_with("0");
47        let min_left = input.chars().position(|x| x == '.').unwrap_or(input.len());
48        let min_right = input.chars().position(|x| x == '.').map(|x| input.len() - 1 - x).unwrap_or(0);
49        let before_dot = if startzero { min_left } else { 0 };
50        let after_dot = if endzero { min_right } else { 0 };
51        Self { cost: 1, padding: (before_dot, after_dot), min_size: (min_left, min_right) }
52    }
53}
54
55impl FormatFloat {
56    pub fn name() ->  &'static str {
57        "float.fmt"
58    }
59}
60
61impl std::fmt::Display for FormatFloat {
62    fn fmt(&self,f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "float.fmt #left:{} #right:{}", self.padding.0, self.padding.1)
64    }
65}
66
67impl Default for FormatFloat {
68    fn default() -> Self {
69        Self::from_config(&Default::default())
70    }
71}
72
73impl Enumerator1 for FormatFloat {
74    fn enumerate(&self, this: &'static crate::expr::ops::Op1Enum, exec: &'static crate::forward::executor::Executor, opnt: [usize; 1]) -> Result<(), ()> { Ok(()) }
75}
76
77crate::impl_formatop!(FormatFloat, Float, |this: &FormatFloat| this.cost);
78
79impl FormattingOp for FormatFloat {
80    fn format(&self, input: &'static str) -> Option<(Self, crate::value::ConstValue, &'static str)> {
81        let regex = Regex::new(r"^\-?\d+(\.\d*)?".to_string().as_str()).unwrap();
82        if let Some(a) = regex.find(input) {
83            if a.as_str().ends_with(".") { return None; }
84            if let Ok(r) = a.as_str().parse::<f64>() {
85                let cv: ConstValue = F64::new(r).into();
86                Some((Self::get_format(a.as_str()), cv, &input[a.as_str().len()..]))
87            } else { None }
88        } else { None }
89    }
90
91    fn union(self, other: Self) -> Option<Self> {
92        let left = conflict(self.padding.0, other.padding.0)?;
93        let right = conflict(self.padding.1, other.padding.1)?;
94        let min_left = min(self.min_size.0, other.min_size.0);
95        let min_right = min(self.min_size.1, other.min_size.1);
96        if left > min_left { return None; }
97        if right > min_right { return None; }
98        Some(Self{ cost: 1, padding: (left, right), min_size: (min_left, min_right)})
99    }
100
101    fn bad_value() -> crate::value::ConstValue {
102        crate::value::ConstValue::Float(0.0.into())
103    }
104}
105
106fn conflict(a: usize, b: usize) -> Option<usize> {
107    if a > 0 && b > 0 && a != b { return None; }
108    Some(max(a, b))
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::text::formatting::FormatFloat;
114
115    #[test]
116    fn format() {
117        let a = "001234000.01010";
118        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
119        let a = "001234000.0101";
120        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
121        let a = "1234000.01010";
122        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
123        let a = "1234000.01010";
124        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
125        let a = "-01234000.01010";
126        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
127        let a = "-1234000.0101000";
128        assert_eq!(FormatFloat::get_format(a).format_single(a.parse::<f64>().unwrap().into()), a);
129    }
130}