431 lines
14 KiB
Rust
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("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<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);
|
|
}
|
|
}
|