Skip to main content

lutra_bin/
printer.rs

1#![cfg(feature = "std")]
2
3use std::collections::HashMap;
4use std::string;
5
6use crate::ir;
7use crate::visitor::Visitor;
8use crate::{Error, Result};
9
10pub fn print_source(buf: &[u8], ty: &ir::Ty, ty_defs: &[ir::TyDef]) -> Result<String> {
11    let mut printer = Printer {
12        indent: 0,
13        ty_defs: ty_defs.iter().map(|def| (&def.name, &def.ty)).collect(),
14    };
15
16    printer.visit(buf, ty)
17}
18
19#[derive(Clone)]
20struct Printer<'t> {
21    indent: usize,
22    ty_defs: HashMap<&'t ir::Path, &'t ir::Ty>,
23}
24
25const INDENT: usize = 2;
26
27impl<'t> Printer<'t> {
28    fn indent(&mut self) {
29        self.indent += INDENT;
30    }
31
32    fn deindent(&mut self) {
33        self.indent -= INDENT;
34    }
35
36    fn new_line(&self) -> String {
37        let mut r = "\n".to_string();
38        r += &" ".repeat(self.indent);
39        r
40    }
41}
42
43impl<'t, B> Visitor<'t, B> for Printer<'t>
44where
45    B: bytes::Buf + Clone,
46{
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(&mut self, buf: B, ty: &'t ir::Ty) -> Result<Self::Res, crate::Error> {
54        // special case
55        #[cfg(feature = "chrono")]
56        if let ir::TyKind::Ident(ty_ident) = &ty.kind
57            && ty_ident.0 == ["std", "Date"]
58        {
59            use crate::Decode;
60            let days = i32::decode(buf.chunk())?;
61
62            if let Some(date) = chrono::NaiveDate::from_epoch_days(days) {
63                return Ok(format!("@{date}"));
64            } else {
65                // fallback to printing integers
66                // (this might happen when date is out range)
67            }
68        }
69        if let ir::TyKind::Ident(ty_ident) = &ty.kind
70            && ty_ident.0 == ["std", "Time"]
71        {
72            use crate::Decode;
73            let micros_t = i64::decode(buf.chunk())?;
74
75            let micros = (micros_t % 1000000).abs();
76            let sec_t = micros_t / 1000000;
77
78            let sec = (sec_t % 60).abs();
79            let min_t = sec_t / 60;
80
81            let min = (min_t % 60).abs();
82            let h_t = min_t / 60;
83
84            return Ok(format!("@{h_t:02}:{min:02}:{sec:02}.{micros:06}"));
85        }
86        if let ir::TyKind::Ident(ty_ident) = &ty.kind
87            && ty_ident.0 == ["std", "Timestamp"]
88        {
89            use crate::Decode;
90            let micros = i64::decode(buf.chunk())?;
91
92            if let Some(dt) = chrono::DateTime::from_timestamp_micros(micros) {
93                let dt = dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
94                return Ok(format!("@{}", dt.trim_end_matches('Z')));
95            } else {
96                // fallback
97            }
98        }
99        if let ir::TyKind::Ident(ty_ident) = &ty.kind
100            && ty_ident.0 == ["std", "Decimal"]
101        {
102            use crate::Decode;
103            let val = i64::decode(buf.chunk())?;
104            return Ok(format!("{}.{:02}", val / 100, (val % 100).abs()));
105        }
106
107        // general case
108        let ty = Visitor::<B>::get_mat_ty(self, ty);
109
110        self.visit_concrete(buf, ty)
111    }
112
113    fn visit_bool(&mut self, v: bool) -> Result<Self::Res, Error> {
114        Ok(if v {
115            "true".to_string()
116        } else {
117            "false".to_string()
118        })
119    }
120
121    fn visit_int8(&mut self, v: i8) -> Result<Self::Res, Error> {
122        Ok(format!("{v}"))
123    }
124    fn visit_int16(&mut self, v: i16) -> Result<Self::Res, Error> {
125        Ok(format!("{v}"))
126    }
127    fn visit_int32(&mut self, v: i32) -> Result<Self::Res, Error> {
128        Ok(format!("{v}"))
129    }
130    fn visit_int64(&mut self, v: i64) -> Result<Self::Res, Error> {
131        Ok(format!("{v}"))
132    }
133    fn visit_uint8(&mut self, v: u8) -> Result<Self::Res, Error> {
134        Ok(format!("{v}"))
135    }
136    fn visit_uint16(&mut self, v: u16) -> Result<Self::Res, Error> {
137        Ok(format!("{v}"))
138    }
139    fn visit_uint32(&mut self, v: u32) -> Result<Self::Res, Error> {
140        Ok(format!("{v}"))
141    }
142    fn visit_uint64(&mut self, v: u64) -> Result<Self::Res, Error> {
143        Ok(format!("{v}"))
144    }
145    fn visit_float32(&mut self, v: f32) -> Result<Self::Res, Error> {
146        Ok(format!("{v:#?}"))
147    }
148    fn visit_float64(&mut self, v: f64) -> Result<Self::Res, Error> {
149        Ok(format!("{v:#?}"))
150    }
151
152    fn visit_text(&mut self, mut contents: B, len: usize) -> Result<Self::Res, Error> {
153        let mut buf = vec![0; len];
154        contents.copy_to_slice(&mut buf);
155
156        let s = string::String::from_utf8(buf).map_err(|_| Error::InvalidData)?;
157        Ok(quote_text(&s))
158    }
159
160    fn visit_tuple(
161        &mut self,
162        fields: impl Iterator<Item = (B, &'t ir::TyTupleField)>,
163    ) -> Result<Self::Res, Error> {
164        let mut r = "{".to_string();
165        let mut had_a_field = false;
166        self.indent();
167        for (field, ty) in fields {
168            had_a_field = true;
169            r += &self.new_line();
170
171            if let Some(name) = &ty.name {
172                r += name;
173                r += " = ";
174            }
175
176            r += &self.visit(field, &ty.ty)?;
177            r += ",";
178        }
179        self.deindent();
180        if had_a_field {
181            r += &self.new_line();
182        }
183        r += "}";
184        Ok(r)
185    }
186
187    fn visit_array(
188        &mut self,
189        items: impl Iterator<Item = B>,
190        ty_items: &'t ir::Ty,
191    ) -> Result<Self::Res, Error> {
192        let mut r = "[".to_string();
193
194        let mut items = items.peekable();
195        if items.peek().is_some() {
196            self.indent();
197            for item in items {
198                r += &self.new_line();
199                r += &self.visit(item, ty_items)?;
200                r += ",";
201            }
202            self.deindent();
203            r += &self.new_line();
204        }
205
206        r += "]";
207        Ok(r)
208    }
209
210    fn visit_enum(
211        &mut self,
212        tag: usize,
213        inner: B,
214        ty_variants: &'t [ir::TyEnumVariant],
215    ) -> Result<Self::Res, Error> {
216        let variant = ty_variants.get(tag).ok_or(Error::InvalidData)?;
217
218        let mut r = variant.name.to_string();
219
220        let is_unit = variant.ty.kind.as_tuple().is_some_and(|x| x.is_empty());
221        if !is_unit {
222            r += "(";
223            r += &self.visit(inner, &variant.ty)?;
224            r += ")";
225        }
226
227        Ok(r)
228    }
229}
230
231fn quote_text(text: &str) -> String {
232    let mut result = String::new();
233    result.push('"');
234
235    for c in text.chars() {
236        match c {
237            '\n' => result.push_str("\\n"),
238            '\r' => result.push_str("\\r"),
239            '\t' => result.push_str("\\t"),
240            '\\' => result.push_str("\\\\"),
241            '"' => result.push_str("\\\""),
242            c if c.is_ascii_control() => {
243                let hex = format!("\\x{:02X}", c as u8);
244                result.push_str(&hex);
245            }
246            _ => result.push(c),
247        }
248    }
249    result.push('"');
250    result
251}