summaryrefslogtreecommitdiff
path: root/src/bin/bedrock-asm.rs
blob: 2a29ee3cb23535384013ac48ac551af97a48b0b4 (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
use bedrock_asm::*;

use std::io::{Read, Write};
use std::path::{Path, PathBuf};


static mut VERBOSE: bool = false;

macro_rules! verbose {
    ($($tokens:tt)*) => { if unsafe { VERBOSE } {
            eprint!("[INFO] "); eprintln!($($tokens)*);
    } };
}
macro_rules! error {
    ($($tokens:tt)*) => {{
        eprint!("[ERROR] "); eprintln!($($tokens)*); std::process::exit(1);
    }};
}


fn main() {
    let args = Arguments::from_env_or_exit();

    // -----------------------------------------------------------------------
    // RESOLVE syntactic symbols
    let ext = args.ext.unwrap_or(String::from("brc"));
    let mut resolver = if let Some(path) = &args.source {
        match SourceUnit::from_path(&path, &ext) {
            Ok(source_unit) => SymbolResolver::from_source_unit(source_unit),
            Err(err) => match err {
                ParseError::InvalidExtension => error!(
                    "File {path:?} has invalid extension, must be '.{ext}'"),
                ParseError::NotFound => error!(
                    "File {path:?} was not found"),
                ParseError::InvalidUtf8 => error!(
                    "File {path:?} does not contain valid UTF-8 text"),
                ParseError::NotReadable => error!(
                    "File {path:?} is not readable"),
                ParseError::IsADirectory => error!(
                    "File {path:?} is a directory"),
                ParseError::Unknown => error!(
                    "Unknown error while attempting to read from {path:?}")
            }
        }
    } else {
        let mut source_code = String::new();
        verbose!("Reading program source from standard input");
        if let Err(err) = std::io::stdin().read_to_string(&mut source_code) {
            eprintln!("Could not read from standard input, exiting.");
            eprintln!("({err:?})");
            std::process::exit(1);
        }
        let path = "<standard input>";
        let source_unit = SourceUnit::from_source_code(source_code, path);
        SymbolResolver::from_source_unit(source_unit)
    };
    // Load project libraries.
    if let Some(path) = &args.source {
        if !args.no_libs && !args.no_project_libs {
            let project_library = gather_project_libraries(path, &ext);
            resolver.add_library_units(project_library);
        }
    }
    // Load environment libraries.
    if !args.no_libs && !args.no_env_libs {
        for env_library in gather_environment_libraries(&ext) {
            resolver.add_library_units(env_library);
        }
    }
    resolver.resolve();

    // -----------------------------------------------------------------------
    // PRINT information, generate merged source code
    if args.tree {
        print_source_tree(&resolver);
    }
    if print_resolver_errors(&resolver) {
        std::process::exit(1);
    };
    let merged_source = resolver.get_merged_source_code();
    if args.resolve {
        write_bytes_and_exit(merged_source.as_bytes(), args.output.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);
    // let symbols = generate_symbols_file(&semantic_tokens);
    write_bytes_and_exit(&bytecode, args.output.as_ref());
}


fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! {
    if let Some(path) = path {
        if let Err(err) = std::fs::write(path, bytes) {
            eprintln!("Could not write to path {:?}, exiting.", path.as_ref());
            eprintln!("({err:?})");
            std::process::exit(1);
        }
    } else {
        if let Err(err) = std::io::stdout().write_all(bytes) {
            eprintln!("Could not write to standard output, exiting.");
            eprintln!("({err:?})");
            std::process::exit(1);
        }
    }
    std::process::exit(0);
}


xflags::xflags! {
    cmd arguments {
        /// Print additional debug information
        optional --verbose
        /// Print the assembler version and exit
        optional --version


        /// Bedrock source code file to assemble.
        optional source: PathBuf
        /// Destination path for assembler output.
        optional output: PathBuf
        /// File extension to identify source files.
        optional ext: String

        /// Don't include libraries or resolve references.
        optional --no-libs
        /// Don't include project libraries
        optional --no-project-libs
        /// Don't include environment libraries.
        optional --no-env-libs

        /// Show the resolved source file heirarchy
        optional --tree
        /// Assemble the program without saving any output
        optional --check
        /// Only return resolved source code.
        optional --resolve
    }
}