use crate::formats::*;
use bedrock_asm::*;
use log::{info, fatal};
use switchboard::*;
use std::io::{Read, Write};
use std::path::Path;
pub fn main(mut args: Switchboard) {
args.positional("source");
args.positional("destination");
args.named("extension").default("brc");
args.named("no-libs");
args.named("no-project-libs");
args.named("no-env-libs");
args.named("format").default("binary");
args.named("dry-run").short('n');
args.named("tree");
args.named("with-symbols");
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_path = 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 dry_run = args.get("dry-run").as_bool();
let print_tree = args.get("tree").as_bool();
let export_symbols = args.get("with-symbols").as_bool();
// -----------------------------------------------------------------------
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("BEDROCK_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_path.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 AssembledProgram { mut bytecode, symbols } = generate_bytecode(&semantic);
// Remove null bytes from end of bytecode.
while let Some(0) = bytecode.last() {
bytecode.pop();
}
let length = bytecode.len();
let percentage = (length as f32 / 65536.0 * 100.0).round() as u16;
info!("Assembled program in {length} bytes ({percentage}% of maximum)");
if !dry_run {
if export_symbols {
if let Some(path) = &destination_path {
let mut symbols_path = path.to_path_buf();
symbols_path.add_extension("sym");
let mut symbols_string = String::new();
for symbol in &symbols {
let address = &symbol.address;
let name = &symbol.name;
let location = &symbol.source.location();
symbols_string.push_str(&format!(
"{address:04x} {name} {location}\n"
));
}
match std::fs::write(&symbols_path, symbols_string) {
Ok(_) => info!("Saved symbols to {symbols_path:?}"),
Err(err) => info!("Could not write symbols to {symbols_path:?}\n{err:?}"),
}
}
}
let bytes = match format {
Format::Binary => bytecode,
Format::Clang => format_clang(&bytecode),
Format::Source => unreachable!("Source output is handled before full assembly"),
};
write_bytes_and_exit(&bytes, destination_path.as_ref());
}
}
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.as_ref()),
Err(err) => fatal!("Could not write to {:?}\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);
}