summaryrefslogtreecommitdiff
path: root/src/bin/br/asm.rs
blob: f4620cf66194de35dd9aef17965051be7ae13af7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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);
}