factorio_codegen/generator/
expression.rs1use std::fmt::Write as _;
2
3use factorio_ir::expression::Expression;
4
5use crate::LuaGenerator;
6
7fn prototype_lua_type(struct_name: &str) -> Option<&'static str> {
10 match struct_name {
11 "BoolSetting" => Some("bool-setting"),
12 "IntSetting" => Some("int-setting"),
13 "DoubleSetting" => Some("double-setting"),
14 "StringSetting" => Some("string-setting"),
15 _ => None,
16 }
17}
18
19impl LuaGenerator {
20 #[must_use]
21 pub fn generate_expression(&self, expression: &Expression) -> String {
22 self.generate_expression_prec(expression, 0)
23 }
24
25 pub(crate) fn generate_expression_prec(&self, expression: &Expression, min_prec: u8) -> String {
26 match expression {
27 Expression::BinaryOp { lhs, op, rhs } => {
28 let is_zero_lhs = match lhs.as_ref() {
31 Expression::Literal(factorio_ir::literal::Literal::Int(0)) => true,
32 Expression::Literal(factorio_ir::literal::Literal::Float(f)) => *f == 0.0,
33 _ => false,
34 };
35 if matches!(op, factorio_ir::operator::Operator::Sub) && is_zero_lhs {
36 let rhs_str = self.generate_expression_prec(rhs, 100);
37 let result = format!("-{rhs_str}");
38 return if 0 < min_prec {
39 format!("({result})")
40 } else {
41 result
42 };
43 }
44
45 let prec = Self::operator_precedence(*op);
46 let lhs_str = self.generate_expression_prec(lhs, prec);
47 let rhs_str = self.generate_expression_prec(rhs, prec.saturating_add(1));
48 let result = format!("{} {} {}", lhs_str, Self::generate_operator(*op), rhs_str);
49
50 if prec < min_prec {
51 format!("({result})")
52 } else {
53 result
54 }
55 }
56 _ => self.generate_atom(expression),
57 }
58 }
59
60 pub(crate) fn generate_atom(&self, expression: &Expression) -> String {
62 match expression {
63 Expression::Literal(literal) => Self::generate_literal(literal),
64 Expression::Identifier(name) => self.generate_identifier(name),
65 Expression::FieldAccess { base, field } => {
66 let base = self.generate_expression(base);
67 format!("{base}.{field}")
68 }
69 Expression::QualifiedPath { segments } => self.generate_qualified_path(segments),
70 Expression::Call { func, args } => self.generate_call(func, args),
71 Expression::MethodCall {
72 receiver,
73 method,
74 args,
75 } => self.generate_method_call(receiver, method, args),
76 Expression::StructLiteral {
77 struct_name,
78 fields,
79 } => self.generate_struct_literal(struct_name.as_deref(), fields),
80 Expression::EnumLiteral {
81 enum_name,
82 variant,
83 fields,
84 } => self.generate_enum_literal(enum_name, variant, fields),
85 Expression::FormatConcat { parts } => parts
86 .iter()
87 .map(|part| self.generate_expression(part))
88 .collect::<Vec<_>>()
89 .join(" .. "),
90 Expression::Array { elements } => {
91 let elements = elements
92 .iter()
93 .map(|element| self.generate_expression(element))
94 .collect::<Vec<_>>()
95 .join(", ");
96 format!("{{ {elements} }}")
97 }
98 Expression::Index { base, key } => self.generate_index(base, key),
99 Expression::Not(inner) => self.generate_not(inner),
100 Expression::Len(inner) => {
101 let inner = self.generate_expression(inner);
102 format!("#{inner}")
103 }
104 Expression::If {
105 condition,
106 then_expr,
107 else_expr,
108 } => self.generate_if_expr(condition, then_expr, else_expr),
109 Expression::Closure { params, body } => self.generate_closure(params, body),
110 Expression::BinaryOp { .. } => {
111 unreachable!("binary operators are handled by generate_expression_prec")
112 }
113 }
114 }
115
116 fn generate_identifier(&self, name: &str) -> String {
118 if self.exported_functions.contains(name)
119 && let Some(module_table) = &self.current_module_table
120 {
121 return format!("{module_table}.{name}");
122 }
123 name.to_string()
124 }
125
126 fn generate_qualified_path(&self, segments: &[String]) -> String {
127 if let Some((struct_name, table_path)) = &self.struct_table_context
128 && segments
129 .first()
130 .is_some_and(|segment| segment == struct_name)
131 {
132 let suffix = segments
133 .get(1..)
134 .map_or_else(String::new, |rest| rest.join("."));
135 if suffix.is_empty() {
136 return table_path.clone();
137 }
138 return format!("{table_path}.{suffix}");
139 }
140
141 segments.join(".")
142 }
143
144 fn generate_call(&self, func: &Expression, args: &[Expression]) -> String {
145 if let Expression::QualifiedPath { segments } = func
146 && args.is_empty()
147 && segments
148 .last()
149 .is_some_and(|s| s == "new" || s == "default")
150 {
151 match segments[0].as_str() {
152 "LuaAny" => return "nil".to_string(),
153 "Vec" if segments.last().is_some_and(|s| s == "new") => {
154 return "{}".to_string();
155 }
156 _ if segments.last().is_some_and(|s| s == "default") => {
157 return "{}".to_string();
158 }
159 _ => {}
160 }
161 }
162
163 let func_is_closure = matches!(func, Expression::Closure { .. });
164 let func = self.generate_expression(func);
165 let args = self.generate_arg_list(args);
166 if func_is_closure {
167 format!("({func})({args})")
168 } else {
169 format!("{func}({args})")
170 }
171 }
172
173 fn generate_method_call(
174 &self,
175 receiver: &Expression,
176 method: &str,
177 args: &[Expression],
178 ) -> String {
179 if method == "get" && args.len() == 1 && is_storage_receiver(receiver) {
182 let receiver = self.generate_expression(receiver);
183 let key = self.generate_expression(&args[0]);
184 return format!("{receiver}[{key}]");
185 }
186
187 if method == "get" && args.len() == 1 {
188 let receiver = self.generate_expression(receiver);
189 let key = self.generate_expression(&args[0]);
190 return format!("{receiver}[{key}].value");
191 }
192
193 if method == "set" && args.len() == 2 && is_storage_receiver(receiver) {
195 let receiver = self.generate_expression(receiver);
196 let key = self.generate_expression(&args[0]);
197 let value = self.generate_expression(&args[1]);
198 return format!("{receiver}[{key}] = {value}");
199 }
200
201 if method == "len" && args.is_empty() {
202 let receiver = self.generate_expression(receiver);
203 return format!("#{receiver}");
204 }
205
206 if method == "push" && args.len() == 1 {
207 let receiver = self.generate_expression(receiver);
208 let item = self.generate_expression(&args[0]);
209 return format!("table.insert({receiver}, {item})");
210 }
211
212 if method == "is_empty" && args.is_empty() {
213 let receiver = self.generate_expression(receiver);
214 return format!("#{receiver} == 0");
215 }
216
217 let trimmed = trim_trailing_nils(args);
218 if trimmed.is_empty() {
219 let receiver = self.generate_expression(receiver);
220 if args.is_empty() {
223 return format!("{receiver}.{method}");
224 }
225 return format!("{receiver}.{method}()");
226 }
227
228 let receiver = self.generate_expression(receiver);
229 let args_lua = self.generate_arg_list(trimmed);
230 format!("{receiver}.{method}({args_lua})")
231 }
232
233 fn generate_arg_list(&self, args: &[Expression]) -> String {
235 trim_trailing_nils(args)
236 .iter()
237 .map(|arg| self.generate_expression(arg))
238 .collect::<Vec<_>>()
239 .join(", ")
240 }
241
242 fn generate_struct_literal(
243 &self,
244 struct_name: Option<&str>,
245 fields: &[(String, Expression)],
246 ) -> String {
247 let injected_type = struct_name.and_then(prototype_lua_type);
248 let type_prefix = injected_type.map(|t| format!("type = \"{t}\", "));
249
250 let field_strs = fields
251 .iter()
252 .filter(|(name, value)| {
253 if matches!(
254 value,
255 Expression::Literal(factorio_ir::literal::Literal::Nil)
256 ) {
257 return false;
258 }
259 injected_type.is_none() || (name != "type" && name != "r#type")
260 })
261 .map(|(name, value)| {
262 let lua_name = if name == "r#type" {
263 "type"
264 } else {
265 name.as_str()
266 };
267 format!("{lua_name} = {}", self.generate_expression(value))
268 })
269 .collect::<Vec<_>>()
270 .join(", ");
271
272 let inner = match type_prefix {
273 Some(prefix) if !field_strs.is_empty() => format!("{prefix}{field_strs}"),
274 Some(prefix) => prefix.trim_end_matches(", ").to_string(),
275 None => field_strs,
276 };
277 let literal = format!("{{ {inner} }}");
278
279 if let Some((_, table_path)) = &self.struct_table_context {
280 format!("setmetatable({literal}, {{ __index = {table_path} }})")
281 } else {
282 literal
283 }
284 }
285
286 fn generate_enum_literal(
287 &self,
288 enum_name: &str,
289 variant: &str,
290 fields: &[(String, Expression)],
291 ) -> String {
292 let mut parts = vec![format!("tag = \"{variant}\"")];
293 parts.extend(
294 fields
295 .iter()
296 .map(|(name, value)| format!("{name} = {}", self.generate_expression(value))),
297 );
298 let literal = format!("{{ {} }}", parts.join(", "));
299 if let Some((name, table_path)) = &self.struct_table_context
300 && name == enum_name
301 {
302 format!("setmetatable({literal}, {{ __index = {table_path} }})")
303 } else {
304 literal
305 }
306 }
307
308 fn generate_index(&self, base: &Expression, key: &Expression) -> String {
309 let base = self.generate_expression(base);
310
311 let key = match key {
314 Expression::Literal(factorio_ir::literal::Literal::Int(index)) => {
315 (*index + 1).to_string()
316 }
317 _ => self.generate_expression(key),
318 };
319 format!("{base}[{key}]")
320 }
321
322 fn generate_not(&self, inner: &Expression) -> String {
323 if let Expression::MethodCall {
324 receiver,
325 method,
326 args,
327 } = inner
328 && method == "is_empty"
329 && args.is_empty()
330 {
331 let receiver = self.generate_expression(receiver);
332 return format!("#{receiver} ~= 0");
333 }
334
335 let needs_parens = matches!(inner, Expression::BinaryOp { .. });
336 let inner_str = self.generate_expression(inner);
337 if needs_parens {
338 format!("not ({inner_str})")
339 } else {
340 format!("not {inner_str}")
341 }
342 }
343
344 fn generate_if_expr(
346 &self,
347 condition: &Expression,
348 then_expr: &Expression,
349 else_expr: &Expression,
350 ) -> String {
351 let condition = self.generate_expression(condition);
352 let then_expr = self.generate_expression(then_expr);
353 let else_expr = self.generate_expression(else_expr);
354 format!(
355 "(function() if {condition} then return {then_expr} else return {else_expr} end end)()"
356 )
357 }
358
359 fn generate_closure(&self, params: &[String], body: &factorio_ir::block::Block) -> String {
360 let params = params.join(", ");
361 if let [factorio_ir::statement::Statement::Return(Some(expr))] = body.statements.as_slice()
363 {
364 let expr = self.generate_expression(expr);
365 return format!("function({params}) return {expr} end");
366 }
367
368 let mut temp = self.fork_expr_emitter();
369 let _ = writeln!(temp.output, "function({params})");
370 temp.indent_level = 1;
371 let _ = temp.generate_block(body, None);
372 temp.indent_level = 0;
373 temp.write_line("end");
374 while temp.output.ends_with('\n') {
376 temp.output.pop();
377 }
378 temp.output
379 }
380}
381
382fn is_storage_receiver(receiver: &Expression) -> bool {
383 match receiver {
384 Expression::Identifier(name) => name == "storage",
385 Expression::QualifiedPath { segments } => {
386 segments.last().is_some_and(|name| name == "storage")
387 }
388 _ => false,
389 }
390}
391
392fn trim_trailing_nils(args: &[Expression]) -> &[Expression] {
394 let mut end = args.len();
395 while end > 0 {
396 match &args[end - 1] {
397 Expression::Literal(factorio_ir::literal::Literal::Nil) => end -= 1,
398 _ => break,
399 }
400 }
401 &args[..end]
402}