tx2m4as/
main.rs

1use std::error::Error;
2use std::ffi::OsString;
3use std::fmt::{self, Display, Formatter};
4use std::path::PathBuf;
5
6use clap::ArgAction::{Set, SetTrue};
7use clap::Parser;
8use tracing::{Level, event, span};
9use tracing_subscriber::prelude::*;
10
11use assembler::*;
12
13// Thanks to Google for allowing this code to be open-sourced.  I
14// generally prefer to correspond about this project using my
15// personal email address rather than my work one, though.
16const AUTHOR: &str = "James Youngman <james@youngman.org>";
17
18/// Assembler for the historical TX-2 computer
19#[derive(Parser, Debug)]
20#[clap(author = AUTHOR, version, about, long_about = None)]
21struct Cli {
22    /// File from which assembly source is read.
23    #[clap(action=Set)]
24    input: OsString,
25
26    /// File to which assembler output is written
27    #[clap(action = Set, short = 'o', long)]
28    output: OsString,
29
30    /// When set, list the assembler output (analogous to the listing
31    /// produced by the M4 assembler's LIST command).
32    #[clap(action = SetTrue, long)]
33    list: bool,
34}
35
36#[derive(Debug)]
37enum Fail {
38    /// We iniitialised the assembler but then it fails.
39    AsmFail(AssemblerFailure),
40    /// We were not able to correctly initialise the assembler.
41    InitialisationFailure(String),
42}
43
44impl Display for Fail {
45    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
46        match self {
47            Fail::AsmFail(assembler_failure) => assembler_failure.fmt(f),
48            Fail::InitialisationFailure(msg) => f.write_str(msg.as_str()),
49        }
50    }
51}
52
53impl Error for Fail {}
54fn run_asembler() -> Result<(), Fail> {
55    let cli = Cli::parse();
56
57    // See
58    // https://docs.rs/tracing-subscriber/0.2.19/tracing_subscriber/fmt/index.html#filtering-events-with-environment-variables
59    // for instructions on how to select which trace messages get
60    // printed.
61    let fmt_layer = tracing_subscriber::fmt::layer().with_target(true);
62    let filter_layer = match tracing_subscriber::EnvFilter::try_from_default_env()
63        .or_else(|_| tracing_subscriber::EnvFilter::try_new("info"))
64    {
65        Err(e) => {
66            return Err(Fail::InitialisationFailure(format!(
67                "failed to initialise tracing filter (perhaps there is a problem with environment variables): {e}"
68            )));
69        }
70        Ok(layer) => layer,
71    };
72
73    tracing_subscriber::registry()
74        .with(filter_layer)
75        .with(fmt_layer)
76        .init();
77
78    let span = span!(Level::ERROR, "assemble", input=?cli.input, output=?cli.output);
79    let _enter = span.enter();
80    let output_path = PathBuf::from(cli.output);
81    let options: OutputOptions = OutputOptions { list: cli.list };
82    let result = assemble_file(&cli.input, &output_path, options).map_err(Fail::AsmFail);
83    if let Err(e) = &result {
84        event!(Level::ERROR, "assembly failed: {:?}", e);
85    } else {
86        event!(Level::INFO, "assembly succeeded");
87    }
88    result
89}
90
91fn main() {
92    unsafe { backtrace_on_stack_overflow::enable() };
93
94    match run_asembler() {
95        Err(e) => {
96            eprintln!("{e}");
97            std::process::exit(1);
98        }
99        Ok(()) => {
100            std::process::exit(0);
101        }
102    }
103}