Skip to main content

zerodds_lang_render/
c.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Rendering the C CST. Spec §5.
4//!
5//! One style, the one the emitters this replaces wrote: four spaces,
6//! the opening brace on the same line, `#include`s grouped as the tree
7//! holds them.
8
9use crate::writer::{Writer, comment_lines, escape_char, escape_string, verbatim_lines};
10use alloc::format;
11use alloc::string::{String, ToString};
12use alloc::vec::Vec;
13use zerodds_lang_cst::c::{
14    Block, Decl, Define, Enum, Enumerator, Expr, File, Function, Include, Member, Param, Statement,
15    Struct, Type, Typedef, Union, Variable,
16};
17use zerodds_lang_cst::common::{Doc, Ident, Literal};
18
19/// Render a whole file.
20#[must_use]
21pub fn file(file: &File) -> String {
22    let mut w = Writer::spaces4();
23    doc(&mut w, &file.doc);
24    if let Some(guard) = &file.guard {
25        let mut line = String::from("#ifndef ");
26        line.push_str(guard.as_str());
27        w.line(&line);
28        let mut define = String::from("#define ");
29        define.push_str(guard.as_str());
30        w.line(&define);
31        w.blank_line();
32    }
33    for include in &file.includes {
34        w.line(&include_text(include));
35    }
36    if !file.includes.is_empty() {
37        w.blank_line();
38    }
39    for (index, decl) in file.decls.iter().enumerate() {
40        if index > 0 {
41            w.blank_line();
42        }
43        render_decl(&mut w, decl);
44    }
45    if let Some(guard) = &file.guard {
46        w.blank_line();
47        let mut line = String::from("#endif /* ");
48        line.push_str(guard.as_str());
49        line.push_str(" */");
50        w.line(&line);
51    }
52    w.finish()
53}
54
55/// Render one declaration, for a caller that has only one.
56#[must_use]
57pub fn decl(node: &Decl) -> String {
58    let mut w = Writer::spaces4();
59    render_decl(&mut w, node);
60    w.finish()
61}
62
63/// Render one type, for a caller that has only one.
64#[must_use]
65pub fn type_(node: &Type) -> String {
66    type_text(node)
67}
68
69/// One expression, as it would stand inside a statement.
70///
71/// For a generator that writes a statement around an expression a rule
72/// produced — a union's case label, spelled the way the codec spells it.
73#[must_use]
74pub fn expr(node: &Expr) -> String {
75    expr_text(node)
76}
77
78/// An `#include` line; the C++ renderer writes the same one.
79pub(crate) fn include_text(include: &Include) -> String {
80    let mut out = String::from("#include ");
81    if include.system {
82        out.push('<');
83        out.push_str(&include.path);
84        out.push('>');
85    } else {
86        out.push('"');
87        out.push_str(&include.path);
88        out.push('"');
89    }
90    out
91}
92
93fn render_decl(w: &mut Writer, node: &Decl) {
94    match node {
95        Decl::Struct(s) => render_struct(w, s),
96        Decl::Union(u) => render_union(w, u),
97        Decl::Enum(e) => render_enum(w, e),
98        Decl::Typedef(t) => render_typedef(w, t),
99        Decl::Function(f) => render_function(w, f),
100        Decl::Variable(v) => render_variable(w, v),
101        Decl::Define(d) => render_define(w, d),
102        Decl::Comment(c) => doc(w, c),
103        Decl::Verbatim(text) => verbatim_lines(w, text),
104    }
105}
106
107fn render_struct(w: &mut Writer, node: &Struct) {
108    doc(w, &node.doc);
109    let mut head = String::from("struct");
110    if let Some(name) = &node.name {
111        head.push(' ');
112        head.push_str(name.as_str());
113    }
114    head.push_str(" {");
115    w.line(&head);
116    w.indent();
117    for text in &node.body_text.begin {
118        verbatim_lines(w, text);
119    }
120    for member in &node.members {
121        render_member(w, member);
122    }
123    for text in &node.body_text.end {
124        verbatim_lines(w, text);
125    }
126    w.dedent();
127    w.line("};");
128}
129
130fn render_union(w: &mut Writer, node: &Union) {
131    doc(w, &node.doc);
132    let mut head = String::from("union");
133    if let Some(name) = &node.name {
134        head.push(' ');
135        head.push_str(name.as_str());
136    }
137    head.push_str(" {");
138    w.line(&head);
139    w.indent();
140    for member in &node.members {
141        render_member(w, member);
142    }
143    w.dedent();
144    w.line("};");
145}
146
147fn render_member(w: &mut Writer, node: &Member) {
148    doc(w, &node.doc);
149    let mut line = declarator_text(&node.type_, node.name.as_str(), &node.dims);
150    if let Some(bits) = node.bits {
151        line.push_str(" : ");
152        line.push_str(&alloc::format!("{bits}"));
153    }
154    line.push(';');
155    w.line(&line);
156}
157
158/// `int32_t name[2][3]` — the dimensions belong to the declarator, not
159/// to the type, which is what makes C's declaration syntax what it is.
160fn declarator_text(type_: &Type, name: &str, dims: &[Expr]) -> String {
161    let mut out = type_text(type_);
162    if !out.ends_with('*') {
163        out.push(' ');
164    }
165    out.push_str(name);
166    for dim in dims {
167        out.push('[');
168        out.push_str(&expr_text(dim));
169        out.push(']');
170    }
171    out
172}
173
174fn render_enum(w: &mut Writer, node: &Enum) {
175    doc(w, &node.doc);
176    let mut head = String::from("enum");
177    if let Some(name) = &node.name {
178        head.push(' ');
179        head.push_str(name.as_str());
180    }
181    head.push_str(" {");
182    w.line(&head);
183    w.indent();
184    for (index, enumerator) in node.enumerators.iter().enumerate() {
185        render_enumerator(w, enumerator, index + 1 == node.enumerators.len());
186    }
187    w.dedent();
188    w.line("};");
189}
190
191fn render_enumerator(w: &mut Writer, node: &Enumerator, last: bool) {
192    doc(w, &node.doc);
193    let mut line = String::from(node.name.as_str());
194    if let Some(value) = &node.value {
195        line.push_str(" = ");
196        line.push_str(&expr_text(value));
197    }
198    if !last {
199        line.push(',');
200    }
201    w.line(&line);
202}
203
204fn render_typedef(w: &mut Writer, node: &Typedef) {
205    doc(w, &node.doc);
206    let mut line = String::from("typedef ");
207    line.push_str(&declarator_text(
208        &node.type_,
209        node.name.as_str(),
210        &node.dims,
211    ));
212    line.push(';');
213    w.line(&line);
214}
215
216fn render_function(w: &mut Writer, node: &Function) {
217    doc(w, &node.doc);
218    let mut head = String::new();
219    if node.is_static {
220        head.push_str("static ");
221    }
222    if node.is_inline {
223        head.push_str("inline ");
224    }
225    // Ahead of the return type, where GCC and Clang both take it on a
226    // definition. See `c::Function::is_maybe_unused` for the one case
227    // that sets it.
228    if node.is_maybe_unused {
229        head.push_str("__attribute__((unused)) ");
230    }
231    head.push_str(&type_text(&node.returns));
232    if !head.ends_with('*') {
233        head.push(' ');
234    }
235    head.push_str(node.name.as_str());
236    head.push('(');
237    head.push_str(&params_text(&node.params));
238    head.push(')');
239    match &node.body {
240        None => {
241            head.push(';');
242            w.line(&head);
243        }
244        Some(block) => {
245            head.push_str(" {");
246            w.line(&head);
247            w.indent();
248            render_block(w, block);
249            w.dedent();
250            w.line("}");
251        }
252    }
253}
254
255/// `void` for an empty parameter list: in C an empty `()` means
256/// "unspecified", which is not what a generated prototype states.
257fn params_text(params: &[Param]) -> String {
258    if params.is_empty() {
259        return "void".to_string();
260    }
261    let parts: Vec<String> = params
262        .iter()
263        .map(|param| match &param.name {
264            Some(name) => declarator_text(&param.type_, name.as_str(), &[]),
265            None => type_text(&param.type_),
266        })
267        .collect();
268    parts.join(", ")
269}
270
271fn render_variable(w: &mut Writer, node: &Variable) {
272    doc(w, &node.doc);
273    let mut line = String::new();
274    if node.is_static {
275        line.push_str("static ");
276    }
277    if node.is_const {
278        line.push_str("const ");
279    }
280    line.push_str(&declarator_text(&node.type_, node.name.as_str(), &[]));
281    if let Some(value) = &node.value {
282        line.push_str(" = ");
283        line.push_str(&expr_text(value));
284    }
285    line.push(';');
286    w.line(&line);
287}
288
289fn render_define(w: &mut Writer, node: &Define) {
290    doc(w, &node.doc);
291    let mut line = String::from("#define ");
292    line.push_str(node.name.as_str());
293    if let Some(value) = &node.value {
294        line.push(' ');
295        line.push_str(&expr_text(value));
296    }
297    w.line(&line);
298}
299
300/// zerodds-lint: recursion-depth 64 (one level per nested block of a
301/// generated body. The codec walk emits a routine per type rather than
302/// inlining one — `lang-rules/src/emit/encode.rs` — so a body nests by
303/// its own framing and by one loop per array dimension, never by the
304/// type graph, whose descent stops at 64 anyway; the else-if chain, one
305/// arm per union case, is walked iteratively and costs no depth.)
306fn render_block(w: &mut Writer, block: &Block) {
307    for statement in &block.statements {
308        render_statement(w, statement);
309    }
310}
311
312/// An `if` and its `else`, with a lone nested `if` written as `else if`.
313///
314/// The tree has no `else if` node — an else branch is a block, and a
315/// chain is a block holding one `if`. Rendering that literally nests one
316/// pair of braces per arm, which is correct C and is also a cliff: a
317/// `@mutable` aggregate's decoder dispatches on the member id with one
318/// arm per member, and C99 §5.2.4.1 requires a compiler to support only
319/// 127 levels of nested compound statement. A hundred-and-thirty-member
320/// type would have generated a file no conforming compiler must accept.
321/// Flattening the chain costs one comparison here and removes the
322/// limit.
323///
324/// The arm is taken in a loop and not by a call to this function again.
325/// The chain's length is the union's case count, the enumeration's
326/// enumerator count or the `@mutable` aggregate's member count — the
327/// walk in `lang-rules/src/emit/encode.rs` builds one `If` per arm, each
328/// in the previous one's `else` — and nothing in the model bounds any of
329/// the three. Recursing once per arm would have made the renderer's own
330/// stack the limit on how many cases a union may have.
331///
332/// zerodds-lint: recursion-depth 64 (block nesting only; the arm chain
333/// is walked iteratively, and what remains is one level per nested block
334/// of a generated body — the codec walk emits a routine per type rather
335/// than inlining, so a body nests by its framing and by one loop per
336/// array dimension, never by the type graph)
337fn render_if_chain(w: &mut Writer, node: &Statement, opener: &str) {
338    let mut node = node;
339    let mut opener = opener;
340    loop {
341        let Statement::If {
342            condition,
343            then,
344            otherwise,
345        } = node
346        else {
347            render_statement(w, node);
348            return;
349        };
350        let mut head = String::from(opener);
351        head.push_str(&expr_text(condition));
352        head.push_str(") {");
353        w.line(&head);
354        w.indent();
355        render_block(w, then);
356        w.dedent();
357        match otherwise {
358            Some(block) => match block.statements.as_slice() {
359                [nested @ Statement::If { .. }] => {
360                    node = nested;
361                    opener = "} else if (";
362                }
363                _ => {
364                    w.line("} else {");
365                    w.indent();
366                    render_block(w, block);
367                    w.dedent();
368                    w.line("}");
369                    return;
370                }
371            },
372            None => {
373                w.line("}");
374                return;
375            }
376        }
377    }
378}
379
380/// zerodds-lint: recursion-depth 64 (one level per nested block of a
381/// generated body. The codec walk emits a routine per type rather than
382/// inlining one — `lang-rules/src/emit/encode.rs` — so a body nests by
383/// its own framing and by one loop per array dimension, never by the
384/// type graph, whose descent stops at 64 anyway; the else-if chain, one
385/// arm per union case, is walked iteratively and costs no depth.)
386fn render_statement(w: &mut Writer, node: &Statement) {
387    match node {
388        Statement::Declare { type_, name, value } => {
389            let mut line = declarator_text(type_, name.as_str(), &[]);
390            if let Some(value) = value {
391                line.push_str(" = ");
392                line.push_str(&expr_text(value));
393            }
394            line.push(';');
395            w.line(&line);
396        }
397        Statement::Expr(value) => {
398            let mut line = expr_text(value);
399            line.push(';');
400            w.line(&line);
401        }
402        Statement::Return(value) => {
403            let mut line = String::from("return");
404            if let Some(value) = value {
405                line.push(' ');
406                line.push_str(&expr_text(value));
407            }
408            line.push(';');
409            w.line(&line);
410        }
411        Statement::If { .. } => render_if_chain(w, node, "if ("),
412        Statement::For {
413            init,
414            condition,
415            step,
416            body,
417        } => {
418            let mut head = String::from("for (");
419            head.push_str(statement_inline(init).trim_end_matches(';'));
420            head.push_str("; ");
421            head.push_str(&expr_text(condition));
422            head.push_str("; ");
423            head.push_str(&expr_text(step));
424            head.push_str(") {");
425            w.line(&head);
426            w.indent();
427            render_block(w, body);
428            w.dedent();
429            w.line("}");
430        }
431        Statement::While { condition, body } => {
432            let mut head = String::from("while (");
433            head.push_str(&expr_text(condition));
434            head.push_str(") {");
435            w.line(&head);
436            w.indent();
437            render_block(w, body);
438            w.dedent();
439            w.line("}");
440        }
441        Statement::Break => w.line("break;"),
442        Statement::Comment(c) => doc(w, c),
443    }
444}
445
446/// A statement written on one line, for a `for` header.
447fn statement_inline(node: &Statement) -> String {
448    match node {
449        Statement::Declare { type_, name, value } => {
450            let mut out = declarator_text(type_, name.as_str(), &[]);
451            if let Some(value) = value {
452                out.push_str(" = ");
453                out.push_str(&expr_text(value));
454            }
455            out
456        }
457        Statement::Expr(value) => expr_text(value),
458        _ => String::new(),
459    }
460}
461
462/// zerodds-lint: recursion-depth 3 (`const T *`, and no deeper. The C
463/// materialiser gives every collection and every aggregate a `typedef`
464/// of its own (`lang-rules/src/adapter/c.rs`), so a `Type` here is a tag
465/// or a primitive under at most a `const` and a pointer; the IDL's own
466/// nesting never reaches this walk.)
467fn type_text(node: &Type) -> String {
468    match node {
469        Type::Primitive(primitive) => primitive.as_str().to_string(),
470        Type::Named(name) => name.as_str().to_string(),
471        Type::StructTag(name) => {
472            let mut out = String::from("struct ");
473            out.push_str(name.as_str());
474            out
475        }
476        Type::UnionTag(name) => {
477            let mut out = String::from("union ");
478            out.push_str(name.as_str());
479            out
480        }
481        Type::EnumTag(name) => {
482            let mut out = String::from("enum ");
483            out.push_str(name.as_str());
484            out
485        }
486        Type::Pointer(target) => {
487            let mut out = type_text(target);
488            if !out.ends_with('*') {
489                out.push(' ');
490            }
491            out.push('*');
492            out
493        }
494        Type::Const(target) => {
495            let mut out = String::from("const ");
496            out.push_str(&type_text(target));
497            out
498        }
499        Type::Void => "void".to_string(),
500    }
501}
502
503/// zerodds-lint: recursion-depth = the built expression's nesting. A walk
504/// calls a routine per type rather than nesting one, so the one operand
505/// tree that grows with the IDL is an array's default value: one composite
506/// literal per dimension, built in `lang-rules/src/default_c.rs`, and
507/// nothing bounds the dimension count. No number is claimed: this
508/// signature returns no error.
509fn expr_text(node: &Expr) -> String {
510    match node {
511        Expr::Literal(literal) => literal_text(literal),
512        Expr::Name(name) => name.as_str().to_string(),
513        Expr::Call { callee, args } => {
514            let mut out = expr_text(callee);
515            out.push('(');
516            let parts: Vec<String> = args.iter().map(expr_text).collect();
517            out.push_str(&parts.join(", "));
518            out.push(')');
519            out
520        }
521        Expr::Member { value, name } => {
522            let mut out = expr_text(value);
523            out.push('.');
524            out.push_str(name.as_str());
525            out
526        }
527        Expr::Arrow { value, name } => {
528            let mut out = expr_text(value);
529            out.push_str("->");
530            out.push_str(name.as_str());
531            out
532        }
533        Expr::Index { value, index } => {
534            let mut out = expr_text(value);
535            out.push('[');
536            out.push_str(&expr_text(index));
537            out.push(']');
538            out
539        }
540        Expr::Unary { op, value } => {
541            let mut out = String::from(*op);
542            out.push_str(&expr_text(value));
543            out
544        }
545        Expr::AddressOf(value) => {
546            let mut out = String::from("&");
547            out.push_str(&expr_text(value));
548            out
549        }
550        Expr::Deref(value) => {
551            let mut out = String::from("*");
552            out.push_str(&expr_text(value));
553            out
554        }
555        Expr::Binary { op, lhs, rhs } => {
556            let mut out = expr_text(lhs);
557            out.push(' ');
558            out.push_str(op);
559            out.push(' ');
560            out.push_str(&expr_text(rhs));
561            out
562        }
563        Expr::Cast { type_, value } => {
564            let mut out = String::from("(");
565            out.push_str(&type_text(type_));
566            out.push(')');
567            out.push_str(&expr_text(value));
568            out
569        }
570        Expr::Paren(inner) => {
571            let mut out = String::from("(");
572            out.push_str(&expr_text(inner));
573            out.push(')');
574            out
575        }
576        Expr::SizeOf(type_) => {
577            let mut out = String::from("sizeof(");
578            out.push_str(&type_text(type_));
579            out.push(')');
580            out
581        }
582        Expr::Initializer(elements) => {
583            let mut out = String::from("{ ");
584            let parts: Vec<String> = elements.iter().map(expr_text).collect();
585            out.push_str(&parts.join(", "));
586            out.push_str(" }");
587            out
588        }
589    }
590}
591
592fn literal_text(literal: &Literal) -> String {
593    match literal {
594        Literal::Int(text) | Literal::Float(text) => text.clone(),
595        Literal::Bool(value) => {
596            if *value {
597                "true".to_string()
598            } else {
599                "false".to_string()
600            }
601        }
602        Literal::Char(ch) => {
603            let mut out = String::from("'");
604            out.push_str(&escape_char(*ch));
605            out.push('\'');
606            out
607        }
608        Literal::Str(text) => {
609            let mut out = String::from("\"");
610            out.push_str(&escape_string(text));
611            out.push('"');
612            out
613        }
614        Literal::Bytes(bytes) => {
615            let mut out = String::from("{ ");
616            let parts: Vec<String> = bytes.iter().map(|b| alloc::format!("0x{b:02X}")).collect();
617            out.push_str(&parts.join(", "));
618            out.push_str(" }");
619            out
620        }
621        Literal::Null => "NULL".to_string(),
622    }
623}
624
625fn doc(w: &mut Writer, doc: &Doc) {
626    if doc.is_empty() {
627        return;
628    }
629    // `//` rather than `/* */` per line: a block comment opened per
630    // line and never closed is not C, and closing it per line is what
631    // the emitters this replaces avoided by using the C99 form.
632    comment_lines(w, "// ", doc.as_lines());
633}
634
635/// The identifier a name renders as, for a caller assembling one.
636#[must_use]
637pub fn ident(name: &Ident) -> String {
638    name.as_str().to_string()
639}
640
641/// An integer as the source text of a C constant expression.
642///
643/// Every value but one is its own decimal. −2^63 is not a literal in C
644/// at all: C99 6.4.4.1 has no negative integer constant, so
645/// `-9223372036854775808` is a unary minus over the decimal constant
646/// `9223372036854775808`, and that constant fits no signed type in the
647/// list 6.4.4.1 gives it — the implementation may give it an extended
648/// unsigned type, and the negation that follows is then an
649/// implementation-defined conversion rather than the value the source
650/// wrote. Wrapping it in `INT64_C(..)` changes nothing: C99 7.18.4.1
651/// makes that macro paste a suffix onto the token it is handed, so the
652/// same too-large constant is what the minus applies to. Measured, gcc
653/// 16.1.0 answers "integer constant is so large that it is unsigned"
654/// and Apple clang 21.0.0 `-Wimplicitly-unsigned-literal`.
655///
656/// The subtraction `<stdint.h>` itself writes `INT64_MIN` as is exact
657/// and has no such constant in it. `crates/wire-probe/src/fill.rs`,
658/// `fill_ros.rs` and `fill_cpp.rs` already spell the value this way.
659#[must_use]
660pub fn integer_text(value: i128) -> String {
661    if value == i128::from(i64::MIN) {
662        return "(-9223372036854775807LL - 1)".to_string();
663    }
664    format!("{value}")
665}