use std::collections::HashMap;
use std::fmt::Display;
use std::ops::Deref;
use std::ops::DerefMut;
use std::str::FromStr;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TritonVmEnvVars(HashMap<u8, Vec<(String, String)>>);
impl Deref for TritonVmEnvVars {
type Target = HashMap<u8, Vec<(String, String)>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TritonVmEnvVars {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Display for TritonVmEnvVars {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.is_empty() {
return write!(f, "TritonVmEnvVars: {{}}");
}
writeln!(f, "TritonVmEnvVars: {{")?;
let mut heights: Vec<&u8> = self.0.keys().collect();
heights.sort();
for (i, height) in heights.iter().enumerate() {
let vars = self.0.get(height).unwrap();
if vars.is_empty() {
writeln!(f, " log2 padded Height {}: {{}}", height)?;
} else {
writeln!(f, " log2 padded Height {}: {{", height)?;
for (j, (key, value)) in vars.iter().enumerate() {
let comma = if j == vars.len() - 1 { "" } else { "," };
writeln!(f, " {} = {}{}", key, value, comma)?;
}
writeln!(f, "}}")?;
}
if i < heights.len() - 1 {
writeln!(f)?;
}
}
write!(f, "}}")
}
}
impl FromStr for TritonVmEnvVars {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Ok(Self::default());
}
let mut map = HashMap::new();
for entry in s.split(',') {
let (key_str, vars_str) = entry
.split_once(':')
.ok_or_else(|| format!("TVM env var: Invalid entry (missing ':'): '{}'", entry))?;
let key: u8 = key_str
.trim()
.parse()
.map_err(|_| format!("TVM env var: Invalid u8 key: '{}'", key_str))?;
if !(8..=31).contains(&key) {
return Err(format!("TVM env var: {key} not in range 8..=31"));
}
let vars_str = vars_str.trim();
let vars_str = vars_str
.strip_prefix('"')
.and_then(|v| v.strip_suffix('"'))
.unwrap_or(vars_str);
let vars_str = vars_str
.strip_prefix("'")
.and_then(|v| v.strip_suffix("'"))
.unwrap_or(vars_str);
let mut pairs = Vec::new();
for var_val in vars_str.split_whitespace() {
let (var, val) = var_val
.split_once('=')
.ok_or_else(|| format!("TVM env var: Invalid VAR=VAL: '{}'", var_val))?;
pairs.push((var.to_string(), val.to_string()));
}
map.insert(key, pairs);
}
Ok(TritonVmEnvVars(map))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn parses_no_entries() {
let parsed = TritonVmEnvVars::from_str("").unwrap();
assert_eq!(HashMap::default(), parsed.0);
}
#[test]
fn parses_single_entry() {
let input = r#"24:"RAYON_NUM_THREADS=50 TVM_LDE_TRACE=no_cache""#;
let parsed = TritonVmEnvVars::from_str(input).unwrap();
let mut expected = HashMap::new();
expected.insert(
24,
vec![
("RAYON_NUM_THREADS".to_string(), "50".to_string()),
("TVM_LDE_TRACE".to_string(), "no_cache".to_string()),
],
);
assert_eq!(expected, parsed.0);
}
#[test]
fn parses_multiple_entries() {
let input = r#"24:"RAYON_NUM_THREADS=50 TVM_LDE_TRACE=no_cache",25:"RAYON_NUM_THREADS=20 TVM_LDE_TRACE=no_cache""#;
let parsed = TritonVmEnvVars::from_str(input).unwrap();
let mut expected = HashMap::new();
expected.insert(
24,
vec![
("RAYON_NUM_THREADS".to_string(), "50".to_string()),
("TVM_LDE_TRACE".to_string(), "no_cache".to_string()),
],
);
expected.insert(
25,
vec![
("RAYON_NUM_THREADS".to_string(), "20".to_string()),
("TVM_LDE_TRACE".to_string(), "no_cache".to_string()),
],
);
assert_eq!(expected, parsed.0);
}
}