diff --git a/src/od/od.rs b/src/od/od.rs index d98f8eaac..99056985d 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -84,7 +84,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optflagmulti("f", "", "floating point single precision (32-bit) units"); opts.optflagmulti("F", "", "floating point double precision (64-bit) units"); - opts.optopt("t", "format", "select output format or formats", "TYPE"); + opts.optmulti("t", "format", "select output format or formats", "TYPE"); opts.optflag("v", "output-duplicates", "do not use * to mark line suppression"); opts.optflagopt("w", "width", ("output BYTES bytes per output line. 32 is implied when BYTES is not \ @@ -146,7 +146,13 @@ pub fn uumain(args: Vec) -> i32 { inputs.push(InputSource::Stdin); } - let formats = parse_format_flags(&args); + let formats = match parse_format_flags(&args) { + Ok(f) => f, + Err(e) => { + disp_err!("{}", e); + return 1; + } + }; let mut line_bytes = match matches.opt_default("w", "32") { None => 16, diff --git a/src/od/parse_formats.rs b/src/od/parse_formats.rs index 00ddce0ee..5682e0ac2 100644 --- a/src/od/parse_formats.rs +++ b/src/od/parse_formats.rs @@ -24,10 +24,8 @@ macro_rules! hashmap { /// arguments with parameters like -w16 can only appear at the end: -fvoxw16 /// parameters of -t/--format specify 1 or more formats. /// if -- appears on the commandline, parsing should stop. -pub fn parse_format_flags(args: &Vec) -> Vec { - // Gather up format flags, we don't use getopts becase we need keep them in order. +pub fn parse_format_flags(args: &Vec) -> Result, String> { - // TODO: -t fmts let known_formats = hashmap![ 'a' => FORMAT_ITEM_A, 'B' => FORMAT_ITEM_OCT16, @@ -57,38 +55,240 @@ pub fn parse_format_flags(args: &Vec) -> Vec { // args[0] is the name of the binary let mut arg_iter = args.iter().skip(1); + let mut expect_type_string = false; while let Some(arg) = arg_iter.next() { - if arg.starts_with("--") { + if expect_type_string { + match parse_type_string(arg) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + expect_type_string = false; + } + else if arg.starts_with("--") { if arg.len() == 2 { break; } + if arg.starts_with("--format=") { + let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect(); + match parse_type_string(¶ms) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + } + if arg == "--format" { + expect_type_string = true; + } } else if arg.starts_with("-") { let mut flags = arg.chars().skip(1); + let mut format_spec = String::new(); while let Some(c) = flags.next() { - if ignored_arg_opts.contains(&c) { + if expect_type_string { + format_spec.push(c); + } + else if ignored_arg_opts.contains(&c) { break; } - match known_formats.get(&c) { - None => {} // not every option is a format - Some(r) => { - formats.push(*r) + else if c=='t' { + expect_type_string = true; + } + else { + match known_formats.get(&c) { + None => {} // not every option is a format + Some(r) => { + formats.push(*r) + } } } } + if !format_spec.is_empty() { + match parse_type_string(&format_spec) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + expect_type_string = false; + } } } + if expect_type_string { + return Err(format!("missing format specification after '--format' / '-t'")); + } if formats.is_empty() { formats.push(FORMAT_ITEM_OCT16); // 2 byte octal is the default } - formats + Ok(formats) +} + +#[derive(PartialEq, Eq, Debug)] +enum ParseState { + ExpectSize, // expect optional size character like L for long. + ExpectDecimal, // expect optional additional digits, like for 16. + ExpectDump, // expect optional 'z'. + Finished // no more characters may appear. +} + +fn parse_type_string(params: &String) -> Result, String> { + + let type_chars: HashSet<_> = ['a', 'c'].iter().cloned().collect(); + let type_ints: HashSet<_> = ['d', 'o', 'u', 'x'].iter().cloned().collect(); + let type_floats: HashSet<_> = ['f'].iter().cloned().collect(); + let type_all: HashSet<_> = + type_chars.iter() + .chain(type_ints.iter()) + .chain(type_floats.iter()) + .collect(); + + let mut formats = Vec::new(); + + // first split a type string into parts refering a single type + let mut type_parts = Vec::new(); + let mut s = String::new(); + for c in params.chars() { + if type_all.contains(&c) { + if !s.is_empty() { + type_parts.push(s); + s = String::new(); + } + s.push(c); + } + else { + if s.is_empty() { + return Err(format!("unexpected char '{}' in format specification '{}'", c, params)); + } + s.push(c); + } + } + if !s.is_empty() { + type_parts.push(s); + } + + for format_type in type_parts.iter() { + let mut chars=format_type.chars(); + + let type_char = chars.next().unwrap(); + + let mut parse_state = ParseState::ExpectSize; + let mut decimal_size = String::new(); + let mut byte_size = 0u8; + let mut show_ascii_dump = false; + + if type_chars.contains(&type_char) { + parse_state = ParseState::ExpectDump; + } + + loop { + match chars.next() { + None => break, + Some('z') if parse_state != ParseState::Finished => { + show_ascii_dump = true; + parse_state = ParseState::Finished; + }, + Some(d) if d.is_digit(10) + && (parse_state == ParseState::ExpectSize || parse_state == ParseState::ExpectDecimal) => { + decimal_size.push(d); + parse_state = ParseState::ExpectDecimal; + }, + + Some('C') if type_ints.contains(&type_char) && parse_state == ParseState::ExpectSize => { + byte_size = 1; + parse_state = ParseState::ExpectDump; + }, + Some('S') if type_ints.contains(&type_char) && parse_state == ParseState::ExpectSize => { + byte_size = 2; + parse_state = ParseState::ExpectDump; + }, + Some('I') if type_ints.contains(&type_char) && parse_state == ParseState::ExpectSize => { + byte_size = 4; + parse_state = ParseState::ExpectDump; + }, + Some('L') if type_ints.contains(&type_char) && parse_state == ParseState::ExpectSize => { + byte_size = 8; + parse_state = ParseState::ExpectDump; + }, + + Some('F') if type_char == 'f' && parse_state == ParseState::ExpectSize => { + byte_size = 4; + parse_state = ParseState::ExpectDump; + }, + Some('D') if type_char == 'f' && parse_state == ParseState::ExpectSize => { + byte_size = 8; + parse_state = ParseState::ExpectDump; + }, + // Some('L') if type_char == 'f' => byte_size = 16, // TODO support f128 + + Some(c) => { + return Err(format!("unexpected char '{}' in format specification '{}'", c, format_type)); + } + } + } + + if !decimal_size.is_empty() { + byte_size=match decimal_size.parse() { + Err(_) => return Err(format!("invalid number '{}' in format specification '{}'", decimal_size, format_type)), + Ok(n) => n, + } + } + + match type_char { + 'a' => formats.push(FORMAT_ITEM_A), + 'c' => formats.push(FORMAT_ITEM_C), + 'd' => { + formats.push(match byte_size { + 1 => FORMAT_ITEM_DEC8S, + 2 => FORMAT_ITEM_DEC16S, + 4|0 => FORMAT_ITEM_DEC32S, + 8 => FORMAT_ITEM_DEC64S, + _ => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, format_type)), + }); + }, + 'o' => { + formats.push(match byte_size { + 1 => FORMAT_ITEM_OCT8, + 2 => FORMAT_ITEM_OCT16, + 4|0 => FORMAT_ITEM_OCT32, + 8 => FORMAT_ITEM_OCT64, + _ => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, format_type)), + }); + }, + 'u' => { + formats.push(match byte_size { + 1 => FORMAT_ITEM_DEC8U, + 2 => FORMAT_ITEM_DEC16U, + 4|0 => FORMAT_ITEM_DEC32U, + 8 => FORMAT_ITEM_DEC64U, + _ => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, format_type)), + }); + }, + 'x' => { + formats.push(match byte_size { + 1 => FORMAT_ITEM_HEX8, + 2 => FORMAT_ITEM_HEX16, + 4|0 => FORMAT_ITEM_HEX32, + 8 => FORMAT_ITEM_HEX64, + _ => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, format_type)), + }); + }, + 'f' => { + formats.push(match byte_size { + 4|0 => FORMAT_ITEM_F32, + 8 => FORMAT_ITEM_F64, + _ => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, format_type)), + }); + }, + _ => unreachable!(), + } + + if show_ascii_dump { /*TODO*/ } + } + + Ok(formats) } #[allow(dead_code)] -pub fn parse_format_flags_str(args_str: &Vec<&'static str>) -> Vec { +pub fn parse_format_flags_str(args_str: &Vec<&'static str>) -> Result, String> { let args = args_str.iter().map(|s| s.to_string()).collect(); parse_format_flags(&args) } @@ -96,48 +296,188 @@ pub fn parse_format_flags_str(args_str: &Vec<&'static str>) -> Vec