use crate::{Error, Result};
fn find_scalar_rhs<'a>(source: &'a str, field: &str) -> Option<&'a str> {
let mut from = 0;
while let Some(rel) = source[from..].find("mpc.") {
let after_dot = from + rel + "mpc.".len();
from = after_dot; let Some(tail) = source[after_dot..].strip_prefix(field) else {
continue;
};
if tail
.bytes()
.next()
.is_some_and(|b| b.is_ascii_alphanumeric() || b == b'_')
{
continue;
}
let Some(rhs) = tail.trim_start().strip_prefix('=') else {
continue; };
let rhs = rhs.trim_start();
if rhs.starts_with('[') {
continue; }
return Some(rhs);
}
None
}
pub(crate) fn find_scalar(source: &str, field: &str) -> Result<Option<f64>> {
let Some(rhs) = find_scalar_rhs(source, field) else {
return Ok(None);
};
let Some(end) = rhs.find(';') else {
return Ok(None);
};
let value = rhs[..end]
.trim_end()
.trim_matches(|c| c == '\'' || c == '"');
value.parse::<f64>().map(Some).map_err(|_| Error::BadFloat {
field: leak_field(field),
row: 0,
value: value.to_string(),
})
}
pub(crate) fn scalar_from_assignment(raw: &str, field: &str) -> Result<Option<f64>> {
let stripped = super::tokens::strip_comments(raw);
find_scalar(&stripped, field)
}
pub(crate) fn for_each_matrix_row<F>(assignment: &str, field: &str, mut f: F) -> Result<()>
where
F: FnMut(&[f64], usize) -> Result<()>,
{
let mut buf: Vec<f64> = Vec::with_capacity(24);
let mut row = 0usize;
let mut inside = false; let mut done = false; for line in assignment.lines() {
if done {
break;
}
let mut code = super::tokens::comment_split(line).0;
if !inside {
let Some(open) = code.find('[') else {
continue;
};
code = &code[open + 1..];
inside = true;
}
if let Some(close) = code.find(']') {
code = &code[..close];
done = true;
}
let mut continuation = false;
let trimmed = code.trim_end();
if let Some(stripped) = trimmed.strip_suffix("...") {
code = stripped;
continuation = true;
}
let bytes = code.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b';' {
if !buf.is_empty() {
f(&buf, row)?;
row += 1;
buf.clear();
}
i += 1;
continue;
}
if b.is_ascii_whitespace() {
i += 1;
continue;
}
let start = i;
while i < bytes.len() && bytes[i] != b';' && !bytes[i].is_ascii_whitespace() {
i += 1;
}
let mut tok = &bytes[start..i];
while tok.last() == Some(&b',') {
tok = &tok[..tok.len() - 1];
}
if tok.is_empty() {
continue;
}
buf.push(parse_float(tok).ok_or_else(|| Error::BadFloat {
field: leak_field(field),
row,
value: String::from_utf8_lossy(tok).into_owned(),
})?);
}
if !continuation && !buf.is_empty() {
f(&buf, row)?;
row += 1;
buf.clear();
}
}
if inside && !done {
return Err(Error::UnbalancedBrackets(leak_field(field)));
}
if !buf.is_empty() {
f(&buf, row)?;
}
Ok(())
}
fn parse_float(tok: &[u8]) -> Option<f64> {
match tok {
b"Inf" | b"inf" | b"+Inf" | b"+inf" => Some(f64::INFINITY),
b"-Inf" | b"-inf" => Some(f64::NEG_INFINITY),
b"NaN" | b"nan" => Some(f64::NAN),
_ => lexical_core::parse::<f64>(tok).ok(),
}
}
fn leak_field(field: &str) -> &'static str {
match field {
"baseMVA" => "baseMVA",
"bus" => "bus",
"branch" => "branch",
"dcline" => "dcline",
"gen" => "gen",
"gencost" => "gencost",
"storage" => "storage",
"version" => "version",
_ => "(unknown)",
}
}