use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::{IoError, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScreenInput {
pub ner: i32,
pub nei: i32,
pub maxl: i32,
pub irrh: i32,
pub iend: i32,
pub lfxc: i32,
pub emin: f64,
pub emax: f64,
pub eimax: f64,
pub ermin: f64,
pub rfms: f64,
pub nrptx0: i32,
pub icore: i32,
}
impl Default for ScreenInput {
fn default() -> Self {
Self {
ner: 40,
nei: 20,
maxl: 4,
irrh: 1,
iend: 0,
lfxc: 0,
emin: -40.0,
emax: 0.0,
eimax: 2.0,
ermin: 0.001,
rfms: 4.0,
nrptx0: 251,
icore: -1,
}
}
}
impl ScreenInput {
pub fn parse_str(source: impl Into<PathBuf>, text: &str) -> Result<Self> {
let mut parser = ScreenInputParser::new(source.into(), text);
parser.parse()
}
}
pub fn screen_input_string(input: &ScreenInput) -> Result<String> {
validate_screen_input(input)?;
Ok(format!(
concat!(
" ner{:12}\n",
" nei{:12}\n",
" maxl{:12}\n",
" irrh{:12}\n",
" iend{:12}\n",
" lfxc{:12}\n",
" emin{:21.15} \n",
" emax{:21.16} \n",
" eimax{:21.16} \n",
" ermin{}\n",
" rfms{:21.16} \n",
" nrptx0{:12}\n",
" icore{:12}\n",
),
input.ner,
input.nei,
input.maxl,
input.irrh,
input.iend,
input.lfxc,
input.emin,
input.emax,
input.eimax,
screen_fortran_exp(input.ermin, 26, 16),
input.rfms,
input.nrptx0,
input.icore
))
}
fn validate_screen_input(input: &ScreenInput) -> Result<()> {
validate_finite("emin", input.emin)?;
validate_finite("emax", input.emax)?;
validate_finite("eimax", input.eimax)?;
validate_finite("ermin", input.ermin)?;
validate_finite("rfms", input.rfms)
}
fn validate_finite(field: &'static str, value: f64) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(IoError::Parse {
path: "screen.inp".into(),
line: 0,
message: format!("{field} must be finite"),
})
}
}
fn screen_fortran_exp(value: f64, width: usize, precision: usize) -> String {
let raw = format!("{value:.precision$E}");
let Some((mantissa, exponent)) = raw.split_once('E') else {
return format!("{raw:>width$}");
};
let (sign, digits) = match exponent.as_bytes().first() {
Some(b'-') => ('-', &exponent[1..]),
Some(b'+') => ('+', &exponent[1..]),
_ => ('+', exponent),
};
let field = format!("{mantissa}E{sign}{digits:0>3}");
format!("{field:>width$}")
}
struct ScreenInputParser<'a> {
source: PathBuf,
lines: std::iter::Enumerate<std::str::Lines<'a>>,
}
impl<'a> ScreenInputParser<'a> {
fn new(source: PathBuf, text: &'a str) -> Self {
Self {
source,
lines: text.lines().enumerate(),
}
}
fn parse(&mut self) -> Result<ScreenInput> {
Ok(ScreenInput {
ner: self.parse_keyed_value("ner")?,
nei: self.parse_keyed_value("nei")?,
maxl: self.parse_keyed_value("maxl")?,
irrh: self.parse_keyed_value("irrh")?,
iend: self.parse_keyed_value("iend")?,
lfxc: self.parse_keyed_value("lfxc")?,
emin: self.parse_keyed_value("emin")?,
emax: self.parse_keyed_value("emax")?,
eimax: self.parse_keyed_value("eimax")?,
ermin: self.parse_keyed_value("ermin")?,
rfms: self.parse_keyed_value("rfms")?,
nrptx0: self.parse_keyed_value("nrptx0")?,
icore: self.parse_keyed_value("icore")?,
})
}
fn parse_keyed_value<T>(&mut self, expected_key: &str) -> Result<T>
where
T: FromStr,
{
let (line_number, line) = self.next_line(expected_key)?;
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() < 2 {
return Err(self.parse_error(line_number, format!("expected {expected_key} value row")));
}
if fields[0] != expected_key {
return Err(self.parse_error(
line_number,
format!("expected key {expected_key:?}, found {:?}", fields[0]),
));
}
parse_field(&self.source, line_number, fields[1])
}
fn next_line(&mut self, description: &str) -> Result<(usize, &'a str)> {
self.lines
.next()
.map(|(index, line)| (index + 1, line))
.ok_or_else(|| self.parse_error(0, format!("expected {description}")))
}
fn parse_error(&self, line: usize, message: impl Into<String>) -> IoError {
IoError::Parse {
path: self.source.clone(),
line,
message: message.into(),
}
}
}
fn parse_field<T>(source: &Path, line: usize, field: &str) -> Result<T>
where
T: FromStr,
{
field.parse::<T>().map_err(|_| IoError::Parse {
path: source.to_path_buf(),
line,
message: format!("invalid numeric field {field:?}"),
})
}
#[cfg(test)]
mod tests {
use crate::rdinp;
use super::{ScreenInput, screen_input_string};
#[test]
fn parses_default_screen_input() -> crate::Result<()> {
let screen = ScreenInput::parse_str("screen.inp", &rdinp::screen_inp_string())?;
assert_eq!(screen.ner, 40);
assert_eq!(screen.nei, 20);
assert_eq!(screen.maxl, 4);
assert_eq!(screen.irrh, 1);
assert_eq!(screen.iend, 0);
assert_eq!(screen.lfxc, 0);
assert_eq!(screen.emin, -40.0);
assert_eq!(screen.emax, 0.0);
assert_eq!(screen.eimax, 2.0);
assert_eq!(screen.ermin, 0.001);
assert_eq!(screen.rfms, 4.0);
assert_eq!(screen.nrptx0, 251);
assert_eq!(screen.icore, -1);
Ok(())
}
#[test]
fn renders_default_screen_input() -> crate::Result<()> {
let screen = ScreenInput::parse_str("screen.inp", &rdinp::screen_inp_string())?;
let rendered = screen_input_string(&screen)?;
let reparsed = ScreenInput::parse_str("screen.inp", &rendered)?;
assert_eq!(rendered, rdinp::screen_inp_string());
assert_eq!(reparsed, screen);
Ok(())
}
#[test]
fn rejects_invalid_screen_rendering() {
let input = ScreenInput {
ner: 40,
nei: 20,
maxl: 4,
irrh: 1,
iend: 0,
lfxc: 0,
emin: f64::NAN,
emax: 0.0,
eimax: 2.0,
ermin: 0.001,
rfms: 4.0,
nrptx0: 251,
icore: -1,
};
assert!(screen_input_string(&input).is_err());
}
}