sim-lib-topology 0.1.3

Data-driven topology engine.
Documentation
use sim_kernel::{Expr, Result, Symbol};

use super::util::{data_expr, expr_kind, normalize_key, parse_error};

pub(super) struct Field<'a> {
    name: String,
    value: &'a Expr,
}

pub(super) struct Fields<'a> {
    fields: Vec<Field<'a>>,
}

impl<'a> Fields<'a> {
    pub(super) fn from_map(entries: &'a [(Expr, Expr)], path: &str) -> Result<Self> {
        let mut fields = Vec::with_capacity(entries.len());
        for (key, value) in entries {
            let name = match data_expr(key) {
                Expr::Symbol(symbol) => normalize_key(symbol.name.as_ref()),
                Expr::String(value) => normalize_key(value),
                other => {
                    return Err(parse_error(
                        path,
                        format!("expected symbol key, found {}", expr_kind(other)),
                    ));
                }
            };
            fields.push(Field {
                name,
                value: data_expr(value),
            });
        }
        Ok(Self { fields })
    }

    pub(super) fn from_keywords(items: &'a [Expr], path: &str) -> Result<Self> {
        if !items.len().is_multiple_of(2) {
            return Err(parse_error(path, "keyword list has an odd number of items"));
        }
        let mut fields = Vec::with_capacity(items.len() / 2);
        for pair in items.chunks_exact(2) {
            let key = data_expr(&pair[0]);
            let name = match key {
                Expr::Symbol(symbol) if symbol.name.starts_with(':') => {
                    normalize_key(symbol.name.as_ref())
                }
                Expr::String(value) if value.starts_with(':') => normalize_key(value),
                other => {
                    return Err(parse_error(
                        path,
                        format!("expected keyword key, found {}", expr_kind(other)),
                    ));
                }
            };
            fields.push(Field {
                name,
                value: data_expr(&pair[1]),
            });
        }
        Ok(Self { fields })
    }

    pub(super) fn push(&mut self, name: impl Into<String>, value: &'a Expr) {
        self.fields.push(Field {
            name: name.into(),
            value: data_expr(value),
        });
    }

    pub(super) fn get(&self, name: &str) -> Option<&'a Expr> {
        self.fields
            .iter()
            .rev()
            .find(|field| field.name == name)
            .map(|field| field.value)
    }

    pub(super) fn required(&self, name: &str, path: &str) -> Result<&'a Expr> {
        self.get(name)
            .ok_or_else(|| parse_error(path, "missing required field"))
    }

    pub(super) fn unknown_pairs(&self, known: &[&str]) -> Vec<(Symbol, Expr)> {
        self.fields
            .iter()
            .filter(|field| !known.contains(&field.name.as_str()))
            .map(|field| (Symbol::new(field.name.clone()), field.value.clone()))
            .collect()
    }

    pub(super) fn reject_unknown(&self, known: &[&str]) -> Result<()> {
        if let Some(field) = self
            .fields
            .iter()
            .find(|field| !known.contains(&field.name.as_str()))
        {
            Err(parse_error(
                field.name.as_str(),
                format!("unknown field {}", field.name),
            ))
        } else {
            Ok(())
        }
    }

    pub(super) fn into_pairs(self) -> Vec<(Symbol, Expr)> {
        self.fields
            .into_iter()
            .map(|field| (Symbol::new(field.name), field.value.clone()))
            .collect()
    }
}