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