#![feature(path_add_extension)] mod formats; mod types; mod stages; pub use formats::*; pub use types::*; pub use stages::*; use assembler::*; use log::*; use switchboard::*; use std::io::Read; use std::io::Write; pub const RETURN_MODE: u8 = 0x80; pub const WIDE_MODE: u8 = 0x40; pub const IMMEDIATE_MODE: u8 = 0x20; pub fn assemble(mut args: Switchboard, invocation: &str) { args.positional("source"); args.positional("destination"); args.named("extension").default("brc"); args.named("no-libs"); args.named("no-project-libs"); args.named("no-env-libs"); args.named("no-truncate"); args.named("format").default("raw"); args.named("dry-run").short('n'); args.named("tree"); args.named("with-symbols"); args.named("help").short('h'); args.raise_errors(); if args.get("help").as_bool() { print_help(invocation); std::process::exit(0); } let source_path = args.get("source").as_path_opt().map( |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}"))); let destination_path = args.get("destination").as_path_opt(); let extension = args.get("extension").as_string(); let opt_extension = Some(extension.as_str()); let no_libs = args.get("no-libs").as_bool(); let no_project_libs = args.get("no-project-libs").as_bool(); let no_env_libs = args.get("no-env-libs").as_bool(); let no_truncate = args.get("no-truncate").as_bool(); let format = Format::from_str(args.get("format").as_str()); let dry_run = args.get("dry-run").as_bool(); let print_tree = args.get("tree").as_bool(); let export_symbols = args.get("with-symbols").as_bool(); // ----------------------------------------------------------------------- let mut compiler = new_compiler(); if let Some(path) = &source_path { info!("Reading program source from {path:?}"); compiler.root_from_path(path).unwrap_or_else(|err| fatal!("{err:?}: {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.root_from_string(source_code, "") }; if compiler.error().is_some() && !no_libs && !no_project_libs { compiler.include_libs_from_parent(opt_extension); } if compiler.error().is_some() && !no_libs && !no_env_libs { compiler.include_libs_from_path_variable("BEDROCK_LIBS", opt_extension); } if print_tree { compiler.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 !dry_run && format == Format::Source { write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref()); } // ----------------------------------------------------------------------- let path = Some(""); let syntactic = match parse_syntactic(&merged_source, path) { Ok(tokens) => tokens, Err(errors) => { report_syntactic_errors(&errors, &merged_source); std::process::exit(1); } }; let semantic = match parse_semantic(syntactic) { Ok(tokens) => tokens, Err(errors) => { report_semantic_errors(&errors, &merged_source); std::process::exit(1); } }; let program = match generate_bytecode(&semantic) { Ok(program) => program, Err(errors) => { report_bytecode_errors(&errors, &merged_source); std::process::exit(1); } }; let AssembledProgram { mut bytecode, symbols } = program; 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 !no_truncate { // Remove null bytes from end of bytecode. while let Some(0) = bytecode.last() { bytecode.pop(); } let difference = length - bytecode.len(); if difference > 0 { info!("Truncated program to {length} bytes (saved {difference} bytes)"); } } if !dry_run { if export_symbols { if let Some(path) = &destination_path { let mut symbols_path = path.to_path_buf(); symbols_path.add_extension("sym"); let mut symbols_string = String::new(); for symbol in &symbols { let address = &symbol.address; let name = &symbol.name; let location = &symbol.source.location(); symbols_string.push_str(&format!( "{address:04x} {name} {location}\n" )); } match std::fs::write(&symbols_path, symbols_string) { Ok(_) => info!("Saved symbols to {symbols_path:?}"), Err(err) => info!("Could not write symbols to {symbols_path:?}\n{err:?}"), } } } let bytes = match format { Format::Raw => bytecode, Format::Clang => format_clang(&bytecode), Format::Source => unreachable!("Source output is handled before full assembly"), }; write_bytes_and_exit(&bytes, 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); } fn print_help(invocation: &str) { eprintln!("\ Usage: {invocation} [source] [destination] Convert Bedrock source code into an assembled Bedrock program. Usage: To assemble a Bedrock program from a source file and write to an output file, run `br-asm [source] [destination]`, where [source] is the path of the source file and [destination] is the path to write to. If [destination] is omitted, the assembled program will be written to standard output. If [source] is omitted, the program source code will be read from standard input. Environment variables: BEDROCK_LIBS A list of colon-separated paths which will be searched to find Bedrock source code files to use as libraries when assembling a Bedrock program. If a library file resolves an unresolved symbol in the program being assembled, the library file will be merged into the program. Arguments: [source] Bedrock source code file to assemble. [destination] Destination path for assembler output. Switches: --dry-run (-n) Assemble and show errors only, don't write any output --extension= File extension to identify source files (default is 'brc') --format= Output format to use for assembled program (default is 'raw') --no-project-libs Don't search for libraries in the source parent folder --no-env-libs Don't search for libraries in the BEDROCK_LIBS path variable --no-libs Combination of --no-project-libs and --no-env-libs --no-truncate Don't remove trailing zero-bytes from the assembled program --tree Show a tree diagram of all included library files --with-symbols Also generate debug symbols file with extension '.sym' --help (-h) Print this help information --verbose, (-v) Print additional information --version Print the program version and exit "); }