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, "<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_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("<main>"));
// println!();
// for t in parser {
// println!("{t:?}");
// }
// Parse syntactic tokens from merged source code.
let path = Some("<merged source>");
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<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);
}
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);
}
}
_ => (),
}
}
}