use torque_asm::*; use assembler::FileError; use log::{info, fatal}; use switchboard::*; use std::io::{Read, Write}; use std::path::Path; fn main() { let mut args = Switchboard::from_env(); args.positional("source"); args.positional("destination"); args.positional("extension").default("tq"); args.named("no-libs"); args.named("no-project-libs"); args.named("no-env-libs"); args.named("format").default("debug"); args.named("width"); args.named("dry-run").short('n'); args.named("tree"); args.named("help").short('h'); args.named("version"); args.named("verbose").short('v'); args.raise_errors(); let source_path = args.get("source").as_path_opt().map( |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}"))); let destination = args.get("destination").as_path_opt(); let extension = args.get("extension").as_string(); 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 format = Format::from_str(&args.get("format").as_string()); let width = args.get("width").as_u32_opt(); let dry_run = args.get("dry-run").as_bool(); let print_tree = args.get("tree").as_bool(); let print_help = args.get("help").as_bool(); let print_version = args.get("version").as_bool(); let verbose = args.get("verbose").as_bool(); if verbose { log::set_log_level(log::LogLevel::Info) } if print_version { let version = env!("CARGO_PKG_VERSION"); eprintln!("torque assembler, version {version}"); eprintln!("written by ben bridle"); std::process::exit(0); } if print_help { eprintln!("\ Usage: tq [source] [destination] Torque multi-assembler, see http://benbridle.com/torque for documentation. Arguments: [source] Path to a source file to assemble [destination] Path to which output will be written [extension] File extension to identify library files (default is 'tq') Switches: --format=<fmt> Format to apply to assembled bytecode (default is 'debug') --width=<width> Force a fixed width for all assembled words --no-project-libs Don't search for libraries in the source parent folder --no-env-libs Don't search for libraries in the TORQUE_LIBS path variable --no-libs Combination of --no-project-libs and --no-env-libs --tree Display a tree visualisation of all included library files --dry-run (-n) Assemble and show errors only, don't write any output --help (-h) Prints help --verbose, (-v) Print additional debug information --version Print the assembler version and exit Environment variables: TORQUE_LIBS A list of colon-separated paths which will be searched to find Torque source code files to use as libraries when assembling a Torque program. If a library file resolves an unresolved symbol in the program being assembled, the library file will be merged into the program. Output formats: <debug> Print assembled words as human-readable binary literals. <inhx> Original 8-bit Intel hex format. <inhx32> Modified 16-bit Intel hex format used by Microchip. <raw> Assembled words are converted to big-endian bytestrings and concatenated. Each word is padded to the nearest byte. Words must all be the same width. <source> Print the source file before assembly, with symbols resolved. Created by Ben Bridle. "); std::process::exit(0); } // ----------------------------------------------------------------------- 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, "<standard input>") }; if compiler.error().is_some() && !no_libs && !no_project_libs { compiler.include_libs_from_parent(&extension); } if compiler.error().is_some() && !no_libs && !no_env_libs { compiler.include_libs_from_path_variable("TORQUE_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 !dry_run && format == Format::Source { write_bytes_and_exit(merged_source.as_bytes(), destination.as_ref()); } // ----------------------------------------------------------------------- let path = Some("<merged source>"); 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 intermediate = match parse_intermediate(semantic) { Ok(tokens) => tokens, Err(errors) => { report_intermediate_errors(&errors, &merged_source); std::process::exit(1); } }; let segments = match parse_bytecode(intermediate, width) { Ok(segments) => segments, Err(errors) => { report_bytecode_errors(&errors, &merged_source); std::process::exit(1); } }; if !dry_run { let result = match format { Format::Debug => format_debug(&segments), Format::Inhx => format_inhx(&segments), Format::Inhx32 => format_inhx32(&segments), Format::Raw => format_raw(&segments, width), Format::Source => unreachable!("Source output is handled before merged assembly"), }; match result { Ok(bytes) => write_bytes_and_exit(&bytes, destination.as_ref()), Err(error) => report_format_error(&error, format, &merged_source), } } } fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! { match path { Some(path) => match std::fs::write(path, bytes) { Ok(_) => info!("Wrote output to path {:?}", path.as_ref()), Err(err) => fatal!("Could not write to path {:?}\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); }