From ab84ad75629b0a4124221023ca91411d2cd62a32 Mon Sep 17 00:00:00 2001
From: Ben Bridle <ben@derelict.engineering>
Date: Tue, 25 Mar 2025 12:46:49 +1300
Subject: Restructure program

This commit also includes changes to devices according to the latest
devices specification, in particular the math and system devices.
---
 src/bin/br/run.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 186 insertions(+)
 create mode 100644 src/bin/br/run.rs

(limited to 'src/bin/br/run.rs')

diff --git a/src/bin/br/run.rs b/src/bin/br/run.rs
new file mode 100644
index 0000000..86fdd43
--- /dev/null
+++ b/src/bin/br/run.rs
@@ -0,0 +1,186 @@
+use log::*;
+use crate::*;
+
+use bedrock_pc::*;
+use phosphor::*;
+
+use std::cmp::{min, max};
+use std::num::NonZeroU32;
+
+
+pub fn main(mut args: Switchboard) {
+    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 override_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 initial_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 Program { bytecode, path } = load_program(source.as_ref());
+
+    let symbols_path = path.as_ref().map(|p| {
+        let mut path = p.to_path_buf();
+        path.add_extension("sym");
+        path
+    });
+
+    let metadata = parse_metadata(&bytecode);
+    if metadata.is_none() {
+        info!("Could not read program metadata");
+    }
+
+    let config = EmulatorConfig {
+        initial_dimensions,
+        fullscreen,
+        zoom,
+        override_palette,
+        show_cursor,
+
+        decode_stdin,
+        encode_stdout,
+
+        symbols_path,
+        metadata,
+    };
+
+    if let Ok(phosphor) = Phosphor::new() {
+        match mode {
+            Mode::Dynamic => {
+                info!("Starting dynamic emulator");
+                let mut emulator = DynamicEmulator::new(&config, debug);
+                emulator.load_program(&bytecode);
+                emulator.run();
+                info!("Upgrading dynamic emulator to graphical emulator");
+                let emulator = emulator.to_graphical(config);
+                emulator.run(phosphor);
+            }
+            Mode::Graphical => {
+                info!("Starting graphical emulator");
+                let emulator = GraphicalEmulator::new(config, debug);
+                emulator.run(phosphor);
+            }
+            Mode::Headless => {
+                info!("Starting headless emulator");
+                let mut emulator = HeadlessEmulator::new(&config, debug);
+                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.run();
+            }
+            Mode::Graphical => {
+                fatal!("Could not start graphical event loop");
+            }
+            Mode::Headless => {
+                info!("Starting headless emulator");
+                let mut emulator = HeadlessEmulator::new(&config, debug);
+                emulator.run();
+            }
+        }
+    }
+}
+
+
+fn parse_dimensions(string: &str) -> ScreenDimensions {
+    fn parse_inner(string: &str) -> Option<ScreenDimensions> {
+        let (w_str, h_str) = string.trim().split_once('x')?;
+        Some( ScreenDimensions {
+            width:  w_str.parse().ok()?,
+            height: h_str.parse().ok()?,
+        } )
+    }
+    let dimensions = parse_inner(string).unwrap_or_else(|| {
+        fatal!("Invalid dimensions string '{string}'");
+    });
+    if dimensions.is_zero() {
+        fatal!("Screen dimensions must be greater than zero");
+    }
+    return dimensions;
+}
+
+fn parse_palette(string: &str) -> [Colour; 16] {
+    fn decode_ascii_hex_digit(ascii: u8) -> Option<u8> {
+        match ascii {
+            b'0'..=b'9' => Some(ascii - b'0'),
+            b'a'..=b'f' => Some(ascii - b'a' + 10),
+            b'A'..=b'F' => Some(ascii - b'A' + 10),
+            _ => { None }
+        }
+    }
+    fn parse_inner(string: &str) -> Option<[Colour; 16]> {
+        let mut c = Vec::new();
+        for token in string.split(',') {
+            let mut bytes = token.bytes();
+            if bytes.len() != 3 { return None; }
+            let r = decode_ascii_hex_digit(bytes.next().unwrap())?;
+            let g = decode_ascii_hex_digit(bytes.next().unwrap())?;
+            let b = decode_ascii_hex_digit(bytes.next().unwrap())?;
+            c.push(Colour::from_rgb(r*17, g*17, b*17));
+        }
+        Some(match c.len() {
+            2 => [ c[0],  c[1],  c[0],  c[1],  c[0],  c[1],  c[0],  c[1],
+                   c[0],  c[1],  c[0],  c[1],  c[0],  c[1],  c[0],  c[1] ],
+            3 => [ c[0],  c[1],  c[0],  c[2],  c[0],  c[1],  c[0],  c[2],
+                   c[0],  c[1],  c[0],  c[2],  c[0],  c[1],  c[0],  c[2] ],
+            4 => [ c[0],  c[1],  c[2],  c[3],  c[0],  c[1],  c[2],  c[3],
+                   c[0],  c[1],  c[2],  c[3],  c[0],  c[1],  c[2],  c[3] ],
+            8 => [ c[0],  c[1],  c[2],  c[3],  c[4],  c[5],  c[6],  c[7],
+                   c[0],  c[1],  c[2],  c[3],  c[4],  c[5],  c[6],  c[7] ],
+           16 => [ c[0],  c[1],  c[2],  c[3],  c[4],  c[5],  c[6],  c[7],
+                   c[8],  c[9], c[10], c[11], c[12], c[13], c[14], c[15] ],
+            _ => return None,
+        })
+    }
+    parse_inner(string).unwrap_or_else(|| {
+        fatal!("Invalid palette string '{string}'");
+    })
+}
+
+
+#[derive(Copy, Clone, PartialEq)]
+enum Mode {
+    Graphical,
+    Headless,
+    Dynamic,
+}
+
+impl Mode {
+    pub fn from_str(string: &str) -> Self {
+        match string {
+            "g" | "graphical" => Self::Graphical,
+            "h" | "headless" => Self::Headless,
+            "d" | "dynamic" => Self::Dynamic,
+            _ => fatal!("Invalid mode string '{string}'"),
+        }
+    }
+}
-- 
cgit v1.2.3-70-g09d2