normalize_surface_syntax/output/
lua.rs1use crate::ir::*;
6use crate::traits::Writer;
7use std::fmt::Write;
8
9pub static LUA_WRITER: LuaWriterImpl = LuaWriterImpl;
11
12pub struct LuaWriterImpl;
14
15impl Writer for LuaWriterImpl {
16 fn language(&self) -> &'static str {
17 "lua"
18 }
19
20 fn extension(&self) -> &'static str {
21 "lua"
22 }
23
24 fn write(&self, program: &Program) -> String {
25 LuaWriter::emit(program)
26 }
27}
28
29pub struct LuaWriter {
31 output: String,
32 indent: usize,
33}
34
35impl LuaWriter {
36 pub fn new() -> Self {
37 Self {
38 output: String::new(),
39 indent: 0,
40 }
41 }
42
43 pub fn emit(program: &Program) -> String {
45 let mut writer = Self::new();
46 writer.write_program(program);
47 writer.output
48 }
49
50 fn write_program(&mut self, program: &Program) {
51 for stmt in &program.body {
52 self.write_stmt(stmt);
53 self.output.push('\n');
54 }
55 }
56
57 fn write_indent(&mut self) {
58 for _ in 0..self.indent {
59 self.output.push_str(" ");
60 }
61 }
62
63 fn write_stmt(&mut self, stmt: &Stmt) {
64 self.write_indent();
65 match stmt {
66 Stmt::Expr(expr) => {
67 self.write_expr(expr);
68 }
69
70 Stmt::Let { name, init, .. } => {
71 write!(self.output, "local {}", name).unwrap();
72 if let Some(init) = init {
73 self.output.push_str(" = ");
74 self.write_expr(init);
75 }
76 }
77
78 Stmt::Block(stmts) => {
79 self.output.push_str("do\n");
80 self.indent += 1;
81 for s in stmts {
82 self.write_stmt(s);
83 self.output.push('\n');
84 }
85 self.indent -= 1;
86 self.write_indent();
87 self.output.push_str("end");
88 }
89
90 Stmt::If {
91 test,
92 consequent,
93 alternate,
94 } => {
95 self.output.push_str("if ");
96 self.write_expr(test);
97 self.output.push_str(" then\n");
98 self.indent += 1;
99 self.write_stmt_body(consequent);
100 self.indent -= 1;
101 if let Some(alt) = alternate {
102 self.write_indent();
103 self.output.push_str("else\n");
104 self.indent += 1;
105 self.write_stmt_body(alt);
106 self.indent -= 1;
107 }
108 self.write_indent();
109 self.output.push_str("end");
110 }
111
112 Stmt::While { test, body } => {
113 self.output.push_str("while ");
114 self.write_expr(test);
115 self.output.push_str(" do\n");
116 self.indent += 1;
117 self.write_stmt_body(body);
118 self.indent -= 1;
119 self.write_indent();
120 self.output.push_str("end");
121 }
122
123 Stmt::For {
124 init,
125 test,
126 update,
127 body,
128 } => {
129 if let Some(init) = init {
131 self.write_stmt(init);
132 self.output.push('\n');
133 self.write_indent();
134 }
135 self.output.push_str("while ");
136 if let Some(test) = test {
137 self.write_expr(test);
138 } else {
139 self.output.push_str("true");
140 }
141 self.output.push_str(" do\n");
142 self.indent += 1;
143 self.write_stmt_body(body);
144 if let Some(update) = update {
145 self.write_indent();
146 self.write_expr(update);
147 self.output.push('\n');
148 }
149 self.indent -= 1;
150 self.write_indent();
151 self.output.push_str("end");
152 }
153
154 Stmt::ForIn {
155 variable,
156 iterable,
157 body,
158 } => {
159 write!(self.output, "for _, {} in pairs(", variable).unwrap();
160 self.write_expr(iterable);
161 self.output.push_str(") do\n");
162 self.indent += 1;
163 self.write_stmt_body(body);
164 self.indent -= 1;
165 self.write_indent();
166 self.output.push_str("end");
167 }
168
169 Stmt::Return(expr) => {
170 self.output.push_str("return");
171 if let Some(e) = expr {
172 self.output.push(' ');
173 self.write_expr(e);
174 }
175 }
176
177 Stmt::Break => {
178 self.output.push_str("break");
179 }
180
181 Stmt::Continue => {
182 self.output
184 .push_str("-- continue (not supported in Lua 5.1)");
185 }
186
187 Stmt::TryCatch {
188 body,
189 catch_param,
190 catch_body,
191 finally_body,
192 } => {
193 let param = catch_param.as_deref().unwrap_or("_err");
195 self.output.push_str("local _ok, ");
196 self.output.push_str(param);
197 self.output.push_str(" = pcall(function()\n");
198 self.indent += 1;
199 self.write_stmt_body(body);
200 self.indent -= 1;
201 self.write_indent();
202 self.output.push_str("end)\n");
203 if let Some(cb) = catch_body {
204 self.write_indent();
205 self.output.push_str("if not _ok then\n");
206 self.indent += 1;
207 self.write_stmt_body(cb);
208 self.indent -= 1;
209 self.write_indent();
210 self.output.push_str("end");
211 }
212 if let Some(fb) = finally_body {
213 self.output.push('\n');
214 self.write_stmt_body(fb);
215 }
216 }
217
218 Stmt::Function(f) => {
219 self.write_function(f);
220 }
221 }
222 }
223
224 fn write_stmt_body(&mut self, stmt: &Stmt) {
225 match stmt {
226 Stmt::Block(stmts) => {
227 for s in stmts {
228 self.write_stmt(s);
229 self.output.push('\n');
230 }
231 }
232 _ => {
233 self.write_stmt(stmt);
234 self.output.push('\n');
235 }
236 }
237 }
238
239 fn write_function(&mut self, f: &Function) {
240 if f.name.is_empty() {
241 self.output.push_str("function(");
242 } else {
243 write!(self.output, "function {}(", f.name).unwrap();
244 }
245 for (i, param) in f.params.iter().enumerate() {
246 if i > 0 {
247 self.output.push_str(", ");
248 }
249 self.output.push_str(param);
250 }
251 self.output.push_str(")\n");
252 self.indent += 1;
253 for stmt in &f.body {
254 self.write_stmt(stmt);
255 self.output.push('\n');
256 }
257 self.indent -= 1;
258 self.write_indent();
259 self.output.push_str("end");
260 }
261
262 fn write_expr(&mut self, expr: &Expr) {
263 match expr {
264 Expr::Literal(lit) => self.write_literal(lit),
265
266 Expr::Ident(name) => {
267 self.output.push_str(name);
268 }
269
270 Expr::Binary { left, op, right } => {
271 self.output.push('(');
272 self.write_expr(left);
273 self.output.push(' ');
274 self.write_binary_op(*op);
275 self.output.push(' ');
276 self.write_expr(right);
277 self.output.push(')');
278 }
279
280 Expr::Unary { op, expr } => {
281 self.write_unary_op(*op);
282 self.write_expr(expr);
283 }
284
285 Expr::Call { callee, args } => {
286 self.write_expr(callee);
287 self.output.push('(');
288 for (i, arg) in args.iter().enumerate() {
289 if i > 0 {
290 self.output.push_str(", ");
291 }
292 self.write_expr(arg);
293 }
294 self.output.push(')');
295 }
296
297 Expr::Member {
298 object,
299 property,
300 computed,
301 } => {
302 self.write_expr(object);
303 if *computed {
304 self.output.push('[');
305 self.write_expr(property);
306 self.output.push(']');
307 } else if let Expr::Literal(Literal::String(s)) = property.as_ref() {
308 self.output.push('.');
309 self.output.push_str(s);
310 } else {
311 self.output.push('[');
312 self.write_expr(property);
313 self.output.push(']');
314 }
315 }
316
317 Expr::Array(items) => {
318 self.output.push('{');
319 for (i, item) in items.iter().enumerate() {
320 if i > 0 {
321 self.output.push_str(", ");
322 }
323 self.write_expr(item);
324 }
325 self.output.push('}');
326 }
327
328 Expr::Object(pairs) => {
329 self.output.push('{');
330 for (i, (key, value)) in pairs.iter().enumerate() {
331 if i > 0 {
332 self.output.push_str(", ");
333 }
334 write!(self.output, "[\"{}\"] = ", key).unwrap();
335 self.write_expr(value);
336 }
337 self.output.push('}');
338 }
339
340 Expr::Function(f) => {
341 self.write_function(f);
342 }
343
344 Expr::Conditional {
345 test,
346 consequent,
347 alternate,
348 } => {
349 self.output.push('(');
351 self.write_expr(test);
352 self.output.push_str(" and ");
353 self.write_expr(consequent);
354 self.output.push_str(" or ");
355 self.write_expr(alternate);
356 self.output.push(')');
357 }
358
359 Expr::Assign { target, value } => {
360 self.write_expr(target);
361 self.output.push_str(" = ");
362 self.write_expr(value);
363 }
364 }
365 }
366
367 fn write_literal(&mut self, lit: &Literal) {
368 match lit {
369 Literal::Null => self.output.push_str("nil"),
370 Literal::Bool(b) => write!(self.output, "{}", b).unwrap(),
371 Literal::Number(n) => write!(self.output, "{}", n).unwrap(),
372 Literal::String(s) => write!(self.output, "\"{}\"", escape_string(s)).unwrap(),
373 }
374 }
375
376 fn write_binary_op(&mut self, op: BinaryOp) {
377 let s = match op {
378 BinaryOp::Add => "+",
379 BinaryOp::Sub => "-",
380 BinaryOp::Mul => "*",
381 BinaryOp::Div => "/",
382 BinaryOp::Mod => "%",
383 BinaryOp::Eq => "==",
384 BinaryOp::Ne => "~=",
385 BinaryOp::Lt => "<",
386 BinaryOp::Le => "<=",
387 BinaryOp::Gt => ">",
388 BinaryOp::Ge => ">=",
389 BinaryOp::And => "and",
390 BinaryOp::Or => "or",
391 BinaryOp::Concat => "..",
392 };
393 self.output.push_str(s);
394 }
395
396 fn write_unary_op(&mut self, op: UnaryOp) {
397 let s = match op {
398 UnaryOp::Neg => "-",
399 UnaryOp::Not => "not ",
400 };
401 self.output.push_str(s);
402 }
403}
404
405impl Default for LuaWriter {
406 fn default() -> Self {
407 Self::new()
408 }
409}
410
411fn escape_string(s: &str) -> String {
412 s.replace('\\', "\\\\")
413 .replace('"', "\\\"")
414 .replace('\n', "\\n")
415 .replace('\r', "\\r")
416 .replace('\t', "\\t")
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_simple_let() {
425 let program = Program::new(vec![Stmt::const_decl("x", Expr::number(42))]);
426 let lua = LuaWriter::emit(&program);
427 assert_eq!(lua.trim(), "local x = 42");
428 }
429
430 #[test]
431 fn test_function_call() {
432 let program = Program::new(vec![Stmt::expr(Expr::call(
433 Expr::member(Expr::ident("console"), "log"),
434 vec![Expr::string("hello")],
435 ))]);
436 let lua = LuaWriter::emit(&program);
437 assert_eq!(lua.trim(), "console.log(\"hello\")");
438 }
439
440 #[test]
441 fn test_binary_expr() {
442 let program = Program::new(vec![Stmt::const_decl(
443 "sum",
444 Expr::binary(Expr::number(1), BinaryOp::Add, Expr::number(2)),
445 )]);
446 let lua = LuaWriter::emit(&program);
447 assert_eq!(lua.trim(), "local sum = (1 + 2)");
448 }
449}