diff --git a/Cargo.toml b/Cargo.toml index 9ae7c73..c934ada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [workspace] -members = ["emulator", "common", "assembler", "dsa_editor", "compiler"] +members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "dsx-build"] resolver = "3" [workspace.package] diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index d40c38a..cf293cd 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -24,5 +24,22 @@ pub mod prelude { pub use crate::tooling::project; } +use std::path::Path; + use num_cpus as _; use threadpool as _; + +use crate::prelude::CompilerEngine; + +pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> { + let mut engine = CompilerEngine::new(); + engine.start_compilation(Path::new(input)); + let result = engine.wait_for_result().unwrap(); + for instruction in result { + if let Err(e) = std::fs::write(output, instruction.encode().to_be_bytes()) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } + } + Ok(()) +} diff --git a/dsx-build/Cargo.toml b/dsx-build/Cargo.toml new file mode 100644 index 0000000..3bce473 --- /dev/null +++ b/dsx-build/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "dsx-build" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +compiler = { path = "../compiler" } +assembler = { path = "../assembler" } +chrono = "0.4.43" diff --git a/dsx-build/src/main.rs b/dsx-build/src/main.rs new file mode 100644 index 0000000..85e9512 --- /dev/null +++ b/dsx-build/src/main.rs @@ -0,0 +1,223 @@ +use std::process::{Command, Stdio}; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +/// Run a command and exit on failure. +fn run(cmd: &mut Command) { + let status = cmd.status().expect("failed to execute command"); + if !status.success() { + std::process::exit(1); + } +} + +fn main() { + // Very small CLI – only three sub‑commands. + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: dsx-build [options]"); + std::process::exit(1); + } + match args[1].as_str() { + "new" => cmd_new(&args[2..]), + "build" => cmd_build(), + "package" => todo!("Package manager stub – not implemented yet."), + _ => { + eprintln!("Unknown command: {}", args[1]); + std::process::exit(1); + } + } +} + +// ---------- new project ---------------------------------------------------- +fn cmd_new(args: &[String]) { + let mut lang = "dsa"; + for i in 0..args.len() { + if args[i] == "--lang" && i + 1 < args.len() { + lang = &args[i + 1]; + } + } + + // Determine project root: a subdirectory named after the supplied --name argument. + let mut name_opt = None; + for i in 0..args.len() { + if args[i] == "--name" && i + 1 < args.len() { + name_opt = Some(&args[i + 1]); + break; + } + } + + let project_name = match name_opt { + Some(name) => name.to_string(), + None => { + eprintln!("Error: --name argument required"); + std::process::exit(1); + } + }; + + let cwd = env::current_dir().unwrap(); + let src_path = cwd.join(&project_name).join("src"); + fs::create_dir_all(&src_path).expect("Failed to create project directory"); + + match lang { + "dsa" => { + // Minimal DSA binary template. + let path = src_path.join(format!("main.dsa")); + let template = format!( + r#" +// GENERATED BY DSX-BUILD +// Generated at: {timestamp} +// Project name: {project_name} + +// Imports +include print: "./lib/io/print.dsa" + +// Globals & Reserved Memory +dw stack: 0x10000 +db message: "Process Exited with code:" + +// Entry Point +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + + main: + push bpr + mov spr, bpr + + lli 0, rg0 + stw rg0, bpr, 8 + + mov bpr, spr + pop bpr + return"#, + project_name = project_name, + timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + ); + fs::write(path, template).expect("Unable to write DSA file"); + } + "dsc" => { + let path = src_path.join(format!("main.dsc")); + let template = r#" +include print: "./lib/io/print.dsa"; + +fn main() -> u32 { + return 0; +}"#; + fs::write(path, template).expect("Unable to write DSC file"); + } + _ => { + eprintln!("Unsupported language: {}", lang); + std::process::exit(1); + } + } + + println!( + "Created new {} project in {}.", + lang, + src_path.parent().unwrap().display() + ); +} + +// ---------- build ---------------------------------------------------------- +fn cmd_build() { + let cwd = env::current_dir().unwrap(); + + // Detect .dsc or .dsa files in current directory. + let mut has_dsc = false; + let mut has_dsa = false; + for entry in fs::read_dir(&cwd.join("src")).expect("unable to read dir") { + if let Ok(entry) = entry { + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) == Some("dsc") { + has_dsc = true; + } else if path.extension().and_then(|s| s.to_str()) == Some("dsa") { + has_dsa = true; + } + } + } + + if !has_dsc && !has_dsa { + eprintln!("No .dsc or .dsa source found in src directory."); + std::process::exit(1); + } + + // Assemble main.dsa to a dsb binary. + println!("Assembling Project to a DSB binary..."); + let build_dir = cwd.join("build"); + fs::create_dir_all(&build_dir).expect("Failed to create build directory"); + + // Copy everything from `cwd/src` to the build directory. + fn copy_recursively(src: &Path, dst: &Path) { + if src.is_file() { + fs::create_dir_all(dst.parent().unwrap()) + .expect("Failed to create parent directory"); + fs::copy(src, dst).expect("Failed to copy file"); + } else if src.is_dir() { + for entry in fs::read_dir(src).expect("Unable to read source dir") { + let entry = entry.expect("Failed to read entry"); + let child_src = entry.path(); + let child_dst = dst.join(entry.file_name()); + copy_recursively(&child_src, &child_dst); + } + } + } + + let src_dir = cwd.join("src"); + if src_dir.exists() { + copy_recursively(&src_dir, &build_dir); + } + + // Change current working directory to the build directory. + env::set_current_dir(&build_dir).expect("Failed to change to build directory"); + + if has_dsc { + println!("Compiling DSC to DSA..."); + fn compile_recursive(path: &Path) { + if path.is_dir() { + for entry in fs::read_dir(path).expect("unable to read dir") { + let entry = entry.expect("failed to read entry"); + compile_recursive(&entry.path()); + } + } else if path.extension().and_then(|s| s.to_str()) == Some("dsc") { + let input_path = path; + let output_path = path.with_extension("dsa"); + compiler::compile_file(&input_path, &output_path).unwrap_or_else(|e| { + eprintln!("Failed to compile {:?}: {}", input_path, e); + std::process::exit(1); + }); + } + } + compile_recursive(&build_dir); + } + + // Replace .dsc with .dsa only in include statements, recursively for each file. + let mut sed_cmd = Command::new("bash"); + sed_cmd.args(&[ + "-c", + &format!( + "find \"{}\" -type f -name '*.dsa' -exec sed -i '/^include/ s/\\.dsc/.dsa/g' {{}} +", + build_dir.display() + ), + ]); + run(&mut sed_cmd); + + fs::create_dir_all(&cwd.join("artifacts")).expect("Failed to create build directory"); + assembler::assemble_file("./main.dsa", "../artifacts/out.dsb").unwrap_or_else(|e| { + eprintln!("Failed to assemble {:?}: {}", "./main.dsa", e); + std::process::exit(1); + }); + + println!("Build finished. Binary at {}/main.dsb", build_dir.display()); +}