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_midnight: u64) -> Result<Self::Res, crate::Error> {
108        let micros = micros_midnight % 1_000_000;
109        let sec_t = micros_midnight / 1_000_000;
110        let sec = sec_t % 60;
111        let min_t = sec_t / 60;
112        let min = min_t % 60;
113        let h = min_t / 60;
114        Ok(format!("@{h:02}:{min:02}:{sec:02}.{micros:06}"))
115    }
116
117    fn visit_duration(&mut self, micros_t: i64) -> Result<Self::Res, crate::Error> {
118        let sign = if micros_t < 0 { "-" } else { "" };
119        let abs = micros_t.unsigned_abs();
120        let micros = abs % 1_000_000;
121        let sec_t = abs / 1_000_000;
122        let sec = sec_t % 60;
123        let min_t = sec_t / 60;
124        let min = min_t % 60;
125        let h = min_t / 60;
126        Ok(format!("@{sign}{h:02}:{min:02}:{sec:02}.{micros:06}"))
127    }
128
129    fn visit_timestamp(&mut self, micros: i64) -> Result<Self::Res, crate::Error> {
130        #[cfg(feature = "chrono")]
131        {
132            if let Some(dt) = chrono::DateTime::from_timestamp_micros(micros) {
133                let dt = dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
134                return Ok(format!("@{}", dt.trim_end_matches('Z')));
135            }
136        }
137        ValueVisitor::<'t>::visit_int64(self, micros)
138    }
139
140    fn visit_decimal(&mut self, cent: i64) -> Result<Self::Res, crate::Error> {
141        Ok(format!("{}.{:02}", cent / 100, (cent % 100).abs()))
142    }
143
144    fn visit_tuple(
145        &mut self,
146        fields: &[Value],
147        ty_fields: &'t [ir::TyTupleField],
148    ) -> Result<Self::Res, crate::Error> {
149        let mut r = "{".to_string();
150        self.indent();
151        for (field, ty) in fields.iter().zip(ty_fields) {
152            r += &self.new_line();
153
154            if let Some(name) = &ty.name {
155                r += name;
156                r += " = ";
157            }
158
159            r += &self.visit_value(field, &ty.ty)?;
160            r += ",";
161        }
162        self.deindent();
163        r += &self.new_line();
164        r += "}";
165        Ok(r)
166    }
167
168    fn visit_array(
169        &mut self,
170        items: &[Value],
171        ty_items: &'t ir::Ty,
172    ) -> Result<Self::Res, crate::Error> {
173        let mut r = "[".to_string();
174
175        if !items.is_empty() {
176            self.indent();
177            for item in items {
178                r += &self.new_line();
179                r += &self.visit_value(item, ty_items)?;
180                r += ",";
181            }
182            self.deindent();
183            r += &self.new_line();
184        }
185
186        r += "]";
187        Ok(r)
188    }
189
190    fn visit_enum(
191        &mut self,
192        tag: usize,
193        inner: &Value,
194        ty_variants: &'t [ir::TyEnumVariant],
195    ) -> Result<Self::Res, crate::Error> {
196        let variant = ty_variants.get(tag).ok_or(crate::Error::InvalidData)?;
197
198        let mut r = variant.name.to_string();
199
200        let is_unit = variant.ty.kind.as_tuple().is_some_and(|x| x.is_empty());
201        if !is_unit {
202            r += "(";
203            r += &self.visit_value(inner, &variant.ty)?;
204            r += ")";
205        }
206
207        Ok(r)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use crate::Value;
214    use crate::ir;
215
216    fn path(segments: &[&str]) -> ir::Path {
217        ir::Path(segments.iter().map(|segment| (*segment).into()).collect())
218    }
219
220    fn ty_ident(segments: &[&str]) -> ir::Ty {
221        ir::Ty::new(path(segments))
222    }
223
224    fn ty_def(segments: &[&str], ty: ir::Ty) -> ir::TyDef {
225        ir::TyDef {
226            name: path(segments),
227            ty,
228        }
229    }
230
231    #[test]
232    fn nominal_numeric_dispatch_uses_type_ident() {
233        let ty = ty_ident(&["alias", "Signed"]);
234        let ty_defs = vec![
235            ty_def(&["alias", "Signed"], ty_ident(&["std", "Int32"])),
236            ty_def(&["std", "Int32"], ir::Ty::new(ir::TyPrimitive::Prim32)),
237        ];
238
239        let value = Value::Prim32((-1_i32) as u32);
240
241        assert_eq!(value.print_source(&ty, &ty_defs).unwrap(), "-1");
242    }
243
244    #[test]
245    fn decimal_alias_keeps_special_printing() {
246        let ty = ty_ident(&["alias", "Money"]);
247        let ty_defs = vec![
248            ty_def(&["alias", "Money"], ty_ident(&["std", "Decimal"])),
249            ty_def(&["std", "Decimal"], ir::Ty::new(ir::TyPrimitive::Prim64)),
250        ];
251
252        let value = Value::Prim64((-123_i64) as u64);
253
254        assert_eq!(value.print_source(&ty, &ty_defs).unwrap(), "-1.23");
255    }
256}