diff --git a/src/driver.rs b/src/driver.rs index e921f429..74be7e91 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -44,7 +44,11 @@ pub enum OutputFormat LogiSim8, LogiSim16, AddressSpan, - + TCGame { + base: usize, + group: usize, + }, + Symbols, SymbolsMesenMlb, } @@ -56,20 +60,20 @@ pub fn drive_from_commandline( -> Result<(), ()> { util::enable_windows_ansi_support(); - + let mut report = diagn::Report::new(); - + let maybe_command = parse_command( &mut report, args); if let Ok(command) = maybe_command - { + { let maybe_result = assemble_with_command( &mut report, fileserver, &command); - + report.print_all( &mut std::io::stderr(), fileserver, @@ -98,7 +102,7 @@ pub fn drive( let command = parse_command( report, args)?; - + let maybe_result = assemble_with_command( report, fileserver, @@ -119,7 +123,7 @@ fn assemble_with_command( print_usage(command.use_colors); return Ok(asm::AssemblyResult::new()); } - + if command.show_version { print_version_full(); @@ -173,7 +177,7 @@ fn assemble_with_command( { println!(""); } - + println!( "{}", String::from_utf8_lossy(&formatted)); @@ -193,7 +197,7 @@ fn assemble_with_command( } } } - + if !command.quiet { println!( @@ -201,7 +205,7 @@ fn assemble_with_command( iterations_taken, if iterations_taken == 1 { "" } else { "s" }); } - + Ok(assembly) } @@ -211,7 +215,7 @@ fn make_opts() -> getopts::Options let asm_opts = asm::AssemblyOptions::new(); let mut opts = getopts::Options::new(); - + opts.optopt( "f", "format", "The format of the output file.\n\ @@ -224,7 +228,7 @@ fn make_opts() -> getopts::Options "FILE", getopts::HasArg::Maybe, getopts::Occur::Optional); - + opts.opt( "t", "iters", &format!( @@ -233,7 +237,7 @@ fn make_opts() -> getopts::Options "NUM", getopts::HasArg::Maybe, getopts::Occur::Optional); - + opts.optflag( "p", "print", "Print the output to the screen instead of writing to a file."); @@ -248,14 +252,14 @@ fn make_opts() -> getopts::Options "VALUE", getopts::HasArg::Yes, getopts::Occur::Multi); - + opts.opt( "", "color", "Style the output with colors. [on/off]", "VALUE", getopts::HasArg::Maybe, getopts::Occur::Optional); - + opts.optflag( "", "debug-iters", "Print debug info for the resolution iterations."); @@ -275,7 +279,7 @@ fn make_opts() -> getopts::Options opts.optflag( "h", "help", "Display this information."); - + opts } @@ -360,7 +364,7 @@ fn parse_command( command.opts.optimize_instruction_matching &= !parsed.opt_present("debug-no-optimize-matcher"); - + if parsed.opt_present("color") { command.use_colors = { @@ -376,7 +380,7 @@ fn parse_command( } }; } - + if let Some(t) = parsed.opt_str("t") { command.opts.max_iterations = { @@ -456,18 +460,18 @@ fn derive_output_filename( let mut output_filename = std::path::PathBuf::from(input_filename); output_filename.set_extension(extension); - + let output_filename = output_filename .to_string_lossy() .into_owned() .replace("\\", "/"); - + if output_filename == input_filename { report.error("cannot derive safe output filename"); return Err(()); } - + Ok(output_filename) } @@ -491,7 +495,7 @@ pub fn parse_output_format( .collect::>(); let param_id = param_split[0]; - + if param_split.len() == 1 { params.insert(param_id.to_string(), "".to_string()); @@ -542,7 +546,7 @@ pub fn parse_output_format( format_id, param_id, value)); - + Err(()) } } @@ -558,6 +562,11 @@ pub fn parse_output_format( [2, 4, 8, 16, 32, 64, 128].contains(&base) }; + let check_2_or_16 = &mut |base: usize| -> bool + { + [2, 16].contains(&base) + }; + let format = { match format_id { @@ -583,7 +592,7 @@ pub fn parse_output_format( "bindump" => OutputFormat::BinDump, "hexdump" => OutputFormat::HexDump, - + "mif" => OutputFormat::Mif, "intelhex" => OutputFormat::IntelHex, @@ -600,7 +609,17 @@ pub fn parse_output_format( "logisim16" => OutputFormat::LogiSim16, "addrspan" => OutputFormat::AddressSpan, - + + "tcgame" => OutputFormat::TCGame { + base: get_arg_usize("base", 16, check_2_or_16)?, + group: get_arg_usize("group", 2, check_nonzero)?, + }, + + "tcgamebin" => OutputFormat::TCGame { + base: 2, + group: 8, + }, + "symbols" => OutputFormat::Symbols, "mesen-mlb" => OutputFormat::SymbolsMesenMlb, @@ -682,8 +701,8 @@ fn parse_define_arg( None, diagn::Span::new_dummy(), if has_negative_sign { split[1].get(1..).unwrap() } else { split[1] }); - - + + use std::ops::Neg; match maybe_value @@ -691,7 +710,7 @@ fn parse_define_arg( Ok(value) => expr::Value::make_integer( if has_negative_sign { value.neg() } else { value }), - + Err(()) => { report.error( @@ -726,10 +745,13 @@ pub fn format_output( { OutputFormat::Binary => return output.format_binary(), - + OutputFormat::Annotated { base, group } => output.format_annotated(fileserver, base, group), - + + OutputFormat::TCGame { base, group } => + output.format_tcgame(fileserver, base, group), + OutputFormat::BinStr => output.format_binstr(), OutputFormat::HexStr => output.format_hexstr(), @@ -750,9 +772,9 @@ pub fn format_output( OutputFormat::LogiSim8 => output.format_logisim(8), OutputFormat::LogiSim16 => output.format_logisim(16), - + OutputFormat::AddressSpan => output.format_addrspan(fileserver), - + OutputFormat::Symbols => decls.symbols.format_default(decls, defs), OutputFormat::SymbolsMesenMlb => decls.symbols.format_mesen_mlb(decls, defs), } diff --git a/src/usage_help.md b/src/usage_help.md index f88d4192..3c3eb41c 100644 --- a/src/usage_help.md +++ b/src/usage_help.md @@ -61,8 +61,7 @@ Examples: * `annotated,base:16,group:2` Annotates the output data with snippets - of the source code. - + of the source code. * `annotatedbin` Same as: `annotated,base:2,group:8` @@ -87,5 +86,15 @@ Examples: * `addrspan` +* `tcgame,base:16,group:2` + Annotates the output data with snippets + of the source code in a format compatible + with the assembly editor for the game + "Turing Complete". Supports base 2 and 16. + Comments out annotations with `#` and prefixes + each group with `0x` or `0b`. +* `tcgamebin` + Same as: `tcgame,base:2,group:8` + * `symbols` * `mesen-mlb` \ No newline at end of file diff --git a/src/util/bitvec_format.rs b/src/util/bitvec_format.rs index f41b62a0..c2117773 100644 --- a/src/util/bitvec_format.rs +++ b/src/util/bitvec_format.rs @@ -6,7 +6,7 @@ impl util::BitVec pub fn format_binary(&self) -> Vec { let mut result = Vec::new(); - + let mut index = 0; while index < self.len() { @@ -17,30 +17,30 @@ impl util::BitVec byte |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + result.push(byte); } - + result } - + pub fn format_binstr(&self) -> String { self.format_str(1) } - - + + pub fn format_hexstr(&self) -> String { self.format_str(4) } - - + + pub fn format_str(&self, bits_per_digit: usize) -> String { let mut result = String::new(); - + let mut index = 0; while index < self.len() { @@ -51,18 +51,18 @@ impl util::BitVec digit |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + let c = if digit < 10 { ('0' as u8 + digit) as char } else { ('a' as u8 + digit - 10) as char }; - + result.push(c); } - + result } - + pub fn format_bindump(&self) -> String { @@ -84,74 +84,74 @@ impl util::BitVec -> String { let mut result = String::new(); - + let line_start = 0 / (byte_bits * bytes_per_line); let line_end = (self.len() + (bytes_per_line - 1) * byte_bits) / (byte_bits * bytes_per_line); - + let line_end = if self.len() < byte_bits { line_start + 1 } else { line_end }; - + let addr_max_width = format!("{:x}", (line_end - 1) * bytes_per_line).len(); - + for line_index in line_start..line_end { result.push_str(&format!(" {:01$x} | ", line_index * bytes_per_line, addr_max_width)); - + for byte_index in 0..bytes_per_line { for digit_index in 0..(byte_bits / digit_bits) { let digit_first_bit = (line_index * bytes_per_line + byte_index) * byte_bits + digit_index * digit_bits; - + if digit_first_bit >= self.len() { result.push('.'); continue; } - + let mut digit = 0; for bit_index in 0..digit_bits { digit <<= 1; digit |= if self.read_bit(digit_first_bit + bit_index) { 1 } else { 0 }; } - + let c = if digit < 10 { ('0' as u8 + digit) as char } else { ('a' as u8 + digit - 10) as char }; - + result.push(c); } - + result.push(' '); - + if byte_index % 4 == 3 && byte_index < bytes_per_line - 1 { result.push(' '); } } - + result.push_str("| "); - + if byte_bits == 8 { for byte_index in 0..bytes_per_line { let byte_first_bit = (line_index * bytes_per_line + byte_index) * byte_bits; - + if byte_first_bit >= self.len() { result.push('.'); continue; } - + let mut byte = 0u8; for bit_index in 0..byte_bits { byte <<= 1; byte |= if self.read_bit(byte_first_bit + bit_index) { 1 } else { 0 }; } - + let c = byte as char; - + if c == ' ' || c == '\t' || c == '\r' || c == '\n' { result.push(' '); } else if c as u8 >= 0x80 || c < ' ' || c == '|' @@ -159,23 +159,23 @@ impl util::BitVec else { result.push(c); } } - + result.push_str(" |"); } - + result.push('\n'); } - + result } - - + + pub fn format_mif(&self) -> String { let mut result = String::new(); - + let byte_num = self.len() / 8 + if self.len() % 8 != 0 { 1 } else { 0 }; - + result.push_str(&format!("DEPTH = {};\n", byte_num)); result.push_str("WIDTH = 8;\n"); result.push_str("ADDRESS_RADIX = HEX;\n"); @@ -183,14 +183,14 @@ impl util::BitVec result.push_str("\n"); result.push_str("CONTENT\n"); result.push_str("BEGIN\n"); - + let addr_max_width = format!("{:x}", byte_num - 1).len(); - + let mut index = 0; while index < self.len() { result.push_str(&format!(" {:1$X}: ", index / 8, addr_max_width)); - + let mut byte: u8 = 0; for _ in 0..8 { @@ -198,36 +198,36 @@ impl util::BitVec byte |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + result.push_str(&format!("{:02X};\n", byte)); } - + result.push_str("END;"); result } - - + + pub fn format_intelhex(&self) -> String { let mut result = String::new(); - + let mut bytes_left = self.len() / 8 + if self.len() % 8 != 0 { 1 } else { 0 }; - + let mut index = 0; while index < self.len() { let bytes_in_row = if bytes_left > 32 { 32 } else { bytes_left }; - + result.push(':'); result.push_str(&format!("{:02X}", bytes_in_row)); result.push_str(&format!("{:04X}", index / 8)); result.push_str("00"); - + let mut checksum = 0_u8; checksum = checksum.wrapping_add(bytes_in_row as u8); checksum = checksum.wrapping_add(((index / 8) >> 8) as u8); checksum = checksum.wrapping_add((index / 8) as u8); - + for _ in 0..bytes_in_row { let mut byte: u8 = 0; @@ -237,25 +237,25 @@ impl util::BitVec byte |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + result.push_str(&format!("{:02X}", byte)); checksum = checksum.wrapping_add(byte); } - + bytes_left -= bytes_in_row; result.push_str(&format!("{:02X}", (!checksum).wrapping_add(1))); result.push('\n'); } - + result.push_str(":00000001FF"); result } - - + + pub fn format_separator(&self, radix: usize, separator: &str) -> String { let mut result = String::new(); - + let mut index = 0; while index < self.len() { @@ -266,39 +266,39 @@ impl util::BitVec byte |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + match radix { 10 => result.push_str(&format!("{}", byte)), 16 => result.push_str(&format!("0x{:02x}", byte)), _ => panic!("invalid radix") } - + if index < self.len() - { + { result.push_str(separator); - + if (index / 8) % 16 == 0 { result.push('\n'); } } } - + result } - - + + pub fn format_c_array(&self, radix: usize) -> String { let mut result = String::new(); - + result.push_str("const unsigned char data[] = {\n"); - + let byte_num = self.len() / 8 + if self.len() % 8 != 0 { 1 } else { 0 }; let addr_max_width = format!("{:x}", byte_num - 1).len(); - + let mut index = 0; result.push_str(&format!("\t/* 0x{:01$x} */ ", 0, addr_max_width)); - + while index < self.len() { let mut byte: u8 = 0; @@ -308,36 +308,36 @@ impl util::BitVec byte |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + match radix { 10 => result.push_str(&format!("{}", byte)), 16 => result.push_str(&format!("0x{:02x}", byte)), _ => panic!("invalid radix") } - + if index < self.len() - { + { result.push_str(", "); - + if (index / 8) % 16 == 0 { result.push_str(&format!("\n\t/* 0x{:01$x} */ ", index / 8, addr_max_width)); } } } - + result.push_str("\n};"); result } - - + + // From: https://github.com/milanvidakovic/customasm/blob/master/src/asm/binary_output.rs#L84 pub fn format_logisim(&self, bits_per_chunk: usize) -> String { let mut result = String::new(); result.push_str("v2.0 raw\n"); - + let mut index = 0; while index < self.len() { @@ -348,16 +348,16 @@ impl util::BitVec value |= if self.read_bit(index) { 1 } else { 0 }; index += 1; } - + result.push_str(&format!("{:01$x} ", value, bits_per_chunk / 4)); if (index / 8) % 16 == 0 { result.push('\n'); } } - + result } - - + + pub fn format_annotated( &self, fileserver: &dyn util::FileServer, @@ -369,28 +369,28 @@ impl util::BitVec let bits_per_digit = (base - 1).count_ones() as usize; let bits_per_group = digits_per_group * bits_per_digit; - + let mut outp_width = 2; let mut outp_bit_width = 1; let mut addr_width = 4; let mut content_width = (digits_per_group + 1) * 1 - 1; - + let mut sorted_spans = self.spans.clone(); sorted_spans.sort_by(|a, b| a.offset.cmp(&b.offset)); - + for span in &sorted_spans { if let Some(offset) = span.offset { outp_width = std::cmp::max( - outp_width, + outp_width, format!("{:x}", offset / bits_per_group).len()); - + outp_bit_width = std::cmp::max( - outp_bit_width, + outp_bit_width, format!("{:x}", offset % bits_per_group).len()); - + addr_width = std::cmp::max( addr_width, format!("{:x}", span.addr).len()); @@ -406,16 +406,16 @@ impl util::BitVec } } } - + result.push_str(&format!(" {:>1$} |", "outp", outp_width + outp_bit_width + 1)); result.push_str(&format!(" {:>1$} |", "addr", addr_width)); result.push_str(&format!(" data (base {})", base)); result.push_str("\n"); result.push_str("\n"); - + let mut prev_file_handle = util::FileServerHandle::MAX; let mut prev_file_chars = "".to_string(); - + for span in &sorted_spans { if let Some(offset) = span.offset @@ -428,17 +428,17 @@ impl util::BitVec result.push_str(&format!(" {:>1$}", "--", outp_width)); result.push_str(&format!(":{:>1$} | ", "-", outp_bit_width)); } - + result.push_str(&format!("{:1$x} | ", span.addr, addr_width)); - + let mut contents_str = String::new(); - + let digit_num = span.size / bits_per_digit + if span.size % bits_per_digit == 0 { 0 } else { 1 }; for digit_index in 0..digit_num { if digit_index > 0 && digit_index % digits_per_group == 0 { contents_str.push_str(" "); } - + let mut digit = 0; for bit_index in 0..bits_per_digit { @@ -448,46 +448,180 @@ impl util::BitVec digit <<= 1; digit |= if bit { 1 } else { 0 }; } - + let c = if digit < 10 { ('0' as u8 + digit) as char } else { ('a' as u8 + digit - 10) as char }; - + contents_str.push(c); } - + if span.span.file_handle != prev_file_handle { prev_file_handle = span.span.file_handle; prev_file_chars = fileserver .get_str_unwrap(prev_file_handle); } - + let span_location = span.span.location().unwrap(); let char_counter = util::CharCounter::new(&prev_file_chars); - + result.push_str(&format!("{:1$}", contents_str, content_width)); result.push_str(&format!(" ; {}", char_counter.get_excerpt(span_location.0, span_location.1))); result.push_str("\n"); } - + + result + } + + + // Turing Complete is a game in which you advance from nand gates to + // computer architecture. Its assembly editor uses `#` comments, + // `0b` and `0x` prefixes for binary and hex, and groups of 8 or 8x4 bytes. + // This format produces annotated bytecode that meets these constraints. + // + // The implementation is a clone of format_annotated. Large portions + // could easily be factored out and shared or this could be converted into + // a configuration option for format_annotated with some work. + pub fn format_tcgame( + &self, + fileserver: &dyn util::FileServer, + base: usize, + digits_per_group: usize) + -> String + { + let mut result = String::new(); + assert!(base == 2 || base == 16); + let prefix: &str = if base == 2 { "0b" } else { "0x" }; + let comment: &str = "#"; + + let bits_per_digit = (base - 1).count_ones() as usize; + let bits_per_group = digits_per_group * bits_per_digit; + + let mut outp_width = 2; + let mut outp_bit_width = 1; + let mut addr_width = 4; + let mut content_width = (digits_per_group + 1) * 1 - 1; + + let mut sorted_spans = self.spans.clone(); + sorted_spans.sort_by(|a, b| + a.offset.cmp(&b.offset)); + + for span in &sorted_spans + { + if let Some(offset) = span.offset + { + outp_width = std::cmp::max( + outp_width, + format!("{:x}", offset / bits_per_group).len()); + + outp_bit_width = std::cmp::max( + outp_bit_width, + format!("{:x}", offset % bits_per_group).len()); + + addr_width = std::cmp::max( + addr_width, + format!("{:x}", span.addr).len()); + + let data_digits = span.size / bits_per_digit + if span.size % bits_per_digit == 0 { 0 } else { 1 }; + let this_content_width = data_digits + data_digits / digits_per_group; + + if this_content_width > 1 && this_content_width <= (digits_per_group + 1) * 5 + { + content_width = std::cmp::max( + content_width, + this_content_width - 1); + } + } + } + result.push_str(&format!("{comment} {:>1$} |", "outp", outp_width + outp_bit_width + 1)); + result.push_str(&format!(" {:>1$} |", "addr", addr_width)); + result.push_str(&format!(" data (base {})", base)); + result.push_str("\n"); + result.push_str("\n"); + + let mut prev_file_handle = util::FileServerHandle::MAX; + let mut prev_file_chars = "".to_string(); + + for span in &sorted_spans + { + result.push_str(&format!("{comment} ")); + // offset + if let Some(offset) = span.offset + { + result.push_str(&format!(" {:1$x}", offset / bits_per_group, outp_width)); + result.push_str(&format!(":{:1$x} | ", offset % bits_per_group, outp_bit_width)); + } + else + { + result.push_str(&format!(" {:>1$}", "--", outp_width)); + result.push_str(&format!(":{:>1$} | ", "-", outp_bit_width)); + } + + // addr + result.push_str(&format!("{:1$x} \n", span.addr, addr_width)); + + // instruction excerpt + if span.span.file_handle != prev_file_handle + { + prev_file_handle = span.span.file_handle; + prev_file_chars = fileserver + .get_str_unwrap(prev_file_handle); + } + let span_location = span.span.location().unwrap(); + let char_counter = util::CharCounter::new(&prev_file_chars); + result.push_str(&format!("{comment} {}\n", char_counter.get_excerpt(span_location.0, span_location.1))); + + // bytecode + let mut contents_str = String::new(); + let digit_num = span.size / bits_per_digit + if span.size % bits_per_digit == 0 { 0 } else { 1 }; + for digit_index in 0..digit_num + { + if digit_index % digits_per_group == 0 + { + if digit_index > 0 + { + contents_str.push_str(" "); + } + contents_str.push_str(prefix); + } + + let mut digit = 0; + for bit_index in 0..bits_per_digit + { + let i = span.offset.unwrap() + digit_index * bits_per_digit + bit_index; + let bit = self.read_bit(i); + + digit <<= 1; + digit |= if bit { 1 } else { 0 }; + } + + let c = if digit < 10 + { ('0' as u8 + digit) as char } + else + { ('a' as u8 + digit - 10) as char }; + + contents_str.push(c); + } + result.push_str(&format!("{:1$}\n", contents_str, content_width)); + } result } - + pub fn format_addrspan(&self, fileserver: &dyn util::FileServer) -> String { let mut result = String::new(); - + let mut sorted_spans = self.spans.clone(); sorted_spans.sort_by(|a, b| a.offset.cmp(&b.offset)); - + result.push_str("; "); result.push_str("physical address : bit offset | "); result.push_str("logical address | "); result.push_str("file : line start : column start : line end : column end\n"); - + for span in &sorted_spans { let chars = @@ -496,7 +630,7 @@ impl util::BitVec None, span.span.file_handle) .unwrap(); - + let counter = util::CharCounter::new(&chars); if let Some(offset) = span.offset @@ -509,7 +643,7 @@ impl util::BitVec } result.push_str(&format!("{:x} | ", span.addr)); - + if let Some((start, end)) = span.span.location() { let (line_start, col_start) = counter.get_line_column_at_index(start); @@ -526,10 +660,10 @@ impl util::BitVec { result.push_str(&format!("{}:-:-:-:-", &span.span.file_handle)); }; - + result.push_str("\n"); } - + result } } diff --git a/web/index.html b/web/index.html index 105d1a68..c9cccb37 100644 --- a/web/index.html +++ b/web/index.html @@ -112,6 +112,8 @@ + +