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