Skip to main content

gen_bazel/
render.rs

1//! Canonical pretty-printer for the Starlark emission AST.
2
3use crate::ast::{KwArg, StarlarkStmt, StarlarkValue};
4
5/// Render a sequence of statements to a BUILD/MODULE file body.
6pub fn render_file(stmts: &[StarlarkStmt]) -> String {
7    let mut out = String::new();
8    for (i, stmt) in stmts.iter().enumerate() {
9        if i > 0 {
10            out.push('\n');
11        }
12        render_stmt(&mut out, stmt);
13        out.push('\n');
14    }
15    out
16}
17
18fn render_stmt(out: &mut String, stmt: &StarlarkStmt) {
19    match stmt {
20        StarlarkStmt::Load { module, symbols } => {
21            out.push_str("load(\"");
22            out.push_str(module);
23            out.push('"');
24            for s in symbols {
25                out.push_str(", \"");
26                out.push_str(s);
27                out.push('"');
28            }
29            out.push(')');
30        }
31        StarlarkStmt::Call { func, args } => {
32            render_call(out, func, args, 0);
33        }
34        StarlarkStmt::Assign { name, value } => {
35            out.push_str(name);
36            out.push_str(" = ");
37            render_value(out, value, 0);
38        }
39    }
40}
41
42fn render_call(out: &mut String, func: &str, args: &[KwArg], indent: usize) {
43    out.push_str(func);
44    out.push('(');
45    if args.is_empty() {
46        out.push(')');
47        return;
48    }
49    let multi = args.len() > 2 || args_have_multiline(args);
50    if multi {
51        for arg in args {
52            out.push('\n');
53            push_indent(out, indent + 1);
54            render_kwarg(out, arg, indent + 1);
55            out.push(',');
56        }
57        out.push('\n');
58        push_indent(out, indent);
59    } else {
60        for (i, arg) in args.iter().enumerate() {
61            if i > 0 {
62                out.push_str(", ");
63            }
64            render_kwarg(out, arg, indent);
65        }
66    }
67    out.push(')');
68}
69
70fn render_kwarg(out: &mut String, arg: &KwArg, indent: usize) {
71    match arg {
72        KwArg::Positional(v) => render_value(out, v, indent),
73        KwArg::Named { name, value } => {
74            out.push_str(name);
75            out.push_str(" = ");
76            render_value(out, value, indent);
77        }
78    }
79}
80
81fn render_value(out: &mut String, v: &StarlarkValue, indent: usize) {
82    match v {
83        StarlarkValue::None => out.push_str("None"),
84        StarlarkValue::Bool(true) => out.push_str("True"),
85        StarlarkValue::Bool(false) => out.push_str("False"),
86        StarlarkValue::Int(i) => out.push_str(&i.to_string()),
87        StarlarkValue::Str(s) => {
88            out.push('"');
89            for c in s.chars() {
90                match c {
91                    '"' => out.push_str("\\\""),
92                    '\\' => out.push_str("\\\\"),
93                    '\n' => out.push_str("\\n"),
94                    c => out.push(c),
95                }
96            }
97            out.push('"');
98        }
99        StarlarkValue::Ident(s) => out.push_str(s),
100        StarlarkValue::List(items) => render_list(out, items, indent),
101        StarlarkValue::Dict(entries) => render_dict(out, entries, indent),
102        StarlarkValue::Call { func, args } => render_call(out, func, args, indent),
103    }
104}
105
106fn render_list(out: &mut String, items: &[StarlarkValue], indent: usize) {
107    if items.is_empty() {
108        out.push_str("[]");
109        return;
110    }
111    let multi = items.len() > 3 || any_value_multiline(items);
112    out.push('[');
113    if multi {
114        for v in items {
115            out.push('\n');
116            push_indent(out, indent + 1);
117            render_value(out, v, indent + 1);
118            out.push(',');
119        }
120        out.push('\n');
121        push_indent(out, indent);
122    } else {
123        for (i, v) in items.iter().enumerate() {
124            if i > 0 {
125                out.push_str(", ");
126            }
127            render_value(out, v, indent);
128        }
129    }
130    out.push(']');
131}
132
133fn render_dict(out: &mut String, entries: &[(String, StarlarkValue)], indent: usize) {
134    if entries.is_empty() {
135        out.push_str("{}");
136        return;
137    }
138    out.push('{');
139    for (k, v) in entries {
140        out.push('\n');
141        push_indent(out, indent + 1);
142        out.push('"');
143        out.push_str(k);
144        out.push_str("\": ");
145        render_value(out, v, indent + 1);
146        out.push(',');
147    }
148    out.push('\n');
149    push_indent(out, indent);
150    out.push('}');
151}
152
153fn args_have_multiline(args: &[KwArg]) -> bool {
154    args.iter().any(|a| match a {
155        KwArg::Positional(v) | KwArg::Named { value: v, .. } => is_value_multiline(v),
156    })
157}
158
159fn any_value_multiline(items: &[StarlarkValue]) -> bool {
160    items.iter().any(is_value_multiline)
161}
162
163fn is_value_multiline(v: &StarlarkValue) -> bool {
164    match v {
165        StarlarkValue::List(items) => items.len() > 3 || any_value_multiline(items),
166        StarlarkValue::Dict(_) => true,
167        StarlarkValue::Call { args, .. } => args.len() > 2 || args_have_multiline(args),
168        _ => false,
169    }
170}
171
172fn push_indent(out: &mut String, level: usize) {
173    for _ in 0..level {
174        out.push_str("    ");
175    }
176}