1use crate::writer::{Writer, escape_string, verbatim_lines};
32use alloc::string::{String, ToString};
33use alloc::vec::Vec;
34use zerodds_lang_cst::common::{Doc, Ident, Literal};
35use zerodds_lang_cst::ocaml::{
36 Arm, ClassTypeDecl, Constructor, Decl, Expr, Field, File, LetDecl, Method, Pattern, Type,
37 TypeBody, TypeDecl, TypeGroup,
38};
39
40#[must_use]
42pub fn file(file: &File) -> String {
43 let mut w = Writer::new(" ");
44 doc(&mut w, &file.doc);
45 for decl in &file.decls {
46 w.blank_line();
47 render_decl(&mut w, decl);
48 }
49 w.finish()
50}
51
52#[must_use]
54pub fn decl(node: &Decl) -> String {
55 let mut w = Writer::new(" ");
56 render_decl(&mut w, node);
57 w.finish()
58}
59
60#[must_use]
62pub fn type_(node: &Type) -> String {
63 type_text(node)
64}
65
66#[must_use]
68pub fn ident(name: &Ident) -> String {
69 name.as_str().to_string()
70}
71
72fn render_decl(w: &mut Writer, node: &Decl) {
73 match node {
74 Decl::Types(group) => render_group(w, group),
75 Decl::Let(binding) => render_let(w, binding),
76 Decl::ClassType(class) => render_class_type(w, class),
77 Decl::Comment(lines) => doc(w, lines),
78 Decl::Verbatim(text) => verbatim_lines(w, text),
79 }
80}
81
82fn render_group(w: &mut Writer, group: &TypeGroup) {
89 doc(w, &group.doc);
90 for (index, item) in group.items.iter().enumerate() {
91 if index > 0 {
92 w.blank_line();
93 }
94 render_type_decl(w, item, if index == 0 { "type" } else { "and" });
95 }
96}
97
98fn render_type_decl(w: &mut Writer, node: &TypeDecl, keyword: &str) {
99 doc(w, &node.doc);
100 let mut head = String::from(keyword);
101 head.push(' ');
102 head.push_str(node.name.as_str());
103 match &node.body {
104 TypeBody::Abstract => w.line(&head),
105 TypeBody::Alias(target) => {
106 head.push_str(" = ");
107 head.push_str(&type_text(target));
108 w.line(&head);
109 }
110 TypeBody::Record(fields) => {
111 head.push_str(" = {");
112 w.line(&head);
113 w.indent();
114 for text in &node.body_text.begin {
115 verbatim_lines(w, text);
116 }
117 for field in fields {
118 render_field(w, field);
119 }
120 for text in &node.body_text.end {
121 verbatim_lines(w, text);
122 }
123 w.dedent();
124 w.line("}");
125 }
126 TypeBody::Variant(constructors) => {
127 head.push_str(" =");
128 w.line(&head);
129 w.indent();
130 for constructor in constructors {
131 render_constructor(w, constructor);
132 }
133 w.dedent();
134 }
135 }
136}
137
138fn render_field(w: &mut Writer, node: &Field) {
139 doc(w, &node.doc);
140 let mut line = String::from(node.name.as_str());
141 line.push_str(" : ");
142 line.push_str(&type_text(&node.type_));
143 line.push(';');
146 w.line(&line);
147}
148
149fn render_constructor(w: &mut Writer, node: &Constructor) {
150 doc(w, &node.doc);
151 let mut line = String::from("| ");
152 line.push_str(node.name.as_str());
153 if !node.args.is_empty() {
154 line.push_str(" of ");
155 let parts: Vec<String> = node.args.iter().map(type_text).collect();
156 line.push_str(&parts.join(" * "));
157 }
158 w.line(&line);
159}
160
161fn render_let(w: &mut Writer, node: &LetDecl) {
162 doc(w, &node.doc);
163 let mut head = String::from("let ");
164 head.push_str(node.name.as_str());
165 if let Some(type_) = &node.type_ {
166 head.push_str(" : ");
167 head.push_str(&type_text(type_));
168 }
169 head.push_str(" = ");
170 match &node.body {
171 Expr::Function(arms) => {
175 head.push_str("function");
176 w.line(&head);
177 w.indent();
178 for arm in arms {
179 render_arm(w, arm);
180 }
181 w.dedent();
182 }
183 other => {
184 head.push_str(&expr_text(other));
185 w.line(&head);
186 }
187 }
188}
189
190fn render_arm(w: &mut Writer, node: &Arm) {
191 let mut line = String::from("| ");
192 line.push_str(&pattern_text(&node.pattern));
193 line.push_str(" -> ");
194 line.push_str(&expr_text(&node.body));
195 w.line(&line);
196}
197
198fn render_class_type(w: &mut Writer, node: &ClassTypeDecl) {
199 doc(w, &node.doc);
200 let mut head = String::from("class type ");
201 head.push_str(node.name.as_str());
202 head.push_str(" = object");
203 if node.methods.is_empty() {
204 head.push_str(" end");
207 w.line(&head);
208 return;
209 }
210 w.line(&head);
211 w.indent();
212 for method in &node.methods {
213 render_method(w, method);
214 }
215 w.dedent();
216 w.line("end");
217}
218
219fn render_method(w: &mut Writer, node: &Method) {
220 doc(w, &node.doc);
221 let mut line = String::from("method ");
222 line.push_str(node.name.as_str());
223 line.push_str(" : ");
224 for param in &node.params {
225 line.push_str(&type_text(param));
226 line.push_str(" -> ");
227 }
228 line.push_str(&type_text(&node.result));
229 w.line(&line);
230}
231
232fn type_text(node: &Type) -> String {
236 match node {
237 Type::Primitive(primitive) => primitive.as_str().to_string(),
238 Type::Named(name) => name.as_str().to_string(),
239 Type::Array(element) => {
243 let mut out = type_text(element);
244 out.push_str(" array");
245 out
246 }
247 Type::Option(inner) => {
248 let mut out = type_text(inner);
249 out.push_str(" option");
250 out
251 }
252 Type::Tuple(parts) => {
256 let mut out = String::from("(");
257 let rendered: Vec<String> = parts.iter().map(type_text).collect();
258 out.push_str(&rendered.join(" * "));
259 out.push(')');
260 out
261 }
262 Type::Qualified { module, name } => {
263 let mut out = String::new();
264 for segment in module {
265 out.push_str(segment.as_str());
266 out.push('.');
267 }
268 out.push_str(name.as_str());
269 out
270 }
271 }
272}
273
274fn expr_text(node: &Expr) -> String {
281 match node {
282 Expr::Literal(literal) => literal_text(literal),
283 Expr::Name(name) => name.as_str().to_string(),
284 Expr::Construct(name) => name.as_str().to_string(),
285 Expr::Apply { func, args } => {
286 let mut out = expr_text(func);
287 for arg in args {
288 out.push(' ');
289 out.push_str(&expr_text(arg));
290 }
291 out
292 }
293 Expr::Function(arms) => {
297 let mut out = String::from("function ");
298 let rendered: Vec<String> = arms
299 .iter()
300 .map(|arm| {
301 let mut text = pattern_text(&arm.pattern);
302 text.push_str(" -> ");
303 text.push_str(&expr_text(&arm.body));
304 text
305 })
306 .collect();
307 out.push_str(&rendered.join(" | "));
308 out
309 }
310 Expr::Paren(inner) => {
311 let mut out = String::from("(");
312 out.push_str(&expr_text(inner));
313 out.push(')');
314 out
315 }
316 }
317}
318
319fn pattern_text(node: &Pattern) -> String {
320 match node {
321 Pattern::Literal(literal) => literal_text(literal),
322 Pattern::Construct(name) | Pattern::Bind(name) => name.as_str().to_string(),
323 }
324}
325
326fn literal_text(literal: &Literal) -> String {
327 match literal {
328 Literal::Int(text) | Literal::Float(text) => text.clone(),
333 Literal::Bool(value) => {
334 if *value {
335 "true".to_string()
336 } else {
337 "false".to_string()
338 }
339 }
340 Literal::Char(ch) => alloc::format!("{}", u32::from(*ch)),
345 Literal::Str(text) => {
346 let mut out = String::from("\"");
347 out.push_str(&escape_string(text));
348 out.push('"');
349 out
350 }
351 Literal::Bytes(bytes) => {
354 let mut out = String::from("\"");
355 for byte in bytes {
356 out.push_str(&alloc::format!("\\x{byte:02x}"));
357 }
358 out.push('"');
359 out
360 }
361 Literal::Null => "None".to_string(),
362 }
363}
364
365fn doc(w: &mut Writer, doc: &Doc) {
372 let lines = doc.as_lines();
373 let Some((first, rest)) = lines.split_first() else {
374 return;
375 };
376 if rest.is_empty() {
377 let mut only = String::from("(** ");
378 only.push_str(&comment_safe(first));
379 only.push_str(" *)");
380 w.line(&only);
381 return;
382 }
383 let mut head = String::from("(** ");
384 head.push_str(&comment_safe(first));
385 w.line(&head);
386 for (index, line) in rest.iter().enumerate() {
387 let last = index + 1 == rest.len();
388 if line.is_empty() && !last {
392 w.line("");
393 continue;
394 }
395 let mut text = String::from(" ");
396 text.push_str(&comment_safe(line));
397 if last {
398 text.push_str(" *)");
399 }
400 w.line(&text);
401 }
402}
403
404#[must_use]
418pub fn comment_safe(text: &str) -> String {
419 text.replace('"', "'")
420 .replace("*)", "* )")
421 .replace("(*", "( *")
422}
423
424use alloc::format;
442use zerodds_lang_cst::ocaml::{CodeExpr, CodecFn, Stmt, UNSIGNED_HOLDER, ValueFn, ValueKind};
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446enum EnvForm {
447 Plain,
449 Cell,
451 Scratch,
454}
455
456type Env = Vec<(String, ValueKind, EnvForm)>;
460
461fn env_get<'a>(env: &'a Env, name: &str) -> Option<(&'a ValueKind, EnvForm)> {
462 env.iter()
463 .rev()
464 .find(|(bound, _, _)| bound == name)
465 .map(|(_, kind, form)| (kind, *form))
466}
467
468#[allow(clippy::too_many_lines)]
473fn runtime_signature(method: &str) -> Option<(&'static [ValueKind], ValueKind)> {
474 use ValueKind as K;
475 const I: K = K::Int;
476 const I64: K = K::Int64;
477 const I32: K = K::Int32;
478 const F: K = K::Float;
479 const B: K = K::Bool;
480 const T: K = K::Text;
481 const U: K = K::UnitValue;
482 Some(match method {
483 "write_bool" => (&[B], U),
484 "write_u8" | "write_i8" | "write_char" | "write_i16" | "write_u16" | "write_u32" => {
485 (&[I], U)
486 }
487 "write_wchar" => (&[I, I, T, T], U),
488 "write_i32" => (&[I32], U),
489 "write_i64" => (&[I64], U),
490 "write_u64" | "write_string" | "write_f128" => (&[T], U),
491 "write_string_bounded" => (&[T, I], U),
492 "write_f32" | "write_f64" => (&[F], U),
493 "write_wstring_utf8" => (&[T, T, T], U),
494 "write_wstring_bounded" => (&[T, I, T, T], U),
495 "check_sequence_bound" | "check_map_bound" => (&[I, I], U),
496 "write_fixed" => (&[T, I, I, T], U),
497 "write_enum" => (&[I, I64], U),
498 "write_bitmask" | "write_bitset" => (&[I, I64], U),
499 "write_union_overlay" => (&[I64, I], U),
500 "bitfield" => (&[I64, I, I], I64),
501 "write_dheader" => (&[], K::Handle("dheader")),
502 "finish_dheader" => (&[K::Handle("dheader")], U),
503 "write_emheader" => (&[I, I, B], K::Handle("emheader")),
504 "finish_emheader" => (&[K::Handle("emheader")], U),
505 "write_pid" | "write_pid_extended" => (&[I, K::Handle("role"), B], K::Handle("pid")),
506 "finish_pid" => (&[K::Handle("pid")], U),
507 "write_sentinel" => (&[], U),
508 "read_bool" => (&[], B),
509 "read_u8" | "read_i8" | "read_char" | "read_i16" | "read_u16" | "read_u32" => (&[], I),
510 "read_wchar" => (&[I, T, T], I),
511 "read_i32" => (&[], I32),
512 "read_i64" => (&[], I64),
513 "read_u64" | "read_string_alloc" | "read_f128" => (&[], T),
514 "read_f32" | "read_f64" => (&[], F),
515 "read_wstring_utf8" => (&[T, T], T),
516 "read_fixed" => (&[I, I, T], T),
517 "read_enum" | "read_bitmask" => (&[I], I64),
518 "read_bitset" => (&[I], I64),
519 "read_dheader" | "reader_position" | "reader_remaining" => (&[], I),
520 "reader_status" => (&[], K::Handle("status")),
521 "reader_refuse" => (&[K::Handle("status")], U),
522 "reader_clear_discard" => (&[], B),
523 "reader_skip_to" => (&[I], U),
524 "reader_save" => (&[], K::Handle("reader_mark")),
525 "reader_restore" => (&[K::Handle("reader_mark")], U),
526 "read_pid" => (&[], K::Handle("pid_read")),
527 "read_pid_as" => (&[K::Handle("role")], K::Handle("pid_read")),
528 "finish_pid_read" => (&[K::Handle("pid_read")], U),
529 "read_emheader" => (&[], K::Handle("emheader_read")),
530 "trim_utf8" | "trim_utf16" => (&[T, I], T),
531 "no_dheader" => (&[], K::Handle("dheader")),
533 "utf8_octets" | "utf16_units" => (&[T], I),
534 "pid_is_user_member" => (&[K::Handle("pid_read")], B),
535 "bit_pattern_int" => (&[I64, I], I64),
536 "bit_pattern_bool" => (&[B], I64),
537 "bit_pattern_f32" | "bit_pattern_f64" => (&[F], I64),
538 "u64_less" | "strcmp" => (&[T, T], if method == "strcmp" { I } else { B }),
539 "u64_of_dec" => (&[T], I64),
540 _ => return None,
541 })
542}
543
544fn header_field_name(kind: &ValueKind, field: &str) -> String {
546 let mapped = match (kind, field) {
547 (ValueKind::Handle("emheader_read"), "member_id") => "em_member_id",
548 (ValueKind::Handle("emheader_read"), "length_code") => "em_length_code",
549 (ValueKind::Handle("emheader_read"), "must_understand") => "em_must_understand",
550 (ValueKind::Handle("emheader_read"), "extent") => "em_extent",
551 (ValueKind::Handle("pid_read"), "pid") => "pr_pid",
552 (ValueKind::Handle("pid_read"), "length") => "pr_length",
553 (ValueKind::Handle("pid_read"), "end") => "pr_end",
554 (ValueKind::Handle("pid_read"), "must_understand") => "pr_must_understand",
555 (ValueKind::Handle("pid_read"), "ignore") => "pr_ignore",
556 _ => return format!("zd_unknown_header_field_{field}"),
559 };
560 mapped.to_string()
561}
562
563fn header_field_kind(field: &str) -> ValueKind {
564 match field {
565 "must_understand" | "end" | "ignore" => ValueKind::Bool,
566 _ => ValueKind::Int,
567 }
568}
569
570fn literal_in(value: i128, kind: &ValueKind) -> String {
572 match kind {
573 ValueKind::Int32 => format!("({value}l)"),
574 ValueKind::Int64 => format!("({value}L)"),
575 ValueKind::Text => format!("\"{value}\""),
576 ValueKind::Bool => (if value != 0 { "true" } else { "false" }).to_string(),
577 ValueKind::Float => format!("({value}.0)"),
578 ValueKind::Enum(name) => format!("({name}_of_int32 ({value}l))"),
579 _ => {
580 if value < 0 {
581 format!("({value})")
582 } else {
583 format!("{value}")
584 }
585 }
586 }
587}
588
589fn convert(text: String, from: &ValueKind, to: &ValueKind) -> String {
594 use ValueKind as K;
595 if from == to {
596 return text;
597 }
598 match (from, to) {
599 (K::Int, K::Int64) => format!("(Int64.of_int {text})"),
600 (K::Int, K::Int32) => format!("(Int32.of_int {text})"),
601 (K::Int32, K::Int64) => format!("(Int64.of_int32 {text})"),
602 (K::Int32, K::Int) => format!("(Int32.to_int {text})"),
603 (K::Int64, K::Int) => format!("(Int64.to_int {text})"),
604 (K::Int64, K::Int32) => format!("(Int64.to_int32 {text})"),
605 (K::Enum(name), K::Int64) => format!("(Int64.of_int32 (int32_of_{name} {text}))"),
606 (K::Enum(name), K::Int32) => format!("(int32_of_{name} {text})"),
607 (K::Enum(name), K::Int) => format!("(Int32.to_int (int32_of_{name} {text}))"),
608 (K::Int32, K::Enum(name)) => format!("({name}_of_int32 {text})"),
609 (K::Int, K::Enum(name)) => format!("({name}_of_int32 (Int32.of_int {text}))"),
610 (K::Int64, K::Enum(name)) => format!("({name}_of_int32 (Int64.to_int32 {text}))"),
611 (K::Text, K::Int64) => format!("(Zdwire.u64_of_dec {text})"),
614 (K::Int64, K::Text) => format!("(Zdwire.u64_to_dec {text})"),
615 _ => text,
616 }
617}
618
619fn natural_kind(env: &Env, expr: &CodeExpr) -> ValueKind {
628 match expr {
629 CodeExpr::Name(name) => {
630 env_get(env, name).map_or(ValueKind::Aggregate, |(kind, _)| kind.clone())
631 }
632 CodeExpr::Whole(name) => {
633 env_get(env, name).map_or(ValueKind::Aggregate, |(kind, _)| kind.clone())
634 }
635 CodeExpr::Int(_) => ValueKind::Int,
636 CodeExpr::Int64Lit(_) => ValueKind::Int64,
637 CodeExpr::BoolLit(_) => ValueKind::Bool,
638 CodeExpr::TextLit(_) => ValueKind::Text,
639 CodeExpr::FloatLit(_) => ValueKind::Float,
640 CodeExpr::Runtime { method, .. } | CodeExpr::Static { method, .. } => {
641 runtime_signature(method).map_or(ValueKind::Aggregate, |(_, ret)| ret)
642 }
643 CodeExpr::HeaderField { field, .. } => header_field_kind(field),
644 CodeExpr::CountOf(_) => ValueKind::Int,
645 CodeExpr::IsSome { .. } => ValueKind::Bool,
646 CodeExpr::Absent => ValueKind::Aggregate,
647 CodeExpr::Binary { op, lhs, rhs } => match *op {
648 "==" | "!=" | "<" | ">" | "<=" | ">=" | "&&" | "||" => ValueKind::Bool,
649 _ => {
653 let left = natural_kind(env, lhs);
654 if left == ValueKind::Aggregate || matches!(lhs.as_ref(), CodeExpr::Int(_)) {
655 natural_kind(env, rhs)
656 } else {
657 left
658 }
659 }
660 },
661 CodeExpr::Unary { op, value } => {
662 if *op == "!" {
663 ValueKind::Bool
664 } else {
665 natural_kind(env, value)
666 }
667 }
668 CodeExpr::Paren(inner) => natural_kind(env, inner),
669 CodeExpr::Convert { target, .. } => target.kind.clone(),
670 CodeExpr::RuntimeConst { kind, .. } | CodeExpr::Raw { kind, .. } => kind.clone(),
671 CodeExpr::Member { base, field, case } => {
676 let record = env_get(env, base).map(|(kind, _)| kind.clone());
677 let field_kind = field.split('.').fold(record, |kind, segment| match kind {
680 Some(ValueKind::RecordOf(fields)) => fields
681 .iter()
682 .find(|(name, _)| name == segment)
683 .map(|(_, kind)| kind.clone()),
684 _ => None,
685 });
686 let field_kind = field_kind.unwrap_or(ValueKind::Aggregate);
687 if *case {
688 match field_kind {
689 ValueKind::OptionOf(inner) => *inner,
690 other => other,
691 }
692 } else {
693 field_kind
694 }
695 }
696 CodeExpr::OptionGet(inner) => match natural_kind(env, inner) {
697 ValueKind::OptionOf(kind) => *kind,
698 other => other,
699 },
700 CodeExpr::Index { base, .. } => match natural_kind(env, base) {
701 ValueKind::ArrayOf(element) => *element,
702 _ => ValueKind::Aggregate,
703 },
704 CodeExpr::EntryHalf { entry, key } => match natural_kind(env, entry) {
705 ValueKind::RecordOf(fields) => {
706 let half = if *key { "key" } else { "value" };
707 fields
708 .iter()
709 .find(|(name, _)| name == half)
710 .map_or(ValueKind::Aggregate, |(_, kind)| kind.clone())
711 }
712 _ => ValueKind::Aggregate,
713 },
714 CodeExpr::Elements(base) => {
715 env_get(env, base).map_or(ValueKind::Aggregate, |(kind, _)| kind.clone())
716 }
717 CodeExpr::OptionSlot { .. } | CodeExpr::Construct(_) | CodeExpr::Call { .. } => {
718 ValueKind::Aggregate
719 }
720 }
721}
722
723fn name_value(env: &Env, name: &str) -> String {
725 match env_get(env, name) {
726 Some((_, EnvForm::Cell)) => format!("(!{name})"),
727 _ => name.to_string(),
728 }
729}
730
731fn ocaml_string_literal(text: &str) -> String {
733 let mut out = String::from("\"");
734 for byte in text.bytes() {
735 match byte {
736 b'"' => out.push_str("\\\""),
737 b'\\' => out.push_str("\\\\"),
738 b'\n' => out.push_str("\\n"),
739 b'\r' => out.push_str("\\r"),
740 b'\t' => out.push_str("\\t"),
741 byte if !(0x20..0x7F).contains(&byte) => out.push_str(&format!("\\x{byte:02x}")),
742 byte => out.push(byte as char),
743 }
744 }
745 out.push('"');
746 out
747}
748
749fn arithmetic(op: &str, kind: &ValueKind, lhs: &str, rhs: &str) -> String {
751 match kind {
752 ValueKind::Int64 => {
753 let name = match op {
754 "+" => "Int64.add",
755 "-" => "Int64.sub",
756 "*" => "Int64.mul",
757 "/" => "Int64.div",
758 "%" => "Int64.rem",
759 "&" => "Int64.logand",
760 "|" => "Int64.logor",
761 "^" => "Int64.logxor",
762 "<<" => "Int64.shift_left",
763 ">>" => "Int64.shift_right_logical",
764 other => other,
765 };
766 format!("({name} {lhs} {rhs})")
767 }
768 ValueKind::Float => {
769 let symbol = match op {
770 "+" => "+.",
771 "-" => "-.",
772 "*" => "*.",
773 "/" => "/.",
774 other => other,
775 };
776 format!("({lhs} {symbol} {rhs})")
777 }
778 _ => {
779 let symbol = match op {
780 "%" => "mod",
781 "&" => "land",
782 "|" => "lor",
783 "^" => "lxor",
784 "<<" => "lsl",
785 ">>" => "lsr",
786 other => other,
787 };
788 format!("({lhs} {symbol} {rhs})")
789 }
790 }
791}
792
793#[allow(clippy::too_many_lines)]
803fn value(env: &Env, expr: &CodeExpr, expected: Option<&ValueKind>) -> String {
804 if let (CodeExpr::Int(number), Some(kind)) = (expr, expected) {
807 return literal_in(*number, kind);
808 }
809 if let (CodeExpr::Int64Lit(number), Some(ValueKind::Int)) = (expr, expected) {
812 return literal_in(i128::from(*number), &ValueKind::Int);
813 }
814 let natural = natural_kind(env, expr);
815 let text = match expr {
816 CodeExpr::Name(name) => name_value(env, name),
817 CodeExpr::Whole(name) => name_value(env, name),
818 CodeExpr::Int(number) => {
819 if *number < 0 {
820 format!("({number})")
821 } else {
822 format!("{number}")
823 }
824 }
825 CodeExpr::Int64Lit(number) => format!("({number}L)"),
826 CodeExpr::BoolLit(flag) => (if *flag { "true" } else { "false" }).to_string(),
827 CodeExpr::TextLit(text) => ocaml_string_literal(text),
828 CodeExpr::FloatLit(text) => format!("({text})"),
829 CodeExpr::Runtime {
830 receiver,
831 method,
832 args,
833 } => {
834 let mut out = format!("(Zdwire.{method} {receiver}");
835 let params = runtime_signature(method).map(|(params, _)| params);
836 for (index, arg) in args.iter().enumerate() {
837 let expected = params.and_then(|params| params.get(index));
838 out.push(' ');
839 out.push_str(&value(env, arg, expected));
840 }
841 out.push(')');
842 out
843 }
844 CodeExpr::Static { method, args } => {
845 let mut out = format!("(Zdwire.{method}");
846 let params = runtime_signature(method).map(|(params, _)| params);
847 if args.is_empty() {
848 out.push_str(" ()");
849 }
850 for (index, arg) in args.iter().enumerate() {
851 let expected = params.and_then(|params| params.get(index));
852 out.push(' ');
853 out.push_str(&value(env, arg, expected));
854 }
855 out.push(')');
856 out
857 }
858 CodeExpr::Call { callee, args } => {
859 let mut out = format!("({callee}");
860 if args.is_empty() {
861 out.push_str(" ()");
862 }
863 for arg in args {
864 out.push(' ');
865 out.push_str(&value(env, arg, None));
866 }
867 out.push(')');
868 out
869 }
870 CodeExpr::Member { base, field, case } => {
871 let read = format!("{}.{field}", name_value(env, base));
872 if *case {
873 format!("(Option.get {read})")
874 } else {
875 read
876 }
877 }
878 CodeExpr::OptionSlot { base, field } => {
879 format!("(Option.get {}.{field})", name_value(env, base))
881 }
882 CodeExpr::OptionGet(inner) => format!("(Option.get {})", value(env, inner, None)),
883 CodeExpr::IsSome { base, field } => {
884 format!("({}.{field} <> None)", name_value(env, base))
885 }
886 CodeExpr::Absent => "None".to_string(),
887 CodeExpr::Index { base, index } => format!(
888 "{}.({})",
889 value(env, base, None),
890 value(env, index, Some(&ValueKind::Int))
891 ),
892 CodeExpr::EntryHalf { entry, key } => format!(
893 "{}.{}",
894 value(env, entry, None),
895 if *key { "key" } else { "value" }
896 ),
897 CodeExpr::HeaderField {
898 value: header,
899 field,
900 } => {
901 let kind = natural_kind(env, header);
902 format!(
903 "{}.Zdwire.{}",
904 value(env, header, None),
905 header_field_name(&kind, field)
906 )
907 }
908 CodeExpr::CountOf(base) => format!("(Array.length {})", name_value(env, base)),
909 CodeExpr::Elements(base) => name_value(env, base),
910 CodeExpr::Construct(name) => name.clone(),
911 CodeExpr::Binary { op, lhs, rhs } => match *op {
912 "&&" | "||" => format!(
913 "({} {op} {})",
914 value(env, lhs, Some(&ValueKind::Bool)),
915 value(env, rhs, Some(&ValueKind::Bool))
916 ),
917 "==" | "!=" | "<" | ">" | "<=" | ">=" => {
918 let left_kind = natural_kind(env, lhs);
920 let right_kind = natural_kind(env, rhs);
921 let joint = if left_kind == ValueKind::Aggregate
922 || matches!(lhs.as_ref(), CodeExpr::Int(_))
923 {
924 right_kind
925 } else {
926 left_kind
927 };
928 let expect = if joint == ValueKind::Aggregate {
929 None
930 } else {
931 Some(&joint)
932 };
933 let symbol = match *op {
934 "==" => "=",
935 "!=" => "<>",
936 other => other,
937 };
938 format!(
939 "({} {symbol} {})",
940 value(env, lhs, expect),
941 value(env, rhs, expect)
942 )
943 }
944 op => {
945 let left_kind = natural_kind(env, lhs);
949 let right_kind = natural_kind(env, rhs);
950 let joint = if left_kind == ValueKind::Aggregate
951 || matches!(lhs.as_ref(), CodeExpr::Int(_))
952 {
953 right_kind.clone()
954 } else {
955 left_kind.clone()
956 };
957 let shift = op == "<<" || op == ">>";
958 let right_expect = if shift && joint == ValueKind::Int64 {
959 ValueKind::Int
960 } else {
961 joint.clone()
962 };
963 arithmetic(
964 op,
965 &joint,
966 &value(env, lhs, Some(&joint)),
967 &value(env, rhs, Some(&right_expect)),
968 )
969 }
970 },
971 CodeExpr::Unary { op, value: operand } => match *op {
972 "!" => format!("(not {})", value(env, operand, Some(&ValueKind::Bool))),
973 "~" => {
974 let kind = natural_kind(env, operand);
975 if kind == ValueKind::Int64 {
976 format!("(Int64.lognot {})", value(env, operand, None))
977 } else {
978 format!("(lnot {})", value(env, operand, None))
979 }
980 }
981 op => format!("({op} {})", value(env, operand, None)),
982 },
983 CodeExpr::Paren(inner) => value(env, inner, None),
984 CodeExpr::Convert {
985 target,
986 value: operand,
987 } => {
988 if target.text == "signed char" {
989 let inner = value(env, operand, Some(&ValueKind::Int));
992 format!("((({inner} + 128) mod 256) - 128)")
993 } else if let CodeExpr::Int(number) = operand.as_ref() {
994 literal_in(*number, &target.kind)
995 } else if target.text == UNSIGNED_HOLDER {
996 let from = natural_kind(env, operand);
1007 let inner = value(env, operand, None);
1008 match from {
1009 ValueKind::Int32 => {
1010 format!("(Int64.logand (Int64.of_int32 {inner}) 0xFFFFFFFFL)")
1011 }
1012 other => convert(inner, &other, &ValueKind::Int64),
1013 }
1014 } else {
1015 let from = natural_kind(env, operand);
1016 convert(value(env, operand, None), &from, &target.kind)
1017 }
1018 }
1019 CodeExpr::RuntimeConst { text, .. } => text.clone(),
1020 CodeExpr::Raw { text, .. } => text.clone(),
1021 };
1022 match expected {
1023 Some(kind) if *kind != ValueKind::Aggregate => convert(text, &natural, kind),
1024 _ => text,
1025 }
1026}
1027
1028fn record_update(base: &str, field: &str, value_text: &str) -> String {
1032 fn update(record: &str, segments: &[&str], value_text: &str) -> String {
1034 match segments {
1035 [] => value_text.to_string(),
1036 [last] => format!("{{ {record} with {last} = {value_text} }}"),
1037 [head, rest @ ..] => {
1038 let inner = update(&format!("({record}).{head}"), rest, value_text);
1039 format!("{{ {record} with {head} = {inner} }}")
1040 }
1041 }
1042 }
1043 let segments: Vec<&str> = field.split('.').collect();
1044 update(&format!("!{base}"), &segments, value_text)
1045}
1046
1047fn store(env: &Env, place: &CodeExpr, value_text: &str, value_is_absent: bool) -> String {
1049 match place {
1050 CodeExpr::Whole(name) | CodeExpr::Name(name) => match env_get(env, name) {
1051 Some((_, EnvForm::Scratch)) => format!("ignore {value_text};"),
1052 _ => format!("{name} := {value_text};"),
1053 },
1054 CodeExpr::Member { base, field, case } => {
1055 let wrapped = if *case && !value_is_absent {
1056 format!("Some {value_text}")
1057 } else {
1058 value_text.to_string()
1059 };
1060 format!("{base} := {};", record_update(base, field, &wrapped))
1061 }
1062 CodeExpr::OptionSlot { base, field } => {
1063 let wrapped = if value_is_absent {
1064 value_text.to_string()
1065 } else {
1066 format!("Some {value_text}")
1067 };
1068 format!("{base} := {};", record_update(base, field, &wrapped))
1069 }
1070 CodeExpr::Index { base, index } => format!(
1071 "{}.({}) <- {value_text};",
1072 value(env, base, None),
1073 value(env, index, Some(&ValueKind::Int))
1074 ),
1075 CodeExpr::EntryHalf { entry, key } => {
1076 let half = if *key { "key" } else { "value" };
1077 match entry.as_ref() {
1078 CodeExpr::Name(name)
1081 if matches!(env_get(env, name), Some((_, EnvForm::Scratch))) =>
1082 {
1083 format!("ignore {value_text};")
1084 }
1085 CodeExpr::Index { base, index } => {
1086 let array = value(env, base, None);
1087 let at = value(env, index, Some(&ValueKind::Int));
1088 format!("{array}.({at}) <- {{ {array}.({at}) with {half} = {value_text} }};")
1089 }
1090 _ => format!("zd_unsupported_store {value_text};"),
1091 }
1092 }
1093 _ => format!("zd_unsupported_store {value_text};"),
1094 }
1095}
1096
1097fn place_kind(env: &Env, place: &CodeExpr) -> Option<ValueKind> {
1099 match place {
1100 CodeExpr::Whole(name) | CodeExpr::Name(name) => {
1101 env_get(env, name).map(|(kind, _)| kind.clone())
1102 }
1103 _ => None,
1104 }
1105}
1106
1107fn pad(out: &mut String, depth: usize) {
1109 for _ in 0..depth {
1110 out.push_str(" ");
1111 }
1112}
1113
1114#[allow(clippy::too_many_lines)]
1128fn block(env: &mut Env, statements: &[Stmt], depth: usize, out: &mut String) {
1129 for statement in statements {
1130 match statement {
1131 Stmt::Let {
1132 type_,
1133 name,
1134 value: init,
1135 } => {
1136 let rendered = match init {
1137 Some(expr) => value(env, expr, Some(&type_.kind)),
1138 None => format!("({})", type_.default),
1139 };
1140 pad(out, depth);
1141 out.push_str(&format!(
1142 "let {name} : {} ref = ref {rendered} in\n",
1143 type_.text
1144 ));
1145 env.push((name.clone(), type_.kind.clone(), EnvForm::Cell));
1146 }
1147 Stmt::Scratch { name } => {
1148 pad(out, depth);
1149 out.push_str(&format!(
1150 "(* {name}: the counting pass reads and discards *)\n"
1151 ));
1152 env.push((name.clone(), ValueKind::Aggregate, EnvForm::Scratch));
1153 }
1154 Stmt::Assign {
1155 place,
1156 value: given,
1157 } => {
1158 let expected = place_kind(env, place);
1159 let text = value(env, given, expected.as_ref());
1160 pad(out, depth);
1161 out.push_str(&store(env, place, &text, matches!(given, CodeExpr::Absent)));
1162 out.push('\n');
1163 }
1164 Stmt::Store {
1165 place,
1166 type_,
1167 value: given,
1168 } => {
1169 let text = value(env, given, Some(&type_.kind));
1170 pad(out, depth);
1171 out.push_str(&store(env, place, &text, matches!(given, CodeExpr::Absent)));
1172 out.push('\n');
1173 }
1174 Stmt::ReadInto {
1175 callee,
1176 place,
1177 behaviour,
1178 } => {
1179 let call = format!("({callee} r {})", value(env, behaviour, None));
1180 pad(out, depth);
1181 out.push_str(&store(env, place, &call, false));
1182 out.push('\n');
1183 }
1184 Stmt::Perform(expr) => {
1185 let unit_valued = match expr {
1186 CodeExpr::Runtime { method, .. } | CodeExpr::Static { method, .. } => {
1187 runtime_signature(method)
1188 .is_some_and(|(_, ret)| ret == ValueKind::UnitValue)
1189 }
1190 CodeExpr::Call { .. } => true,
1193 _ => false,
1194 };
1195 pad(out, depth);
1196 if unit_valued {
1197 out.push_str(&format!("{};\n", value(env, expr, None)));
1198 } else {
1199 out.push_str(&format!("ignore {};\n", value(env, expr, None)));
1200 }
1201 }
1202 Stmt::Discard(expr) => {
1203 pad(out, depth);
1204 out.push_str(&format!("ignore {};\n", value(env, expr, None)));
1205 }
1206 Stmt::Return => {
1207 pad(out, depth);
1208 out.push_str("raise Zdwire.Zd_return;\n");
1209 }
1210 Stmt::When {
1211 condition,
1212 then,
1213 otherwise,
1214 } => {
1215 pad(out, depth);
1216 out.push_str(&format!(
1217 "if {} then begin\n",
1218 value(env, condition, Some(&ValueKind::Bool))
1219 ));
1220 let mut inner = env.clone();
1221 block(&mut inner, then, depth + 1, out);
1222 match otherwise {
1223 Some(other) => {
1224 pad(out, depth);
1225 out.push_str("end\n");
1226 pad(out, depth);
1227 out.push_str("else begin\n");
1228 let mut inner = env.clone();
1229 block(&mut inner, other, depth + 1, out);
1230 pad(out, depth);
1231 out.push_str("end;\n");
1232 }
1233 None => {
1234 pad(out, depth);
1235 out.push_str("end;\n");
1236 }
1237 }
1238 }
1239 Stmt::For {
1240 counter,
1241 bound,
1242 body,
1243 } => {
1244 pad(out, depth);
1245 out.push_str("(try\n");
1246 pad(out, depth + 1);
1247 out.push_str(&format!(
1248 "for {counter} = 0 to {} - 1 do\n",
1249 value(env, bound, Some(&ValueKind::Int))
1250 ));
1251 let mut inner = env.clone();
1252 inner.push((counter.clone(), ValueKind::Int, EnvForm::Plain));
1253 block(&mut inner, body, depth + 2, out);
1254 pad(out, depth + 1);
1255 out.push_str("done\n");
1256 pad(out, depth);
1257 out.push_str("with Zdwire.Zd_break -> ());\n");
1258 }
1259 Stmt::While { condition, body } => {
1260 pad(out, depth);
1261 out.push_str("(try\n");
1262 pad(out, depth + 1);
1263 out.push_str(&format!(
1264 "while {} do\n",
1265 value(env, condition, Some(&ValueKind::Bool))
1266 ));
1267 let mut inner = env.clone();
1268 block(&mut inner, body, depth + 2, out);
1269 pad(out, depth + 1);
1270 out.push_str("done\n");
1271 pad(out, depth);
1272 out.push_str("with Zdwire.Zd_break -> ());\n");
1273 }
1274 Stmt::Leave => {
1275 pad(out, depth);
1276 out.push_str("raise Zdwire.Zd_break;\n");
1277 }
1278 Stmt::Note(doc) => {
1279 for line in doc.as_lines() {
1280 pad(out, depth);
1281 out.push_str(&format!("(* {} *)\n", comment_safe(line.as_str())));
1282 }
1283 }
1284 Stmt::Alloc {
1285 base,
1286 element,
1287 count,
1288 } => {
1289 pad(out, depth);
1290 out.push_str(&format!(
1291 "{base} := Array.make {} ({});\n",
1292 value(env, count, Some(&ValueKind::Int)),
1293 element.default
1294 ));
1295 }
1296 Stmt::KeepFirst { base, count } => {
1297 pad(out, depth);
1298 out.push_str(&format!(
1299 "{base} := Array.sub (!{base}) 0 {};\n",
1300 value(env, count, Some(&ValueKind::Int))
1301 ));
1302 }
1303 }
1304 }
1305 pad(out, depth);
1306 out.push_str("()\n");
1307}
1308
1309fn codec_fn(function: &CodecFn, keyword: &str, out: &mut String) {
1311 for line in function.doc.as_lines() {
1312 out.push_str(&format!("(* {} *)\n", comment_safe(line.as_str())));
1313 }
1314 let mut env: Env = Vec::new();
1315 let mut head = format!("{keyword} {}", function.name);
1316 if function.reading {
1317 head.push_str(" (r : Zdwire.reader)");
1318 env.push(("r".to_string(), ValueKind::Handle("reader"), EnvForm::Plain));
1319 } else {
1320 head.push_str(" (w : Zdwire.writer)");
1321 env.push(("w".to_string(), ValueKind::Handle("writer"), EnvForm::Plain));
1322 head.push_str(&format!(" (v : {})", function.sample.text));
1323 env.push((
1324 "v".to_string(),
1325 function.sample.kind.clone(),
1326 EnvForm::Plain,
1327 ));
1328 }
1329 for (type_, name) in &function.extra {
1330 head.push_str(&format!(" ({name} : {})", type_.text));
1331 env.push((name.clone(), type_.kind.clone(), EnvForm::Plain));
1332 }
1333 if function.reading {
1334 head.push_str(&format!(" : {} =\n", function.sample.text));
1335 out.push_str(&head);
1336 let seed = function
1337 .defaulter
1338 .clone()
1339 .unwrap_or_else(|| function.sample.default.clone());
1340 out.push_str(&format!(" let v = ref ({seed} ()) in\n"));
1343 env.push(("v".to_string(), function.sample.kind.clone(), EnvForm::Cell));
1344 out.push_str(" (try\n");
1345 block(&mut env, &function.body, 2, out);
1346 out.push_str(" with Zdwire.Zd_return -> ());\n");
1347 out.push_str(" !v\n");
1348 } else {
1349 head.push_str(" : unit =\n");
1350 out.push_str(&head);
1351 block(&mut env, &function.body, 1, out);
1352 }
1353}
1354
1355#[must_use]
1359pub fn codec_chain(functions: &[CodecFn]) -> String {
1360 let mut out = String::new();
1361 for (index, function) in functions.iter().enumerate() {
1362 out.push('\n');
1363 codec_fn(
1364 function,
1365 if index == 0 { "let rec" } else { "and" },
1366 &mut out,
1367 );
1368 }
1369 out
1370}
1371
1372#[must_use]
1374pub fn value_chain(functions: &[ValueFn]) -> String {
1375 let mut out = String::new();
1376 for (index, function) in functions.iter().enumerate() {
1377 out.push('\n');
1378 for line in function.doc.as_lines() {
1379 out.push_str(&format!("(* {} *)\n", comment_safe(line.as_str())));
1380 }
1381 let keyword = if index == 0 { "let rec" } else { "and" };
1382 out.push_str(&format!(
1383 "{keyword} {} () : {} =\n {}\n",
1384 function.name, function.result, function.body
1385 ));
1386 }
1387 out
1388}
1389
1390#[cfg(test)]
1391mod tests {
1392 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
1393 use super::*;
1394 use zerodds_lang_cst::ocaml::{Primitive, int32_literal};
1395
1396 fn record(name: &str, fields: Vec<Field>) -> TypeDecl {
1397 TypeDecl {
1398 body_text: zerodds_lang_cst::common::BodyText::none(),
1399 doc: Doc::none(),
1400 name: Ident::new(name),
1401 body: TypeBody::Record(fields),
1402 }
1403 }
1404
1405 fn field(name: &str, type_: Type) -> Field {
1406 Field {
1407 doc: Doc::none(),
1408 name: Ident::new(name),
1409 type_,
1410 }
1411 }
1412
1413 #[test]
1414 fn a_record_writes_one_field_per_line_with_a_trailing_semicolon() {
1415 let text = decl(&Decl::Types(TypeGroup::one(record(
1416 "pose",
1417 alloc::vec![
1418 field("x", Type::Primitive(Primitive::Int32)),
1419 field("y", Type::Primitive(Primitive::Float)),
1420 ],
1421 ))));
1422 assert_eq!(text, "type pose = {\n x : int32;\n y : float;\n}\n");
1423 }
1424
1425 #[test]
1426 fn a_group_of_two_joins_them_with_and() {
1427 let group = TypeGroup {
1430 doc: Doc::none(),
1431 items: alloc::vec![
1432 record("a", alloc::vec![field("b", Type::named("b"))]),
1433 record("b", alloc::vec![field("a", Type::named("a"))]),
1434 ],
1435 };
1436 let text = decl(&Decl::Types(group));
1437 assert!(text.starts_with("type a = {"), "{text}");
1438 assert!(text.contains("\nand b = {"), "{text}");
1439 }
1440
1441 #[test]
1442 fn a_postfix_type_needs_no_brackets_at_any_depth() {
1443 let nested = Type::option(Type::array(Type::array(Type::Primitive(Primitive::Int32))));
1444 assert_eq!(type_(&nested), "int32 array array option");
1445 let other = Type::array(Type::option(Type::Primitive(Primitive::String)));
1446 assert_eq!(type_(&other), "string option array");
1447 }
1448
1449 #[test]
1450 fn a_tuple_is_always_bracketed_because_star_binds_loosest() {
1451 let tuple = Type::array(Type::Tuple(alloc::vec![
1452 Type::Primitive(Primitive::Int32),
1453 Type::Primitive(Primitive::String),
1454 ]));
1455 assert_eq!(type_(&tuple), "(int32 * string) array");
1456 }
1457
1458 #[test]
1459 fn a_variant_writes_a_leading_bar_per_constructor() {
1460 let text = decl(&Decl::Types(TypeGroup::one(TypeDecl {
1461 body_text: zerodds_lang_cst::common::BodyText::none(),
1462 doc: Doc::none(),
1463 name: Ident::new("mode"),
1464 body: TypeBody::Variant(alloc::vec![
1465 Constructor {
1466 doc: Doc::none(),
1467 name: Ident::new("IDLE"),
1468 args: Vec::new(),
1469 },
1470 Constructor {
1471 doc: Doc::none(),
1472 name: Ident::new("ACTIVE"),
1473 args: Vec::new(),
1474 },
1475 ]),
1476 })));
1477 assert_eq!(text, "type mode =\n | IDLE\n | ACTIVE\n");
1478 }
1479
1480 #[test]
1481 fn a_function_binding_writes_one_arm_per_line() {
1482 let text = decl(&Decl::Let(LetDecl {
1483 doc: Doc::none(),
1484 name: Ident::new("int32_of_mode"),
1485 type_: None,
1486 body: Expr::Function(alloc::vec![
1487 Arm {
1488 pattern: Pattern::Construct(Ident::new("IDLE")),
1489 body: Expr::Literal(int32_literal(0)),
1490 },
1491 Arm {
1492 pattern: Pattern::Construct(Ident::new("ACTIVE")),
1493 body: Expr::Literal(int32_literal(4)),
1494 },
1495 ]),
1496 }));
1497 assert_eq!(
1498 text,
1499 "let int32_of_mode = function\n | IDLE -> 0l\n | ACTIVE -> 4l\n"
1500 );
1501 }
1502
1503 #[test]
1504 fn an_abstract_type_writes_only_its_name() {
1505 let text = decl(&Decl::Types(TypeGroup::one(TypeDecl {
1506 body_text: zerodds_lang_cst::common::BodyText::none(),
1507 doc: Doc::none(),
1508 name: Ident::new("handle"),
1509 body: TypeBody::Abstract,
1510 })));
1511 assert_eq!(text, "type handle\n");
1512 }
1513
1514 #[test]
1515 fn an_empty_class_type_stays_on_one_line() {
1516 let text = decl(&Decl::ClassType(ClassTypeDecl {
1517 doc: Doc::none(),
1518 name: Ident::new("nothing"),
1519 methods: Vec::new(),
1520 }));
1521 assert_eq!(text, "class type nothing = object end\n");
1522 }
1523
1524 #[test]
1525 fn a_method_writes_its_parameters_as_arrows() {
1526 let text = decl(&Decl::ClassType(ClassTypeDecl {
1527 doc: Doc::none(),
1528 name: Ident::new("control"),
1529 methods: alloc::vec![
1530 Method {
1531 doc: Doc::none(),
1532 name: Ident::new("go"),
1533 params: alloc::vec![Type::named("pose")],
1534 result: Type::Primitive(Primitive::Unit),
1535 },
1536 Method {
1537 doc: Doc::none(),
1538 name: Ident::new("status"),
1539 params: Vec::new(),
1540 result: Type::Primitive(Primitive::Int32),
1541 },
1542 ],
1543 }));
1544 assert!(text.contains("method go : pose -> unit"), "{text}");
1545 assert!(text.contains("method status : int32"), "{text}");
1546 }
1547
1548 #[test]
1549 fn a_quote_in_a_comment_is_replaced_because_ocamlc_refuses_the_file() {
1550 assert_eq!(comment_safe("the \"wire\" form"), "the 'wire' form");
1554 }
1555
1556 #[test]
1557 fn a_comment_delimiter_inside_a_comment_is_neutralised() {
1558 assert_eq!(
1559 comment_safe("ends here *) and then"),
1560 "ends here * ) and then"
1561 );
1562 assert_eq!(comment_safe("opens (* here"), "opens ( * here");
1563 }
1564
1565 #[test]
1566 fn a_multi_line_comment_is_one_block_and_not_one_block_per_line() {
1567 let mut w = Writer::new(" ");
1568 doc(&mut w, &Doc::lines(&["first", "second"]));
1569 assert_eq!(w.finish(), "(** first\n second *)\n");
1570 }
1571}