diff --git a/src/od/od.rs b/src/od/od.rs index 8270e74a3..31a2c932e 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -28,6 +28,7 @@ mod parse_formats; mod parse_inputs; mod inputoffset; mod inputdecoder; +mod output_info; #[cfg(test)] mod mockstream; @@ -39,14 +40,14 @@ use partialreader::*; use peekreader::*; use formatteriteminfo::*; use parse_nrofbytes::parse_number_of_bytes; -use parse_formats::{parse_format_flags, ParsedFormatterItemInfo}; +use parse_formats::parse_format_flags; use prn_char::format_ascii_dump; use parse_inputs::{parse_inputs, CommandLineInputs}; use inputoffset::{InputOffset, Radix}; use inputdecoder::{InputDecoder,MemoryDecoder}; +use output_info::OutputInfo; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); -const MAX_BYTES_PER_UNIT: usize = 8; const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes static USAGE: &'static str = @@ -93,6 +94,8 @@ Any type specification can have a "z" suffic, which will add a ASCII dump at If an error occurred, a diagnostic message will be printed to stderr, and the exitcode will be non-zero."#; +/// parses and validates commandline parameters, prepares data structures, +/// opens the input and calls `odfunc` to process the input. pub fn uumain(args: Vec) -> i32 { let mut opts = getopts::Options::new(); @@ -233,65 +236,27 @@ pub fn uumain(args: Vec) -> i32 { } }; - let mut input = open_input_peek_reader(&input_strings, skip_bytes, read_bytes); - - let mut input_decoder = InputDecoder::new(&mut input, line_bytes, PEEK_BUFFER_SIZE, byte_order); - let mut input_offset = InputOffset::new(Radix::Octal, skip_bytes, label); if let Err(e) = input_offset.parse_radix_from_commandline(matches.opt_str("A")) { disp_err!("Invalid -A/--address-radix\n{}", e); return 1; } - odfunc(&mut input_decoder, &mut input_offset, line_bytes, &formats[..], - output_duplicates) + let mut input = open_input_peek_reader(&input_strings, skip_bytes, read_bytes); + let mut input_decoder = InputDecoder::new(&mut input, line_bytes, PEEK_BUFFER_SIZE, byte_order); + + let output_info = OutputInfo::new(line_bytes, &formats[..], output_duplicates); + + odfunc(&mut input_offset, &mut input_decoder, &output_info) } -// TODO: refactor, too many arguments -fn odfunc(input_decoder: &mut InputDecoder, input_offset: &mut InputOffset, line_bytes: usize, - formats: &[ParsedFormatterItemInfo], output_duplicates: bool) -> i32 +/// Loops through the input line by line, calling print_bytes to take care of the output. +fn odfunc(input_offset: &mut InputOffset, input_decoder: &mut InputDecoder, + output_info: &OutputInfo) -> i32 where I : PeekRead+HasError { - let mut duplicate_line = false; let mut previous_bytes: Vec = Vec::new(); - - let byte_size_block = formats.iter().fold(1, |max, next| cmp::max(max, next.formatter_item_info.byte_size)); - let print_width_block = formats - .iter() - .fold(1, |max, next| { - cmp::max(max, next.formatter_item_info.print_width * (byte_size_block / next.formatter_item_info.byte_size)) - }); - let print_width_line = print_width_block * (line_bytes / byte_size_block); - - if byte_size_block > MAX_BYTES_PER_UNIT { - panic!("{}-bits types are unsupported. Current max={}-bits.", - 8 * byte_size_block, - 8 * MAX_BYTES_PER_UNIT); - } - - let mut spaced_formatters: Vec = formats - .iter() - .map(|f| SpacedFormatterItemInfo { frm: *f, spacing: [0; MAX_BYTES_PER_UNIT] }) - .collect(); - - // calculate proper alignment for each item - for sf in &mut spaced_formatters { - let mut byte_size = sf.frm.formatter_item_info.byte_size; - let mut items_in_block = byte_size_block / byte_size; - let thisblock_width = sf.frm.formatter_item_info.print_width * items_in_block; - let mut missing_spacing = print_width_block - thisblock_width; - - while items_in_block > 0 { - let avg_spacing: usize = missing_spacing / items_in_block; - for i in 0..items_in_block { - sf.spacing[i * byte_size] += avg_spacing; - missing_spacing -= avg_spacing; - } - // this assumes the size of all types is a power of 2 (1, 2, 4, 8, 16, ...) - items_in_block /= 2; - byte_size *= 2; - } - } + let line_bytes = output_info.byte_size_line; loop { // print each line data (or multi-format raster of several lines describing the same data). @@ -308,7 +273,7 @@ fn odfunc(input_decoder: &mut InputDecoder, input_offset: &mut InputOffset // not enough byte for a whole element, this should only happen on the last line. if length != line_bytes { // set zero bytes in the part of the buffer that will be used, but is not filled. - let mut max_used = length + MAX_BYTES_PER_UNIT; + let mut max_used = length + output_info.byte_size_block; if max_used > line_bytes { max_used = line_bytes; } @@ -316,7 +281,7 @@ fn odfunc(input_decoder: &mut InputDecoder, input_offset: &mut InputOffset memory_decoder.zero_out_buffer(length, max_used); } - if !output_duplicates + if !output_info.output_duplicates && length == line_bytes && memory_decoder.get_buffer(0) == &previous_bytes[..] { if !duplicate_line { @@ -332,7 +297,7 @@ fn odfunc(input_decoder: &mut InputDecoder, input_offset: &mut InputOffset } print_bytes(&input_offset.format_byte_offset(), &memory_decoder, - &spaced_formatters, byte_size_block, print_width_line); + &output_info); } input_offset.increase_position(length); @@ -352,25 +317,25 @@ fn odfunc(input_decoder: &mut InputDecoder, input_offset: &mut InputOffset } } -fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, - formats: &[SpacedFormatterItemInfo], byte_size_block: usize, print_width_line: usize) { +/// Outputs a single line of input, into one or more lines human readable output. +fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) { let mut first = true; // First line of a multi-format raster. - for f in formats { + for f in output_info.spaced_formatters_iter() { let mut output_text = String::new(); let mut b = 0; while b < input_decoder.length() { output_text.push_str(&format!("{:>width$}", "", - width = f.spacing[b % byte_size_block])); + width = f.spacing[b % output_info.byte_size_block])); - match f.frm.formatter_item_info.formatter { + match f.formatter_item_info.formatter { FormatWriter::IntWriter(func) => { - let p = input_decoder.read_uint(b, f.frm.formatter_item_info.byte_size); + let p = input_decoder.read_uint(b, f.formatter_item_info.byte_size); output_text.push_str(&func(p)); } FormatWriter::FloatWriter(func) => { - let p = input_decoder.read_float(b, f.frm.formatter_item_info.byte_size); + let p = input_decoder.read_float(b, f.formatter_item_info.byte_size); output_text.push_str(&func(p)); } FormatWriter::MultibyteWriter(func) => { @@ -378,11 +343,11 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, } } - b += f.frm.formatter_item_info.byte_size; + b += f.formatter_item_info.byte_size; } - if f.frm.add_ascii_dump { - let missing_spacing = print_width_line.saturating_sub(output_text.chars().count()); + if f.add_ascii_dump { + let missing_spacing = output_info.print_width_line.saturating_sub(output_text.chars().count()); output_text.push_str(&format!("{:>width$} {}", "", format_ascii_dump(input_decoder.get_buffer(0)), @@ -423,8 +388,3 @@ fn open_input_peek_reader<'a>(input_strings: &'a Vec, skip_bytes: usize, let input = PeekReader::new(pr); input } - -struct SpacedFormatterItemInfo { - frm: ParsedFormatterItemInfo, - spacing: [usize; MAX_BYTES_PER_UNIT], -} diff --git a/src/od/output_info.rs b/src/od/output_info.rs new file mode 100644 index 000000000..4af3bef9e --- /dev/null +++ b/src/od/output_info.rs @@ -0,0 +1,244 @@ +use std::cmp; +use std::slice::Iter; +use parse_formats::ParsedFormatterItemInfo; +use formatteriteminfo::FormatterItemInfo; + +/// Size in bytes of the max datatype. ie set to 16 for 128-bit numbers. +const MAX_BYTES_PER_UNIT: usize = 8; + +/// Contains information to output single output line in human readable form +pub struct SpacedFormatterItemInfo { + /// Contains a function pointer to output data, and information about the output format. + pub formatter_item_info: FormatterItemInfo, + /// Contains the number of spaces to add to align data with other output formats. + /// + /// If the corresponding data is a single byte, each entry in this array contains + /// the number of spaces to insert when outputting each byte. If the corresponding + /// data is multi-byte, only the fist byte position is used. For example a 32-bit + /// datatype, could use positions 0, 4, 8, 12, .... + /// As each block is formatted identically, only the spacing for a single block is set. + pub spacing: [usize; MAX_BYTES_PER_UNIT], + /// if set adds a ascii dump at the end of the line + pub add_ascii_dump: bool, +} + +/// Contains information about all output lines. +pub struct OutputInfo { + /// The number of bytes of a line. + pub byte_size_line: usize, + /// The width of a line in human readable format. + pub print_width_line: usize, + + /// The number of bytes in a block. (This is the size of the largest datatype in `spaced_formatters`.) + pub byte_size_block: usize, + /// The width of a block in human readable format. (The size of the largest format.) + pub print_width_block: usize, + /// All formats. + spaced_formatters: Vec, + /// determines if duplicate output lines should be printed, or + /// skipped with a "*" showing one or more skipped lines. + pub output_duplicates: bool, +} + + +impl OutputInfo { + /// Returns an iterator over the `SpacedFormatterItemInfo` vector. + pub fn spaced_formatters_iter(&self) -> Iter { + self.spaced_formatters.iter() + } + + /// Creates a new `OutputInfo` based on the parameters + pub fn new(line_bytes: usize, formats: &[ParsedFormatterItemInfo], output_duplicates: bool) -> OutputInfo { + + let byte_size_block = formats.iter().fold(1, |max, next| cmp::max(max, next.formatter_item_info.byte_size)); + let print_width_block = formats + .iter() + .fold(1, |max, next| { + cmp::max(max, next.formatter_item_info.print_width * (byte_size_block / next.formatter_item_info.byte_size)) + }); + let print_width_line = print_width_block * (line_bytes / byte_size_block); + + let spaced_formatters = OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block); + + OutputInfo { + byte_size_line: line_bytes, + print_width_line: print_width_line, + byte_size_block: byte_size_block, + print_width_block: print_width_block, + spaced_formatters: spaced_formatters, + output_duplicates: output_duplicates, + } + } + + fn create_spaced_formatter_info(formats: &[ParsedFormatterItemInfo], + byte_size_block: usize, print_width_block: usize) -> Vec { + formats + .iter() + .map(|f| SpacedFormatterItemInfo { + formatter_item_info: f.formatter_item_info, + add_ascii_dump: f.add_ascii_dump, + spacing: OutputInfo::calculate_alignment(f, byte_size_block, print_width_block) + }) + .collect() + } + + /// calculates proper alignment for a single line of output + /// + /// Multiple representations of the same data, will be right-aligned for easy reading. + /// For example a 64 bit octal and a 32-bit decimal with a 16-bit hexadecimal looks like this: + /// ``` + /// 1777777777777777777777 1777777777777777777777 + /// 4294967295 4294967295 4294967295 4294967295 + /// ffff ffff ffff ffff ffff ffff ffff ffff + /// ``` + /// In this example is additional spacing before the first and third decimal number, + /// and there is additional spacing before the 1st, 3rd, 5th and 7th hexadecimal number. + /// This way both the octal and decimal, aswell the decimal and hexadecimal numbers + /// left align. Note that the alignment below both octal numbers is identical. + /// + /// This function calculates the required spacing for a single line, given the size + /// of a block, and the width of a block. The size of a block is the largest type + /// and the width is width of the the type which needs the most space to print that + /// number of bytes. So both numbers might refer to different types. All widths + /// include a space at the front. For example the width of a 8-bit hexadecimal, + /// is 3 characters, for example " FF". + /// + /// This algorithm first calculates how many spaces needs to be added, based the + /// block size and the size of the type, and the widths of the block and the type. + /// The required spaces are spread across the available positions. + /// If the blocksize is 8, and the size of the type is 8 too, there will be just + /// one value in a block, so all spacing will be assigned to position 0. + /// If the blocksize is 8, and the size of the type is 2, the spacing will be + /// spread across position 0, 2, 4, 6. All 4 positions will get an additional + /// space as long as there are more then 4 spaces available. If there are 2 + /// spaces available, they will be assigend to position 0 and 4. If there is + /// 1 space available, it will be assigned to position 0. This will be combined, + /// For example 7 spaces will be assigned to position 0, 2, 4, 6 like: 3, 1, 2, 1. + /// And 7 spaces with 2 positions will be assigned to position 0 and 4 like 4, 3. + /// + /// Here is another example showing the alignment of 64-bit unsigned decimal numbers, + /// 32-bit hexadecimal number, 16-bit octal numbers and 8-bit hexadecimal numbers: + /// ``` + /// 18446744073709551615 18446744073709551615 + /// ffffffff ffffffff ffffffff ffffffff + /// 177777 177777 177777 177777 177777 177777 177777 177777 + /// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + /// ``` + /// + /// This algorithm assumes the size of all types is a power of 2 (1, 2, 4, 8, 16, ...) + /// Increase MAX_BYTES_PER_UNIT to allow larger types. + fn calculate_alignment(sf: &TypeSizeInfo, byte_size_block: usize, + print_width_block: usize) -> [usize; MAX_BYTES_PER_UNIT] { + + if byte_size_block > MAX_BYTES_PER_UNIT { + panic!("{}-bits types are unsupported. Current max={}-bits.", + 8 * byte_size_block, + 8 * MAX_BYTES_PER_UNIT); + } + let mut spacing = [0; MAX_BYTES_PER_UNIT]; + + let mut byte_size = sf.byte_size(); + let mut items_in_block = byte_size_block / byte_size; + let thisblock_width = sf.print_width() * items_in_block; + let mut missing_spacing = print_width_block - thisblock_width; + + while items_in_block > 0 { + let avg_spacing: usize = missing_spacing / items_in_block; + for i in 0..items_in_block { + spacing[i * byte_size] += avg_spacing; + missing_spacing -= avg_spacing; + } + + items_in_block /= 2; + byte_size *= 2; + } + + spacing + } +} + +trait TypeSizeInfo { + fn byte_size(&self) -> usize; + fn print_width(&self) -> usize; +} + +impl TypeSizeInfo for ParsedFormatterItemInfo { + fn byte_size(&self) -> usize { self.formatter_item_info.byte_size } + fn print_width(&self) -> usize { self.formatter_item_info.print_width } +} + +#[cfg(test)] +struct TypeInfo { + byte_size: usize, + print_width: usize, +} + +#[cfg(test)] +impl TypeSizeInfo for TypeInfo { + fn byte_size(&self) -> usize { self.byte_size } + fn print_width(&self) -> usize { self.print_width } +} + +#[test] +fn test_calculate_alignment() { + + // For this example `byte_size_block` is 8 and 'print_width_block' is 23: + // 1777777777777777777777 1777777777777777777777 + // 4294967295 4294967295 4294967295 4294967295 + // ffff ffff ffff ffff ffff ffff ffff ffff + + // the first line has no additional spacing: + assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:23}, 8, 23)); + // the second line a single space at the start of the block: + assert_eq!([1, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:11}, 8, 23)); + // the third line two spaces at pos 0, and 1 space at pos 4: + assert_eq!([2, 0, 0, 0, 1, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:5}, 8, 23)); + + // For this example `byte_size_block` is 8 and 'print_width_block' is 28: + // 18446744073709551615 18446744073709551615 + // ffffffff ffffffff ffffffff ffffffff + // 177777 177777 177777 177777 177777 177777 177777 177777 + // ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + assert_eq!([7, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:21}, 8, 28)); + assert_eq!([5, 0, 0, 0, 5, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:9}, 8, 28)); + assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:7}, 8, 28)); + assert_eq!([1, 0, 1, 0, 1, 0, 1, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:3}, 8, 28)); + + // 9 tests where 8 .. 16 spaces are spread across 8 positions + assert_eq!([1, 1, 1, 1, 1, 1, 1, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+8)); + assert_eq!([2, 1, 1, 1, 1, 1, 1, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+9)); + assert_eq!([2, 1, 1, 1, 2, 1, 1, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+10)); + assert_eq!([3, 1, 1, 1, 2, 1, 1, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+11)); + assert_eq!([2, 1, 2, 1, 2, 1, 2, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+12)); + assert_eq!([3, 1, 2, 1, 2, 1, 2, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+13)); + assert_eq!([3, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+14)); + assert_eq!([4, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+15)); + assert_eq!([2, 2, 2, 2, 2, 2, 2, 2], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+16)); + + // 4 tests where 15 spaces are spread across 8, 4, 2 or 1 position(s) + assert_eq!([4, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16+15)); + assert_eq!([5, 0, 3, 0, 4, 0, 3, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:4}, 8, 16+15)); + assert_eq!([8, 0, 0, 0, 7, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:8}, 8, 16+15)); + assert_eq!([15, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:16}, 8, 16+15)); +}