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 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}