use crate::schema::attr::AttrType;
use crate::schema::interner::Interner;
use crate::schema::layout::SlotLayoutBuilder;
use crate::schema::schema::Schema;
use crate::{AttrId, KindId};
use ahash::AHashMap;
use std::sync::Arc;
pub struct SchemaBuilder {
kind_names: Interner<KindId>,
attr_names: Interner<AttrId>,
kind_attrs: Vec<Vec<(AttrId, AttrType)>>,
value_interner: ValueInterner,
}
#[derive(Default, Debug)]
pub struct ValueInterner {
table: Vec<String>,
index: AHashMap<String, u32>,
}
impl ValueInterner {
#[allow(clippy::expect_used)] pub fn intern(&mut self, s: &str) -> u32 {
if let Some(&id) = self.index.get(s) {
return id;
}
let id = u32::try_from(self.table.len()).expect("value interner overflow");
self.table.push(s.to_owned());
self.index.insert(s.to_owned(), id);
id
}
#[must_use]
pub fn get(&self, s: &str) -> Option<u32> {
self.index.get(s).copied()
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] pub fn lookup(&self, id: u32) -> &str {
&self.table[id as usize]
}
#[must_use]
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
self.table.len()
}
}
impl SchemaBuilder {
#[must_use]
pub fn new() -> Self {
Self {
kind_names: Interner::new(),
attr_names: Interner::new(),
kind_attrs: Vec::new(),
value_interner: ValueInterner::default(),
}
}
#[must_use]
pub fn kind(&mut self, name: &str, attrs: &[(&str, AttrType)]) -> KindId {
let kid = self.kind_names.intern(name);
let mut resolved = Vec::with_capacity(attrs.len());
for &(aname, ty) in attrs {
let attr_id = self.attr_names.intern(aname);
resolved.push((attr_id, ty));
}
while self.kind_attrs.len() <= usize::from(kid.0) {
self.kind_attrs.push(Vec::new());
}
self.kind_attrs[usize::from(kid.0)] = resolved;
kid
}
#[must_use]
pub fn enum_value(&mut self, s: &str) -> u32 {
self.value_interner.intern(s)
}
#[must_use]
pub fn build(self) -> Arc<Schema> {
let mut layout = SlotLayoutBuilder::new();
for attrs in &self.kind_attrs {
layout.push_kind(attrs);
}
Arc::new(Schema {
kind_names: self.kind_names,
attr_names: self.attr_names,
value_interner: self.value_interner,
slot_layout: layout.build(),
})
}
}
impl Default for SchemaBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn build_produces_resolvable_schema() {
let mut b = SchemaBuilder::new();
let kid = b.kind(
"audience",
&[("male_frac", AttrType::F32), ("dwell_secs", AttrType::Int)],
);
let schema = b.build();
let slot = schema
.slot_layout
.resolve(kid, schema.attr_names.get("male_frac").unwrap())
.unwrap();
assert_eq!(slot.offset, 0);
}
#[test]
fn enum_values_get_stable_codes() {
let mut b = SchemaBuilder::new();
let c1 = b.enum_value("young_male");
let c2 = b.enum_value("young_female");
let c1_again = b.enum_value("young_male");
assert_eq!(c1, c1_again);
assert_ne!(c1, c2);
}
}