use crate::types::ExprType;
use crate::value::ExprValue;
use std::collections::HashMap;
pub const MAX_SYMBOL_TABLE_ENTRIES: usize = 100_000;
#[derive(Debug, Clone)]
pub struct SymbolTableError {
pub key: String,
pub conflict: String,
}
impl std::fmt::Display for SymbolTableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Cannot set '{}': '{}' is not a table",
self.key, self.conflict
)
}
}
impl std::error::Error for SymbolTableError {}
impl From<SymbolTableError> for crate::error::ExpressionError {
fn from(e: SymbolTableError) -> Self {
crate::error::ExpressionError::new(e.to_string())
}
}
#[derive(Debug, Clone)]
pub enum SymbolTableEntry {
Table(SymbolTable),
Value(ExprValue),
}
#[derive(Debug, Clone, Default)]
pub struct SymbolTable {
pub(crate) table: HashMap<String, SymbolTableEntry>,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
table: HashMap::new(),
}
}
pub fn from_pairs<'a, I>(pairs: I) -> Result<Self, SymbolTableError>
where
I: IntoIterator<Item = (&'a str, ExprValue)>,
{
let mut st = Self::new();
for (k, v) in pairs {
st.set(k, v)?;
}
Ok(st)
}
pub fn set_table(&mut self, key: &str, subtable: SymbolTable) {
self.table
.insert(key.to_string(), SymbolTableEntry::Table(subtable));
}
pub fn get_table(&self, key: &str) -> Option<&SymbolTable> {
match self.get(key) {
Some(SymbolTableEntry::Table(t)) => Some(t),
_ => None,
}
}
pub fn set(&mut self, key: &str, value: impl Into<ExprValue>) -> Result<(), SymbolTableError> {
self.set_value(key, value.into())
}
fn set_value(&mut self, key: &str, value: ExprValue) -> Result<(), SymbolTableError> {
let parts: Vec<&str> = key.split('.').collect();
if parts.len() == 1 {
if matches!(self.table.get(key), Some(SymbolTableEntry::Table(_))) {
return Err(SymbolTableError {
key: key.to_string(),
conflict: key.to_string(),
});
}
self.table
.insert(key.to_string(), SymbolTableEntry::Value(value));
return Ok(());
}
let mut current = self;
for &part in &parts[..parts.len() - 1] {
let entry = current
.table
.entry(part.to_string())
.or_insert_with(|| SymbolTableEntry::Table(SymbolTable::new()));
current = match entry {
SymbolTableEntry::Table(t) => t,
_ => {
return Err(SymbolTableError {
key: key.to_string(),
conflict: part.to_string(),
})
}
};
}
let last = parts.last().unwrap().to_string();
if matches!(current.table.get(&last), Some(SymbolTableEntry::Table(_))) {
return Err(SymbolTableError {
key: key.to_string(),
conflict: last,
});
}
current.table.insert(last, SymbolTableEntry::Value(value));
Ok(())
}
pub fn set_string(&mut self, key: &str, value: &str) -> Result<(), SymbolTableError> {
self.set(key, ExprValue::String(value.to_string()))
}
pub fn get(&self, key: &str) -> Option<&SymbolTableEntry> {
let parts: Vec<&str> = key.split('.').collect();
let mut current = self;
for (i, &part) in parts.iter().enumerate() {
match current.table.get(part) {
Some(SymbolTableEntry::Table(t)) if i < parts.len() - 1 => current = t,
Some(entry) if i == parts.len() - 1 => return Some(entry),
_ => return None,
}
}
None
}
pub fn get_value(&self, key: &str) -> Option<&ExprValue> {
match self.get(key) {
Some(SymbolTableEntry::Value(v)) => Some(v),
_ => None,
}
}
pub fn get_string(&self, key: &str) -> Option<&str> {
match self.get_value(key) {
Some(ExprValue::String(s)) => Some(s),
Some(ExprValue::Path { value, .. }) => Some(value),
_ => None,
}
}
pub fn contains(&self, key: &str) -> bool {
self.get(key).is_some()
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.table.keys().map(|s| s.as_str())
}
pub fn all_paths(&self, prefix: &str) -> Vec<String> {
let mut out = Vec::new();
self.collect_paths(prefix, &mut out);
out
}
fn collect_paths(&self, prefix: &str, out: &mut Vec<String>) {
for (key, entry) in &self.table {
let path = if prefix.is_empty() {
key.clone()
} else {
format!("{prefix}.{key}")
};
match entry {
SymbolTableEntry::Value(_) => out.push(path),
SymbolTableEntry::Table(sub) => sub.collect_paths(&path, out),
}
}
}
pub fn merge_from(&mut self, other: &SymbolTable) {
for (key, entry) in &other.table {
match entry {
SymbolTableEntry::Value(v) => {
self.table
.insert(key.clone(), SymbolTableEntry::Value(v.clone()));
}
SymbolTableEntry::Table(sub) => match self.table.get_mut(key) {
Some(SymbolTableEntry::Table(existing)) => existing.merge_from(sub),
_ => {
self.table
.insert(key.clone(), SymbolTableEntry::Table(sub.clone()));
}
},
}
}
}
}
impl serde::Serialize for SymbolTable {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeSeq;
let paths = self.all_paths("");
let entries: Vec<_> = paths
.iter()
.filter_map(|p| {
self.get_value(p).and_then(|v| {
if matches!(v, ExprValue::Unresolved(_)) {
None
} else {
Some((p, v))
}
})
})
.collect();
let mut seq = s.serialize_seq(Some(entries.len()))?;
for (path, value) in entries {
seq.serialize_element(&serde_json::json!({
"name": path,
"value": value.transport_value(),
"type": value.expr_type().to_string(),
}))?;
}
seq.end()
}
}
impl<'de> serde::Deserialize<'de> for SymbolTable {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let arr: Vec<serde_json::Value> = serde::Deserialize::deserialize(d)?;
if arr.len() > MAX_SYMBOL_TABLE_ENTRIES {
return Err(serde::de::Error::custom(format!(
"SymbolTable: too many entries ({}); maximum is {}",
arr.len(),
MAX_SYMBOL_TABLE_ENTRIES,
)));
}
let mut st = SymbolTable::new();
for entry in &arr {
let name = entry
.get("name")
.and_then(|n| n.as_str())
.ok_or_else(|| serde::de::Error::missing_field("name"))?;
let type_str = entry
.get("type")
.and_then(|t| t.as_str())
.ok_or_else(|| serde::de::Error::missing_field("type"))?;
let binding_type = ExprType::parse(type_str).map_err(serde::de::Error::custom)?;
let raw_value = entry
.get("value")
.ok_or_else(|| serde::de::Error::missing_field("value"))?;
let value = ExprValue::from_transport_value(
raw_value,
&binding_type,
crate::path_mapping::PathFormat::Posix,
)
.map_err(serde::de::Error::custom)?;
st.set(name, value).map_err(serde::de::Error::custom)?;
}
Ok(st)
}
}
impl<'a> FromIterator<(&'a str, ExprValue)> for SymbolTable {
fn from_iter<I: IntoIterator<Item = (&'a str, ExprValue)>>(iter: I) -> Self {
let mut st = Self::new();
for (k, v) in iter {
st.set(k, v)
.expect("SymbolTable path conflict in FromIterator");
}
st
}
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(transparent)]
pub struct SerializedSymbolTable(serde_json::Value);
impl SerializedSymbolTable {
pub fn from_value(v: serde_json::Value) -> Self {
Self(v)
}
pub fn from_json_str(s: &str) -> Result<Self, serde_json::Error> {
Ok(Self(serde_json::from_str(s)?))
}
pub fn from_symtab(st: &SymbolTable) -> Self {
Self(serde_json::to_value(st).expect("SymbolTable serialization cannot fail"))
}
pub fn to_symtab(
&self,
path_format: crate::path_mapping::PathFormat,
) -> Result<SymbolTable, String> {
let arr = self
.0
.as_array()
.ok_or("SerializedSymbolTable: expected JSON array")?;
if arr.len() > MAX_SYMBOL_TABLE_ENTRIES {
return Err(format!(
"SerializedSymbolTable: too many entries ({}); maximum is {}",
arr.len(),
MAX_SYMBOL_TABLE_ENTRIES,
));
}
let mut st = SymbolTable::new();
for entry in arr {
let name = entry
.get("name")
.and_then(|n| n.as_str())
.ok_or("SerializedSymbolTable: missing 'name' field")?;
let type_str = entry
.get("type")
.and_then(|t| t.as_str())
.ok_or("SerializedSymbolTable: missing 'type' field")?;
let binding_type = ExprType::parse(type_str)
.map_err(|e| format!("SerializedSymbolTable: bad type '{type_str}': {e}"))?;
let raw_value = entry
.get("value")
.ok_or("SerializedSymbolTable: missing 'value' field")?;
let value = ExprValue::from_transport_value(raw_value, &binding_type, path_format)
.map_err(|e| format!("SerializedSymbolTable: {e}"))?;
st.set(name, value)
.map_err(|e| format!("SerializedSymbolTable: {e}"))?;
}
Ok(st)
}
pub fn as_value(&self) -> &serde_json::Value {
&self.0
}
}
impl<'de> serde::Deserialize<'de> for SerializedSymbolTable {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v: serde_json::Value = serde::Deserialize::deserialize(d)?;
Ok(Self(v))
}
}
#[macro_export]
macro_rules! symtab {
($($key:expr => $val:expr),* $(,)?) => {{
let mut st = $crate::SymbolTable::new();
$(st.set($key, $val).expect("symtab! path conflict");)*
st
}};
}