Skip to main content

lutra_bin/value/
print_source.rs

1#![cfg(feature = "std")]
2
3use std::collections::HashMap;
4
5use crate::ir;
6use crate::{Result, Value};
7
8use super::fold::ValueVisitor;
9
10impl Value {
11    pub fn print_source(&self, ty: &ir::Ty, ty_defs: &[ir::TyDef]) -> Result<String> {
12        let ty_defs: HashMap<_, _, _> = ty_defs.iter().map(|def| (&def.name, &def.ty)).collect();
13        let mut printer = Printer {
14            indent: 0,
15            ty_defs: &ty_defs,
16        };
17
18        printer.visit_value(self, ty)
19    }
20}
21
22#[derive(Clone)]
23struct Printer<'t> {
24    indent: usize,
25    ty_defs: &'t HashMap<&'t ir::Path, &'t ir::Ty>,
26}
27
28const INDENT: usize = 2;
29
30impl<'t> Printer<'t> {
31    fn indent(&mut self) {
32        self.indent += INDENT;
33    }
34
35    fn deindent(&mut self) {
36        self.indent -= INDENT;
37    }
38
39    fn new_line(&self) -> String {
40        let mut r = "\n".to_string();
41        r += &" ".repeat(self.indent);
42        r
43    }
44}
45
46impl<'t> ValueVisitor<'t> for Printer<'t> {
47    type Res = String;
48
49    fn get_ty(&self, name: &ir::Path) -> &'t ir::Ty {
50        self.ty_defs.get(name).unwrap()
51    }
52
53    fn visit_bool(&mut self, v: bool) -> Result<Self::Res, crate::Error> {
54        Ok(if v {
55            "true".to_string()
56        } else {
57            "false".to_string()
58        })
59    }
60
61    fn visit_int8(&mut self, v: i8) -> Result<Self::Res, crate::Error> {
62        Ok(format!("{v}"))
63    }
64    fn visit_int16(&mut self, v: i16) -> Result<Self::Res, crate::Error> {
65        Ok(format!("{v}"))
66    }
67    fn visit_int32(&mut self, v: i32) -> Result<Self::Res, crate::Error> {
68        Ok(format!("{v}"))
69    }
70    fn visit_int64(&mut self, v: i64) -> Result<Self::Res, crate::Error> {
71        Ok(format!("{v}"))
72    }
73    fn visit_uint8(&mut self, v: u8) -> Result<Self::Res, crate::Error> {
74        Ok(format!("{v}"))
75    }
76    fn visit_uint16(&mut self, v: u16) -> Result<Self::Res, crate::Error> {
77        Ok(format!("{v}"))
78    }
79    fn visit_uint32(&mut self, v: u32) -> Result<Self::Res, crate::Error> {
80        Ok(format!("{v}"))
81    }
82    fn visit_uint64(&mut self, v: u64) -> Result<Self::Res, crate::Error> {
83        Ok(format!("{v}"))
84    }
85    fn visit_float32(&mut self, v: f32) -> Result<Self::Res, crate::Error> {
86        Ok(format!("{v:#?}"))
87    }
88    fn visit_float64(&mut self, v: f64) -> Result<Self::Res, crate::Error> {
89        Ok(format!("{v:#?}"))
90    }
91
92    fn visit_text(&mut self, v: &str) -> Result<Self::Res, crate::Error> {
93        // TODO escape strings
94        Ok(format!("\"{v}\""))
95    }
96
97    fn visit_date(&mut self, days: i32) -> Result<Self::Res, crate::Error> {
98        #[cfg(feature = "chrono")]
99        {
100            if let Some(date) = chrono::NaiveDate::from_epoch_days(days) {
101                return Ok(format!("@{date}"));
102            }
103        }
104        ValueVisitor::<'t>::visit_int32(self, days)
105    }
106
107    fn visit_time(&mut self, micros_t: i64) -> Result<Self::Res, crate::Error> {
108        let micros = (micros_t % 1000000).abs();
109        let sec_t = micros_t / 1000000;
110
111        let sec = (sec_t % 60).abs();
112        let min_t = sec_t / 60;
113
114        let min = (min_t % 60).abs();
115        let h_t = min_t / 60;
116
117        Ok(format!("@{h_t:02}:{min:02}:{sec:02}.{micros:06}"))
118    }
119
120    fn visit_timestamp(&mut self, micros: i64) -> Result<Self::Res, crate::Error> {
121        #[cfg(feature = "chrono")]
122        {
123            if let Some(dt) = chrono::DateTime::from_timestamp_micros(micros) {
124                let dt = dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
125                return Ok(format!("@{}", dt.trim_end_matches('Z')));
126            }
127        }
128        ValueVisitor::<'t>::visit_int64(self, micros)
129    }
130
131    fn visit_decimal(&mut self, cent: i64) -> Result<Self::Res, crate::Error> {
132        Ok(format!("{}.{:02}", cent / 100, (cent % 100).abs()))
133    }
134
135    fn visit_tuple(
136        &mut self,
137        fields: &[Value],
138        ty_fields: &'t [ir::TyTupleField],
139    ) -> Result<Self::Res, crate::Error> {
140        let mut r = "{".to_string();
141        self.indent();
142        for (field, ty) in fields.iter().zip(ty_fields) {
143            r += &self.new_line();
144
145            if let Some(name) = &ty.name {
146                r += name;
147                r += " = ";
148            }
149
150            r += &self.visit_value(field, &ty.ty)?;
151            r += ",";
152        }
153        self.deindent();
154        r += &self.new_line();
155        r += "}";
156        Ok(r)
157    }
158
159    fn visit_array(
160        &mut self,
161        items: &[Value],
162        ty_items: &'t ir::Ty,
163    ) -> Result<Self::Res, crate::Error> {
164        let mut r = "[".to_string();
165
166        if !items.is_empty() {
167            self.indent();
168            for item in items {
169                r += &self.new_line();
170                r += &self.visit_value(item, ty_items)?;
171                r += ",";
172            }
173            self.deindent();
174            r += &self.new_line();
175        }
176
177        r += "]";
178        Ok(r)
179    }
180
181    fn visit_enum(
182        &mut self,
183        tag: usize,
184        inner: &Value,
185        ty_variants: &'t [ir::TyEnumVariant],
186    ) -> Result<Self::Res, crate::Error> {
187        let variant = ty_variants.get(tag).ok_or(crate::Error::InvalidData)?;
188
189        let mut r = variant.name.to_string();
190
191        let is_unit = variant.ty.kind.as_tuple().is_some_and(|x| x.is_empty());
192        if !is_unit {
193            r += "(";
194            r += &self.visit_value(inner, &variant.ty)?;
195            r += ")";
196        }
197
198        Ok(r)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use crate::Value;
205    use crate::ir;
206
207    fn path(segments: &[&str]) -> ir::Path {
208        ir::Path(segments.iter().map(|segment| (*segment).into()).collect())
209    }
210
211    fn ty_ident(segments: &[&str]) -> ir::Ty {
212        ir::Ty::new(path(segments))
213    }
214
215    fn ty_def(segments: &[&str], ty: ir::Ty) -> ir::TyDef {
216        ir::TyDef {
217            name: path(segments),
218            ty,
219        }
220    }
221
222    #[test]
223    fn nominal_numeric_dispatch_uses_type_ident() {
224        let ty = ty_ident(&["alias", "Signed"]);
225        let ty_defs = vec![
226            ty_def(&["alias", "Signed"], ty_ident(&["std", "Int32"])),
227            ty_def(&["std", "Int32"], ir::Ty::new(ir::TyPrimitive::Prim32)),
228        ];
229
230        let value = Value::Prim32((-1_i32) as u32);
231
232        assert_eq!(value.print_source(&ty, &ty_defs).unwrap(), "-1");
233    }
234
235    #[test]
236    fn decimal_alias_keeps_special_printing() {
237        let ty = ty_ident(&["alias", "Money"]);
238        let ty_defs = vec![
239            ty_def(&["alias", "Money"], ty_ident(&["std", "Decimal"])),
240            ty_def(&["std", "Decimal"], ir::Ty::new(ir::TyPrimitive::Prim64)),
241        ];
242
243        let value = Value::Prim64((-123_i64) as u64);
244
245        assert_eq!(value.print_source(&ty, &ty_defs).unwrap(), "-1.23");
246    }
247}