summaryrefslogtreecommitdiff
path: root/src/bin/br/asm.rs
blob: cb8c35a2b5b8434c36a318602fe3df7a740034b5 (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
use crate::*;

use bedrock_asm::*;
use log::{info, fatal};

use std::io::{Read, Write};


pub fn main(mut args: Switchboard) {
    let source_path = args.positional("source").as_path_opt().map(
        |p| p.canonicalize().unwrap_or(p));
    let destination_path = args.positional("source").as_path_opt();
    let extension = args.positional("extension").default("brc").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("BEDROCK_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());
    }

    // -----------------------------------------------------------------------
    // PARSE semantic tokens from merged source code
    let path = Some("<merged source>");
    let mut semantic_tokens = generate_semantic_tokens(&merged_source, path);
    if print_semantic_errors(&semantic_tokens, &merged_source) {
        std::process::exit(1);
    };

    // -----------------------------------------------------------------------
    // GENERATE symbols file and bytecode
    let bytecode = generate_bytecode(&mut semantic_tokens);

    if export_symbols && !dry_run {
        if let Some(path) = &destination_path {
            let mut symbols_path = path.to_path_buf();
            symbols_path.set_extension("br.sym");
            let symbols = generate_symbols_file(&semantic_tokens);
            match std::fs::write(&symbols_path, symbols) {
                Ok(_) => info!("Saved debug symbols to {symbols_path:?}"),
                Err(err) => info!("Could not write symbols to {symbols_path:?}\n{err:?}"),
            }
        }
    }

    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 {
        write_bytes_and_exit(&bytecode, 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);
}