mod parse;
pub use parse::{
parse_model, resolve_mount_target, EdgeDecl, Model, Mount, NodeDecl, RefDecl, RelDecl,
};
use quarb::{AstAdapter, NodeId, QueryResult, Value};
use std::cell::OnceCell;
use std::collections::HashMap;
const MODEL_TAG: u64 = 1 << 63;
const CIDX_SHIFT: u64 = 44;
const VAL_MASK: u64 = (1 << CIDX_SHIFT) - 1;
enum Members {
Values(Vec<Value>),
Nodes(Vec<NodeId>),
}
impl Members {
fn len(&self) -> usize {
match self {
Members::Values(v) => v.len(),
Members::Nodes(n) => n.len(),
}
}
}
struct Container {
name: String,
role: String,
trait_name: String,
members: Members,
by_str: HashMap<String, usize>,
}
enum Member {
Value(Value),
Node(NodeId),
}
impl Container {
fn member(&self, v: usize) -> Option<Member> {
if v == 0 {
return None;
}
match &self.members {
Members::Values(vs) => vs.get(v - 1).cloned().map(Member::Value),
Members::Nodes(ns) => ns.get(v - 1).copied().map(Member::Node),
}
}
}
struct Fabric {
alias: HashMap<NodeId, (NodeId, String)>,
resolve: HashMap<(NodeId, String), NodeId>,
ref_back: HashMap<NodeId, Vec<(String, NodeId)>>,
ref_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
rel_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
rel_back: HashMap<NodeId, Vec<(String, NodeId)>>,
edges: HashMap<NodeId, Vec<(String, NodeId)>>,
}
pub struct ModelAdapter<A: AstAdapter> {
base: A,
model: Model,
containers: OnceCell<Vec<Container>>,
fabric: OnceCell<Fabric>,
}
impl<A: AstAdapter> ModelAdapter<A> {
pub fn new(base: A, model: Model) -> Self {
ModelAdapter {
base,
model,
containers: OnceCell::new(),
fabric: OnceCell::new(),
}
}
pub fn base(&self) -> &A {
&self.base
}
pub fn locator(&self, node: NodeId, base_locator: impl Fn(NodeId) -> String) -> String {
match self.decode(node) {
None => base_locator(node),
Some((c, 0)) => format!("/{}", self.containers()[c].name),
Some((c, v)) => {
let cont = &self.containers()[c];
format!("/{}/{}[{}]", cont.name, cont.role, v)
}
}
}
fn seeded_defs(&self) -> quarb::Defs {
quarb::parse_defs(&self.model.defs_text).unwrap_or_default()
}
fn containers(&self) -> &[Container] {
self.containers.get_or_init(|| {
let defs = self.seeded_defs();
let mut built: Vec<Container> = Vec::new();
for decl in &self.model.nodes {
let members = self.members(&decl.query, &defs, &built);
let by_str = match &members {
Members::Values(vs) => vs
.iter()
.enumerate()
.map(|(i, v)| (v.to_string(), i))
.collect(),
Members::Nodes(ns) => ns
.iter()
.enumerate()
.filter_map(|(i, n)| self.base_key(*n).map(|k| (k, i)))
.collect(),
};
built.push(Container {
name: decl.name.clone(),
role: decl.role.clone(),
trait_name: decl.trait_name.clone(),
members,
by_str,
});
}
built
})
}
fn members(&self, query: &str, defs: &quarb::Defs, prior: &[Container]) -> Members {
let result = if prior.is_empty() {
quarb::run_with_defs(query, defs, &self.base)
} else {
let scratch = PriorView {
base: &self.base,
prior,
};
quarb::run_with_defs(query, defs, &scratch)
};
match result {
Ok(QueryResult::Values(vs)) => {
let mut seen = std::collections::HashSet::new();
Members::Values(vs.into_iter().filter(|v| seen.insert(v.to_string())).collect())
}
Ok(QueryResult::Nodes(ns)) => {
let mut seen = std::collections::HashSet::new();
Members::Nodes(ns.into_iter().filter(|n| seen.insert(*n)).collect())
}
Err(_) => Members::Values(Vec::new()),
}
}
fn base_key(&self, node: NodeId) -> Option<String> {
self.base
.default_value(node)
.map(|v| v.to_string())
.or_else(|| self.base.name(node))
}
fn aliased(&self, node: NodeId) -> Option<NodeId> {
match self.decode(node) {
Some((c, v)) if v > 0 => match &self.containers()[c].members {
Members::Nodes(ns) => ns.get(v - 1).copied(),
Members::Values(_) => None,
},
_ => None,
}
}
fn elevated(&self, node: NodeId) -> Option<Value> {
match self.decode(node) {
Some((c, v)) if v > 0 => match &self.containers()[c].members {
Members::Values(vs) => vs.get(v - 1).cloned(),
Members::Nodes(_) => None,
},
_ => None,
}
}
fn container_node(c: usize) -> NodeId {
NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
}
fn value_node(c: usize, v: usize) -> NodeId {
NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (v as u64 + 1))
}
fn decode(&self, node: NodeId) -> Option<(usize, usize)> {
if node.0 & MODEL_TAG == 0 {
return None;
}
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
(c < self.containers().len()).then_some((c, v))
}
fn container_by_name(&self, name: &str) -> Option<usize> {
self.containers().iter().position(|c| c.name == name)
}
fn find_value(&self, container: usize, value: &Value) -> Option<NodeId> {
let idx = *self.containers()[container].by_str.get(&value.to_string())?;
Some(Self::value_node(container, idx))
}
fn fabric(&self) -> &Fabric {
self.fabric.get_or_init(|| {
let defs = self.seeded_defs();
let mut f = Fabric {
alias: HashMap::new(),
resolve: HashMap::new(),
ref_back: HashMap::new(),
ref_fwd: HashMap::new(),
rel_fwd: HashMap::new(),
rel_back: HashMap::new(),
edges: HashMap::new(),
};
for (c, cont) in self.containers().iter().enumerate() {
if let Members::Nodes(ns) = &cont.members {
for (i, n) in ns.iter().enumerate() {
f.alias
.entry(*n)
.or_insert((Self::value_node(c, i), cont.role.clone()));
}
}
}
for decl in &self.model.refs {
let Some(container) = self.container_by_name(&decl.container) else {
continue;
};
let by_key: Option<HashMap<String, NodeId>> =
decl.key_field.as_deref().map(|f| {
(1..=self.containers()[container].members.len())
.filter_map(|v| {
let n = Self::value_node(container, v - 1);
self.property(n, f).map(|k| (k.to_string(), n))
})
.collect()
});
for node in self.scope_nodes(&decl.scope, &defs) {
let Some(value) = self.base.property(node, &decl.field) else {
continue;
};
if matches!(value, Value::Null) {
continue;
}
let target = match &by_key {
Some(idx) => idx.get(&value.to_string()).copied(),
None => self.find_value(container, &value),
};
let Some(target) = target else {
continue;
};
let fwd = self.containers()[container].role.clone();
let (back_node, back) = match f.alias.get(&node) {
Some((alias, role)) => (*alias, role.clone()),
None => (node, scope_role(&decl.scope)),
};
f.resolve.insert((node, decl.field.clone()), target);
f.ref_fwd.entry(node).or_default().push((fwd, target));
f.ref_back.entry(target).or_default().push((back, back_node));
}
}
for decl in &self.model.rels {
let fwd = scope_role(&decl.target);
let back = scope_role(&decl.source);
for source in self.view_nodes(&decl.source, &defs) {
let cond = self.bind_driver(&decl.cond, source);
let query = format!("{}{}", decl.target, cond);
for target in self.view_nodes(&query, &defs) {
if target == source {
continue;
}
f.rel_fwd
.entry(source)
.or_default()
.push((fwd.clone(), target));
f.rel_back
.entry(target)
.or_default()
.push((back.clone(), source));
}
}
}
for decl in &self.model.edges {
let ca = self.field_container(&decl.field_a);
let cb = self.field_container(&decl.field_b);
let (Some((ca, la)), Some((cb, lb))) = (ca, cb) else {
continue;
};
let mut seen = std::collections::HashSet::new();
for node in self.scope_nodes(&decl.scope, &defs) {
let (Some(va), Some(vb)) = (
self.base.property(node, &decl.field_a),
self.base.property(node, &decl.field_b),
) else {
continue;
};
if matches!(va, Value::Null) || matches!(vb, Value::Null) {
continue;
}
let (Some(na), Some(nb)) =
(self.find_value(ca, &va), self.find_value(cb, &vb))
else {
continue;
};
if seen.insert((na, nb)) {
f.edges.entry(na).or_default().push((lb.clone(), nb));
f.edges.entry(nb).or_default().push((la.clone(), na));
}
}
}
f
})
}
fn field_container(&self, field: &str) -> Option<(usize, String)> {
let decl = self.model.refs.iter().find(|r| r.field == field)?;
let c = self.container_by_name(&decl.container)?;
Some((c, self.containers()[c].role.clone()))
}
fn scope_nodes(&self, scope: &str, defs: &quarb::Defs) -> Vec<NodeId> {
match quarb::run_with_defs(scope, defs, &self.base) {
Ok(QueryResult::Nodes(ns)) => ns,
_ => Vec::new(),
}
}
fn view_nodes(&self, path: &str, defs: &quarb::Defs) -> Vec<NodeId> {
let view = PriorView {
base: &self.base,
prior: self.containers(),
};
match quarb::run_with_defs(path, defs, &view) {
Ok(QueryResult::Nodes(ns)) => ns,
_ => Vec::new(),
}
}
fn bind_driver(&self, cond: &str, source: NodeId) -> String {
let view = PriorView {
base: &self.base,
prior: self.containers(),
};
let b: Vec<char> = cond.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < b.len() {
match b[i] {
q @ ('\'' | '"') => {
out.push(q);
i += 1;
while i < b.len() && b[i] != q {
if b[i] == '\\' && i + 1 < b.len() {
out.push(b[i]);
i += 1;
}
out.push(b[i]);
i += 1;
}
if i < b.len() {
out.push(b[i]);
i += 1;
}
}
'$' if b.get(i + 1) == Some(&'$') => {
i += 2;
let value = if b.get(i) == Some(&':') && b.get(i + 1) == Some(&':') {
i += 2;
let start = i;
while i < b.len()
&& (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '-')
{
i += 1;
}
let field: String = b[start..i].iter().collect();
view.property(source, &field)
} else {
view.default_value(source)
};
out.push_str(&literal(value.unwrap_or(Value::Null)));
}
c => {
out.push(c);
i += 1;
}
}
}
out
}
}
fn literal(v: Value) -> String {
match v {
Value::Int(n) => n.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
Value::Null => "''".to_string(),
other => format!("'{}'", other.to_string().replace('\\', "\\\\").replace('\'', "\\'")),
}
}
struct PriorView<'a, A: AstAdapter> {
base: &'a A,
prior: &'a [Container],
}
impl<A: AstAdapter> AstAdapter for PriorView<'_, A> {
fn root(&self) -> NodeId {
self.base.root()
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
if v == 0 && c < self.prior.len() {
return (0..self.prior[c].members.len())
.map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
.collect();
}
return Vec::new();
}
let mut kids = self.base.children(node);
if node == self.base.root() {
for c in 0..self.prior.len() {
kids.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
}
}
kids
}
fn name(&self, node: NodeId) -> Option<String> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
let cont = self.prior.get(c)?;
return Some(if v == 0 {
cont.name.clone()
} else {
cont.role.clone()
});
}
self.base.name(node)
}
fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
if node == self.base.root() {
let mut out = self.base.children_named(node, name);
if let Some(c) = self.prior.iter().position(|k| k.name == name) {
out.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
}
return out;
}
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
let Some(cont) = self.prior.get(c) else {
return Vec::new();
};
if v == 0 && name == cont.role {
return (0..cont.members.len())
.map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
.collect();
}
return Vec::new();
}
self.base.children_named(node, name)
}
fn traits(&self, node: NodeId) -> Vec<String> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
return match self.prior.get(c) {
Some(cont) if v > 0 => vec![cont.trait_name.clone()],
_ => Vec::new(),
};
}
self.base.traits(node)
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
return Some(if v == 0 {
self.base.root()
} else {
NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
});
}
self.base.parent(node)
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
return match self.prior.get(c)?.member(v)? {
Member::Value(_) => None,
Member::Node(n) => self.base.property(n, name),
};
}
self.base.property(node, name)
}
fn default_value(&self, node: NodeId) -> Option<Value> {
if node.0 & MODEL_TAG != 0 {
let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
let v = (node.0 & VAL_MASK) as usize;
return match self.prior.get(c)?.member(v)? {
Member::Value(val) => Some(val),
Member::Node(n) => self.base.default_value(n),
};
}
self.base.default_value(node)
}
}
impl<A: AstAdapter> AstAdapter for ModelAdapter<A> {
fn root(&self) -> NodeId {
self.base.root()
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
match self.decode(node) {
Some((c, 0)) => (0..self.containers()[c].members.len())
.map(|v| Self::value_node(c, v))
.collect(),
Some(_) => match self.aliased(node) {
Some(base) => self.base.children(base),
None => Vec::new(),
},
None => {
let mut kids = self.base.children(node);
if node == self.base.root() {
for c in 0..self.containers().len() {
kids.push(Self::container_node(c));
}
}
kids
}
}
}
fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
if node == self.base.root() {
let mut out = self.base.children_named(node, name);
if let Some(c) = self.container_by_name(name) {
out.push(Self::container_node(c));
}
return out;
}
match self.decode(node) {
Some((c, 0)) => {
let cont = &self.containers()[c];
if name == cont.role {
(0..cont.members.len()).map(|v| Self::value_node(c, v)).collect()
} else {
Vec::new()
}
}
Some(_) => match self.aliased(node) {
Some(base) => self.base.children_named(base, name),
None => Vec::new(),
},
None => self.base.children_named(node, name),
}
}
fn name(&self, node: NodeId) -> Option<String> {
match self.decode(node) {
Some((c, 0)) => Some(self.containers()[c].name.clone()),
Some((c, _)) => Some(self.containers()[c].role.clone()),
None => self.base.name(node),
}
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
match self.decode(node) {
Some((_, 0)) => Some(self.base.root()),
Some((c, _)) => Some(Self::container_node(c)),
None => self.base.parent(node),
}
}
fn traits(&self, node: NodeId) -> Vec<String> {
match self.decode(node) {
Some((c, v)) if v > 0 => {
let mut out = vec![self.containers()[c].trait_name.clone()];
if let Some(base) = self.aliased(node) {
out.extend(self.base.traits(base));
}
out
}
Some(_) => Vec::new(),
None => self.base.traits(node),
}
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
match self.decode(node) {
Some((_, v)) if v > 0 => match self.aliased(node) {
Some(base) => self.base.property(base, name),
None => None,
},
Some(_) => None,
None => self.base.property(node, name),
}
}
fn default_value(&self, node: NodeId) -> Option<Value> {
match self.decode(node) {
Some((_, v)) if v > 0 => match self.aliased(node) {
Some(base) => self.base.default_value(base),
None => self.elevated(node),
},
Some(_) => None,
None => self.base.default_value(node),
}
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
match self.decode(node) {
Some((c, 0)) if key == "n-rows" => {
Some(Value::Int(self.containers()[c].members.len() as i64))
}
Some(_) => None,
None => self.base.metadata(node, key),
}
}
fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
let under = self.aliased(node).unwrap_or(node);
if self.decode(under).is_none() {
if let Some(&target) = self.fabric().resolve.get(&(under, property.to_string())) {
return Some(target);
}
}
self.base.resolve(under, property, hint)
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
let f = self.fabric();
match self.decode(node) {
Some((_, v)) if v > 0 => {
let mut out = f.edges.get(&node).cloned().unwrap_or_default();
if let Some(rels) = f.rel_fwd.get(&node) {
out.extend(rels.iter().cloned());
}
if let Some(base) = self.aliased(node) {
out.extend(self.base.links(base));
if let Some(refs) = f.ref_fwd.get(&base) {
out.extend(refs.iter().cloned());
}
}
out
}
Some(_) => Vec::new(),
None => {
let mut out = self.base.links(node);
if let Some(refs) = f.ref_fwd.get(&node) {
out.extend(refs.iter().cloned());
}
if let Some(rels) = f.rel_fwd.get(&node) {
out.extend(rels.iter().cloned());
}
out
}
}
}
fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
let f = self.fabric();
match self.decode(node) {
Some((_, v)) if v > 0 => {
let mut out = f.ref_back.get(&node).cloned().unwrap_or_default();
if let Some(e) = f.edges.get(&node) {
out.extend(e.iter().cloned());
}
if let Some(rels) = f.rel_back.get(&node) {
out.extend(rels.iter().cloned());
}
out
}
Some(_) => Vec::new(),
None => {
let mut out = self.base.backlinks(node);
if let Some(rels) = f.rel_back.get(&node) {
out.extend(rels.iter().cloned());
}
out
}
}
}
fn quantifier_bound(&self) -> usize {
self.base.quantifier_bound()
}
fn allow_shell(&self) -> bool {
self.base.allow_shell()
}
fn invocation_instant(&self) -> Option<(i64, u32)> {
self.base.invocation_instant()
}
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
self.base.unit_scale(expr)
}
}
fn scope_role(scope: &str) -> String {
scope
.split('/')
.filter(|seg| {
!seg.is_empty()
&& !seg.starts_with('*')
&& !seg.starts_with('[')
&& !seg.chars().next().is_some_and(|c| c.is_ascii_digit())
})
.next_back()
.unwrap_or("")
.split(['[', ':'])
.next()
.unwrap_or("")
.to_string()
}
pub struct Borrowed<'a>(pub &'a dyn AstAdapter);
impl AstAdapter for Borrowed<'_> {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, n: NodeId) -> Vec<NodeId> {
self.0.children(n)
}
fn name(&self, n: NodeId) -> Option<String> {
self.0.name(n)
}
fn parent(&self, n: NodeId) -> Option<NodeId> {
self.0.parent(n)
}
fn traits(&self, n: NodeId) -> Vec<String> {
self.0.traits(n)
}
fn children_named(&self, n: NodeId, name: &str) -> Vec<NodeId> {
self.0.children_named(n, name)
}
fn property(&self, n: NodeId, name: &str) -> Option<Value> {
self.0.property(n, name)
}
fn default_value(&self, n: NodeId) -> Option<Value> {
self.0.default_value(n)
}
fn metadata(&self, n: NodeId, key: &str) -> Option<Value> {
self.0.metadata(n, key)
}
fn links(&self, n: NodeId) -> Vec<(String, NodeId)> {
self.0.links(n)
}
fn backlinks(&self, n: NodeId) -> Vec<(String, NodeId)> {
self.0.backlinks(n)
}
fn resolve(&self, n: NodeId, p: &str, h: Option<&str>) -> Option<NodeId> {
self.0.resolve(n, p, h)
}
fn link_property(&self, s: NodeId, l: &str, t: NodeId, name: &str) -> Option<Value> {
self.0.link_property(s, l, t, name)
}
fn quantifier_bound(&self) -> usize {
self.0.quantifier_bound()
}
fn allow_shell(&self) -> bool {
self.0.allow_shell()
}
fn invocation_instant(&self) -> Option<(i64, u32)> {
self.0.invocation_instant()
}
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
self.0.unit_scale(expr)
}
}