Skip to main content

zerodds_lang_render/
java.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Rendering the Java CST. Spec ยง5.
4//!
5//! One style, the Oracle convention: four spaces, the opening brace on
6//! the same line, one member per line with a blank line between methods.
7
8use crate::writer::{Writer, comment_lines, escape_char, escape_string, verbatim_lines};
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
12use zerodds_lang_cst::java::{
13    Annotation, AnnotationArg, AnnotationElement, AnnotationType, Block, Class, Constructor, Enum,
14    EnumConstant, Expr, Field, File, Import, Interface, Member, Method, Modifier, Param, Record,
15    Statement, SwitchCase, Type, TypeDecl, TypeParam,
16};
17
18/// Render a whole file.
19#[must_use]
20pub fn file(file: &File) -> String {
21    let mut w = Writer::spaces4();
22    // The file's leading comment documents no declaration: above
23    // `package` Javadoc is attached to nothing, and above a type that
24    // has its own it is a second one attached to nothing. Line comments,
25    // as for every other comment that documents nothing.
26    if !file.doc.is_empty() {
27        line_comment(&mut w, &file.doc);
28        w.blank_line();
29    }
30    if let Some(package) = &file.package {
31        let mut line = String::from("package ");
32        line.push_str(&path_text(package));
33        line.push(';');
34        w.line(&line);
35        w.blank_line();
36    }
37    for import in &file.imports {
38        w.line(&import_text(import));
39    }
40    if !file.imports.is_empty() {
41        w.blank_line();
42    }
43    for (index, decl) in file.types.iter().enumerate() {
44        if index > 0 {
45            w.blank_line();
46        }
47        render_type_decl(&mut w, decl);
48    }
49    w.finish()
50}
51
52/// Render one type declaration, for a caller that has only one.
53#[must_use]
54pub fn type_decl(node: &TypeDecl) -> String {
55    let mut w = Writer::spaces4();
56    render_type_decl(&mut w, node);
57    w.finish()
58}
59
60/// Render one type, for a caller that has only one.
61#[must_use]
62pub fn type_(node: &Type) -> String {
63    type_text(node)
64}
65
66fn import_text(import: &Import) -> String {
67    let mut out = String::from("import ");
68    if import.static_ {
69        out.push_str("static ");
70    }
71    out.push_str(&path_text(&import.path));
72    if import.wildcard {
73        out.push_str(".*");
74    }
75    out.push(';');
76    out
77}
78
79fn render_type_decl(w: &mut Writer, node: &TypeDecl) {
80    match node {
81        TypeDecl::Class(class) => render_class(w, class),
82        TypeDecl::Interface(interface) => render_interface(w, interface),
83        TypeDecl::Enum(e) => render_enum(w, e),
84        TypeDecl::Record(record) => render_record(w, record),
85        TypeDecl::AnnotationType(annotation) => render_annotation_type(w, annotation),
86        TypeDecl::Verbatim(text) => verbatim_lines(w, text),
87    }
88}
89
90fn render_class(w: &mut Writer, node: &Class) {
91    doc(w, &node.doc);
92    annotations(w, &node.annotations);
93    let mut head = modifiers_text(&node.modifiers);
94    head.push_str("class ");
95    head.push_str(node.name.as_str());
96    head.push_str(&generics_text(&node.generics));
97    if let Some(extends) = &node.extends {
98        head.push_str(" extends ");
99        head.push_str(&type_text(extends));
100    }
101    if !node.implements.is_empty() {
102        head.push_str(" implements ");
103        let parts: Vec<String> = node.implements.iter().map(type_text).collect();
104        head.push_str(&parts.join(", "));
105    }
106    head.push_str(" {");
107    w.line(&head);
108    w.indent();
109    render_members(w, &node.members);
110    w.dedent();
111    w.line("}");
112}
113
114fn render_interface(w: &mut Writer, node: &Interface) {
115    doc(w, &node.doc);
116    annotations(w, &node.annotations);
117    let mut head = modifiers_text(&node.modifiers);
118    head.push_str("interface ");
119    head.push_str(node.name.as_str());
120    head.push_str(&generics_text(&node.generics));
121    if !node.extends.is_empty() {
122        head.push_str(" extends ");
123        let parts: Vec<String> = node.extends.iter().map(type_text).collect();
124        head.push_str(&parts.join(", "));
125    }
126    head.push_str(" {");
127    w.line(&head);
128    w.indent();
129    render_members(w, &node.members);
130    w.dedent();
131    w.line("}");
132}
133
134fn render_enum(w: &mut Writer, node: &Enum) {
135    doc(w, &node.doc);
136    annotations(w, &node.annotations);
137    let mut head = modifiers_text(&node.modifiers);
138    head.push_str("enum ");
139    head.push_str(node.name.as_str());
140    if !node.implements.is_empty() {
141        head.push_str(" implements ");
142        let parts: Vec<String> = node.implements.iter().map(type_text).collect();
143        head.push_str(&parts.join(", "));
144    }
145    head.push_str(" {");
146    w.line(&head);
147    w.indent();
148    for (index, constant) in node.constants.iter().enumerate() {
149        let last = index + 1 == node.constants.len();
150        render_enum_constant(w, constant, last, node.members.is_empty());
151    }
152    if !node.members.is_empty() {
153        w.blank_line();
154        render_members(w, &node.members);
155    }
156    w.dedent();
157    w.line("}");
158}
159
160/// The constants end in `;` when members follow and in nothing when they
161/// do not โ€” which is what the Java grammar requires and what a reader
162/// checking the output against the language expects.
163fn render_enum_constant(w: &mut Writer, node: &EnumConstant, last: bool, no_members: bool) {
164    doc(w, &node.doc);
165    annotations(w, &node.annotations);
166    let mut line = String::from(node.name.as_str());
167    if !node.args.is_empty() {
168        line.push('(');
169        let parts: Vec<String> = node.args.iter().map(expr_text).collect();
170        line.push_str(&parts.join(", "));
171        line.push(')');
172    }
173    if !node.body.is_empty() {
174        line.push_str(" {");
175        w.line(&line);
176        w.indent();
177        render_members(w, &node.body);
178        w.dedent();
179        w.line(if last { "}" } else { "}," });
180        return;
181    }
182    if last {
183        if !no_members {
184            line.push(';');
185        }
186    } else {
187        line.push(',');
188    }
189    w.line(&line);
190}
191
192fn render_record(w: &mut Writer, node: &Record) {
193    doc(w, &node.doc);
194    annotations(w, &node.annotations);
195    let mut head = modifiers_text(&node.modifiers);
196    head.push_str("record ");
197    head.push_str(node.name.as_str());
198    head.push_str(&generics_text(&node.generics));
199    head.push('(');
200    head.push_str(&params_text(&node.components));
201    head.push(')');
202    if !node.implements.is_empty() {
203        head.push_str(" implements ");
204        let parts: Vec<String> = node.implements.iter().map(type_text).collect();
205        head.push_str(&parts.join(", "));
206    }
207    head.push_str(" {");
208    w.line(&head);
209    w.indent();
210    render_members(w, &node.members);
211    w.dedent();
212    w.line("}");
213}
214
215fn render_annotation_type(w: &mut Writer, node: &AnnotationType) {
216    doc(w, &node.doc);
217    annotations(w, &node.annotations);
218    let mut head = modifiers_text(&node.modifiers);
219    head.push_str("@interface ");
220    head.push_str(node.name.as_str());
221    head.push_str(" {");
222    w.line(&head);
223    w.indent();
224    for element in &node.elements {
225        render_annotation_element(w, element);
226    }
227    w.dedent();
228    w.line("}");
229}
230
231fn render_annotation_element(w: &mut Writer, node: &AnnotationElement) {
232    doc(w, &node.doc);
233    let mut line = type_text(&node.type_);
234    line.push(' ');
235    line.push_str(node.name.as_str());
236    line.push_str("()");
237    if let Some(default) = &node.default {
238        line.push_str(" default ");
239        line.push_str(&expr_text(default));
240    }
241    line.push(';');
242    w.line(&line);
243}
244
245/// Fields run together; anything with a body gets a blank line before it.
246fn render_members(w: &mut Writer, members: &[Member]) {
247    let mut previous_had_body = false;
248    for (index, member) in members.iter().enumerate() {
249        let has_body = !matches!(member, Member::Field(_) | Member::Comment(_));
250        if index > 0 && (has_body || previous_had_body) {
251            w.blank_line();
252        }
253        render_member(w, member);
254        previous_had_body = has_body;
255    }
256}
257
258fn render_member(w: &mut Writer, node: &Member) {
259    match node {
260        Member::Field(field) => render_field(w, field),
261        Member::Method(method) => render_method(w, method),
262        Member::Constructor(constructor) => render_constructor(w, constructor),
263        Member::Nested(decl) => render_type_decl(w, decl),
264        Member::Initialiser { static_, body } => {
265            w.line(if *static_ { "static {" } else { "{" });
266            w.indent();
267            render_block(w, body);
268            w.dedent();
269            w.line("}");
270        }
271        Member::Comment(c) => line_comment(w, c),
272        Member::Verbatim(text) => verbatim_lines(w, text),
273    }
274}
275
276fn render_field(w: &mut Writer, node: &Field) {
277    doc(w, &node.doc);
278    annotations(w, &node.annotations);
279    let mut line = modifiers_text(&node.modifiers);
280    line.push_str(&type_text(&node.type_));
281    line.push(' ');
282    line.push_str(node.name.as_str());
283    if let Some(value) = &node.value {
284        line.push_str(" = ");
285        line.push_str(&expr_text(value));
286    }
287    line.push(';');
288    w.line(&line);
289}
290
291fn render_method(w: &mut Writer, node: &Method) {
292    doc(w, &node.doc);
293    annotations(w, &node.annotations);
294    let mut head = modifiers_text(&node.modifiers);
295    let generics = generics_text(&node.generics);
296    if !generics.is_empty() {
297        head.push_str(&generics);
298        head.push(' ');
299    }
300    head.push_str(&type_text(&node.returns));
301    head.push(' ');
302    head.push_str(node.name.as_str());
303    head.push('(');
304    head.push_str(&params_text(&node.params));
305    head.push(')');
306    if !node.throws.is_empty() {
307        head.push_str(" throws ");
308        let parts: Vec<String> = node.throws.iter().map(type_text).collect();
309        head.push_str(&parts.join(", "));
310    }
311    match &node.body {
312        None => {
313            head.push(';');
314            w.line(&head);
315        }
316        Some(block) => {
317            head.push_str(" {");
318            w.line(&head);
319            w.indent();
320            render_block(w, block);
321            w.dedent();
322            w.line("}");
323        }
324    }
325}
326
327fn render_constructor(w: &mut Writer, node: &Constructor) {
328    doc(w, &node.doc);
329    annotations(w, &node.annotations);
330    let mut head = modifiers_text(&node.modifiers);
331    head.push_str(node.name.as_str());
332    head.push('(');
333    head.push_str(&params_text(&node.params));
334    head.push(')');
335    if !node.throws.is_empty() {
336        head.push_str(" throws ");
337        let parts: Vec<String> = node.throws.iter().map(type_text).collect();
338        head.push_str(&parts.join(", "));
339    }
340    head.push_str(" {");
341    w.line(&head);
342    w.indent();
343    render_block(w, &node.body);
344    w.dedent();
345    w.line("}");
346}
347
348/// zerodds-lint: recursion-depth = the generated body's block nesting.
349/// The codec walk emits a routine per type rather than inlining one
350/// (`lang-rules/src/emit/encode.rs`), so nothing here follows the type
351/// graph. What does grow with the IDL is the dispatch chain: a union's
352/// cases, an enumeration's enumerators and a `@mutable` aggregate's
353/// members each become one `If` per arm, in the previous arm's `else`.
354/// The model bounds none of the three counts, and Java nests the arms in
355/// its own output too, so they cannot be walked iteratively the way
356/// `c.rs` walks them. No number is claimed: this signature returns no
357/// error, so nothing here could report one.
358fn render_block(w: &mut Writer, block: &Block) {
359    for statement in &block.statements {
360        render_statement(w, statement);
361    }
362}
363
364/// zerodds-lint: recursion-depth = the generated body's block nesting.
365/// The codec walk emits a routine per type rather than inlining one
366/// (`lang-rules/src/emit/encode.rs`), so nothing here follows the type
367/// graph. What does grow with the IDL is the dispatch chain: a union's
368/// cases, an enumeration's enumerators and a `@mutable` aggregate's
369/// members each become one `If` per arm, in the previous arm's `else`.
370/// The model bounds none of the three counts, and Java nests the arms in
371/// its own output too, so they cannot be walked iteratively the way
372/// `c.rs` walks them. No number is claimed: this signature returns no
373/// error, so nothing here could report one.
374fn render_statement(w: &mut Writer, node: &Statement) {
375    match node {
376        Statement::Declare {
377            final_,
378            type_,
379            name,
380            value,
381        } => {
382            let mut line = String::new();
383            if *final_ {
384                line.push_str("final ");
385            }
386            line.push_str(&type_text(type_));
387            line.push(' ');
388            line.push_str(name.as_str());
389            if let Some(value) = value {
390                line.push_str(" = ");
391                line.push_str(&expr_text(value));
392            }
393            line.push(';');
394            w.line(&line);
395        }
396        Statement::Expr(value) => {
397            let mut line = expr_text(value);
398            line.push(';');
399            w.line(&line);
400        }
401        Statement::Return(value) => {
402            let mut line = String::from("return");
403            if let Some(value) = value {
404                line.push(' ');
405                line.push_str(&expr_text(value));
406            }
407            line.push(';');
408            w.line(&line);
409        }
410        Statement::If {
411            condition,
412            then,
413            otherwise,
414        } => {
415            let mut head = String::from("if (");
416            head.push_str(&expr_text(condition));
417            head.push_str(") {");
418            w.line(&head);
419            w.indent();
420            render_block(w, then);
421            w.dedent();
422            match otherwise {
423                Some(block) => {
424                    w.line("} else {");
425                    w.indent();
426                    render_block(w, block);
427                    w.dedent();
428                    w.line("}");
429                }
430                None => w.line("}"),
431            }
432        }
433        Statement::For {
434            init,
435            condition,
436            step,
437            body,
438        } => {
439            let mut head = String::from("for (");
440            head.push_str(statement_inline(init).trim_end_matches(';'));
441            head.push_str("; ");
442            head.push_str(&expr_text(condition));
443            head.push_str("; ");
444            head.push_str(&expr_text(step));
445            head.push_str(") {");
446            w.line(&head);
447            w.indent();
448            render_block(w, body);
449            w.dedent();
450            w.line("}");
451        }
452        Statement::While { condition, body } => {
453            let mut head = String::from("while (");
454            head.push_str(&expr_text(condition));
455            head.push_str(") {");
456            w.line(&head);
457            w.indent();
458            render_block(w, body);
459            w.dedent();
460            w.line("}");
461        }
462        Statement::Break => w.line("break;"),
463        Statement::ForEach {
464            type_,
465            binding,
466            range,
467            body,
468        } => {
469            let mut head = String::from("for (");
470            head.push_str(&type_text(type_));
471            head.push(' ');
472            head.push_str(binding.as_str());
473            head.push_str(" : ");
474            head.push_str(&expr_text(range));
475            head.push_str(") {");
476            w.line(&head);
477            w.indent();
478            render_block(w, body);
479            w.dedent();
480            w.line("}");
481        }
482        Statement::Switch { value, cases } => {
483            let mut head = String::from("switch (");
484            head.push_str(&expr_text(value));
485            head.push_str(") {");
486            w.line(&head);
487            for case in cases {
488                render_case(w, case);
489            }
490            w.line("}");
491        }
492        Statement::Throw(value) => {
493            let mut line = String::from("throw ");
494            line.push_str(&expr_text(value));
495            line.push(';');
496            w.line(&line);
497        }
498        Statement::Comment(c) => line_comment(w, c),
499    }
500}
501
502fn render_case(w: &mut Writer, case: &SwitchCase) {
503    if case.labels.is_empty() {
504        w.line("default:");
505    } else {
506        for label in &case.labels {
507            let mut line = String::from("case ");
508            line.push_str(&expr_text(label));
509            line.push(':');
510            w.line(&line);
511        }
512    }
513    w.indent();
514    render_block(w, &case.body);
515    if case.breaks {
516        w.line("break;");
517    }
518    w.dedent();
519}
520
521fn modifiers_text(modifiers: &[Modifier]) -> String {
522    let mut out = String::new();
523    for modifier in modifiers {
524        out.push_str(modifier.as_str());
525        out.push(' ');
526    }
527    out
528}
529
530fn annotations(w: &mut Writer, annotations: &[Annotation]) {
531    for annotation in annotations {
532        w.line(&annotation_text(annotation));
533    }
534}
535
536/// zerodds-lint: recursion-depth = the built expression's nesting; an
537/// annotation value nests only as deep as the IDL's annotation application,
538/// and `expr_text` above states why no number is claimed.
539fn annotation_text(annotation: &Annotation) -> String {
540    let mut out = String::from("@");
541    out.push_str(&path_text(&annotation.path));
542    if !annotation.args.is_empty() {
543        out.push('(');
544        let parts: Vec<String> = annotation
545            .args
546            .iter()
547            .map(|arg| match arg {
548                AnnotationArg::Value(value) => expr_text(value),
549                AnnotationArg::Pair { name, value } => {
550                    let mut text = name.as_str().to_string();
551                    text.push_str(" = ");
552                    text.push_str(&expr_text(value));
553                    text
554                }
555            })
556            .collect();
557        out.push_str(&parts.join(", "));
558        out.push(')');
559    }
560    out
561}
562
563fn generics_text(params: &[TypeParam]) -> String {
564    if params.is_empty() {
565        return String::new();
566    }
567    let mut out = String::from("<");
568    let parts: Vec<String> = params
569        .iter()
570        .map(|param| {
571            let mut text = param.name.as_str().to_string();
572            if !param.bounds.is_empty() {
573                text.push_str(" extends ");
574                let bounds: Vec<String> = param.bounds.iter().map(type_text).collect();
575                text.push_str(&bounds.join(" & "));
576            }
577            text
578        })
579        .collect();
580    out.push_str(&parts.join(", "));
581    out.push('>');
582    out
583}
584
585fn params_text(params: &[Param]) -> String {
586    let parts: Vec<String> = params
587        .iter()
588        .map(|param| {
589            let mut text = String::new();
590            for annotation in &param.annotations {
591                text.push_str(&annotation_text(annotation));
592                text.push(' ');
593            }
594            if param.final_ {
595                text.push_str("final ");
596            }
597            text.push_str(&type_text(&param.type_));
598            if param.varargs {
599                text.push_str("...");
600            }
601            text.push(' ');
602            text.push_str(param.name.as_str());
603            text
604        })
605        .collect();
606    parts.join(", ")
607}
608
609fn path_text(path: &Path) -> String {
610    let parts: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
611    parts.join(".")
612}
613
614/// zerodds-lint: recursion-depth 64 (one level per collection, alias or
615/// generic argument of the spelled type; `MAX_TYPE_DEPTH` in
616/// `lang-rules/src/adapter/java.rs` refuses a type that nests deeper)
617fn type_text(node: &Type) -> String {
618    match node {
619        Type::Primitive(primitive) => primitive.as_str().to_string(),
620        Type::Named { path, args } => {
621            let mut out = path_text(path);
622            if !args.is_empty() {
623                out.push('<');
624                let parts: Vec<String> = args.iter().map(type_text).collect();
625                out.push_str(&parts.join(", "));
626                out.push('>');
627            }
628            out
629        }
630        Type::Array(element) => {
631            let mut out = type_text(element);
632            out.push_str("[]");
633            out
634        }
635        Type::Wildcard { extends, super_ } => {
636            let mut out = String::from("?");
637            if let Some(bound) = extends {
638                out.push_str(" extends ");
639                out.push_str(&type_text(bound));
640            } else if let Some(bound) = super_ {
641                out.push_str(" super ");
642                out.push_str(&type_text(bound));
643            }
644            out
645        }
646        Type::Void => "void".to_string(),
647        Type::Var => "var".to_string(),
648    }
649}
650
651/// zerodds-lint: recursion-depth = the built expression's nesting. A walk
652/// calls a routine per type rather than nesting one, so the one operand
653/// tree that grows with the IDL is an array's default value: one composite
654/// literal per dimension, built in `lang-rules/src/default_java.rs`, and
655/// nothing bounds the dimension count. No number is claimed: this
656/// signature returns no error.
657#[allow(clippy::too_many_lines)]
658fn expr_text(node: &Expr) -> String {
659    match node {
660        Expr::Literal(literal) => literal_text(literal),
661        Expr::Path(path) => path_text(path),
662        Expr::MethodCall {
663            receiver,
664            name,
665            args,
666        } => {
667            let mut out = String::new();
668            if let Some(receiver) = receiver {
669                out.push_str(&expr_text(receiver));
670                out.push('.');
671            }
672            out.push_str(name.as_str());
673            out.push('(');
674            let parts: Vec<String> = args.iter().map(expr_text).collect();
675            out.push_str(&parts.join(", "));
676            out.push(')');
677            out
678        }
679        Expr::Field { value, name } => {
680            let mut out = expr_text(value);
681            out.push('.');
682            out.push_str(name.as_str());
683            out
684        }
685        Expr::Index { value, index } => {
686            let mut out = expr_text(value);
687            out.push('[');
688            out.push_str(&expr_text(index));
689            out.push(']');
690            out
691        }
692        Expr::New { type_, args } => {
693            let mut out = String::from("new ");
694            out.push_str(&type_text(type_));
695            out.push('(');
696            let parts: Vec<String> = args.iter().map(expr_text).collect();
697            out.push_str(&parts.join(", "));
698            out.push(')');
699            out
700        }
701        Expr::NewArray {
702            element,
703            lengths,
704            elements,
705        } => {
706            // The element's own brackets go behind the sized ones:
707            // `new int[n][]` is `n` rows of `int[]`, and `new int[][n]`
708            // is not a form Java has.
709            let mut base = element.as_ref();
710            let mut trailing = 0;
711            while let Type::Array(inner) = base {
712                base = inner;
713                trailing += 1;
714            }
715            if lengths.is_empty() {
716                let mut out = String::from("new ");
717                out.push_str(&type_text(element));
718                out.push_str("[]{");
719                let parts: Vec<String> = elements.iter().map(expr_text).collect();
720                out.push_str(&parts.join(", "));
721                out.push('}');
722                return out;
723            }
724            let mut out = String::from("new ");
725            out.push_str(&type_text(base));
726            for length in lengths {
727                out.push('[');
728                out.push_str(&expr_text(length));
729                out.push(']');
730            }
731            for _ in 0..trailing {
732                out.push_str("[]");
733            }
734            out
735        }
736        Expr::Binary { op, lhs, rhs } => {
737            let mut out = expr_text(lhs);
738            out.push(' ');
739            out.push_str(op);
740            out.push(' ');
741            out.push_str(&expr_text(rhs));
742            out
743        }
744        Expr::Paren(value) => {
745            let mut out = String::from("(");
746            out.push_str(&expr_text(value));
747            out.push(')');
748            out
749        }
750        Expr::Cast { type_, value } => {
751            let mut out = String::from("(");
752            out.push_str(&type_text(type_));
753            out.push_str(") ");
754            out.push_str(&expr_text(value));
755            out
756        }
757        Expr::InstanceOf { value, type_ } => {
758            let mut out = expr_text(value);
759            out.push_str(" instanceof ");
760            out.push_str(&type_text(type_));
761            out
762        }
763        Expr::Ternary {
764            condition,
765            then,
766            otherwise,
767        } => {
768            let mut out = expr_text(condition);
769            out.push_str(" ? ");
770            out.push_str(&expr_text(then));
771            out.push_str(" : ");
772            out.push_str(&expr_text(otherwise));
773            out
774        }
775        Expr::This => "this".to_string(),
776        Expr::Lambda { params, body } => {
777            let mut out = String::from("(");
778            let parts: Vec<&str> = params.iter().map(Ident::as_str).collect();
779            out.push_str(&parts.join(", "));
780            out.push_str(") -> ");
781            out.push_str(&expr_text(body));
782            out
783        }
784        Expr::Annotation(annotation) => annotation_text(annotation),
785        Expr::ArrayInit(elements) => {
786            let parts: Vec<String> = elements.iter().map(expr_text).collect();
787            let mut out = String::from("{");
788            out.push_str(&parts.join(", "));
789            out.push('}');
790            out
791        }
792    }
793}
794
795fn literal_text(literal: &Literal) -> String {
796    match literal {
797        Literal::Int(text) | Literal::Float(text) => text.clone(),
798        Literal::Bool(value) => {
799            if *value {
800                "true".to_string()
801            } else {
802                "false".to_string()
803            }
804        }
805        Literal::Char(ch) => {
806            let mut out = String::from("'");
807            out.push_str(&escape_char(*ch));
808            out.push('\'');
809            out
810        }
811        Literal::Str(text) => {
812            let mut out = String::from("\"");
813            out.push_str(&escape_string(text));
814            out.push('"');
815            out
816        }
817        Literal::Bytes(bytes) => {
818            let mut out = String::from("new byte[]{");
819            let parts: Vec<String> = bytes
820                .iter()
821                .map(|b| alloc::format!("(byte) 0x{b:02X}"))
822                .collect();
823            out.push_str(&parts.join(", "));
824            out.push('}');
825            out
826        }
827        Literal::Null => "null".to_string(),
828    }
829}
830
831fn doc(w: &mut Writer, doc: &Doc) {
832    if doc.is_empty() {
833        return;
834    }
835    // Javadoc, which is what the convention and the tooling expect.
836    w.line("/**");
837    comment_lines(w, " * ", doc.as_lines());
838    w.line(" */");
839}
840
841/// A comment that documents no declaration after it โ€” what is left of a
842/// typedef or a native type, a note between statements โ€” as line comments.
843/// Javadoc there is a documentation comment attached to nothing, which
844/// `javac -Xlint` reports as `dangling-doc-comments` (JDK 23 on) and
845/// `-Werror` refuses.
846fn line_comment(w: &mut Writer, doc: &Doc) {
847    if doc.is_empty() {
848        return;
849    }
850    comment_lines(w, "// ", doc.as_lines());
851}
852
853/// A statement as it appears inside a `for` clause: no semicolon, no
854/// line of its own.
855///
856/// Only the two forms a `for` header can hold. Anything else returns
857/// nothing rather than a half-rendered statement, which would put a
858/// silently empty initialiser in generated source.
859fn statement_inline(node: &Statement) -> String {
860    match node {
861        Statement::Declare {
862            final_,
863            type_,
864            name,
865            value,
866        } => {
867            let mut out = String::new();
868            if *final_ {
869                out.push_str("final ");
870            }
871            out.push_str(&type_text(type_));
872            out.push(' ');
873            out.push_str(name.as_str());
874            if let Some(value) = value {
875                out.push_str(" = ");
876                out.push_str(&expr_text(value));
877            }
878            out
879        }
880        Statement::Expr(value) => expr_text(value),
881        _ => String::new(),
882    }
883}