use std::fs;
use crate::data_line;
use crate::file_extension;
use crate::option_line;
use crate::utils;
use crate::Network;
#[derive(Debug)]
struct ParserState {
data_lines: Vec<String>,
option_line_parsed: bool,
}
pub fn read_file(file_path: String) -> Network {
tracing::debug!("Parsing touchstone file: {}", file_path);
let contents = fs::read_to_string(&file_path).expect("Should have been able to read the file");
let file_type = file_path
.split(".")
.last()
.expect("Failed to get file type from file path");
let file_type_is_valid = file_extension::is_valid_file_extension(file_type);
if !file_type_is_valid {
panic!(
"File type not supported: {}, only sNp supported, where n is an integer without leading zeros.",
file_type
);
}
let n_ports_str = &file_type[1..file_type.len() - 1];
let n_ports = n_ports_str
.parse::<i32>()
.expect("Failed to parse number of ports from file type");
let mut parsed_options = option_line::Options::default();
let mut parser_state = ParserState {
option_line_parsed: false,
data_lines: Vec::new(),
};
let mut comment_lines: Vec<String> = Vec::new();
let mut comments_after_option_line: Vec<String> = Vec::new();
let mut s: Vec<data_line::ParsedDataLine> = Vec::new();
let mut f: Vec<f64> = Vec::new();
let mut frequency_unit = String::new();
let mut parameter = String::new();
let mut format = String::new();
let mut resistance_string = String::new();
let mut reference_resistance = String::new();
let count_values_on_line = |line: &str| -> usize {
line.split_whitespace()
.filter(|s| !s.starts_with('!'))
.count()
};
let expected_values = (1 + 2 * n_ports * n_ports) as usize;
let mut current_data_segment: Vec<String> = Vec::new();
let mut current_value_count: usize = 0;
for line in contents.lines() {
let is_option_line = line.starts_with("#");
let is_comment = line.starts_with("!");
if is_option_line {
if !parser_state.option_line_parsed {
option_line::parse_option_line(line.to_string(), &mut parsed_options);
frequency_unit = parsed_options.frequency_unit.clone();
parameter = parsed_options.parameter.clone();
format = parsed_options.format.clone();
resistance_string = parsed_options.resistance_string.clone();
reference_resistance = parsed_options.reference_resistance.clone();
parser_state.option_line_parsed = true;
} else {
panic!("Multiple option lines found in file. Only one option line is allowed.");
}
} else if is_comment {
if !parser_state.option_line_parsed {
comment_lines.push(line.to_string());
} else {
comments_after_option_line.push(line.to_string());
}
} else if !line.trim().is_empty() {
current_data_segment.push(line.to_string());
current_value_count += count_values_on_line(line);
if current_value_count >= expected_values {
let line_matrix_data = data_line::parse_data_line(
current_data_segment.clone(),
&parsed_options.format,
&n_ports,
&parsed_options.frequency_unit,
);
f.push(line_matrix_data.frequency);
s.push(line_matrix_data);
parser_state.data_lines.extend(current_data_segment.clone());
current_data_segment.clear();
current_value_count = 0;
}
}
}
if !current_data_segment.is_empty() {
let line_matrix_data = data_line::parse_data_line(
current_data_segment.clone(),
&parsed_options.format,
&n_ports,
&parsed_options.frequency_unit,
);
f.push(line_matrix_data.frequency);
s.push(line_matrix_data);
parser_state.data_lines.extend(current_data_segment);
}
tracing::debug!(
num_ports = n_ports,
num_frequencies = f.len(),
format = %parsed_options.format,
frequency_unit = %parsed_options.frequency_unit,
"Parsing complete"
);
Network {
name: file_path,
rank: n_ports,
frequency_unit,
parameter,
format,
resistance_string,
z0: utils::str_to_f64(reference_resistance.as_str()),
comments: comment_lines,
comments_after_option_line,
f,
s,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_2port_ntwk1() {
let network = read_file("files/ntwk1.s2p".to_string());
assert_eq!(network.name, "files/ntwk1.s2p".to_string());
assert_eq!(network.rank, 2);
assert_eq!(network.frequency_unit, "GHz");
assert_eq!(network.parameter, "S");
assert_eq!(network.format, "RI");
assert_eq!(network.resistance_string, "R");
assert_eq!(network.z0, 50.0);
assert_eq!(network.comments.len(), 1);
assert_eq!(network.comments_after_option_line.len(), 3);
assert_eq!(network.f.len(), 91);
assert_eq!(network.s.len(), 91);
}
#[test]
fn parse_2port_ntwk2() {
let network = read_file("files/ntwk2.s2p".to_string());
assert_eq!(network.name, "files/ntwk2.s2p".to_string());
assert_eq!(network.rank, 2);
assert_eq!(network.frequency_unit, "GHz");
assert_eq!(network.parameter, "S");
assert_eq!(network.format, "RI");
assert_eq!(network.resistance_string, "R");
assert_eq!(network.z0, 50.0);
assert_eq!(network.comments.len(), 1);
assert_eq!(network.comments_after_option_line.len(), 3);
assert_eq!(network.f.len(), 91);
assert_eq!(network.s.len(), 91);
}
#[test]
fn parse_2port_ntwk3() {
let network = read_file("files/ntwk3.s2p".to_string());
assert_eq!(network.name, "files/ntwk3.s2p".to_string());
assert_eq!(network.rank, 2);
assert_eq!(network.frequency_unit, "GHz");
assert_eq!(network.parameter, "S");
assert_eq!(network.format, "RI");
assert_eq!(network.resistance_string, "R");
assert_eq!(network.z0, 50.0);
assert_eq!(network.comments.len(), 1);
assert_eq!(network.comments_after_option_line.len(), 3);
assert_eq!(network.f.len(), 91);
assert_eq!(network.s.len(), 91);
}
#[test]
fn parse_3port_hfss() {
let network = read_file("files/hfss_18.2.s3p".to_string());
assert_eq!(network.name, "files/hfss_18.2.s3p".to_string());
assert_eq!(network.rank, 3);
assert_eq!(network.frequency_unit, "GHz");
assert_eq!(network.parameter, "S");
assert_eq!(network.format, "MA");
assert_eq!(network.resistance_string, "R");
assert_eq!(network.z0, 50.0);
assert!(!network.f.is_empty());
assert_eq!(network.f.len(), network.s.len());
for i in 1..=3 {
for j in 1..=3 {
let s_db = network.s_db(i as i8, j as i8);
assert_eq!(s_db.len(), network.f.len());
}
}
}
#[test]
fn parse_4port_agilent() {
let network = read_file("files/Agilent_E5071B.s4p".to_string());
assert_eq!(network.name, "files/Agilent_E5071B.s4p".to_string());
assert_eq!(network.rank, 4);
assert_eq!(network.frequency_unit, "Hz");
assert_eq!(network.parameter, "S");
assert_eq!(network.format, "DB");
assert_eq!(network.resistance_string, "R");
assert_eq!(network.z0, 75.0);
assert!(!network.f.is_empty());
assert_eq!(network.f.len(), network.s.len());
for i in 1..=4 {
for j in 1..=4 {
let s_db = network.s_db(i as i8, j as i8);
assert_eq!(s_db.len(), network.f.len());
}
}
}
}