From 7a7bc28f204ef25ab90cd625adc5e059773dad72 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Wed, 3 Jun 2026 19:56:56 -0500 Subject: [PATCH] feat: emit current subset from cli * rename project --- README.md | 4 +- crates/cli/src/args.rs | 21 +++- crates/cli/src/commands.rs | 111 ++++++++++++++---- crates/cli/src/echo.rs | 7 ++ .../tasks/10_cli_and_build_outputs.md | 18 +-- 5 files changed, 128 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index af90ce5..c7a1e59 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ -# Gleam to WASM Compiler +# Regulus + +Regulus ("Reggie"), is a Gleam to WASM Compiler Lucy WASM diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 1ad8216..6d4acb9 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; #[derive(Debug, Parser)] #[command(name = "gleam-wasm")] @@ -16,6 +16,18 @@ pub enum Command { Compile { /// Gleam source file to compile. input: PathBuf, + /// Path for the generated .wasm file. + #[arg(short, long)] + output: Option, + /// Also write the generated WebAssembly text format. + #[arg(long)] + wat: Option>, + /// Write compiler debug dumps to this directory. + #[arg(long)] + dump_dir: Option, + /// Select the intended runtime target. + #[arg(long, value_enum, default_value_t = Target::Wasmtime)] + target: Target, }, /// Load a Gleam project and print discovered modules. Project { @@ -23,3 +35,10 @@ pub enum Command { input: PathBuf, }, } + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum Target { + Wasmtime, + Browser, + Wasi, +} diff --git a/crates/cli/src/commands.rs b/crates/cli/src/commands.rs index fdb5a2d..f5e0781 100644 --- a/crates/cli/src/commands.rs +++ b/crates/cli/src/commands.rs @@ -1,17 +1,20 @@ -use std::{fs, process::ExitCode}; +use std::{fs, path::PathBuf, process::ExitCode}; use compiler_core::{self, source::SourceFile, source::SourceFileId}; -use crate::{args::Command, echo}; +use crate::{ + args::{Command, Target}, + echo, +}; pub fn run(command: Command) -> ExitCode { match command { - Command::Compile { input } => compile(input), + Command::Compile { input, output, wat, dump_dir, target } => compile(input, output, wat, dump_dir, target), Command::Project { input } => project(input), } } -fn project(input: std::path::PathBuf) -> ExitCode { +fn project(input: PathBuf) -> ExitCode { match compiler_core::project::load_project(&input) { Ok(project) => { echo::status( @@ -30,38 +33,102 @@ fn project(input: std::path::PathBuf) -> ExitCode { } Err(diagnostics) => { echo::error(format!("could not load project {}", input.display())); - for diagnostic in diagnostics { - echo::diagnostic(diagnostic.message); - } + echo::diagnostics(&diagnostics); ExitCode::FAILURE } } } -fn compile(input: std::path::PathBuf) -> ExitCode { +fn compile( + input: PathBuf, output: Option, wat: Option>, dump_dir: Option, target: Target, +) -> ExitCode { + if !matches!(target, Target::Wasmtime) { + echo::status( + "target", + format!("{target:?} selected; using the current generic WASM backend"), + ); + } + let source = match fs::read_to_string(&input) { - Ok(source) => source, + Ok(source) => SourceFile::with_path(SourceFileId(0), input.clone(), source), Err(error) => { echo::error(format!("could not read {}: {error}", input.display())); return ExitCode::FAILURE; } }; - let source = SourceFile::with_path(SourceFileId(0), input.clone(), source); - match compiler_core::compile_source(source) { - Ok(output) => { - echo::status( - "compiled", - format!("{} ({} bytes)", input.display(), output.wasm.bytes.len()), - ); - ExitCode::SUCCESS - } + let compiled = match compile_with_dumps(source) { + Ok(compiled) => compiled, Err(diagnostics) => { echo::error(format!("could not compile {}", input.display())); - for diagnostic in diagnostics { - echo::diagnostic(diagnostic.message); - } - ExitCode::FAILURE + echo::diagnostics(&diagnostics); + return ExitCode::FAILURE; } + }; + + if let Some(dump_dir) = dump_dir + && let Err(error) = write_debug_dumps(&dump_dir, &compiled) + { + echo::error(format!("could not write debug dumps: {error}")); + return ExitCode::FAILURE; + } + + let output = output.unwrap_or_else(|| input.with_extension("wasm")); + if let Err(error) = write_file(&output, &compiled.wasm.bytes) { + echo::error(format!("could not write {}: {error}", output.display())); + return ExitCode::FAILURE; + } + echo::status( + "wasm", + format!("{} ({} bytes)", output.display(), compiled.wasm.bytes.len()), + ); + + if let Some(wat_path) = wat { + let wat_path = wat_path.unwrap_or_else(|| output.with_extension("wat")); + if let Err(error) = write_file(&wat_path, compiled.wasm.wat.as_bytes()) { + echo::error(format!("could not write {}: {error}", wat_path.display())); + return ExitCode::FAILURE; + } + echo::status("wat", wat_path.display().to_string()); + } + + ExitCode::SUCCESS +} + +struct CompiledModule { + ast: compiler_core::ast::Module, + resolved: compiler_core::resolve::ResolvedModule, + typed: compiler_core::types::TypedModule, + ir: compiler_core::ir::Module, + wasm: compiler_core::wasm::WasmModule, +} + +fn compile_with_dumps(source: SourceFile) -> Result { + let cst = compiler_core::parse::parse(source)?; + let ast = compiler_core::ast::build(cst)?; + let resolved = compiler_core::resolve::resolve(ast.clone())?; + let typed = compiler_core::types::check(resolved.clone())?; + let ir = compiler_core::ir::lower(typed.clone())?; + let wasm = compiler_core::wasm::emit(ir.clone())?; + + Ok(CompiledModule { ast, resolved, typed, ir, wasm }) +} + +fn write_debug_dumps(dump_dir: &PathBuf, compiled: &CompiledModule) -> std::io::Result<()> { + fs::create_dir_all(dump_dir)?; + fs::write(dump_dir.join("ast.txt"), format!("{:#?}\n", compiled.ast))?; + fs::write(dump_dir.join("resolved.txt"), format!("{:#?}\n", compiled.resolved))?; + fs::write(dump_dir.join("typed.txt"), format!("{:#?}\n", compiled.typed))?; + fs::write(dump_dir.join("ir.txt"), format!("{:#?}\n", compiled.ir))?; + fs::write(dump_dir.join("wat.wat"), &compiled.wasm.wat)?; + Ok(()) +} + +fn write_file(path: &PathBuf, contents: &[u8]) -> std::io::Result<()> { + if let Some(parent) = path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent)?; } + fs::write(path, contents) } diff --git a/crates/cli/src/echo.rs b/crates/cli/src/echo.rs index 4703e30..d46edf8 100644 --- a/crates/cli/src/echo.rs +++ b/crates/cli/src/echo.rs @@ -1,3 +1,4 @@ +use compiler_core::diagnostic::Diagnostics; use owo_colors::OwoColorize; pub fn status(label: &str, message: impl AsRef) { @@ -11,3 +12,9 @@ pub fn error(message: impl AsRef) { pub fn diagnostic(message: impl AsRef) { eprintln!("{} {}", "diagnostic".bright_yellow().bold(), message.as_ref()); } + +pub fn diagnostics(diagnostics: &Diagnostics) { + for item in diagnostics { + diagnostic(item.render_plain()); + } +} diff --git a/docs/src/internal/tasks/10_cli_and_build_outputs.md b/docs/src/internal/tasks/10_cli_and_build_outputs.md index 7004b94..7659fef 100644 --- a/docs/src/internal/tasks/10_cli_and_build_outputs.md +++ b/docs/src/internal/tasks/10_cli_and_build_outputs.md @@ -9,19 +9,19 @@ Make the CLI compile projects and produce useful artifacts. ### Commands and inputs - [ ] Add project compile command using `gleam.toml`. -- [ ] Keep single-file compilation available for tests and examples. -- [ ] Add output path configuration. -- [ ] Add target selection for supported runtimes: Wasmtime, browser, and WASI +- [x] Keep single-file compilation available for tests and examples. +- [x] Add output path configuration. +- [x] Add target selection for supported runtimes: Wasmtime, browser, and WASI where implemented. - [ ] Add package/dependency discovery flags or configuration once dependency metadata is supported. -- [ ] Return useful exit codes for success, diagnostics, and command misuse. +- [x] Return useful exit codes for success, diagnostics, and command misuse. ### Artifacts -- [ ] Write `.wasm` artifacts. -- [ ] Add optional WAT output. -- [ ] Add optional AST, resolved AST, typed output, and IR debug dumps. +- [x] Write `.wasm` artifacts. +- [x] Add optional WAT output. +- [x] Add optional AST, resolved AST, typed output, and IR debug dumps. - [ ] Add optional runtime layout and ABI debug output where helpful. - [ ] Keep generated artifact names deterministic for multi-module projects. - [ ] Avoid writing partial final artifacts after a failed compile unless the @@ -31,9 +31,9 @@ Make the CLI compile projects and produce useful artifacts. - [ ] Render diagnostics with source snippets, labels, notes, and file paths. - [ ] Group diagnostics across project modules in a stable order. -- [ ] Show unsupported-feature diagnostics from AST, resolver, type, lowering, +- [x] Show unsupported-feature diagnostics from AST, resolver, type, lowering, backend, stdlib, and ABI stages without losing source spans. -- [ ] Keep normal compile output concise; make debug output opt-in. +- [x] Keep normal compile output concise; make debug output opt-in. - [ ] Add human-readable messages for missing project files, duplicate modules, unsupported exports, and backend target mismatches. -- 2.51.2