1use std::fmt::Write as _;
2
3use factorio_ir::expression::Expression;
4
5use crate::{LuaGenerator, attribute_property_for_setter, prototype_lua_typename};
6
7impl LuaGenerator {
8 #[must_use]
9 pub fn generate_expression(&self, expression: &Expression) -> String {
10 self.generate_expression_prec(expression, 0)
11 }
12
13 pub(crate) fn generate_expression_prec(&self, expression: &Expression, min_prec: u8) -> String {
14 match expression {
15 Expression::BinaryOp { lhs, op, rhs } => {
16 let is_zero_lhs = match lhs.as_ref() {
19 Expression::Literal(factorio_ir::literal::Literal::Int(0)) => true,
20 Expression::Literal(factorio_ir::literal::Literal::Float(f)) => *f == 0.0,
21 _ => false,
22 };
23 if matches!(op, factorio_ir::operator::Operator::Sub) && is_zero_lhs {
24 let rhs_str = self.generate_expression_prec(rhs, 100);
25 let result = format!("-{rhs_str}");
26 return if 0 < min_prec {
27 format!("({result})")
28 } else {
29 result
30 };
31 }
32
33 let prec = Self::operator_precedence(*op);
34 let lhs_str = self.generate_expression_prec(lhs, prec);
35 let rhs_str = self.generate_expression_prec(rhs, prec.saturating_add(1));
36 let result = format!("{} {} {}", lhs_str, Self::generate_operator(*op), rhs_str);
37
38 if prec < min_prec {
39 format!("({result})")
40 } else {
41 result
42 }
43 }
44 _ => self.generate_atom(expression),
45 }
46 }
47
48 pub(crate) fn generate_atom(&self, expression: &Expression) -> String {
50 match expression {
51 Expression::Literal(literal) => Self::generate_literal(literal),
52 Expression::Identifier(name) => self.generate_identifier(name),
53 Expression::FieldAccess { base, field } => {
54 let base = self.generate_expression(base);
55 format!("{base}.{field}")
56 }
57 Expression::QualifiedPath { segments } => self.generate_qualified_path(segments),
58 Expression::Call { func, args } => self.generate_call(func, args),
59 Expression::MethodCall {
60 receiver,
61 method,
62 args,
63 } => self.generate_method_call(receiver, method, args),
64 Expression::StructLiteral {
65 struct_name,
66 fields,
67 } => self.generate_struct_literal(struct_name.as_deref(), fields),
68 Expression::EnumLiteral {
69 enum_name,
70 variant,
71 fields,
72 } => self.generate_enum_literal(enum_name, variant, fields),
73 Expression::FormatConcat { parts } => parts
74 .iter()
75 .map(|part| self.generate_expression(part))
76 .collect::<Vec<_>>()
77 .join(" .. "),
78 Expression::Array { elements } => {
79 let elements = elements
80 .iter()
81 .map(|element| self.generate_expression(element))
82 .collect::<Vec<_>>()
83 .join(", ");
84 format!("{{ {elements} }}")
85 }
86 Expression::Index { base, key } => self.generate_index(base, key),
87 Expression::Not(inner) => self.generate_not(inner),
88 Expression::Len(inner) => {
89 let inner = self.generate_expression(inner);
90 format!("#{inner}")
91 }
92 Expression::If {
93 condition,
94 then_expr,
95 else_expr,
96 } => self.generate_if_expr(condition, then_expr, else_expr),
97 Expression::Closure { params, body } => self.generate_closure(params, body),
98 Expression::FatPointer { data, vtable } => {
99 let data = self.generate_expression(data);
100 format!("{{ _data = {data}, _vt = {vtable} }}")
101 }
102 Expression::DynMethodCall {
103 receiver,
104 method,
105 args,
106 } => {
107 let recv = self.generate_expression(receiver);
108 let args_lua = self.generate_arg_list(args);
109 if args_lua.is_empty() {
110 format!("{recv}._vt.{method}({recv})")
111 } else {
112 format!("{recv}._vt.{method}({recv}, {args_lua})")
113 }
114 }
115 Expression::BinaryOp { .. } => {
116 unreachable!("binary operators are handled by generate_expression_prec")
117 }
118 }
119 }
120
121 fn generate_identifier(&self, name: &str) -> String {
123 if self.exported_functions.contains(name)
124 && let Some(module_table) = &self.current_module_table
125 {
126 return format!("{module_table}.{name}");
127 }
128 name.to_string()
129 }
130
131 fn generate_qualified_path(&self, segments: &[String]) -> String {
132 if let Some((struct_name, table_path)) = &self.struct_table_context
133 && segments
134 .first()
135 .is_some_and(|segment| segment == struct_name)
136 {
137 let suffix = segments
138 .get(1..)
139 .map_or_else(String::new, |rest| rest.join("."));
140 if suffix.is_empty() {
141 return table_path.clone();
142 }
143 return format!("{table_path}.{suffix}");
144 }
145
146 segments.join(".")
147 }
148
149 fn generate_call(&self, func: &Expression, args: &[Expression]) -> String {
150 if let Expression::QualifiedPath { segments } = func
151 && args.is_empty()
152 && segments
153 .last()
154 .is_some_and(|s| s == "new" || s == "default")
155 {
156 match segments[0].as_str() {
157 "LuaAny" => return "nil".to_string(),
158 "Vec" if segments.last().is_some_and(|s| s == "new") => {
159 return "{}".to_string();
160 }
161 _ if segments.last().is_some_and(|s| s == "default") => {
162 return "{}".to_string();
163 }
164 _ => {}
165 }
166 }
167
168 let func_is_closure = matches!(func, Expression::Closure { .. });
169 let func = self.generate_expression(func);
170 let args = self.generate_arg_list(args);
171 if func_is_closure {
172 format!("({func})({args})")
173 } else {
174 format!("{func}({args})")
175 }
176 }
177
178 fn generate_method_call(
179 &self,
180 receiver: &Expression,
181 method: &str,
182 args: &[Expression],
183 ) -> String {
184 if method == "get" && args.len() == 1 && is_storage_receiver(receiver) {
187 let receiver = self.generate_expression(receiver);
188 let key = self.generate_expression(&args[0]);
189 return format!("{receiver}[{key}]");
190 }
191
192 if method == "get" && args.len() == 1 {
193 let receiver = self.generate_expression(receiver);
194 let key = self.generate_expression(&args[0]);
195 return format!("{receiver}[{key}].value");
196 }
197
198 if matches!(
200 method,
201 "get_bool" | "get_int" | "get_double" | "get_string" | "setting"
202 ) && args.len() == 1
203 {
204 let receiver = self.generate_expression(receiver);
205 let key = self.generate_expression(&args[0]);
206 if method == "setting" {
207 return format!("{receiver}[{key}]");
208 }
209 return format!("{receiver}[{key}].value");
210 }
211
212 if method == "set" && args.len() == 2 && is_storage_receiver(receiver) {
214 let receiver = self.generate_expression(receiver);
215 let key = self.generate_expression(&args[0]);
216 let value = self.generate_expression(&args[1]);
217 return format!("{receiver}[{key}] = {value}");
218 }
219
220 if method == "len" && args.is_empty() {
221 let receiver = self.generate_expression(receiver);
222 return format!("#{receiver}");
223 }
224
225 if method == "push" && args.len() == 1 {
226 let receiver = self.generate_expression(receiver);
227 let item = self.generate_expression(&args[0]);
228 return format!("table.insert({receiver}, {item})");
229 }
230
231 if method == "is_empty" && args.is_empty() {
232 let receiver = self.generate_expression(receiver);
233 return format!("#{receiver} == 0");
234 }
235
236 let trimmed = trim_trailing_nils(args);
237 if trimmed.is_empty() {
238 let receiver = self.generate_expression(receiver);
239 if args.is_empty() {
242 return format!("{receiver}.{method}");
243 }
244 return format!("{receiver}.{method}()");
245 }
246
247 if trimmed.len() == 1
250 && let Some(property) = attribute_property_for_setter(method)
251 {
252 let receiver = self.generate_expression(receiver);
253 let value = self.generate_expression(&trimmed[0]);
254 return format!("{receiver}.{property} = {value}");
255 }
256
257 let receiver = self.generate_expression(receiver);
258 let args_lua = self.generate_arg_list(trimmed);
259 format!("{receiver}.{method}({args_lua})")
260 }
261
262 fn generate_arg_list(&self, args: &[Expression]) -> String {
264 trim_trailing_nils(args)
265 .iter()
266 .map(|arg| self.generate_expression(arg))
267 .collect::<Vec<_>>()
268 .join(", ")
269 }
270
271 fn generate_struct_literal(
272 &self,
273 struct_name: Option<&str>,
274 fields: &[(String, Expression)],
275 ) -> String {
276 if let Some(literal) = self.try_special_struct_literal(struct_name, fields) {
277 return self.maybe_struct_metatable(literal);
278 }
279
280 let (injected_type, skip_fields): (Option<&str>, &[&str]) =
282 if struct_name == Some("RecipeIngredient") {
283 let is_fluid = fields.iter().any(|(n, v)| {
284 n == "fluid"
285 && matches!(
286 v,
287 Expression::Literal(factorio_ir::literal::Literal::Bool(true))
288 )
289 });
290 (Some(if is_fluid { "fluid" } else { "item" }), &["fluid"])
291 } else {
292 (struct_name.and_then(prototype_lua_typename), &[])
293 };
294
295 let type_prefix = injected_type.map(|t| format!("type = \"{t}\", "));
296 let field_strs = fields
297 .iter()
298 .filter(|(name, value)| {
299 if skip_fields.contains(&name.as_str()) {
300 return false;
301 }
302 if matches!(
303 value,
304 Expression::Literal(factorio_ir::literal::Literal::Nil)
305 ) {
306 return false;
307 }
308 if name == "fluid"
310 && matches!(
311 value,
312 Expression::Literal(factorio_ir::literal::Literal::Bool(false))
313 )
314 {
315 return false;
316 }
317 injected_type.is_none() || (name != "type" && name != "r#type")
318 })
319 .map(|(name, value)| {
320 let lua_name = if name == "r#type" {
321 "type"
322 } else {
323 name.as_str()
324 };
325 format!("{lua_name} = {}", self.generate_expression(value))
326 })
327 .collect::<Vec<_>>()
328 .join(", ");
329
330 let inner = match type_prefix {
331 Some(prefix) if !field_strs.is_empty() => format!("{prefix}{field_strs}"),
332 Some(prefix) => prefix.trim_end_matches(", ").to_string(),
333 None => field_strs,
334 };
335 self.maybe_struct_metatable(format!("{{ {inner} }}"))
336 }
337
338 fn try_special_struct_literal(
340 &self,
341 struct_name: Option<&str>,
342 fields: &[(String, Expression)],
343 ) -> Option<String> {
344 if struct_name == Some("TechnologyUnitIngredient") {
346 let name = fields
347 .iter()
348 .find_map(|(n, v)| (n == "name").then(|| self.generate_expression(v)))?;
349 let amount = fields
350 .iter()
351 .find_map(|(n, v)| (n == "amount").then(|| self.generate_expression(v)))?;
352 return Some(format!("{{ {name}, {amount} }}"));
353 }
354
355 if let Some(name) = struct_name
357 && is_flag_set_struct(name)
358 && let Some(flags_expr) = fields.iter().find_map(|(n, v)| (n == "flags").then_some(v))
359 {
360 return Some(generate_flag_set_table(flags_expr));
361 }
362
363 if matches!(struct_name, Some("Tags" | "PropertyExpressionNames"))
365 && let Some(pairs_expr) = fields.iter().find_map(|(n, v)| (n == "pairs").then_some(v))
366 {
367 return Some(self.generate_string_pair_table(pairs_expr));
368 }
369
370 if struct_name == Some("BoundingBox") {
372 let get = |key: &str| {
373 fields
374 .iter()
375 .find_map(|(n, v)| (n == key).then(|| self.generate_expression(v)))
376 };
377 if let (Some(lx), Some(ly), Some(rx), Some(ry)) = (
378 get("left_top_x"),
379 get("left_top_y"),
380 get("right_bottom_x"),
381 get("right_bottom_y"),
382 ) {
383 return Some(format!("{{{{ {lx}, {ly} }}, {{ {rx}, {ry} }}}}"));
384 }
385 }
386
387 None
388 }
389
390 fn maybe_struct_metatable(&self, literal: String) -> String {
391 if let Some((_, table_path)) = &self.struct_table_context {
392 format!("setmetatable({literal}, {{ __index = {table_path} }})")
393 } else {
394 literal
395 }
396 }
397
398 fn generate_string_pair_table(&self, pairs_expr: &Expression) -> String {
400 let entries = match pairs_expr {
401 Expression::Array { elements } => elements
402 .iter()
403 .filter_map(|item| match item {
404 Expression::StructLiteral { fields, .. } => {
405 let key = fields
406 .iter()
407 .find_map(|(n, v)| (n == "key").then(|| self.generate_expression(v)))?;
408 let value = fields.iter().find_map(|(n, v)| {
409 (n == "value").then(|| self.generate_expression(v))
410 })?;
411 Some(format!("[{key}] = {value}"))
412 }
413 _ => None,
414 })
415 .collect::<Vec<_>>(),
416 _ => Vec::new(),
417 };
418 format!("{{ {} }}", entries.join(", "))
419 }
420
421 fn generate_enum_literal(
422 &self,
423 enum_name: &str,
424 variant: &str,
425 fields: &[(String, Expression)],
426 ) -> String {
427 let mut parts = vec![format!("tag = \"{variant}\"")];
428 parts.extend(
429 fields
430 .iter()
431 .map(|(name, value)| format!("{name} = {}", self.generate_expression(value))),
432 );
433 let literal = format!("{{ {} }}", parts.join(", "));
434 if let Some((name, table_path)) = &self.struct_table_context
435 && name == enum_name
436 {
437 format!("setmetatable({literal}, {{ __index = {table_path} }})")
438 } else {
439 literal
440 }
441 }
442
443 fn generate_index(&self, base: &Expression, key: &Expression) -> String {
444 let base = self.generate_expression(base);
445
446 let key = match key {
449 Expression::Literal(factorio_ir::literal::Literal::Int(index)) => {
450 (*index + 1).to_string()
451 }
452 _ => self.generate_expression(key),
453 };
454 format!("{base}[{key}]")
455 }
456
457 fn generate_not(&self, inner: &Expression) -> String {
458 if let Expression::MethodCall {
459 receiver,
460 method,
461 args,
462 } = inner
463 && method == "is_empty"
464 && args.is_empty()
465 {
466 let receiver = self.generate_expression(receiver);
467 return format!("#{receiver} ~= 0");
468 }
469
470 let needs_parens = matches!(inner, Expression::BinaryOp { .. });
471 let inner_str = self.generate_expression(inner);
472 if needs_parens {
473 format!("not ({inner_str})")
474 } else {
475 format!("not {inner_str}")
476 }
477 }
478
479 fn generate_if_expr(
481 &self,
482 condition: &Expression,
483 then_expr: &Expression,
484 else_expr: &Expression,
485 ) -> String {
486 let condition = self.generate_expression(condition);
487 let then_expr = self.generate_expression(then_expr);
488 let else_expr = self.generate_expression(else_expr);
489 format!(
490 "(function() if {condition} then return {then_expr} else return {else_expr} end end)()"
491 )
492 }
493
494 fn generate_closure(&self, params: &[String], body: &factorio_ir::block::Block) -> String {
495 let params = params.join(", ");
496 if let [factorio_ir::statement::Statement::Return(Some(expr))] = body.statements.as_slice()
498 {
499 let expr = self.generate_expression(expr);
500 return format!("function({params}) return {expr} end");
501 }
502
503 let mut temp = self.fork_expr_emitter();
504 let _ = writeln!(temp.output, "function({params})");
505 temp.indent_level = 1;
506 let _ = temp.generate_block(body, None);
507 temp.indent_level = 0;
508 temp.write_line("end");
509 while temp.output.ends_with('\n') {
511 temp.output.pop();
512 }
513 temp.output
514 }
515}
516
517fn is_storage_receiver(receiver: &Expression) -> bool {
518 match receiver {
519 Expression::Identifier(name) => name == "storage",
520 Expression::QualifiedPath { segments } => {
521 segments.last().is_some_and(|name| name == "storage")
522 }
523 _ => false,
524 }
525}
526
527fn is_flag_set_struct(name: &str) -> bool {
528 matches!(
529 name,
530 "MouseButtonFlags"
531 | "SelectionModeFlags"
532 | "EntityPrototypeFlags"
533 | "ItemPrototypeFlags"
534 | "TriggerTargetMask"
535 )
536}
537
538fn generate_flag_set_table(flags_expr: &Expression) -> String {
540 let keys = match flags_expr {
541 Expression::Array { elements } => elements
542 .iter()
543 .filter_map(|item| match item {
544 Expression::Literal(factorio_ir::literal::Literal::String(s)) => {
545 Some(format!("[\"{s}\"] = true"))
546 }
547 _ => None,
548 })
549 .collect::<Vec<_>>(),
550 _ => Vec::new(),
551 };
552 format!("{{ {} }}", keys.join(", "))
553}
554
555fn trim_trailing_nils(args: &[Expression]) -> &[Expression] {
557 let mut end = args.len();
558 while end > 0 {
559 match &args[end - 1] {
560 Expression::Literal(factorio_ir::literal::Literal::Nil) => end -= 1,
561 _ => break,
562 }
563 }
564 &args[..end]
565}