mod compiler; mod parsers; mod report; mod tokens; mod formats; pub use compiler::*; pub use parsers::*; pub use report::*; pub use tokens::*; pub use formats::*; pub use assembler::*; use log::{info, fatal}; use switchboard::{Switchboard, SwitchQuery}; use std::io::{Read, Write}; use std::str::FromStr; fn print_version() -> ! { let version = env!("CARGO_PKG_VERSION"); eprintln!("torque assembler, version {version}"); eprintln!("written by ben bridle"); std::process::exit(0); } fn main() { let mut args = Switchboard::from_env(); if args.named("version").as_bool() { print_version(); } if args.named("verbose").short('v').as_bool() { log::set_log_level(log::LogLevel::Info); } let source_path = args.positional("source").as_path_opt().map( |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}"))); let destination_path = args.positional("destination").as_path_opt(); let extension = args.named("ext").default("tq").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 format = args.named("format").default("debug").as_string(); let print_tree = args.named("tree").as_bool(); let dry_run = args.named("dry-run").short('n').as_bool(); let Ok(format) = Format::from_str(format.as_str()) else { fatal!("Unknown format '{format}', expected 'debug', 'inhx32', 'raw', or 'source'. "); }; // ----------------------------------------------------------------------- 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("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 format == Format::Source && !dry_run { write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref()); } // ----------------------------------------------------------------------- // Parse syntactic tokens from merged source code. let path = Some(""); let syntactic_tokens = SyntacticParser::new(&merged_source, path).parse(); report_syntactic_errors(&syntactic_tokens, &merged_source); let program = SemanticParser::new(syntactic_tokens).parse(); report_semantic_errors(&program, &merged_source); // program.print_definitions(); let assembled_tokens = program.assemble(); report_assembler_errors(&assembled_tokens, &merged_source); let bytecode = BytecodeGenerator::new(&assembled_tokens).generate(); report_bytecode_errors(&bytecode, &merged_source); if !dry_run { match format { Format::Debug => { let mut output = String::new(); for word in &bytecode.words { output.push_str(&word.to_string()); output.push('\n'); } write_bytes_and_exit(output.as_bytes(), destination_path.as_ref()); } Format::Inhx32 => { let output = format_inhx32(&bytecode.words); write_bytes_and_exit(output.as_bytes(), destination_path.as_ref()); } Format::Raw => { let mut output = Vec::new(); for word in &bytecode.words { let value = word.value as u16; output.extend(value.to_be_bytes()); } write_bytes_and_exit(&output, destination_path.as_ref()); } Format::Source => unreachable!(), } } } 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 {:?}", 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); } #[derive(PartialEq)] enum Format { Debug, Inhx32, Raw, Source, } impl FromStr for Format { type Err = (); fn from_str(string: &str) -> Result { match string { "debug" => Ok(Self::Debug), "inhx32" => Ok(Self::Inhx32), "raw" => Ok(Self::Raw), "source" => Ok(Self::Source), _ => Err(()), } } }