1use crate::ast::{KwArg, StarlarkStmt, StarlarkValue};
4
5pub 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}