use crate::types::*;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid value for {field}: {value}")]
InvalidValue { field: String, value: String },
#[error("Parse error at line {line}: {message}")]
ParseError { line: usize, message: String },
}
#[derive(Debug)]
pub struct ConfigParser;
impl ConfigParser {
pub fn new() -> Self {
Self
}
pub fn parse(&self, source: &str) -> Result<LithConfig, ConfigError> {
let mut config = LithConfig::default();
let mut current_section = String::new();
let mut kv_map: HashMap<String, HashMap<String, String>> = HashMap::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with("//") {
continue;
}
if trimmed.starts_with('[') && trimmed.ends_with(']') {
current_section = trimmed[1..trimmed.len() - 1].to_string();
kv_map.entry(current_section.clone()).or_default();
continue;
}
if let Some((key, value)) = trimmed.split_once('=') {
let key = key.trim().to_string();
let value = value.trim().trim_matches('"').to_string();
kv_map
.entry(current_section.clone())
.or_default()
.insert(key, value);
} else {
return Err(ConfigError::ParseError {
line: line_num + 1,
message: format!("Expected key = value, got: {}", trimmed),
});
}
}
if let Some(target) = kv_map.get("target") {
if let Some(backend) = target.get("backend") {
config.target.backend = backend.clone();
}
if let Some(device) = target.get("device") {
config.target.device = Some(device.clone());
}
if let Some(precision) = target.get("precision") {
config.target.precision = Some(precision.clone());
}
}
if let Some(budget) = kv_map.get("budget") {
if let Some(v) = budget.get("max_flops") {
config.budget.max_flops = v.parse().ok();
}
if let Some(v) = budget.get("max_memory_bytes") {
config.budget.max_memory_bytes = v.parse().ok();
}
if let Some(v) = budget.get("max_time_ms") {
config.budget.max_time_ms = v.parse().ok();
}
if let Some(v) = budget.get("min_fidelity") {
config.budget.min_fidelity = v.parse().ok();
}
if let Some(v) = budget.get("max_circuit_depth") {
config.budget.max_circuit_depth = v.parse().ok();
}
}
if let Some(opt) = kv_map.get("optimisation") {
if let Some(level) = opt.get("level") {
config.optimisation.level = match level.as_str() {
"O0" | "0" => OptLevel::O0,
"O1" | "1" => OptLevel::O1,
"O2" | "2" => OptLevel::O2,
"O3" | "3" => OptLevel::O3,
_ => {
return Err(ConfigError::InvalidValue {
field: "optimisation.level".into(),
value: level.clone(),
})
}
};
}
if let Some(max_iter) = opt.get("max_iterations") {
config.optimisation.max_iterations = max_iter.parse().unwrap_or(10);
}
if let Some(passes) = opt.get("passes") {
config.optimisation.passes = passes
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
if let Some(disabled) = opt.get("disabled_passes") {
config.optimisation.disabled_passes = disabled
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
}
if let Some(sim) = kv_map.get("simulation") {
if let Some(v) = sim.get("shape_propagation") {
config.simulation.enable_shape_propagation = v == "true";
}
if let Some(v) = sim.get("flop_counting") {
config.simulation.enable_flop_counting = v == "true";
}
if let Some(v) = sim.get("memory_analysis") {
config.simulation.enable_memory_analysis = v == "true";
}
if let Some(v) = sim.get("noise_simulation") {
config.simulation.enable_noise_simulation = v == "true";
}
}
if let Some(quantum) = kv_map.get("quantum") {
let qc = QuantumConfig {
topology: quantum
.get("topology")
.cloned()
.unwrap_or_else(|| "linear".into()),
num_qubits: quantum
.get("num_qubits")
.and_then(|v| v.parse().ok())
.unwrap_or(5),
error_mitigation: quantum.get("error_mitigation").cloned(),
shots: quantum.get("shots").and_then(|v| v.parse().ok()),
provider: quantum
.get("provider")
.and_then(|v| QuantumProvider::from_str_opt(v)),
};
config.quantum = Some(qc);
}
Ok(config)
}
pub fn parse_json(&self, json: &str) -> Result<LithConfig, ConfigError> {
serde_json::from_str(json).map_err(|e| ConfigError::ParseError {
line: 0,
message: format!("JSON parse error: {}", e),
})
}
}
impl Default for ConfigParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_lith_config() {
let src = r#"
[target]
backend = "cuda"
device = "A100"
precision = "fp16"
[budget]
max_flops = 1000000000
max_memory_bytes = 80000000000
[optimisation]
level = O3
max_iterations = 20
[simulation]
shape_propagation = true
flop_counting = true
"#;
let parser = ConfigParser::new();
let config = parser.parse(src).unwrap();
assert_eq!(config.target.backend, "cuda");
assert_eq!(config.optimisation.level, OptLevel::O3);
assert_eq!(config.budget.max_flops, Some(1000000000));
}
#[test]
fn test_parse_quantum_config() {
let src = r#"
[target]
backend = "qasm"
[quantum]
topology = "grid"
num_qubits = 27
shots = 4096
[budget]
min_fidelity = 0.95
"#;
let parser = ConfigParser::new();
let config = parser.parse(src).unwrap();
assert!(config.quantum.is_some());
assert_eq!(config.quantum.as_ref().unwrap().num_qubits, 27);
assert_eq!(config.budget.min_fidelity, Some(0.95));
}
}