Skip to main content

factorio_codegen/generator/
expression.rs

1use factorio_ir::expression::Expression;
2
3use crate::LuaGenerator;
4
5/// Map a Rust prototype struct name to its fixed Factorio `type` discriminant string.
6/// Returns `None` for non-prototype structs.
7fn prototype_lua_type(struct_name: &str) -> Option<&'static str> {
8    match struct_name {
9        "BoolSetting" => Some("bool-setting"),
10        "IntSetting" => Some("int-setting"),
11        "DoubleSetting" => Some("double-setting"),
12        "StringSetting" => Some("string-setting"),
13        _ => None,
14    }
15}
16
17impl LuaGenerator {
18    #[must_use]
19    pub fn generate_expression(&self, expression: &Expression) -> String {
20        self.generate_expression_prec(expression, 0)
21    }
22
23    pub(crate) fn generate_expression_prec(&self, expression: &Expression, min_prec: u8) -> String {
24        match expression {
25            Expression::BinaryOp { lhs, op, rhs } => {
26                // `0 - x` (or `0.0 - x`) is the frontend's encoding of unary negation.
27                // Emit as Lua `-x` directly.
28                let is_zero_lhs = match lhs.as_ref() {
29                    Expression::Literal(factorio_ir::literal::Literal::Int(0)) => true,
30                    Expression::Literal(factorio_ir::literal::Literal::Float(f)) => *f == 0.0,
31                    _ => false,
32                };
33                if matches!(op, factorio_ir::operator::Operator::Sub) && is_zero_lhs {
34                    let rhs_str = self.generate_expression_prec(rhs, 100);
35                    let result = format!("-{rhs_str}");
36                    return if 0 < min_prec {
37                        format!("({result})")
38                    } else {
39                        result
40                    };
41                }
42
43                let prec = Self::operator_precedence(*op);
44                let lhs_str = self.generate_expression_prec(lhs, prec);
45                let rhs_str = self.generate_expression_prec(rhs, prec.saturating_add(1));
46                let result = format!("{} {} {}", lhs_str, Self::generate_operator(*op), rhs_str);
47
48                if prec < min_prec {
49                    format!("({result})")
50                } else {
51                    result
52                }
53            }
54            _ => self.generate_atom(expression),
55        }
56    }
57
58    /// Generate the smallest level of code (an atom).
59    pub(crate) fn generate_atom(&self, expression: &Expression) -> String {
60        match expression {
61            Expression::Literal(literal) => Self::generate_literal(literal),
62            Expression::Identifier(name) => name.clone(),
63            Expression::FieldAccess { base, field } => {
64                let base = self.generate_expression(base);
65                format!("{base}.{field}")
66            }
67            Expression::QualifiedPath { segments } => self.generate_qualified_path(segments),
68            Expression::Call { func, args } => self.generate_call(func, args),
69            Expression::MethodCall {
70                receiver,
71                method,
72                args,
73            } => self.generate_method_call(receiver, method, args),
74            Expression::StructLiteral {
75                struct_name,
76                fields,
77            } => self.generate_struct_literal(struct_name.as_deref(), fields),
78            Expression::FormatConcat { parts } => parts
79                .iter()
80                .map(|part| self.generate_expression(part))
81                .collect::<Vec<_>>()
82                .join(" .. "),
83            Expression::Array { elements } => {
84                let elements = elements
85                    .iter()
86                    .map(|element| self.generate_expression(element))
87                    .collect::<Vec<_>>()
88                    .join(", ");
89                format!("{{ {elements} }}")
90            }
91            Expression::Index { base, key } => self.generate_index(base, key),
92            Expression::Not(inner) => self.generate_not(inner),
93            Expression::Len(inner) => {
94                let inner = self.generate_expression(inner);
95                format!("#{inner}")
96            }
97            Expression::BinaryOp { .. } => {
98                unreachable!("binary operators are handled by generate_expression_prec")
99            }
100        }
101    }
102
103    fn generate_qualified_path(&self, segments: &[String]) -> String {
104        if let Some((struct_name, table_path)) = &self.struct_table_context
105            && segments
106                .first()
107                .is_some_and(|segment| segment == struct_name)
108        {
109            let suffix = segments
110                .get(1..)
111                .map_or_else(String::new, |rest| rest.join("."));
112            if suffix.is_empty() {
113                return table_path.clone();
114            }
115            return format!("{table_path}.{suffix}");
116        }
117
118        segments.join(".")
119    }
120
121    fn generate_call(&self, func: &Expression, args: &[Expression]) -> String {
122        if let Expression::QualifiedPath { segments } = func
123            && args.is_empty()
124            && segments
125                .last()
126                .is_some_and(|s| s == "new" || s == "default")
127        {
128            match segments[0].as_str() {
129                "LuaAny" => return "nil".to_string(),
130                "Vec" if segments.last().is_some_and(|s| s == "new") => {
131                    return "{}".to_string();
132                }
133                _ if segments.last().is_some_and(|s| s == "default") => {
134                    return "{}".to_string();
135                }
136                _ => {}
137            }
138        }
139
140        let func = self.generate_expression(func);
141        let args = args
142            .iter()
143            .map(|arg| self.generate_expression(arg))
144            .collect::<Vec<_>>()
145            .join(", ");
146        format!("{func}({args})")
147    }
148
149    fn generate_method_call(
150        &self,
151        receiver: &Expression,
152        method: &str,
153        args: &[Expression],
154    ) -> String {
155        if method == "get" && args.len() == 1 {
156            let receiver = self.generate_expression(receiver);
157            let key = self.generate_expression(&args[0]);
158            return format!("{receiver}[{key}].value");
159        }
160
161        if method == "len" && args.is_empty() {
162            let receiver = self.generate_expression(receiver);
163            return format!("#{receiver}");
164        }
165
166        if method == "push" && args.len() == 1 {
167            let receiver = self.generate_expression(receiver);
168            let item = self.generate_expression(&args[0]);
169            return format!("table.insert({receiver}, {item})");
170        }
171
172        if method == "is_empty" && args.is_empty() {
173            let receiver = self.generate_expression(receiver);
174            return format!("#{receiver} == 0");
175        }
176
177        if args.is_empty() {
178            let receiver = self.generate_expression(receiver);
179            return format!("{receiver}.{method}");
180        }
181
182        let receiver = self.generate_expression(receiver);
183        let args_lua = args
184            .iter()
185            .map(|arg| self.generate_expression(arg))
186            .collect::<Vec<_>>()
187            .join(", ");
188
189        format!("{receiver}.{method}({args_lua})")
190    }
191
192    fn generate_struct_literal(
193        &self,
194        struct_name: Option<&str>,
195        fields: &[(String, Expression)],
196    ) -> String {
197        let injected_type = struct_name.and_then(prototype_lua_type);
198        let type_prefix = injected_type.map(|t| format!("type = \"{t}\", "));
199
200        let field_strs = fields
201            .iter()
202            .filter(|(name, _)| injected_type.is_none() || (name != "type" && name != "r#type"))
203            .map(|(name, value)| {
204                let lua_name = if name == "r#type" {
205                    "type"
206                } else {
207                    name.as_str()
208                };
209                format!("{lua_name} = {}", self.generate_expression(value))
210            })
211            .collect::<Vec<_>>()
212            .join(", ");
213
214        let inner = match type_prefix {
215            Some(prefix) if !field_strs.is_empty() => format!("{prefix}{field_strs}"),
216            Some(prefix) => prefix.trim_end_matches(", ").to_string(),
217            None => field_strs,
218        };
219        let literal = format!("{{ {inner} }}");
220
221        if let Some((_, table_path)) = &self.struct_table_context {
222            format!("setmetatable({literal}, {{ __index = {table_path} }})")
223        } else {
224            literal
225        }
226    }
227
228    fn generate_index(&self, base: &Expression, key: &Expression) -> String {
229        let base = self.generate_expression(base);
230
231        // Lua is 1-indexed, so translate 0 to 1
232        let key = match key {
233            Expression::Literal(factorio_ir::literal::Literal::Int(0)) => "1".to_string(),
234            _ => self.generate_expression(key),
235        };
236        format!("{base}[{key}]")
237    }
238
239    fn generate_not(&self, inner: &Expression) -> String {
240        if let Expression::MethodCall {
241            receiver,
242            method,
243            args,
244        } = inner
245            && method == "is_empty"
246            && args.is_empty()
247        {
248            let receiver = self.generate_expression(receiver);
249            return format!("#{receiver} ~= 0");
250        }
251
252        let needs_parens = matches!(inner, Expression::BinaryOp { .. });
253        let inner_str = self.generate_expression(inner);
254        if needs_parens {
255            format!("not ({inner_str})")
256        } else {
257            format!("not {inner_str}")
258        }
259    }
260}