mod compiler; mod environment; mod parsers; mod tokens; pub use compiler::*; pub use environment::*; pub use parsers::*; pub use tokens::*; pub use assembler::*; use log::{info, fatal}; use switchboard::{Switchboard, SwitchQuery}; use std::io::{Read, Write}; 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 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("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 only_resolve && !dry_run { write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref()); } // ----------------------------------------------------------------------- // // TODO: Remove this block // let code = &compiler.resolver.source_units[0].source_unit.main.source_code; // let parser = SyntacticParser::from_source_code(code, Some("
")); // println!(); // for t in parser { // println!("{t:?}"); // } // Parse syntactic tokens from merged source code. let path = Some(""); let parser = SyntacticParser::from_source_code(&merged_source, path); let syntactic_tokens: Vec<_> = parser.collect(); report_syntactic_errors(&syntactic_tokens, &merged_source); // let mut semantic_parser = SemanticParser::new(syntactic_tokens); // semantic_parser.parse(); } 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); } fn report_syntactic_errors(syntactic_tokens: &[syntactic::Token], source_code: &str) { use syntactic::*; for token in syntactic_tokens { let context = Context { source_code: &source_code, source: &token.source }; match &token.variant { TokenVariant::ConstantExpression(expr) => for t in &expr.tokens { let context = Context { source_code: &source_code, source: &t.source }; if let ConstantExpressionTokenVariant::Error(err) = &t.variant { let ConstantExpressionParseError::InvalidHexadecimalLiteral(hex) = err; let message = format!("Invalid hexadecimal literal {hex:?} in constant expression"); report_source_issue(LogLevel::Error, &context, &message); } } TokenVariant::PackedBinaryLiteral(pbl) => for e in &pbl.errors { let context = Context { source_code: &source_code, source: &e.source }; match e.variant { PackedBinaryLiteralParseErrorVariant::DuplicateFieldName(name) => { let message = format!("Duplicate field name {name:?} in packed binary literal"); report_source_issue(LogLevel::Error, &context, &message); } PackedBinaryLiteralParseErrorVariant::InvalidCharacter(c) => { let message = format!("Invalid character {c:?} in packed binary literal"); report_source_issue(LogLevel::Error, &context, &message); } } } TokenVariant::Error(err) => match err { ParseError::InvalidHexadecimalLiteral(hex) => { let message = format!("Invalid hexadecimal literal {hex:?}"); report_source_issue(LogLevel::Error, &context, &message); } ParseError::InvalidSymbolIdentifier(name) => { let message = format!("Invalid identifier {name:?}"); report_source_issue(LogLevel::Error, &context, &message); } ParseError::UnterminatedComment => { let message = format!("Unterminated comment"); report_source_issue(LogLevel::Error, &context, &message); } ParseError::UnterminatedConstantExpression => { let message = format!("Unterminated constant expression"); report_source_issue(LogLevel::Error, &context, &message); } } _ => (), } } }