SpectraRust/src/tlusty/io/format.rs
2026-03-25 13:31:23 +08:00

431 lines
14 KiB
Rust

//! 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<FormatItem> },
}
/// FORMAT 规范
#[derive(Debug, Clone)]
pub struct FormatSpec {
items: Vec<FormatItem>,
}
impl FormatSpec {
/// 解析 FORMAT 字符串
///
/// # 示例
///
/// ```
/// # use tlusty::io::FormatSpec;
/// let spec = FormatSpec::parse("(I3,2X,A4,6I6,1PD15.3)").unwrap();
/// ```
pub fn parse(s: &str) -> Result<Self> {
// 移除外层括号
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(&quote) {
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<std::str::Chars>) -> Result<usize> {
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<std::str::Chars>) -> 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<std::str::Chars>) -> Result<Vec<FormatItem>> {
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);
}
}