kcl_lib/
fmt.rs

1use serde::Serialize;
2
3use crate::{execution::types::NumericType, pretty::NumericSuffix};
4
5/// For the UI, display a number and its type for debugging purposes. This is
6/// used by TS.
7pub fn human_display_number(value: f64, ty: NumericType) -> String {
8    match ty {
9        NumericType::Known(unit_type) => format!("{value}: number({unit_type})"),
10        NumericType::Default { len, angle } => format!("{value} (no units, defaulting to {len} or {angle})"),
11        NumericType::Unknown => format!("{value} (number with unknown units)"),
12        NumericType::Any => format!("{value} (number with any units)"),
13    }
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, thiserror::Error)]
17#[serde(tag = "type")]
18pub enum FormatNumericSuffixError {
19    #[error("Invalid numeric suffix: {0}")]
20    Invalid(NumericSuffix),
21}
22
23/// For UI code generation, format a number with a suffix. The result must parse
24/// as a literal. If it can't be done, returns an error.
25///
26/// This is used by TS.
27pub fn format_number_literal(value: f64, suffix: NumericSuffix) -> Result<String, FormatNumericSuffixError> {
28    match suffix {
29        // There isn't a syntactic suffix for these. For unknown, we don't want
30        // to ever generate the unknown suffix. We currently warn on it, and we
31        // may remove it in the future.
32        NumericSuffix::Length | NumericSuffix::Angle | NumericSuffix::Unknown => {
33            Err(FormatNumericSuffixError::Invalid(suffix))
34        }
35        NumericSuffix::None
36        | NumericSuffix::Count
37        | NumericSuffix::Mm
38        | NumericSuffix::Cm
39        | NumericSuffix::M
40        | NumericSuffix::Inch
41        | NumericSuffix::Ft
42        | NumericSuffix::Yd
43        | NumericSuffix::Deg
44        | NumericSuffix::Rad => Ok(format!("{value}{suffix}")),
45    }
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, thiserror::Error)]
49#[serde(tag = "type")]
50pub enum FormatNumericTypeError {
51    #[error("Invalid numeric type: {0:?}")]
52    Invalid(NumericType),
53}
54
55/// For UI code generation, format a number value with a suffix such that the
56/// result can parse as a literal. If it can't be done, returns an error.
57///
58/// This is used by TS.
59pub fn format_number_value(value: f64, ty: NumericType) -> Result<String, FormatNumericTypeError> {
60    match ty {
61        NumericType::Default { .. } => Ok(value.to_string()),
62        // There isn't a syntactic suffix for these. For unknown, we don't want
63        // to ever generate the unknown suffix. We currently warn on it, and we
64        // may remove it in the future.
65        NumericType::Unknown | NumericType::Any => Err(FormatNumericTypeError::Invalid(ty)),
66        NumericType::Known(unit_type) => unit_type
67            .to_suffix()
68            .map(|suffix| format!("{value}{suffix}"))
69            .ok_or(FormatNumericTypeError::Invalid(ty)),
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
76    use pretty_assertions::assert_eq;
77
78    use super::*;
79    use crate::execution::types::UnitType;
80
81    #[test]
82    fn test_human_display_number() {
83        assert_eq!(
84            human_display_number(1.0, NumericType::Known(UnitType::Count)),
85            "1: number(Count)"
86        );
87        assert_eq!(
88            human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Meters))),
89            "1: number(m)"
90        );
91        assert_eq!(
92            human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Millimeters))),
93            "1: number(mm)"
94        );
95        assert_eq!(
96            human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Inches))),
97            "1: number(in)"
98        );
99        assert_eq!(
100            human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Feet))),
101            "1: number(ft)"
102        );
103        assert_eq!(
104            human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
105            "1: number(deg)"
106        );
107        assert_eq!(
108            human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
109            "1: number(rad)"
110        );
111        assert_eq!(
112            human_display_number(
113                1.0,
114                NumericType::Default {
115                    len: UnitLength::Millimeters,
116                    angle: UnitAngle::Degrees,
117                }
118            ),
119            "1 (no units, defaulting to mm or deg)"
120        );
121        assert_eq!(
122            human_display_number(
123                1.0,
124                NumericType::Default {
125                    len: UnitLength::Feet,
126                    angle: UnitAngle::Radians,
127                }
128            ),
129            "1 (no units, defaulting to ft or rad)"
130        );
131        assert_eq!(
132            human_display_number(1.0, NumericType::Unknown),
133            "1 (number with unknown units)"
134        );
135        assert_eq!(human_display_number(1.0, NumericType::Any), "1 (number with any units)");
136    }
137
138    #[test]
139    fn test_format_number_literal() {
140        assert_eq!(
141            format_number_literal(1.0, NumericSuffix::Length),
142            Err(FormatNumericSuffixError::Invalid(NumericSuffix::Length))
143        );
144        assert_eq!(
145            format_number_literal(1.0, NumericSuffix::Angle),
146            Err(FormatNumericSuffixError::Invalid(NumericSuffix::Angle))
147        );
148        assert_eq!(format_number_literal(1.0, NumericSuffix::None), Ok("1".to_owned()));
149        assert_eq!(format_number_literal(1.0, NumericSuffix::Count), Ok("1_".to_owned()));
150        assert_eq!(format_number_literal(1.0, NumericSuffix::Mm), Ok("1mm".to_owned()));
151        assert_eq!(format_number_literal(1.0, NumericSuffix::Cm), Ok("1cm".to_owned()));
152        assert_eq!(format_number_literal(1.0, NumericSuffix::M), Ok("1m".to_owned()));
153        assert_eq!(format_number_literal(1.0, NumericSuffix::Inch), Ok("1in".to_owned()));
154        assert_eq!(format_number_literal(1.0, NumericSuffix::Ft), Ok("1ft".to_owned()));
155        assert_eq!(format_number_literal(1.0, NumericSuffix::Yd), Ok("1yd".to_owned()));
156        assert_eq!(format_number_literal(1.0, NumericSuffix::Deg), Ok("1deg".to_owned()));
157        assert_eq!(format_number_literal(1.0, NumericSuffix::Rad), Ok("1rad".to_owned()));
158        assert_eq!(
159            format_number_literal(1.0, NumericSuffix::Unknown),
160            Err(FormatNumericSuffixError::Invalid(NumericSuffix::Unknown))
161        );
162    }
163
164    #[test]
165    fn test_format_number_value() {
166        assert_eq!(
167            format_number_value(
168                1.0,
169                NumericType::Default {
170                    len: UnitLength::Millimeters,
171                    angle: UnitAngle::Degrees,
172                }
173            ),
174            Ok("1".to_owned())
175        );
176        assert_eq!(
177            format_number_value(1.0, NumericType::Known(UnitType::GenericLength)),
178            Err(FormatNumericTypeError::Invalid(NumericType::Known(
179                UnitType::GenericLength
180            )))
181        );
182        assert_eq!(
183            format_number_value(1.0, NumericType::Known(UnitType::GenericAngle)),
184            Err(FormatNumericTypeError::Invalid(NumericType::Known(
185                UnitType::GenericAngle
186            )))
187        );
188        assert_eq!(
189            format_number_value(1.0, NumericType::Known(UnitType::Count)),
190            Ok("1_".to_owned())
191        );
192        assert_eq!(
193            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Millimeters))),
194            Ok("1mm".to_owned())
195        );
196        assert_eq!(
197            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Centimeters))),
198            Ok("1cm".to_owned())
199        );
200        assert_eq!(
201            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Meters))),
202            Ok("1m".to_owned())
203        );
204        assert_eq!(
205            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Inches))),
206            Ok("1in".to_owned())
207        );
208        assert_eq!(
209            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Feet))),
210            Ok("1ft".to_owned())
211        );
212        assert_eq!(
213            format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Yards))),
214            Ok("1yd".to_owned())
215        );
216        assert_eq!(
217            format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
218            Ok("1deg".to_owned())
219        );
220        assert_eq!(
221            format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
222            Ok("1rad".to_owned())
223        );
224        assert_eq!(
225            format_number_value(1.0, NumericType::Unknown),
226            Err(FormatNumericTypeError::Invalid(NumericType::Unknown))
227        );
228        assert_eq!(
229            format_number_value(1.0, NumericType::Any),
230            Err(FormatNumericTypeError::Invalid(NumericType::Any))
231        );
232    }
233}