summaryrefslogtreecommitdiff
path: root/src/bin/br.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/br.rs')
-rw-r--r--src/bin/br.rs521
1 files changed, 0 insertions, 521 deletions
diff --git a/src/bin/br.rs b/src/bin/br.rs
deleted file mode 100644
index 431ae39..0000000
--- a/src/bin/br.rs
+++ /dev/null
@@ -1,521 +0,0 @@
-use bedrock_asm::*;
-use bedrock_pc::*;
-use phosphor::*;
-
-use std::io::{Read, Write};
-use std::path::{Path, PathBuf};
-use std::process::exit;
-
-
-const NORMAL: &str = "\x1b[0m";
-const BOLD: &str = "\x1b[1m";
-const WHITE: &str = "\x1b[37m";
-const RED: &str = "\x1b[31m";
-const BLUE: &str = "\x1b[34m";
-
-static mut VERBOSE: bool = false;
-
-macro_rules! verbose {
- ($($tokens:tt)*) => { if unsafe { VERBOSE } {
- eprint!("{BOLD}{BLUE}[INFO]{NORMAL}: "); eprint!($($tokens)*);
- eprintln!("{NORMAL}");
- } };
-}
-macro_rules! error {
- ($($tokens:tt)*) => {{
- eprint!("{BOLD}{RED}[ERROR]{WHITE}: "); eprint!($($tokens)*);
- eprintln!("{NORMAL}");
- }};
-}
-
-
-fn main() {
- let args = Arguments::from_env_or_exit();
- if args.version {
- 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.verbose {
- unsafe { VERBOSE = true; }
- }
- match args.subcommand {
- ArgumentsCmd::Run(run) => main_run(run),
- ArgumentsCmd::Asm(asm) => main_asm(asm),
- }
-}
-
-fn main_run(args: Run) {
- let program_path = args.program.as_ref().map(|p| p.as_path());
- let Bytecode { bytes: bytecode, path } = load_bytecode(program_path);
- let symbols_path = path.as_ref().map(|p| {
- let mut path = p.to_path_buf();
- path.set_extension("br.sym");
- path
- });
-
- let metadata = parse_metadata(&bytecode);
- if metadata.is_none() {
- verbose!("Could not read program metadata");
- }
-
- let mut config = EmulatorConfig {
- dimensions: ScreenDimensions::ZERO,
- fullscreen: args.fullscreen,
- scale: args.scale(),
- debug_palette: args.palette(),
- show_cursor: args.show_cursor,
- initial_transmission: None,
- decode_stdin: args.decode_stdin,
- encode_stdout: args.encode_stdout,
- symbols_path,
- };
- let phosphor = Phosphor::new();
-
- if phosphor.is_ok() && args.dimensions().is_some() {
- verbose!("Starting graphical emulator");
- let mut phosphor = phosphor.unwrap();
- config.dimensions = args.dimensions().unwrap();
- let cursor = match config.show_cursor {
- true => Some(CursorIcon::Default),
- false => None,
- };
-
- let mut graphical = GraphicalEmulator::new(&config, args.debug, unsafe {VERBOSE});
- graphical.load_program(&bytecode);
- if let EmulatorSignal::Promote = graphical.run() {
- let program_name = match &metadata {
- Some(metadata) => match &metadata.name {
- Some(name) => name.to_string(),
- None => String::from("Bedrock"),
- }
- None => String::from("Bedrock"),
- };
- let window = WindowBuilder {
- dimensions: Some(graphical.dimensions()),
- size_bounds: Some(graphical.size_bounds()),
- fullscreen: graphical.fullscreen,
- scale: graphical.scale,
- title: Some(program_name),
- cursor,
- icon: None,
- program: Box::new(graphical),
- };
-
- phosphor.create_window(window);
- phosphor.run().unwrap();
- }
- } else {
- verbose!("Starting headless emulator");
- let mut headless = HeadlessEmulator::new(&config, args.debug, unsafe {VERBOSE});
- headless.load_program(&bytecode);
- headless.run(args.debug);
- };
-
- std::process::exit(0);
-}
-
-fn load_bytecode(path: Option<&Path>) -> Bytecode {
- // TODO: Etch file location into bytecode.
- if let Some(path) = path {
- if let Ok(bytecode) = load_bytecode_from_file(path) {
- let length = bytecode.bytes.len();
- let path = bytecode.path();
- verbose!("Loaded program from {path:?} ({length} bytes)");
- return bytecode;
- } else if let Some(bytecode) = load_bytecode_from_bedrock_path(path) {
- let length = bytecode.bytes.len();
- let path = bytecode.path();
- verbose!("Loaded program from {path:?} ({length} bytes)");
- return bytecode;
- } else {
- error!("Could not read program from {path:?}, exiting");
- exit(1);
- }
- } else {
- verbose!("Reading program from standard input...");
- if let Ok(bytecode) = load_bytecode_from_stdin() {
- let length = bytecode.bytes.len();
- verbose!("Loaded program from standard input ({length} bytes)");
- return bytecode;
- } else {
- error!("Could not read program from standard input, exiting");
- exit(1);
- }
- }
-}
-
-/// Attempt to load bytecode from a directory in the BEDROCK_PATH environment variable.
-fn load_bytecode_from_bedrock_path(path: &Path) -> Option<Bytecode> {
- if path.is_relative() && path.components().count() == 1 {
- for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') {
- let mut base_path = PathBuf::from(base_path);
- if !base_path.is_absolute() { continue; }
- base_path.push(path);
- verbose!("Attempting to load program from {base_path:?}");
- if let Ok(bytecode) = load_bytecode_from_file(&base_path) {
- return Some(bytecode);
- }
- if path.extension().is_some() { continue; }
- base_path.set_extension("br");
- verbose!("Attempting to load program from {base_path:?}");
- if let Ok(bytecode) = load_bytecode_from_file(&base_path) {
- return Some(bytecode);
- }
- }
- }
- return None;
-}
-
-/// Attempt to load bytecode from a file path.
-fn load_bytecode_from_file(path: &Path) -> Result<Bytecode, std::io::Error> {
- // Canonicalize paths so that symbolic links to program files resolve to
- // the real program directory, which could contain a symbols file.
- let path = match path.canonicalize() {
- Ok(canonical) => canonical,
- Err(_) => path.to_path_buf(),
- };
- load_bytecode_from_readable_source(std::fs::File::open(&path)?, Some(&path))
-}
-
-/// Attempt to load bytecode from standard input.
-fn load_bytecode_from_stdin() -> Result<Bytecode, std::io::Error> {
- load_bytecode_from_readable_source(std::io::stdin(), None)
-}
-
-/// Attempt to load bytecode from a source that implements std::io::Read.
-fn load_bytecode_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<Bytecode, std::io::Error> {
- let mut bytes = Vec::<u8>::new();
- source.take(65536).read_to_end(&mut bytes)?;
- return Ok(Bytecode { bytes, path: path.map(|p| p.to_path_buf()) });
-}
-
-struct Bytecode {
- bytes: Vec<u8>,
- path: Option<PathBuf>,
-}
-
-impl Bytecode {
- fn path(&self) -> String {
- match &self.path {
- Some(path) => path.as_os_str().to_string_lossy().to_string(),
- None => String::from("<unknown>"),
- }
- }
-}
-
-
-fn main_asm(args: Asm) {
- // -----------------------------------------------------------------------
- // RESOLVE syntactic symbols
- let ext = args.ext.unwrap_or(String::from("brc"));
- let source_path = args.source.clone().map(|p| {
- p.canonicalize().unwrap_or(p)
- });
-
- let mut resolver = if let Some(path) = &source_path {
- 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:?}")
- };
- exit(1);
- }
- }
- } 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) {
- error!("Could not read from standard input");
- eprintln!("{err:?}");
- 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) = &source_path {
- 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 = match resolver.get_merged_source_code() {
- Ok(merged_source) => merged_source,
- Err(ids) => {
- print_cyclic_source_units(&ids, &resolver);
- std::process::exit(1);
- },
- };
- if args.resolve && !args.check {
- 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);
-
- if args.symbols && !args.check {
- if let Some(path) = &args.output {
- let mut symbols_path = path.to_path_buf();
- symbols_path.set_extension("br.sym");
- let symbols = generate_symbols_file(&semantic_tokens);
- if let Err(err) = std::fs::write(&symbols_path, symbols) {
- verbose!("Could not write to symbols path {symbols_path:?}");
- eprintln!("{err:?}");
- } else {
- verbose!("Saved debug symbols to {symbols_path:?}");
- }
- }
- }
-
- let length = bytecode.len();
- let percentage = (length as f32 / 65536.0 * 100.0).round() as u16;
- verbose!("Assembled program in {length} bytes ({percentage}% of maximum)");
-
- if !args.check {
- 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) {
- error!("Could not write to path {:?}", path.as_ref());
- eprintln!("{err:?}");
- exit(1);
- }
- } else {
- if let Err(err) = std::io::stdout().write_all(bytes) {
- error!("Could not write to standard output");
- eprintln!("{err:?}");
- exit(1);
- }
- }
- exit(0);
-}
-
-
-xflags::xflags! {
- /// Integrated Bedrock assembler and emulator.
- ///
- /// The arguments and options shown are for running a Bedrock program.
- /// To see the documentation for the assembler, run `br asm --help`
- ///
- /// Usage:
- /// To load a Bedrock program from a file, run `br [path]`, where
- /// [path] is the path of 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 which 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 which 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.
- cmd arguments {
- /// Print additional debug information
- optional -v, --verbose
- /// Print the assembler version and exit
- optional --version
-
- /// Run a Bedrock program (this is the default command)
- default cmd run {
- /// Path to a Bedrock program to run
- optional program: PathBuf
-
- /// Show debug information while the program is running
- optional -d, --debug
- /// Start the program in fullscreen mode (toggle with F11)
- optional -f, --fullscreen
- /// Set the initial window size in the format <width>x<height>
- optional -s, --screen size: ParsedDimensions
- /// Set the pixel size for the screen (change with F5/F6)
- optional -z, --zoom factor: u32
- /// Set a debug colour palette in the format <rgb>,... (toggle with F2)
- optional --palette colours: ParsedPalette
- /// Show the operating system cursor over the window
- optional --show-cursor
- /// Decode standard input
- optional -i, --decode-stdin
- /// Encode standard output
- optional -o, --encode-stdout
- }
-
- /// Assemble a Bedrock program from source code
- cmd asm {
- /// 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
- /// Generate debug symbols with file extension '.br.sym'
- optional --symbols
- }
- }
-}
-
-
-impl Run {
- pub fn dimensions(&self) -> Option<geometry::Dimensions<u16>> {
- let size = match &self.screen {
- Some(parsed) => geometry::Dimensions::new(parsed.width, parsed.height),
- None => DEFAULT_SCREEN_SIZE / (self.scale() as u16),
- };
- match size.is_zero() {
- true => None,
- false => Some(size),
- }
- }
-
- pub fn scale(&self) -> u32 {
- std::cmp::max(1, self.zoom.unwrap_or(1))
- }
-
- pub fn palette(&self) -> Option<[Colour; 16]> {
- self.palette.as_ref().map(|p| p.palette)
- }
-}
-
-
-#[derive(Debug)]
-struct ParsedDimensions { width: u16, height: u16 }
-impl std::str::FromStr for ParsedDimensions {
- type Err = ParsedDimensionsError;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- if s == "none" {
- Ok( Self { width: 0, height: 0 } )
- } else {
- let (w_str, h_str) = s.split_once('x').ok_or(ParsedDimensionsError)?;
- Ok( Self {
- width: u16::from_str(w_str).or(Err(ParsedDimensionsError))?,
- height: u16::from_str(h_str).or(Err(ParsedDimensionsError))?,
- } )
- }
- }
-}
-
-pub struct ParsedDimensionsError;
-impl std::fmt::Display for ParsedDimensionsError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- f.write_str("try giving a value like 800x600")
- }
-}
-
-
-#[derive(Debug)]
-pub struct ParsedPalette { palette: [Colour; 16] }
-impl std::str::FromStr for ParsedPalette {
- type Err = ParsedPaletteError;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- fn decode_ascii_hex_digit(ascii: u8) -> Result<u8, ParsedPaletteError> {
- match ascii {
- b'0'..=b'9' => Ok(ascii - b'0'),
- b'a'..=b'f' => Ok(ascii - b'a' + 10),
- b'A'..=b'F' => Ok(ascii - b'A' + 10),
- _ => { Err(ParsedPaletteError)}
- }
- }
- let mut colours = Vec::new();
- for token in s.split(',') {
- let mut bytes = token.bytes();
- if bytes.len() != 3 { return Err(ParsedPaletteError); }
- 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())?;
- colours.push(Colour::from_rgb(r*17, g*17, b*17));
- }
- let c = &colours;
- let palette = match colours.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 Err(ParsedPaletteError),
- };
- Ok( Self { palette })
- }
-}
-
-pub struct ParsedPaletteError;
-impl std::fmt::Display for ParsedPaletteError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- f.write_str("try giving a value like 000,fff,880,808")
- }
-}