//! Fortran FORMAT 语句模拟 //! //! 解析和应用 Fortran FORMAT 规范。 use super::{IoError, Result}; /// FORMAT 规范项 #[derive(Debug, Clone, PartialEq)] pub enum FormatItem { /// 整数 (Iw) Integer { width: usize }, /// 浮点数 (Fw.d) Float { width: usize, decimals: usize }, /// 科学计数法 (Ew.d / Dw.d) Exponential { width: usize, decimals: usize, use_d: bool, }, /// 字符串 (Aw) String { width: usize }, /// 字面字符 ('xxx') Literal(String), /// 空格 (nX) Spaces(usize), /// 换行 (/) Newline, /// 跳过 (nX 或 Tn) Skip(usize), /// 重复组 (n(...)) Group { repeat: usize, items: Vec }, } /// FORMAT 规范 #[derive(Debug, Clone)] pub struct FormatSpec { items: Vec, } impl FormatSpec { /// 解析 FORMAT 字符串 /// /// # 示例 /// /// ``` /// # use tlusty::io::FormatSpec; /// let spec = FormatSpec::parse("(I3,2X,A4,6I6,1PD15.3)").unwrap(); /// ``` pub fn parse(s: &str) -> Result { // 移除外层括号 let s = s.trim(); let s = if s.starts_with('(') && s.ends_with(')') { &s[1..s.len() - 1] } else { s }; let mut items = Vec::new(); let mut chars = s.chars().peekable(); while let Some(&c) = chars.peek() { match c { // 跳过空白 ' ' | '\t' | ',' => { chars.next(); } // 字面字符串 '\'' | '"' => { chars.next(); let quote = c; let mut literal = String::new(); while let Some(&ch) = chars.peek() { if ch == quote { chars.next(); // 检查是否是转义引号 if chars.peek() == Some("e) { literal.push(quote); chars.next(); } else { break; } } else { literal.push(ch); chars.next(); } } items.push(FormatItem::Literal(literal)); } // 换行 '/' => { chars.next(); items.push(FormatItem::Newline); } // 重复计数或格式码 '0'..='9' => { let repeat = parse_number(&mut chars)?; match chars.peek() { Some(&'I') | Some(&'i') => { chars.next(); let width = parse_number(&mut chars)?; items.push(FormatItem::Integer { width }); } Some(&'F') | Some(&'f') => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Float { width, decimals }); } Some(&'E') | Some(&'e') => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: false, }); } Some(&'D') | Some(&'d') => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: true, }); } Some(&'A') | Some(&'a') => { chars.next(); let width = parse_number(&mut chars).unwrap_or(0); items.push(FormatItem::String { width }); } Some(&'X') | Some(&'x') => { chars.next(); items.push(FormatItem::Spaces(repeat)); } Some(&'P') | Some(&'p') => { // 比例因子,通常与 E/D 一起使用 chars.next(); // 下一个应该是 E 或 D if let Some(&next) = chars.peek() { if next == 'E' || next == 'e' || next == 'D' || next == 'd' { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: next == 'D' || next == 'd', }); } } } Some(&'(') => { // 重复组 chars.next(); let group_items = parse_group(&mut chars)?; items.push(FormatItem::Group { repeat, items: group_items, }); } _ => { // 默认重复计数应用于下一个格式项 // 暂时忽略 } } } // X (空格) 'X' | 'x' => { chars.next(); items.push(FormatItem::Spaces(1)); } // 格式码 'I' | 'i' => { chars.next(); let width = parse_number(&mut chars)?; items.push(FormatItem::Integer { width }); } 'F' | 'f' => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Float { width, decimals }); } 'E' | 'e' => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: false, }); } 'D' | 'd' => { chars.next(); let (width, decimals) = parse_width_decimals(&mut chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: true, }); } 'A' | 'a' => { chars.next(); let width = parse_number(&mut chars).unwrap_or(0); items.push(FormatItem::String { width }); } 'T' | 't' => { // Tab 定位 chars.next(); let _pos = parse_number(&mut chars)?; items.push(FormatItem::Skip(0)); // 简化处理 } // 1H 控制字符 '1' => { chars.next(); if chars.peek() == Some(&'H') { chars.next(); if let Some(&ch) = chars.peek() { chars.next(); match ch { '1' => items.push(FormatItem::Literal("\n".to_string())), '0' => items.push(FormatItem::Literal("\n\n".to_string())), ' ' => {} // 空格 _ => items.push(FormatItem::Literal(ch.to_string())), } } } } _ => { chars.next(); } } } Ok(FormatSpec { items }) } /// 获取格式项 pub fn items(&self) -> &[FormatItem] { &self.items } } /// 解析数字 fn parse_number(chars: &mut std::iter::Peekable) -> Result { let mut num = String::new(); while let Some(&c) = chars.peek() { if c.is_ascii_digit() { num.push(c); chars.next(); } else { break; } } num.parse() .map_err(|e| IoError::FormatError(format!("invalid number: {}", e))) } /// 解析宽度和小数位数 fn parse_width_decimals(chars: &mut std::iter::Peekable) -> Result<(usize, usize)> { let width = parse_number(chars)?; let decimals = if chars.peek() == Some(&'.') { chars.next(); parse_number(chars)? } else { 0 }; Ok((width, decimals)) } /// 解析重复组 fn parse_group(chars: &mut std::iter::Peekable) -> Result> { let mut items = Vec::new(); while let Some(&c) = chars.peek() { if c == ')' { chars.next(); break; } match c { ' ' | '\t' | ',' => { chars.next(); } '\'' | '"' => { chars.next(); let quote = c; let mut literal = String::new(); while let Some(&ch) = chars.peek() { if ch == quote { chars.next(); break; } literal.push(ch); chars.next(); } items.push(FormatItem::Literal(literal)); } '/' => { chars.next(); items.push(FormatItem::Newline); } '0'..='9' => { let repeat = parse_number(chars)?; match chars.peek() { Some(&'I') | Some(&'i') => { chars.next(); let width = parse_number(chars)?; for _ in 0..repeat { items.push(FormatItem::Integer { width }); } } Some(&'F') | Some(&'f') => { chars.next(); let (width, decimals) = parse_width_decimals(chars)?; for _ in 0..repeat { items.push(FormatItem::Float { width, decimals }); } } Some(&'E') | Some(&'e') => { chars.next(); let (width, decimals) = parse_width_decimals(chars)?; for _ in 0..repeat { items.push(FormatItem::Exponential { width, decimals, use_d: false, }); } } Some(&'X') | Some(&'x') => { chars.next(); items.push(FormatItem::Spaces(repeat)); } _ => {} } } 'I' | 'i' => { chars.next(); let width = parse_number(chars)?; items.push(FormatItem::Integer { width }); } 'F' | 'f' => { chars.next(); let (width, decimals) = parse_width_decimals(chars)?; items.push(FormatItem::Float { width, decimals }); } 'E' | 'e' => { chars.next(); let (width, decimals) = parse_width_decimals(chars)?; items.push(FormatItem::Exponential { width, decimals, use_d: false, }); } _ => { chars.next(); } } } Ok(items) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_integer() { let spec = FormatSpec::parse("(I3)").unwrap(); assert_eq!(spec.items().len(), 1); assert_eq!(spec.items()[0], FormatItem::Integer { width: 3 }); } #[test] fn test_parse_float() { let spec = FormatSpec::parse("(F10.3)").unwrap(); assert_eq!(spec.items().len(), 1); assert_eq!(spec.items()[0], FormatItem::Float { width: 10, decimals: 3 }); } #[test] fn test_parse_exponential() { // 1PD15.3 means scale factor 1P with D format let spec = FormatSpec::parse("(1PD15.3)").unwrap(); assert_eq!(spec.items().len(), 1); assert_eq!( spec.items()[0], FormatItem::Exponential { width: 15, decimals: 3, use_d: true // D format } ); // 1PE15.3 means scale factor 1P with E format let spec2 = FormatSpec::parse("(1PE15.3)").unwrap(); assert_eq!( spec2.items()[0], FormatItem::Exponential { width: 15, decimals: 3, use_d: false // E format } ); } #[test] fn test_parse_complex() { let spec = FormatSpec::parse("(I3,2X,A4,6I6)").unwrap(); assert!(spec.items().len() >= 3); } #[test] fn test_parse_literal() { let spec = FormatSpec::parse("('HELLO')").unwrap(); assert_eq!(spec.items().len(), 1); assert_eq!(spec.items()[0], FormatItem::Literal("HELLO".to_string())); } #[test] fn test_parse_newline() { let spec = FormatSpec::parse("(/)").unwrap(); assert_eq!(spec.items().len(), 1); assert_eq!(spec.items()[0], FormatItem::Newline); } }