luna-aot 4.0.2

Ahead-of-time compiler from Lua source to a self-contained native binary, built on luna-core's VM: the program is embedded as luna bytecode and the hot traces of a warmup run are compiled to machine code, all linked into one binary. A build-time tool, not a runtime dependency of the produced binary; embedders that want a runtime JIT use `luna-jit`, and pure interpretation `luna-core`.
Documentation
//! `luna-aot` CLI surface — clap-derive. Sub-commands:
//!
//! - `luna-aot compile <input.lua> --out <output>` — the scaffold's
//!   only working command.
//!
//! Possible future commands:
//!
//! - `luna-aot run <input.lua>` — compile + execute in one shot
//!   (convenience).
//! - `luna-aot dump <input.lua>` — print the dump bytes for
//!   inspection without touching the linker.

use std::path::PathBuf;
use std::process::ExitCode;

use clap::{Parser, Subcommand, ValueEnum};

use luna_core::version::LuaVersion;

use crate::embed::{AotError, compile_and_link, embed_bytecode};

/// `luna-aot` — Lua source → native binary compiler.
#[derive(Parser, Debug)]
#[command(name = "luna-aot", version, about, long_about = None)]
pub struct Cli {
    /// The sub-command to execute (currently only `compile`).
    #[command(subcommand)]
    pub command: Command,
}

/// Top-level sub-commands. Only `compile` is wired in the scaffold;
/// the `Help` shape stays here so `luna-aot --help` lists what's
/// coming.
#[derive(Subcommand, Debug)]
pub enum Command {
    /// Compile a Lua source file into a native binary embedding the
    /// luna bytecode dump (scaffold: runs a tiny C entry that prints
    /// the embedded section size; the default path links the real
    /// Vm-driven runtime).
    Compile(CompileArgs),
}

/// Arguments for `luna-aot compile`.
#[derive(clap::Args, Debug)]
pub struct CompileArgs {
    /// Lua source file to compile.
    pub input: PathBuf,

    /// Output binary path. Defaults to the input's stem (e.g.
    /// `foo.lua` → `foo`).
    #[arg(short = 'o', long = "out")]
    pub out: Option<PathBuf>,

    /// Target triple. Scaffold only supports the host triple;
    /// anything else errors out at the `embed_bytecode` boundary.
    #[arg(long = "target")]
    pub target: Option<String>,

    /// Lua dialect. Defaults to 5.5 (matches the `luna` runner at
    /// `crates/luna-jit/src/bin/luna.rs`).
    #[arg(long = "dialect", default_value = "lua55")]
    pub dialect: DialectArg,

    /// **Escape hatch**: when set, fall back to the scaffold path
    /// that emits a C entry which only
    /// prints the embedded section length to stderr (no `Vm`, no
    /// luna-runtime-helpers link). Useful for benchmarking the
    /// link step in isolation or when the runtime staticlib is
    /// known-broken on the host. Default (off) uses the real
    /// interp-runtime link via [`compile_and_link`].
    #[arg(long = "scaffold-only", default_value_t = false)]
    pub scaffold_only: bool,
}

/// Dialect choice surface for the CLI. Mirrors
/// [`LuaVersion`] but is `clap::ValueEnum` so `--dialect 5.5` parses.
#[derive(ValueEnum, Copy, Clone, Debug)]
#[clap(rename_all = "lower")]
pub enum DialectArg {
    /// Lua 5.1
    #[clap(name = "5.1", alias = "lua51")]
    Lua51,
    /// Lua 5.2
    #[clap(name = "5.2", alias = "lua52")]
    Lua52,
    /// Lua 5.3
    #[clap(name = "5.3", alias = "lua53")]
    Lua53,
    /// Lua 5.4
    #[clap(name = "5.4", alias = "lua54")]
    Lua54,
    /// Lua 5.5 (default)
    #[clap(name = "5.5", alias = "lua55")]
    Lua55,
    /// MacroLua (5.4 base + compile-time macros).
    #[clap(name = "macrolua", alias = "macro")]
    MacroLua,
}

impl From<DialectArg> for LuaVersion {
    fn from(d: DialectArg) -> Self {
        match d {
            DialectArg::Lua51 => LuaVersion::Lua51,
            DialectArg::Lua52 => LuaVersion::Lua52,
            DialectArg::Lua53 => LuaVersion::Lua53,
            DialectArg::Lua54 => LuaVersion::Lua54,
            DialectArg::Lua55 => LuaVersion::Lua55,
            DialectArg::MacroLua => LuaVersion::MacroLua,
        }
    }
}

/// Entry point invoked from `main.rs`. Parses argv, dispatches to the
/// requested sub-command, prints errors to `stderr` and returns the
/// process exit code.
pub fn run() -> ExitCode {
    let cli = Cli::parse();
    match cli.command {
        Command::Compile(args) => match do_compile(args) {
            Ok(()) => ExitCode::SUCCESS,
            Err(e) => {
                eprintln!("luna-aot: {e}");
                ExitCode::FAILURE
            }
        },
    }
}

fn do_compile(args: CompileArgs) -> Result<(), AotError> {
    let out = args.out.unwrap_or_else(|| default_out_for(&args.input));
    let version = args.dialect.into();
    if args.scaffold_only {
        embed_bytecode(&args.input, &out, args.target.as_deref(), version)
    } else {
        compile_and_link(&args.input, &out, args.target.as_deref(), version)
    }
}

fn default_out_for(input: &std::path::Path) -> PathBuf {
    let stem = input
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("a")
        .to_string();
    let mut p = input
        .parent()
        .map(|p| p.to_path_buf())
        .unwrap_or_else(|| PathBuf::from("."));
    p.push(stem);
    p
}