1use crate::c::include_text;
11use crate::writer::{Writer, comment_lines, escape_char, escape_string, verbatim_lines};
12use alloc::format;
13use alloc::string::{String, ToString};
14use alloc::vec::Vec;
15use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
16use zerodds_lang_cst::cpp::{
17 Alias, Base, Block, CastKind, Class, ClassMember, Constructor, Decl, Destructor, Enum,
18 Enumerator, Expr, Field, File, Function, FunctionBody, Method, Namespace, Param, Section,
19 Statement, SwitchCase, Template, TemplateParamKind, Type, Variable,
20};
21
22#[must_use]
24pub fn file(file: &File) -> String {
25 let mut w = Writer::spaces4();
26 doc(&mut w, &file.doc);
27 if file.pragma_once {
28 w.line("#pragma once");
29 w.blank_line();
30 }
31 for include in &file.includes {
32 w.line(&include_text(include));
33 }
34 if !file.includes.is_empty() {
35 w.blank_line();
36 }
37 for (index, decl) in file.decls.iter().enumerate() {
38 if index > 0 {
39 w.blank_line();
40 }
41 render_decl(&mut w, decl);
42 }
43 w.finish()
44}
45
46#[must_use]
48pub fn decl(node: &Decl) -> String {
49 let mut w = Writer::spaces4();
50 render_decl(&mut w, node);
51 w.finish()
52}
53
54#[must_use]
56pub fn type_(node: &Type) -> String {
57 type_text(node)
58}
59
60#[must_use]
69pub fn expr(node: &Expr) -> String {
70 expr_text(node)
71}
72
73fn render_decl(w: &mut Writer, node: &Decl) {
77 match node {
78 Decl::Namespace(namespace) => render_namespace(w, namespace),
79 Decl::Class(class) => render_class(w, class),
80 Decl::Enum(e) => render_enum(w, e),
81 Decl::Alias(alias) => render_alias(w, alias),
82 Decl::Function(f) => render_function(w, f),
83 Decl::Variable(v) => render_variable(w, v),
84 Decl::Template(t) => render_template(w, t),
85 Decl::Comment(c) => doc(w, c),
86 Decl::Verbatim(text) => verbatim_lines(w, text),
87 Decl::Forward { is_struct, name } => {
88 let mut line = String::from(if *is_struct { "struct " } else { "class " });
89 line.push_str(name.as_str());
90 line.push(';');
91 w.line(&line);
92 }
93 }
94}
95
96fn render_namespace(w: &mut Writer, node: &Namespace) {
103 doc(w, &node.doc);
104 let mut head = String::from("namespace ");
105 head.push_str(node.name.as_str());
106 head.push_str(" {");
107 w.line(&head);
108 for (index, decl) in node.decls.iter().enumerate() {
109 if index > 0 {
110 w.blank_line();
111 }
112 render_decl(w, decl);
113 }
114 let mut close = String::from("} // namespace ");
115 close.push_str(node.name.as_str());
116 w.line(&close);
117}
118
119fn render_class(w: &mut Writer, node: &Class) {
120 render_class_specialised(w, node, "");
121}
122
123fn render_class_specialised(w: &mut Writer, node: &Class, suffix: &str) {
133 doc(w, &node.doc);
134 let mut head = String::from(if node.is_struct { "struct " } else { "class " });
135 head.push_str(node.name.as_str());
136 head.push_str(suffix);
137 if !node.bases.is_empty() {
138 head.push_str(" : ");
139 let parts: Vec<String> = node.bases.iter().map(base_text).collect();
140 head.push_str(&parts.join(", "));
141 }
142 head.push_str(" {");
143 w.line(&head);
144 for section in &node.sections {
145 render_section(w, section);
146 }
147 w.line("};");
148}
149
150fn base_text(base: &Base) -> String {
151 let mut out = String::from(base.access.as_str());
152 out.push(' ');
153 if base.virtual_ {
154 out.push_str("virtual ");
155 }
156 out.push_str(&path_text(&base.path));
157 out
158}
159
160fn render_section(w: &mut Writer, node: &Section) {
163 let mut label = String::from(node.access.as_str());
164 label.push(':');
165 w.line(&label);
166 w.indent();
167 for member in &node.members {
168 render_class_member(w, member);
169 }
170 w.dedent();
171 if !node.members.is_empty() {
172 w.blank_line();
173 }
174}
175
176fn render_class_member(w: &mut Writer, node: &ClassMember) {
177 match node {
178 ClassMember::Field(field) => render_field(w, field),
179 ClassMember::Method(method) => render_method(w, method),
180 ClassMember::Constructor(constructor) => render_constructor(w, constructor),
181 ClassMember::Destructor(destructor) => render_destructor(w, destructor),
182 ClassMember::Nested(decl) => render_decl(w, decl),
183 ClassMember::Alias(alias) => render_alias(w, alias),
184 ClassMember::Comment(c) => doc(w, c),
185 ClassMember::Verbatim(text) => verbatim_lines(w, text),
186 }
187}
188
189fn render_field(w: &mut Writer, node: &Field) {
190 doc(w, &node.doc);
191 let mut line = String::new();
192 if node.is_static {
193 line.push_str("static ");
194 }
195 if node.is_constexpr {
196 line.push_str("constexpr ");
197 }
198 line.push_str(&type_text(&node.type_));
199 line.push(' ');
200 line.push_str(node.name.as_str());
201 for dim in &node.dims {
202 line.push('[');
203 line.push_str(&expr_text(dim));
204 line.push(']');
205 }
206 if let Some(bits) = node.bits {
207 line.push_str(" : ");
208 line.push_str(&alloc::format!("{bits}"));
209 }
210 if let Some(default) = &node.default {
211 line.push_str(" = ");
212 line.push_str(&expr_text(default));
213 }
214 line.push(';');
215 w.line(&line);
216}
217
218fn render_method(w: &mut Writer, node: &Method) {
219 doc(w, &node.doc);
220 let mut head = String::new();
221 if node.is_static {
222 head.push_str("static ");
223 }
224 if node.is_virtual {
225 head.push_str("virtual ");
226 }
227 if node.is_constexpr {
228 head.push_str("constexpr ");
229 }
230 head.push_str(&type_text(&node.returns));
231 head.push(' ');
232 head.push_str(node.name.as_str());
233 head.push('(');
234 head.push_str(¶ms_text(&node.params));
235 head.push(')');
236 if node.is_const {
237 head.push_str(" const");
238 }
239 if node.is_noexcept {
240 head.push_str(" noexcept");
241 }
242 if node.is_override {
243 head.push_str(" override");
244 }
245 if node.is_pure {
246 head.push_str(" = 0;");
247 w.line(&head);
248 return;
249 }
250 render_body(w, head, node.body.as_ref());
251}
252
253fn render_body(w: &mut Writer, mut head: String, body: Option<&Block>) {
256 let Some(block) = body else {
257 head.push(';');
258 w.line(&head);
259 return;
260 };
261 if let Some(single) = one_line_body(block) {
262 head.push_str(" { ");
263 head.push_str(&single);
264 head.push_str(" }");
265 w.line(&head);
266 return;
267 }
268 head.push_str(" {");
269 w.line(&head);
270 w.indent();
271 render_block(w, block);
272 w.dedent();
273 w.line("}");
274}
275
276fn one_line_body(block: &Block) -> Option<String> {
278 match block.statements.as_slice() {
279 [Statement::Return(Some(value))] => {
280 let mut out = String::from("return ");
281 out.push_str(&expr_text(value));
282 out.push(';');
283 Some(out)
284 }
285 [Statement::Expr(value)] => {
286 let mut out = expr_text(value);
287 out.push(';');
288 Some(out)
289 }
290 _ => None,
291 }
292}
293
294fn render_constructor(w: &mut Writer, node: &Constructor) {
295 doc(w, &node.doc);
296 let mut head = String::new();
297 if node.is_explicit {
298 head.push_str("explicit ");
299 }
300 if node.is_constexpr {
301 head.push_str("constexpr ");
302 }
303 head.push_str(node.name.as_str());
304 head.push('(');
305 head.push_str(¶ms_text(&node.params));
306 head.push(')');
307 if node.is_noexcept {
308 head.push_str(" noexcept");
309 }
310
311 match &node.body {
312 FunctionBody::Defaulted => {
313 head.push_str(" = default;");
314 w.line(&head);
315 }
316 FunctionBody::Deleted => {
317 head.push_str(" = delete;");
318 w.line(&head);
319 }
320 FunctionBody::None => {
321 head.push(';');
322 w.line(&head);
323 }
324 FunctionBody::Block(block) => {
325 if node.initialisers.is_empty() {
326 render_body(w, head, Some(block));
327 return;
328 }
329 w.line(&head);
332 w.indent();
333 let parts: Vec<String> = node
334 .initialisers
335 .iter()
336 .map(|(name, value)| {
337 let mut text = name.as_str().to_string();
338 text.push('(');
339 text.push_str(&expr_text(value));
340 text.push(')');
341 text
342 })
343 .collect();
344 let mut line = String::from(": ");
345 line.push_str(&parts.join(", "));
346 if block.statements.is_empty() {
347 line.push_str(" {}");
348 w.line(&line);
349 w.dedent();
350 return;
351 }
352 line.push_str(" {");
353 w.line(&line);
354 w.indent();
355 render_block(w, block);
356 w.dedent();
357 w.line("}");
358 w.dedent();
359 }
360 }
361}
362
363fn render_destructor(w: &mut Writer, node: &Destructor) {
364 doc(w, &node.doc);
365 let mut head = String::new();
366 if node.is_virtual {
367 head.push_str("virtual ");
368 }
369 head.push('~');
370 head.push_str(node.name.as_str());
371 head.push_str("()");
372 match &node.body {
373 FunctionBody::Defaulted => {
374 head.push_str(" = default;");
375 w.line(&head);
376 }
377 FunctionBody::Deleted => {
378 head.push_str(" = delete;");
379 w.line(&head);
380 }
381 FunctionBody::None => {
382 head.push(';');
383 w.line(&head);
384 }
385 FunctionBody::Block(block) => render_body(w, head, Some(block)),
386 }
387}
388
389fn render_enum(w: &mut Writer, node: &Enum) {
390 doc(w, &node.doc);
391 let mut head = String::from(if node.is_scoped {
392 "enum class "
393 } else {
394 "enum "
395 });
396 head.push_str(node.name.as_str());
397 if let Some(underlying) = &node.underlying {
398 head.push_str(" : ");
399 head.push_str(&type_text(underlying));
400 }
401 head.push_str(" {");
402 w.line(&head);
403 w.indent();
404 for (index, enumerator) in node.enumerators.iter().enumerate() {
405 render_enumerator(w, enumerator, index + 1 == node.enumerators.len());
406 }
407 w.dedent();
408 w.line("};");
409}
410
411fn render_enumerator(w: &mut Writer, node: &Enumerator, last: bool) {
412 doc(w, &node.doc);
413 let mut line = String::from(node.name.as_str());
414 if let Some(value) = &node.value {
415 line.push_str(" = ");
416 line.push_str(&expr_text(value));
417 }
418 if !last {
419 line.push(',');
420 }
421 w.line(&line);
422}
423
424fn render_alias(w: &mut Writer, node: &Alias) {
425 doc(w, &node.doc);
426 let mut line = String::from("using ");
427 line.push_str(node.name.as_str());
428 line.push_str(" = ");
429 line.push_str(&type_text(&node.type_));
430 line.push(';');
431 w.line(&line);
432}
433
434fn render_function(w: &mut Writer, node: &Function) {
435 render_function_specialised(w, node, "");
436}
437
438fn render_function_specialised(w: &mut Writer, node: &Function, suffix: &str) {
442 doc(w, &node.doc);
443 let mut head = String::new();
444 if node.is_maybe_unused {
445 head.push_str("[[maybe_unused]] ");
446 }
447 if node.is_static {
448 head.push_str("static ");
449 }
450 if node.is_inline {
451 head.push_str("inline ");
452 }
453 if node.is_constexpr {
454 head.push_str("constexpr ");
455 }
456 head.push_str(&type_text(&node.returns));
457 head.push(' ');
458 head.push_str(node.name.as_str());
459 head.push_str(suffix);
460 head.push('(');
461 head.push_str(¶ms_text(&node.params));
462 head.push(')');
463 if node.is_noexcept {
464 head.push_str(" noexcept");
465 }
466 match &node.body {
467 FunctionBody::Block(block) => render_body(w, head, Some(block)),
468 FunctionBody::Defaulted => {
469 head.push_str(" = default;");
470 w.line(&head);
471 }
472 FunctionBody::Deleted => {
473 head.push_str(" = delete;");
474 w.line(&head);
475 }
476 FunctionBody::None => {
477 head.push(';');
478 w.line(&head);
479 }
480 }
481}
482
483fn render_variable(w: &mut Writer, node: &Variable) {
484 doc(w, &node.doc);
485 let mut line = String::new();
486 if node.is_static {
487 line.push_str("static ");
488 }
489 if node.is_inline {
490 line.push_str("inline ");
491 }
492 if node.is_constexpr {
493 line.push_str("constexpr ");
494 }
495 line.push_str(&type_text(&node.type_));
496 line.push(' ');
497 line.push_str(node.name.as_str());
498 if let Some(value) = &node.value {
499 line.push_str(" = ");
500 line.push_str(&expr_text(value));
501 }
502 line.push(';');
503 w.line(&line);
504}
505
506fn render_template(w: &mut Writer, node: &Template) {
510 doc(w, &node.doc);
511 let mut head = String::from("template <");
512 let parts: Vec<String> = node
513 .params
514 .iter()
515 .map(|param| {
516 let mut text = match ¶m.kind {
517 TemplateParamKind::TypeName => "typename ".to_string(),
518 TemplateParamKind::Class => "class ".to_string(),
519 TemplateParamKind::NonType(type_) => {
520 let mut out = type_text(type_);
521 out.push(' ');
522 out
523 }
524 };
525 text.push_str(param.name.as_str());
526 if let Some(default) = ¶m.default {
527 text.push_str(" = ");
528 text.push_str(&type_text(default));
529 }
530 text
531 })
532 .collect();
533 head.push_str(&parts.join(", "));
534 head.push('>');
535 w.line(&head);
536 if node.specialisation.is_empty() {
537 render_decl(w, &node.decl);
538 return;
539 }
540 let args: Vec<String> = node.specialisation.iter().map(type_text).collect();
546 let suffix = alloc::format!("<{}>", args.join(", "));
547 match node.decl.as_ref() {
548 Decl::Class(class) => render_class_specialised(w, class, &suffix),
549 Decl::Function(function) => render_function_specialised(w, function, &suffix),
550 other => render_decl(w, other),
555 }
556}
557
558fn params_text(params: &[Param]) -> String {
559 let parts: Vec<String> = params
560 .iter()
561 .map(|param| {
562 let mut text = type_text(¶m.type_);
563 if let Some(name) = ¶m.name {
564 text.push(' ');
565 text.push_str(name.as_str());
566 }
567 if let Some(default) = ¶m.default {
568 text.push_str(" = ");
569 text.push_str(&expr_text(default));
570 }
571 text
572 })
573 .collect();
574 parts.join(", ")
575}
576
577fn render_block(w: &mut Writer, block: &Block) {
588 for statement in &block.statements {
589 render_statement(w, statement);
590 }
591}
592
593fn render_statement(w: &mut Writer, node: &Statement) {
604 match node {
605 Statement::Declare { type_, name, value } => {
606 let mut line = type_text(type_);
607 line.push(' ');
608 line.push_str(name.as_str());
609 if let Some(value) = value {
610 line.push_str(" = ");
611 line.push_str(&expr_text(value));
612 }
613 line.push(';');
614 w.line(&line);
615 }
616 Statement::Expr(value) => {
617 let mut line = expr_text(value);
618 line.push(';');
619 w.line(&line);
620 }
621 Statement::Return(value) => {
622 let mut line = String::from("return");
623 if let Some(value) = value {
624 line.push(' ');
625 line.push_str(&expr_text(value));
626 }
627 line.push(';');
628 w.line(&line);
629 }
630 Statement::If {
631 condition,
632 then,
633 otherwise,
634 } => {
635 let mut head = String::from("if (");
636 head.push_str(&expr_text(condition));
637 head.push_str(") {");
638 w.line(&head);
639 w.indent();
640 render_block(w, then);
641 w.dedent();
642 match otherwise {
643 Some(block) => {
644 w.line("} else {");
645 w.indent();
646 render_block(w, block);
647 w.dedent();
648 w.line("}");
649 }
650 None => w.line("}"),
651 }
652 }
653 Statement::For {
654 init,
655 condition,
656 step,
657 body,
658 } => {
659 let mut head = String::from("for (");
660 head.push_str(statement_inline(init).trim_end_matches(';'));
661 head.push_str("; ");
662 head.push_str(&expr_text(condition));
663 head.push_str("; ");
664 head.push_str(&expr_text(step));
665 head.push_str(") {");
666 w.line(&head);
667 w.indent();
668 render_block(w, body);
669 w.dedent();
670 w.line("}");
671 }
672 Statement::While { condition, body } => {
673 let mut head = String::from("while (");
674 head.push_str(&expr_text(condition));
675 head.push_str(") {");
676 w.line(&head);
677 w.indent();
678 render_block(w, body);
679 w.dedent();
680 w.line("}");
681 }
682 Statement::Break => w.line("break;"),
683 Statement::ForEach {
684 binding,
685 range,
686 body,
687 } => {
688 let mut head = String::from("for (auto& ");
689 head.push_str(binding.as_str());
690 head.push_str(" : ");
691 head.push_str(&expr_text(range));
692 head.push_str(") {");
693 w.line(&head);
694 w.indent();
695 render_block(w, body);
696 w.dedent();
697 w.line("}");
698 }
699 Statement::Switch { value, cases } => {
700 let mut head = String::from("switch (");
701 head.push_str(&expr_text(value));
702 head.push_str(") {");
703 w.line(&head);
704 for case in cases {
705 render_case(w, case);
706 }
707 w.line("}");
708 }
709 Statement::Throw(value) => {
710 let mut line = String::from("throw ");
711 line.push_str(&expr_text(value));
712 line.push(';');
713 w.line(&line);
714 }
715 Statement::Try { body, handlers } => {
716 w.line("try {");
717 w.indent();
718 render_block(w, body);
719 w.dedent();
720 for handler in handlers {
721 let mut head = String::from("} catch (");
722 match &handler.type_ {
723 Some(type_) => {
724 head.push_str("const ");
725 head.push_str(&type_text(type_));
726 head.push('&');
727 if let Some(name) = &handler.binding {
728 head.push(' ');
729 head.push_str(name.as_str());
730 }
731 }
732 None => head.push_str("..."),
733 }
734 head.push_str(") {");
735 w.line(&head);
736 w.indent();
737 render_block(w, &handler.body);
738 w.dedent();
739 }
740 w.line("}");
741 }
742 Statement::Comment(c) => doc(w, c),
743 }
744}
745
746fn render_case(w: &mut Writer, case: &SwitchCase) {
747 if case.labels.is_empty() {
748 w.line("default:");
749 } else {
750 for label in &case.labels {
751 let mut line = String::from("case ");
752 line.push_str(&expr_text(label));
753 line.push(':');
754 w.line(&line);
755 }
756 }
757 w.indent();
758 render_block(w, &case.body);
759 if case.breaks {
760 w.line("break;");
761 }
762 w.dedent();
763}
764
765fn path_text(path: &Path) -> String {
766 let parts: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
767 let joined = parts.join("::");
768 if path.is_rooted() {
769 let mut out = String::from("::");
770 out.push_str(&joined);
771 return out;
772 }
773 joined
774}
775
776fn type_text(node: &Type) -> String {
780 match node {
781 Type::Primitive(primitive) => primitive.as_str().to_string(),
782 Type::Named { path, args } => {
783 let mut out = path_text(path);
784 if !args.is_empty() {
785 out.push('<');
786 let parts: Vec<String> = args.iter().map(type_text).collect();
787 out.push_str(&parts.join(", "));
788 out.push('>');
791 }
792 out
793 }
794 Type::Pointer(target) => {
795 let mut out = type_text(target);
796 out.push('*');
797 out
798 }
799 Type::Ref(target) => {
800 let mut out = type_text(target);
801 out.push('&');
802 out
803 }
804 Type::RvalueRef(target) => {
805 let mut out = type_text(target);
806 out.push_str("&&");
807 out
808 }
809 Type::Const(target) => {
810 let mut out = String::from("const ");
811 out.push_str(&type_text(target));
812 out
813 }
814 Type::Array { element, length } => {
815 let mut out = type_text(element);
816 out.push('[');
817 out.push_str(&expr_text(length));
818 out.push(']');
819 out
820 }
821 Type::Void => "void".to_string(),
822 Type::Auto => "auto".to_string(),
823 }
824}
825
826#[allow(clippy::too_many_lines)]
833fn expr_text(node: &Expr) -> String {
834 match node {
835 Expr::Literal(literal) => literal_text(literal),
836 Expr::Path(path) => path_text(path),
837 Expr::Call { callee, args } => {
838 let mut out = expr_text(callee);
839 out.push('(');
840 let parts: Vec<String> = args.iter().map(expr_text).collect();
841 out.push_str(&parts.join(", "));
842 out.push(')');
843 out
844 }
845 Expr::MethodCall {
846 receiver,
847 name,
848 args,
849 } => {
850 let mut out = expr_text(receiver);
851 out.push('.');
852 out.push_str(name.as_str());
853 out.push('(');
854 let parts: Vec<String> = args.iter().map(expr_text).collect();
855 out.push_str(&parts.join(", "));
856 out.push(')');
857 out
858 }
859 Expr::Member { value, name } => {
860 let mut out = expr_text(value);
861 out.push('.');
862 out.push_str(name.as_str());
863 out
864 }
865 Expr::Arrow { value, name } => {
866 let mut out = expr_text(value);
867 out.push_str("->");
868 out.push_str(name.as_str());
869 out
870 }
871 Expr::Index { value, index } => {
872 let mut out = expr_text(value);
873 out.push('[');
874 out.push_str(&expr_text(index));
875 out.push(']');
876 out
877 }
878 Expr::Construct {
879 type_,
880 args,
881 braced,
882 } => {
883 let mut out = type_text(type_);
884 out.push(if *braced { '{' } else { '(' });
885 let parts: Vec<String> = args.iter().map(expr_text).collect();
886 out.push_str(&parts.join(", "));
887 out.push(if *braced { '}' } else { ')' });
888 out
889 }
890 Expr::Unary { op, value } => {
891 let mut out = String::from(*op);
892 out.push_str(&expr_text(value));
893 out
894 }
895 Expr::Binary { op, lhs, rhs } => {
896 let mut out = expr_text(lhs);
897 out.push(' ');
898 out.push_str(op);
899 out.push(' ');
900 out.push_str(&expr_text(rhs));
901 out
902 }
903 Expr::Cast { kind, type_, value } => {
904 let mut out = String::from(cast_text(*kind));
905 out.push('<');
906 out.push_str(&type_text(type_));
907 out.push_str(">(");
908 out.push_str(&expr_text(value));
909 out.push(')');
910 out
911 }
912 Expr::AddressOf(value) => {
913 let mut out = String::from("&");
914 out.push_str(&expr_text(value));
915 out
916 }
917 Expr::Deref(value) => {
918 let mut out = String::from("*");
919 out.push_str(&expr_text(value));
920 out
921 }
922 Expr::InitList(elements) => {
923 let mut out = String::from("{");
924 let parts: Vec<String> = elements.iter().map(expr_text).collect();
925 out.push_str(&parts.join(", "));
926 out.push('}');
927 out
928 }
929 Expr::SizeOf(type_) => {
930 let mut out = String::from("sizeof(");
931 out.push_str(&type_text(type_));
932 out.push(')');
933 out
934 }
935 Expr::Paren(inner) => {
936 let mut out = String::from("(");
937 out.push_str(&expr_text(inner));
938 out.push(')');
939 out
940 }
941 }
942}
943
944const fn cast_text(kind: CastKind) -> &'static str {
945 kind.as_str()
946}
947
948fn literal_text(literal: &Literal) -> String {
949 match literal {
950 Literal::Int(text) | Literal::Float(text) => text.clone(),
951 Literal::Bool(value) => {
952 if *value {
953 "true".to_string()
954 } else {
955 "false".to_string()
956 }
957 }
958 Literal::Char(ch) => {
959 let mut out = String::from("'");
960 out.push_str(&escape_char(*ch));
961 out.push('\'');
962 out
963 }
964 Literal::Str(text) => {
965 let mut out = String::from("\"");
966 out.push_str(&escape_string(text));
967 out.push('"');
968 out
969 }
970 Literal::Bytes(bytes) => {
971 let mut out = String::from("{");
972 let parts: Vec<String> = bytes.iter().map(|b| alloc::format!("0x{b:02X}")).collect();
973 out.push_str(&parts.join(", "));
974 out.push('}');
975 out
976 }
977 Literal::Null => "nullptr".to_string(),
978 }
979}
980
981fn doc(w: &mut Writer, doc: &Doc) {
982 if doc.is_empty() {
983 return;
984 }
985 comment_lines(w, "// ", doc.as_lines());
986}
987
988fn statement_inline(node: &Statement) -> String {
994 match node {
995 Statement::Declare { type_, name, value } => {
996 let mut out = type_text(type_);
997 out.push(' ');
998 out.push_str(name.as_str());
999 if let Some(value) = value {
1000 out.push_str(" = ");
1001 out.push_str(&expr_text(value));
1002 }
1003 out
1004 }
1005 Statement::Expr(value) => expr_text(value),
1006 _ => String::new(),
1007 }
1008}
1009
1010#[must_use]
1025pub fn integer_text(value: i128) -> String {
1026 if value == i128::from(i64::MIN) {
1027 return "(-9223372036854775807LL - 1)".to_string();
1028 }
1029 format!("{value}")
1030}