summaryrefslogtreecommitdiff
path: root/src/bin/br/main.rs
blob: 81c5ec9ac2996ef24b4be54839ae17cc5c1393c0 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#![feature(path_add_extension)]

mod config;
mod load;
pub use config::*;
pub use load::*;

use bedrock_asm::*;
use bedrock_core::*;
use bedrock_pc::*;
use log::*;
use switchboard::*;
use phosphor::*;

use std::cmp::{min, max};
use std::num::NonZeroU32;


fn main() {
    let mut args = Switchboard::from_env();
    if let Some("asm") = args.peek() {
        args.pop();
        assemble(args, "br asm");
    }

    // -----------------------------------------------------------------------

    args.named("help").short('h');
    args.named("version");
    args.named("verbose").short('v');

    if args.get("help").as_bool() {
        print_help();
        std::process::exit(0);
    }
    if args.get("version").as_bool() {
        let name = env!("CARGO_PKG_NAME");
        let version = env!("CARGO_PKG_VERSION");
        eprintln!("{name} v{version}");
        eprintln!("Written by Ben Bridle.");
        std::process::exit(0);
    }
    if args.get("verbose").as_bool() {
        log::set_log_level(log::LogLevel::Info);
    }

    args.positional("source");
    args.named("debug").short('d');
    args.named("mode").default("dynamic");

    args.named("palette");
    args.named("fullscreen").short('f');
    args.named("show-cursor").short('c');
    args.named("zoom").short('z').quick("3").default("1");
    args.named("size").short('s');
    args.named("decode-stdin").short('i');
    args.named("encode-stdout").short('o');
    args.raise_errors();

    let source             = args.get("source").as_path_opt();
    let debug              = args.get("debug").as_bool();
    let mode               = Mode::from_str(args.get("mode").as_str());
    let palette            = args.get("palette").as_str_opt().map(parse_palette);
    let fullscreen         = args.get("fullscreen").as_bool();
    let show_cursor        = args.get("show-cursor").as_bool();
    let zoom_raw           = min(10, max(1, args.get("zoom").as_u32()));
    let zoom               = unsafe { NonZeroU32::new_unchecked(zoom_raw) };
    let dimensions         = match args.get("size").as_str_opt() {
        Some(string) => parse_dimensions(string),
        None => DEFAULT_SCREEN_SIZE / (zoom_raw as u16),
    };
    let decode_stdin       = args.get("decode-stdin").as_bool();
    let encode_stdout      = args.get("encode-stdout").as_bool();

    // -----------------------------------------------------------------------

    let LoadedProgram { bytecode, path } = load_program(source.as_ref());
    let mut title = String::from("Bedrock program");
    let mut icon = None;

    if let Some(metadata) = Metadata::from(&bytecode) {
        let name = metadata.name().unwrap_or("unnamed".to_string());
        let authors = metadata.authors().unwrap_or_else(Vec::new);
        let mut metadata_string = format!("Program is '{name}'");
        if authors.len() > 0 {
            metadata_string.push_str(&format!(", by {}", authors[0])); }
        if authors.len() > 1 {
            metadata_string.push_str(" and others"); }
        info!("{metadata_string}");

        match name.split_once('/') {
            Some((name, _version)) if !name.is_empty() => title = name.to_string(),
            _ => title = name.to_string(),
        }
        let bg = parse_metadata_colour(metadata.bg_colour()).unwrap_or(Colour::rgb(0x1E1C26));
        let fg = parse_metadata_colour(metadata.fg_colour()).unwrap_or(Colour::rgb(0xED614F));
        match parse_large_icon(metadata.large_icon(), bg, fg) {
            Some(large_icon) => icon = Some(large_icon),
            None => match parse_small_icon(metadata.small_icon(), bg, fg) {
                Some(small_icon) => icon = Some(small_icon),
                None => (),
            }
        }
    } else {
        info!("Program does not contain metadata");
    }

    let symbols_path = path.as_ref().map(|p| {
        let mut path = p.to_path_buf();
        path.add_extension("sym"); path
    });

    let config = EmulatorConfig {
        dimensions, fullscreen, zoom, palette, show_cursor,
        decode_stdin, encode_stdout,
        symbols_path, title, icon,
    };

    if let Ok(phosphor) = Phosphor::new() {
        match mode {
            Mode::Dynamic => {
                info!("Starting graphical emulator");
                let mut emulator = GraphicalEmulator::new(config, debug);
                emulator.load_program(&bytecode);
                emulator.run(phosphor, false);
            }
            Mode::Graphical => {
                info!("Starting graphical emulator");
                let mut emulator = GraphicalEmulator::new(config, debug);
                emulator.load_program(&bytecode);
                emulator.run(phosphor, false);
            }
            Mode::Headless => {
                info!("Starting headless emulator");
                let mut emulator = HeadlessEmulator::new(&config, debug);
                emulator.load_program(&bytecode);
                emulator.run();
            }
        }
    } else {
        match mode {
            Mode::Dynamic => {
                info!("Could not start graphical event loop");
                info!("Starting headless emulator");
                let mut emulator = HeadlessEmulator::new(&config, debug);
                emulator.load_program(&bytecode);
                emulator.run();
            }
            Mode::Graphical => {
                fatal!("Could not start graphical event loop");
            }
            Mode::Headless => {
                info!("Starting headless emulator");
                let mut emulator = HeadlessEmulator::new(&config, debug);
                emulator.load_program(&bytecode);
                emulator.run();
            }
        }
    }
}


fn print_help() {
    eprintln!("\
Usage: br [source]
       br asm [source] [destination]

Emulator and assembler for the Bedrock computer system.

To access the assembler, run `br asm`. To learn how to use the
assembler, run `br asm --help`.

Usage:
  To load a Bedrock program from a file, run `br <path>`, where
  <path> is the path to an assembled Bedrock program.

  To load a Bedrock program from piped input, run `<command> | br`,
  where <command> is a command that generates Bedrock bytecode.

  To assemble a Bedrock program from a source file and write to an
  output file, run `br asm <source> <output>`, where <source> is the
  path of the source file and <output> is the path to write to.

  To assemble and run a Bedrock program without saving to a file,
  run `br asm <source> | br`, where <source> is the path of the
  source file.

Environment variables:
  BEDROCK_PATH
    A list of colon-separated paths that will be searched to find
    a Bedrock program when the program doesn't exist in the current
    directory. This allows the user to run frequently-used programs
    from any directory.
  BEDROCK_LIBS
    A list of colon-separated paths that will be searched to find
    Bedrock source code files to use as libraries when assembling a
    Bedrock program. If a library file resolves an unresolved symbol
    in the program being assembled, the library file will be merged
    into the program.

Arguments:
  [program]            Path to a Bedrock program to run

Switches:
  --fullscreen     (-f)  Start the program in fullscreen mode (toggle with F11)
  --size=<dim>     (-s)  Set the initial window size in the format <width>x<height>
  --zoom=<scale>   (-z)  Set the pixel size for the screen (change with F5/F6)
  --show-cursor    (-c)  Show the operating system cursor over the window
  --palette=<pal>        Set a debug colour palette in the format <rgb>,... (toggle with F2)
  --debug,         (-d)  Show debug information while the program is running
  --decode-stdin   (-i)  Decode transmissions on standard input from text lines.
  --encode-stdout  (-o)  Encode transmissions on standard output as text lines.
  --help           (-h)  Print this help information
  --verbose,       (-v)  Print additional information
  --version              Print the program version and exit
");
}