use crate::*; use bedrock_asm::*; use log::{info, fatal}; use std::io::{Read, Write}; pub fn main(mut args: Switchboard) { let source_path = args.positional("source").as_path_opt().map( |p| p.canonicalize().unwrap_or(p)); let destination_path = args.positional("source").as_path_opt(); let extension = args.positional("extension").default("brc").as_string(); let no_libs = args.named("no-libs").as_bool(); let no_project_libs = args.named("no-project-libs").as_bool(); let no_environment_libs = args.named("no-env-libs").as_bool(); let print_tree = args.named("tree").as_bool(); let dry_run = args.named("dry-run").short('n').as_bool(); let only_resolve = args.named("resolve").as_bool(); let export_symbols = args.named("symbols").as_bool(); // ----------------------------------------------------------------------- let mut compiler = if let Some(path) = &source_path { info!("Reading program source from {path:?}"); Compiler::from_path(path).unwrap_or_else(|err| match err { FileError::InvalidExtension => fatal!( "File {path:?} has invalid extension, must be '.{extension}'"), FileError::NotFound => fatal!( "File {path:?} was not found"), FileError::InvalidUtf8 => fatal!( "File {path:?} does not contain valid UTF-8 text"), FileError::NotReadable => fatal!( "File {path:?} is not readable"), FileError::IsADirectory => fatal!( "File {path:?} is a directory"), FileError::Unknown => fatal!( "Unknown error while attempting to read from {path:?}") }) } else { let mut source_code = String::new(); info!("Reading program source from standard input"); if let Err(err) = std::io::stdin().read_to_string(&mut source_code) { fatal!("Could not read from standard input\n{err:?}"); } Compiler::from_string(source_code, "") }; if compiler.error().is_some() && !no_libs && !no_project_libs { compiler.include_libs_from_parent(&extension); } if compiler.error().is_some() && !no_libs && !no_environment_libs { compiler.include_libs_from_path_variable("BEDROCK_LIBS", &extension); } if print_tree { compiler.resolver.hierarchy().report() } if let Some(error) = compiler.error() { error.report(); std::process::exit(1); } let merged_source = compiler.get_compiled_source().unwrap_or_else( |error| { error.report(); std::process::exit(1); } ); if only_resolve && !dry_run { write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref()); } // ----------------------------------------------------------------------- // PARSE semantic tokens from merged source code let path = Some(""); let mut semantic_tokens = generate_semantic_tokens(&merged_source, path); if print_semantic_errors(&semantic_tokens, &merged_source) { std::process::exit(1); }; // ----------------------------------------------------------------------- // GENERATE symbols file and bytecode let bytecode = generate_bytecode(&mut semantic_tokens); if export_symbols && !dry_run { if let Some(path) = &destination_path { let mut symbols_path = path.to_path_buf(); symbols_path.set_extension("br.sym"); let symbols = generate_symbols_file(&semantic_tokens); match std::fs::write(&symbols_path, symbols) { Ok(_) => info!("Saved debug symbols to {symbols_path:?}"), Err(err) => info!("Could not write symbols to {symbols_path:?}\n{err:?}"), } } } let length = bytecode.len(); let percentage = (length as f32 / 65536.0 * 100.0).round() as u16; info!("Assembled program in {length} bytes ({percentage}% of maximum)"); if !dry_run { write_bytes_and_exit(&bytecode, destination_path.as_ref()); } } fn write_bytes_and_exit>(bytes: &[u8], path: Option<&P>) -> ! { match path { Some(path) => match std::fs::write(path, bytes) { Ok(_) => info!("Wrote output to {:?}", path.as_ref()), Err(err) => fatal!("Could not write to {:?}\n{err:?}", path.as_ref()), } None => match std::io::stdout().write_all(bytes) { Ok(_) => info!("Wrote output to standard output"), Err(err) => fatal!("Could not write to standard output\n{err:?}"), } } std::process::exit(0); }