diff --git a/src/cli.rs b/src/cli.rs index eb1d8a7a..b4f6d75d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,5 @@ use crate::target::{Target, TargetOs}; -use std::{path::PathBuf, process::exit, str::FromStr}; +use std::{path::PathBuf, str::FromStr}; pub struct Command { pub kind: CommandKind, @@ -12,7 +12,7 @@ impl Command { match args.peek().map(|string| string.as_str()) { None | Some("-h" | "--help") => { show_help(); - exit(0); + Err(()) } Some("new") => Self::parse_new_project(args), _ => Self::parse_build_project(args), diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index c1435edf..52cd373b 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -22,51 +22,53 @@ pub struct Compiler<'a> { } impl<'a> Compiler<'a> { - pub fn maybe_execute_result(&self, output_binary_filepath: &Path) { - if self.options.excute_result { - println!(" ==== executing result ===="); + pub fn maybe_execute_result(&self, output_binary_filepath: &Path) -> Result<(), ()> { + if !self.options.excute_result { + return Ok(()); + } - // Avoid using a relative filename to invoke the resulting executable - let output_binary_filepath = if output_binary_filepath.is_relative() { - let Ok(absolute_filename) = absolute(&output_binary_filepath) else { - eprintln!( - "error: Failed to get absolute filename of resulting executable '{}'", - output_binary_filepath.to_string_lossy().as_ref(), - ); - std::process::exit(1); - }; + println!(" ==== executing result ===="); - Cow::Owned(absolute_filename) - } else { - Cow::Borrowed(output_binary_filepath) + // Avoid using a relative filename to invoke the resulting executable + let output_binary_filepath = if output_binary_filepath.is_relative() { + let Ok(absolute_filename) = absolute(&output_binary_filepath) else { + eprintln!( + "error: Failed to get absolute filename of resulting executable '{}'", + output_binary_filepath.to_string_lossy().as_ref(), + ); + return Err(()); }; - let mut last_error = None; + Cow::Owned(absolute_filename) + } else { + Cow::Borrowed(output_binary_filepath) + }; - for retry_duration in [10, 10, 10, 50, 100, 250].map(Duration::from_millis) { - match Command::new(output_binary_filepath.as_os_str()) - .args([] as [&str; 0]) - .spawn() - { - Ok(mut process) => { - let _ = process.wait(); - return; - } - Err(e) => { - last_error = Some(e); + let mut last_error = None; - // Try again in few milliseconds - std::thread::sleep(retry_duration); - } + for retry_duration in [10, 10, 10, 50, 100, 250].map(Duration::from_millis) { + match Command::new(output_binary_filepath.as_os_str()) + .args([] as [&str; 0]) + .spawn() + { + Ok(mut process) => { + let _ = process.wait(); + return Ok(()); } - } + Err(e) => { + last_error = Some(e); - eprintln!( - "error: failed to run resulting executable '{}' - {}", - output_binary_filepath.to_string_lossy().as_ref(), - last_error.unwrap() - ); - std::process::exit(1); + // Try again in few milliseconds + std::thread::sleep(retry_duration); + } + } } + + eprintln!( + "error: failed to run resulting executable '{}' - {}", + output_binary_filepath.to_string_lossy().as_ref(), + last_error.unwrap() + ); + return Err(()); } } diff --git a/src/generate_workspace/mod.rs b/src/generate_workspace/mod.rs index 02456079..54fcc3a2 100644 --- a/src/generate_workspace/mod.rs +++ b/src/generate_workspace/mod.rs @@ -6,15 +6,15 @@ use crate::cli::NewCommand; use indoc::indoc; -use std::{borrow::Borrow, fs, path::Path, process::exit}; +use std::{borrow::Borrow, fs, path::Path}; -pub fn new_project(new_command: NewCommand) { +pub fn new_project(new_command: NewCommand) -> Result<(), ()> { if std::fs::create_dir(&new_command.project_name).is_err() { eprintln!( "error: Failed to create project directory '{}'", &new_command.project_name ); - exit(1); + return Err(()); } let folder = Path::new(&new_command.project_name); @@ -27,7 +27,7 @@ pub fn new_project(new_command: NewCommand) { adept("3.0") } "#}, - ); + )?; put_file( folder.join("main.adept"), @@ -37,12 +37,13 @@ pub fn new_project(new_command: NewCommand) { println("Hello World!") } "#}, - ); + )?; println!("Project created!"); + Ok(()) } -fn put_file(path: impl Borrow, content: &str) { +fn put_file(path: impl Borrow, content: &str) -> Result<(), ()> { let path = path.borrow(); if fs::write(path, content).is_err() { @@ -52,6 +53,8 @@ fn put_file(path: impl Borrow, content: &str) { .unwrap_or(""); eprintln!("error: Failed to create '{}' file", error_filename); - exit(1); + return Err(()); } + + Ok(()) } diff --git a/src/linking/mod.rs b/src/linking/mod.rs index 5b756ffd..8c96f1bd 100644 --- a/src/linking/mod.rs +++ b/src/linking/mod.rs @@ -114,11 +114,11 @@ pub fn link_result( .join(&format!("from_{}_{}", host_arch.unwrap(), host_os.unwrap())) .join("x86_64-w64-mingw32-ld") } else { - please_manually_link(args, diagnostics); + return Err(please_manually_link(args, diagnostics)); } } Some(TargetOs::Mac | TargetOs::Linux | TargetOs::FreeBsd) | None => { - please_manually_link(args, diagnostics); + return Err(please_manually_link(args, diagnostics)); } } }; @@ -143,7 +143,7 @@ pub fn link_result( Ok(start_time.elapsed()) } -fn please_manually_link(args: Vec, diagnostics: &Diagnostics) -> ! { +fn please_manually_link(args: Vec, diagnostics: &Diagnostics) -> BackendError { let args = args.join(OsStr::new(" ")); diagnostics.push(WarningDiagnostic::plain( @@ -153,8 +153,7 @@ fn please_manually_link(args: Vec, diagnostics: &Diagnostics) -> ! { ) )); - eprintln!("Success, but requires manual linking, exiting with 1"); - std::process::exit(1); + BackendError::plain("Success, but requires manual linking, exiting with 1") } fn is_flag_like(string: &str) -> bool { diff --git a/src/main.rs b/src/main.rs index 5e973734..ca689200 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,27 +48,31 @@ mod version; mod workspace; use crate::{cli::BuildCommand, show::Show, source_files::SourceFiles, text::IntoText}; +use cli::CommandKind; use compiler::Compiler; use diagnostics::{DiagnosticFlags, Diagnostics, WarningDiagnostic}; use generate_workspace::new_project; use single_file_only::compile_single_file_only; -use std::{fs::metadata, path::Path, process::exit}; +use std::{fs::metadata, path::Path, process::ExitCode}; use target::{TargetArch, TargetOs}; use text::IntoTextStream; use workspace::compile_workspace; -fn main() { +fn main() -> ExitCode { let Ok(args) = cli::Command::parse_env_args() else { - exit(1) + return ExitCode::FAILURE; }; - match args.kind { - cli::CommandKind::Build(build_command) => build_project(build_command), - cli::CommandKind::New(new_command) => new_project(new_command), + match match args.kind { + CommandKind::Build(build_command) => build_project(build_command), + CommandKind::New(new_command) => new_project(new_command), + } { + Ok(()) => ExitCode::SUCCESS, + Err(()) => ExitCode::FAILURE, } } -fn build_project(build_command: BuildCommand) { +fn build_project(build_command: BuildCommand) -> Result<(), ()> { let BuildCommand { filename, options } = build_command; let source_files = SourceFiles::new(); let filepath = Path::new(&filename); @@ -77,7 +81,7 @@ fn build_project(build_command: BuildCommand) { let Ok(metadata) = metadata(filepath) else { eprintln!("error: File or folder does not exist"); - exit(1); + return Err(()); }; if target.arch().is_none() { @@ -120,20 +124,17 @@ fn build_project(build_command: BuildCommand) { }; if metadata.is_dir() { - compile_workspace(&mut compiler, filepath, None); - return; + return compile_workspace(&mut compiler, filepath, None); } // Experimental header parsing if filepath.extension().unwrap_or_default() == "h" { let source_files = compiler.source_files; - let content = std::fs::read_to_string(filepath) - .map_err(|err| { - eprintln!("{}", err); - exit(1); - }) - .unwrap(); + let content = std::fs::read_to_string(filepath).map_err(|err| { + eprintln!("{}", err); + () + })?; let header_key = source_files.add(filepath.into(), content); @@ -144,22 +145,22 @@ fn build_project(build_command: BuildCommand) { .into_text_stream(header_key) .into_text(); - let preprocessed = exit_unless( + let preprocessed = unerror( c::preprocessor::preprocess(header_contents, &diagnostics), &source_files, - ); + )?; println!("{preprocessed:?}"); - return; + return Ok(()); } let project_folder = filepath.parent().unwrap(); - compile_single_file_only(&mut compiler, project_folder, filepath); + compile_single_file_only(&mut compiler, project_folder, filepath) } -fn exit_unless(result: Result, source_files: &SourceFiles) -> T { +fn unerror(result: Result, source_files: &SourceFiles) -> Result { match result { - Ok(value) => value, + Ok(value) => Ok(value), Err(err) => { let mut message = String::new(); @@ -167,7 +168,7 @@ fn exit_unless(result: Result, source_files: &SourceFiles) -> .expect("show error message"); eprintln!("{message}"); - exit(1); + Err(()) } } } diff --git a/src/single_file_only/mod.rs b/src/single_file_only/mod.rs index e4dd5ead..94ba6d40 100644 --- a/src/single_file_only/mod.rs +++ b/src/single_file_only/mod.rs @@ -7,6 +7,10 @@ use crate::{compiler::Compiler, workspace::compile_workspace}; use std::path::Path; -pub fn compile_single_file_only(compiler: &mut Compiler, project_folder: &Path, filepath: &Path) { +pub fn compile_single_file_only( + compiler: &mut Compiler, + project_folder: &Path, + filepath: &Path, +) -> Result<(), ()> { compile_workspace(compiler, project_folder, Some(filepath.to_path_buf())) } diff --git a/src/workspace/explore_within.rs b/src/workspace/explore_within.rs new file mode 100644 index 00000000..b4689876 --- /dev/null +++ b/src/workspace/explore_within.rs @@ -0,0 +1,37 @@ +use super::{ + explore::{explore, ExploreResult}, + fs::{Fs, FsNodeId}, + module_file::ModuleFile, +}; +use std::path::{Path, PathBuf}; + +pub struct ExploreWithinResult { + pub explored: Option, + pub entry: Option, +} + +pub fn explore_within( + fs: &Fs, + project_folder: &Path, + single_file: Option, +) -> ExploreWithinResult { + if let Some(single_file) = single_file { + let fs_node_id = fs.insert(&single_file, None).expect("inserted"); + + ExploreWithinResult { + explored: Some(ExploreResult { + normal_files: vec![], + module_files: vec![ModuleFile { + path: single_file, + fs_node_id, + }], + }), + entry: Some(fs_node_id), + } + } else { + ExploreWithinResult { + explored: explore(fs, project_folder), + entry: None, + } + } +} diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index f6d6a575..58863432 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -6,6 +6,7 @@ pub mod compile; mod explore; +mod explore_within; mod file; pub mod fs; mod module_file; @@ -17,7 +18,6 @@ use crate::{ ast::{AstWorkspace, Settings}, compiler::Compiler, diagnostics::ErrorDiagnostic, - exit_unless, inflow::Inflow, interpreter_env::{run_build_system_interpreter, setup_build_system_interpreter_symbols}, line_column::Location, @@ -27,12 +27,14 @@ use crate::{ show::Show, source_files::{Source, SourceFileKey}, token::Token, + unerror, }; use compile::{ compile_code_file, module::{compile_module_file, CompiledModule}, }; use explore::{explore, ExploreResult}; +use explore_within::{explore_within, ExploreWithinResult}; use file::CodeFile; use fs::{Fs, FsNodeId}; use indexmap::IndexMap; @@ -45,39 +47,12 @@ use std::{ ffi::OsString, fs::create_dir_all, path::{Path, PathBuf}, - process::exit, sync::Barrier, - time::Instant, }; use thousands::Separable; const NUM_THREADS: usize = 8; -fn explore_constrained( - fs: &Fs, - project_folder: &Path, - single_file: Option, -) -> (Option, Option) { - if let Some(single_file) = single_file { - let fs_node_id = fs.insert(&single_file, None).expect("inserted"); - - let file = ModuleFile { - path: single_file, - fs_node_id, - }; - - return ( - Some(ExploreResult { - normal_files: vec![], - module_files: vec![file], - }), - Some(fs_node_id), - ); - } - - (explore(&fs, project_folder), None) -} - fn queue_dependencies>( compiler: &Compiler, fs: &Fs, @@ -159,23 +134,22 @@ pub fn compile_workspace( compiler: &mut Compiler, project_folder: &Path, single_file: Option, -) { - let start_time = Instant::now(); - let stats = CompilationStats::new(); +) -> Result<(), ()> { + let stats = CompilationStats::start(); let fs = Fs::new(); - let (exploration, guaranteed_entry) = explore_constrained(&fs, project_folder, single_file); + let ExploreWithinResult { explored, entry } = explore_within(&fs, project_folder, single_file); let Some(ExploreResult { module_files, normal_files, - }) = exploration + }) = explored else { eprintln!( "error: Could not locate workspace folder '{}'", project_folder.display() ); - exit(1); + return Err(()); }; let thread_count = (normal_files.len() + module_files.len()).min(NUM_THREADS); @@ -234,7 +208,7 @@ pub fn compile_workspace( } }); - let in_how_many_seconds = start_time.elapsed().as_millis() as f64 / 1000.0; + let in_how_many_seconds = stats.seconds_elapsed(); // SAFETY: This is okay since all modifying threads were joined (and thereby synchronized) let num_module_files_failed = stats.failed_modules_estimate(); @@ -242,8 +216,7 @@ pub fn compile_workspace( eprintln!( "error: {num_module_files_failed} module file(s) were determined to have errors in {in_how_many_seconds:.2} seconds", ); - - exit(1); + return Err(()); } // SAFETY: This is okay since all modifying threads were joined (and thereby synchronized) @@ -252,40 +225,39 @@ pub fn compile_workspace( eprintln!( "error: {num_files_failed} file(s) were determined to have errors in {in_how_many_seconds:.2} seconds", ); - - exit(1); + return Err(()); } let Some(_adept_version) = compiler.version.get() else { eprintln!("error: No Adept version was specified! Use `pragma => adept(\"3.0\")` at the top of the module file"); - exit(1); + return Err(()); }; let module_folders = HashMap::::from_iter(queue.module_folders.into_iter()); let mut files = IndexMap::from_iter(queue.ast_files.into_iter()); if compiler.options.interpret { - if let Some(guaranteed_entry) = guaranteed_entry { + if let Some(guaranteed_entry) = entry { setup_build_system_interpreter_symbols(files.get_mut(&guaranteed_entry).unwrap()); } else { eprintln!( "error: experimental manual interpreter does not properly handle multiple files yet" ); - exit(1); + return Err(()); } } let workspace = AstWorkspace::new(fs, files, compiler.source_files, Some(module_folders)); - let resolved_ast = exit_unless( + let resolved_ast = unerror( resolve(&workspace, &compiler.options), compiler.source_files, - ); + )?; - let ir_module = exit_unless( + let ir_module = unerror( lower(&compiler.options, &resolved_ast, &compiler.target), compiler.source_files, - ); + )?; let project_name = project_folder .file_name() @@ -303,10 +275,10 @@ pub fn compile_workspace( if compiler.options.interpret { match run_build_system_interpreter(&resolved_ast, &ir_module) { - Ok(_) => return, + Ok(_) => return Ok(()), Err(err) => { eprintln!("{}", err); - exit(1); + return Err(()); } } } @@ -320,7 +292,7 @@ pub fn compile_workspace( let exe_filepath = bin_folder.join(compiler.target.default_executable_name(&project_name)); let obj_filepath = obj_folder.join(compiler.target.default_object_file_name(&project_name)); - let linking_duration = exit_unless( + let linking_duration = unerror( unsafe { llvm_backend( compiler, @@ -332,11 +304,11 @@ pub fn compile_workspace( ) }, compiler.source_files, - ); + )?; // Print summary: - let in_how_many_seconds = start_time.elapsed().as_millis() as f64 / 1000.0; + let in_how_many_seconds = stats.seconds_elapsed(); let _linking_took = linking_duration.as_millis() as f64 / 1000.0; // SAFETY: This is okay, as we synchronized by joining @@ -351,5 +323,5 @@ pub fn compile_workspace( bytes_processed, files_processed, in_how_many_seconds, ); - compiler.maybe_execute_result(&exe_filepath); + compiler.maybe_execute_result(&exe_filepath) } diff --git a/src/workspace/stats.rs b/src/workspace/stats.rs index f2d222ae..73856a43 100644 --- a/src/workspace/stats.rs +++ b/src/workspace/stats.rs @@ -1,7 +1,11 @@ use crate::data_units::ByteUnits; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time::Instant, +}; pub struct CompilationStats { + pub start_time: Instant, files_processed: AtomicU64, bytes_processed: AtomicU64, num_files_failed: AtomicU64, @@ -9,8 +13,9 @@ pub struct CompilationStats { } impl CompilationStats { - pub fn new() -> Self { + pub fn start() -> Self { Self { + start_time: Instant::now(), files_processed: AtomicU64::new(0), bytes_processed: AtomicU64::new(0), num_files_failed: AtomicU64::new(0), @@ -18,6 +23,10 @@ impl CompilationStats { } } + pub fn seconds_elapsed(&self) -> f64 { + self.start_time.elapsed().as_millis() as f64 / 1000.0 + } + pub fn failed_files_estimate(&self) -> u64 { self.num_files_failed.load(Ordering::Relaxed) }