use torque_asm::*;
use log::*;
use switchboard::*;
use std::io::{Read, Write};
fn main() {
let mut args = Switchboard::from_env();
args.positional("source");
args.positional("destination");
args.named("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 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 format = Format::from_str(args.get("format").as_str());
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
Switches:
--extension=<ext> File extension to identify library files (default is 'tq')
--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) Print this help information
--verbose, (-v) Print additional 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 = 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, "<standard input>")
};
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("TORQUE_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.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::Cmd => format_cmd(&segments),
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 full 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);
}