factorio_codegen/generator/
expression.rs1use factorio_ir::expression::Expression;
2
3use crate::LuaGenerator;
4
5fn 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 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 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 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}