+
+ fn read_document_record(&mut self) -> Result<()> {
+ println!("{:08x}: document record", self.r.stream_position()?);
+ let n_lines: u32 = self.read_swap()?;
+ println!("\t{n_lines} lines of documents");
+
+ for i in 0..n_lines {
+ print!("\t{:08x}: ", self.r.stream_position()?);
+ let line: [u8; 64] = read_bytes(&mut self.r)?;
+ let line = trim_end(Vec::from(line), b' ');
+ println!("line {i}: \"{}\"", String::from_utf8_lossy(&line));
+ }
+ Ok(())
+ }
+
+ fn read_machine_integer_info(&mut self, size: u32, count: u32) -> Result<()> {
+ let offset = self.r.stream_position()?;
+ let version_major: u32 = self.read_swap()?;
+ let version_minor: u32 = self.read_swap()?;
+ let version_revision: u32 = self.read_swap()?;
+ let machine_code: u32 = self.read_swap()?;
+ let float_representation: u32 = self.read_swap()?;
+ let compression_code: u32 = self.read_swap()?;
+ let integer_representation: u32 = self.read_swap()?;
+ let character_code: u32 = self.read_swap()?;
+
+ println!("{offset:08x}: machine integer info");
+ if size != 4 || count != 8 {
+ Err(anyhow!("Bad size ({size}) or count ({count}) field on record type 7, subtype 3"))?;
+ }
+ println!("\tVersion: {version_major}.{version_minor}.{version_revision}");
+ println!("\tMachine code: {machine_code}");
+ println!("\tFloating point representation: {float_representation} ({})",
+ match float_representation {
+ 1 => "IEEE 754",
+ 2 => "IBM 370",
+ 3 => "DEC VAX",
+ _ => "unknown"
+ });
+ println!("\tCompression code: {compression_code}");
+ println!("\tEndianness: {integer_representation} ({})",
+ match integer_representation {
+ 1 => "big",
+ 2 => "little",
+ _ => "unknown"
+ });
+ println!("\tCharacter code: {character_code}");
+ Ok(())
+ }
+
+ fn read_machine_float_info(&mut self, size: u32, count: u32) -> Result<()> {
+ let offset = self.r.stream_position()?;
+ let sysmis: f64 = self.read_swap()?;
+ let highest: f64 = self.read_swap()?;
+ let lowest: f64 = self.read_swap()?;
+
+ println!("{offset:08x}: machine float info");
+ if size != 4 || count != 8 {
+ Err(anyhow!("Bad size ({size}) or count ({count}) field on extension 4."))?;
+ }
+
+ println!("\tsysmis: {sysmis} ({})", HexFloat(sysmis));
+ println!("\thighest: {highest} ({})", HexFloat(highest));
+ println!("\tlowest: {lowest} ({})", HexFloat(lowest));
+ Ok(())
+ }
+
+ fn read_variable_sets(&mut self, size: u32, count: u32) -> Result<()> {
+ println!("{:08x}: variable sets", self.r.stream_position()?);
+ let mut text = self.open_text_record(size, count)?;
+ loop {
+ while text.match_byte(b'\n') {
+ continue;
+ }
+ let set = match text.tokenize(b'=') {
+ Some(set) => String::from_utf8_lossy(&set).into_owned(),
+ None => break,
+ };
+
+ // Always present even for an empty set.
+ text.match_byte(b' ');
+
+ match text.tokenize(b'\n') {
+ None => println!("\tset \"{set}\" is empty"),
+ Some(variables) => {
+ println!("\tset \"{set}\" contains \"{}\"", String::from_utf8_lossy(variables).trim_end_matches('\r'));
+ },
+ };
+
+ }
+ Ok(())
+ }
+
+ fn read_extra_product_info(&mut self, size: u32, count: u32) -> Result<()> {
+ print!("{:08x}: extra product info", self.r.stream_position()?);
+ let mut text = self.open_text_record(size, count)?;
+
+ }
+
+ fn open_text_record(&mut self, size: u32, count: u32) -> Result<TextRecord> {
+ let n_bytes = match u32::checked_mul(size, count) {
+ Some(n) => n,
+ None => Err(anyhow!("Extension record too large."))?
+ };
+ Ok(TextRecord::new(read_vec(&mut self.r, n_bytes as usize)?))
+ }
+}
+
+struct TextRecord {
+ buffer: Vec<u8>,
+ pos: usize
+}
+
+impl TextRecord {
+ fn new(buffer: Vec<u8>) -> TextRecord {
+ TextRecord { buffer, pos: 0 }
+ }
+
+ fn tokenize<'a>(&'a mut self, delimiter: u8) -> Option<&'a [u8]> {
+ let mut start = self.pos;
+ while self.pos < self.buffer.len() && self.buffer[self.pos] != delimiter && self.buffer[self.pos] != 0 {
+ self.pos += 1
+ }
+ if start == self.pos {
+ None
+ } else {
+ Some(&self.buffer[start..self.pos])
+ }
+ }
+
+ fn match_byte(&mut self, c: u8) -> bool {
+ if self.pos < self.buffer.len() && self.buffer[self.pos] == c {
+ self.pos += 1;
+ true
+ } else {
+ false
+ }
+ }