1mod asm;
2mod compile;
3mod ir;
4mod lua;
5mod meta;
6
7use anyhow::{anyhow, Context, Result};
8use clap::Parser;
9use std::fs;
10use std::path::PathBuf;
11
12#[derive(Parser, Debug)]
13#[command(name = "lua2hcb")]
14#[command(about = "Compile a constrained Lua 5.3 subset (decompiler IR style) back to HCB.")]
15struct Cli {
16 #[arg(long)]
17 meta: PathBuf,
18
19 #[arg(long)]
20 lua: PathBuf,
21
22 #[arg(short, long)]
23 out: PathBuf,
24}
25
26fn main() -> Result<()> {
27 let cli = Cli::parse();
28
29 let meta = meta::load_meta(&cli.meta)
30 .with_context(|| format!("loading meta: {}", cli.meta.display()))?;
31
32 let program = lua::parse_lua(&cli.lua).with_context(|| "parsing lua source")?;
33 if program.functions.is_empty() {
34 return Err(anyhow!("no functions found in lua input"));
35 }
36
37 let (items, layout) = compile::compile_program(&meta, &program).with_context(|| "compiling")?;
38
39 let mut emit_meta = meta.clone();
40 emit_meta.non_volatile_global_count = layout.non_volatile_count;
41 emit_meta.volatile_global_count = layout.volatile_count;
42
43 let (code, labels) = asm::assemble(&emit_meta, &items).with_context(|| "assembling")?;
44
45 let entry_point = labels
46 .get("fn:main")
47 .copied()
48 .ok_or_else(|| anyhow!("missing entry function: main"))?;
49
50 let sysdesc = asm::build_sysdesc(&emit_meta, entry_point).with_context(|| "building sysdesc")?;
51
52 let sys_desc_offset: u32 =
53 4 + u32::try_from(code.len()).map_err(|_| anyhow!("code too large"))?;
54
55 let mut out = Vec::new();
56 out.extend_from_slice(&sys_desc_offset.to_le_bytes());
57 out.extend_from_slice(&code);
58 out.extend_from_slice(&sysdesc);
59
60 fs::write(&cli.out, out).with_context(|| format!("writing output: {}", cli.out.display()))?;
61
62 Ok(())
63}