use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub u64);
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Provenance {
pub source: Option<String>,
pub path: Option<String>,
pub instant: Option<(i64, u32, Option<i16>)>,
pub instant_from: Option<InstantFrom>,
pub dpid: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstantFrom {
Node,
Source,
Access,
}
impl InstantFrom {
pub fn name(self) -> &'static str {
match self {
InstantFrom::Node => "node",
InstantFrom::Source => "source",
InstantFrom::Access => "access",
}
}
}
impl Provenance {
pub fn or(self, outer: Provenance) -> Provenance {
let (instant, instant_from) = if self.instant.is_some() {
(self.instant, self.instant_from)
} else {
(outer.instant, outer.instant_from)
};
Provenance {
source: self.source.or(outer.source),
path: self.path.or(outer.path),
instant,
instant_from,
dpid: self.dpid.or(outer.dpid),
}
}
pub fn is_empty(&self) -> bool {
self.source.is_none() && self.instant.is_none() && self.dpid.is_none()
}
pub fn as_source_rung(mut self) -> Provenance {
if self.instant.is_some() {
self.instant_from = Some(InstantFrom::Source);
}
self
}
pub fn canonical_list(entries: &[Provenance], elided: u32) -> Option<String> {
let mut parts: Vec<String> = Vec::new();
for c in entries.iter().filter_map(|e| e.canonical()) {
let c = c.trim_start_matches('?').to_string();
if !parts.contains(&c) {
parts.push(c);
}
}
if parts.is_empty() {
return None;
}
let mut out = format!("?{}", parts.join(";"));
if elided > 0 {
out.push_str(&format!(";+{elided}"));
}
Some(out)
}
pub fn canonical(&self) -> Option<String> {
if self.is_empty() {
return None;
}
let mut out = String::from("?");
if let Some(src) = &self.source {
out.push_str(src);
}
if let Some((secs, _, offset)) = self.instant {
out.push('@');
out.push_str(&crate::temporal::format_instant(secs, 0, offset));
}
if let Some(dpid) = &self.dpid {
out.push('#');
out.push_str(dpid);
}
Some(out)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ProvenanceList {
pub entries: Vec<Provenance>,
pub elided: u32,
}
impl ProvenanceList {
pub fn single(p: Provenance) -> ProvenanceList {
ProvenanceList {
entries: if p.is_empty() { Vec::new() } else { vec![p] },
elided: 0,
}
}
pub fn first(&self) -> Provenance {
self.entries.first().cloned().unwrap_or_default()
}
pub fn instant_max(&self) -> Option<(i64, u32, Option<i16>)> {
self.entries
.iter()
.filter_map(|p| p.instant)
.max_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)))
}
pub fn canonical(&self) -> Option<String> {
Provenance::canonical_list(&self.entries, self.elided)
}
pub fn or(self, outer: Provenance) -> ProvenanceList {
if self.entries.is_empty() {
return ProvenanceList {
entries: ProvenanceList::single(outer).entries,
elided: self.elided,
};
}
ProvenanceList {
entries: self
.entries
.into_iter()
.map(|p| p.or(outer.clone()))
.collect(),
elided: self.elided,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LinkDir {
Out,
In,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalRef {
pub document: String,
pub within: Option<String>,
}
impl ExternalRef {
pub fn uri(reference: &str) -> Self {
match reference.split_once('#') {
Some((document, fragment)) => ExternalRef {
document: document.to_string(),
within: (!fragment.is_empty()).then(|| fragment.to_string()),
},
None => ExternalRef {
document: reference.to_string(),
within: None,
},
}
}
pub fn to_uri(&self) -> String {
match &self.within {
Some(w) => format!("{}#{w}", self.document),
None => self.document.clone(),
}
}
}
pub trait AstAdapter {
fn root(&self) -> NodeId;
fn children(&self, node: NodeId) -> Vec<NodeId>;
fn name(&self, node: NodeId) -> Option<String>;
fn parent(&self, _node: NodeId) -> Option<NodeId> {
None
}
fn traits(&self, _node: NodeId) -> Vec<String> {
Vec::new()
}
fn property(&self, _node: NodeId, _name: &str) -> Option<Value> {
None
}
fn properties(&self, _node: NodeId) -> Vec<(String, Value)> {
Vec::new()
}
fn walks_for_references(&self) -> bool {
false
}
fn can_refer_to(&self, source: NodeId, _target: NodeId) -> Option<bool> {
self.ref_property(source).map(|_| true)
}
fn reference_properties(&self, source: NodeId, _target: NodeId) -> Vec<String> {
self.ref_property(source).into_iter().collect()
}
fn resolve_needs_label(&self, _node: NodeId, _property: &str) -> bool {
false
}
fn reverse_label(&self, _target: NodeId) -> Option<String> {
None
}
fn tree_identities(&self) -> Vec<(String, NodeId)> {
Vec::new()
}
fn property_names(&self, node: NodeId) -> Vec<String> {
let mut ps: Vec<String> = self.properties(node).into_iter().map(|(k, _)| k).collect();
if let Some(r) = self.ref_property(node)
&& !ps.contains(&r)
{
ps.push(r);
}
for c in self.children(node) {
if let Some(name) = self.name(c)
&& !ps.contains(&name)
{
ps.push(name);
}
}
ps
}
fn resolves_references(&self) -> bool {
true
}
fn index_lookup(&self, _scope: &str, _property: &str, _value: &Value) -> Option<Vec<NodeId>> {
None
}
fn index_referrers(
&self,
_scope: &str,
_property: &str,
_label: Option<&str>,
_target: NodeId,
) -> Option<Vec<NodeId>> {
None
}
fn non_tree_nodes(&self) -> Vec<NodeId> {
Vec::new()
}
fn child_host(&self, node: NodeId) -> NodeId {
node
}
fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
self.children(node)
.into_iter()
.filter(|&c| self.answers_to(c, name))
.collect()
}
fn answers_to(&self, node: NodeId, name: &str) -> bool {
self.name(node).as_deref() == Some(name)
}
fn has_trait(&self, node: NodeId, name: &str) -> bool {
self.traits(node).iter().any(|t| t == name)
}
fn default_value(&self, _node: NodeId) -> Option<Value> {
None
}
fn metadata(&self, _node: NodeId, _key: &str) -> Option<Value> {
None
}
fn aliased_metadata(&self, _node: NodeId) -> &'static [&'static str] {
&[]
}
fn links(&self, _node: NodeId) -> Vec<(String, NodeId)> {
Vec::new()
}
fn backlinks(&self, _node: NodeId) -> Vec<(String, NodeId)> {
Vec::new()
}
fn resolve(&self, _node: NodeId, _property: &str, _hint: Option<&str>) -> Option<NodeId> {
None
}
fn external_ref(
&self,
_node: NodeId,
_property: &str,
_hint: Option<&str>,
) -> Option<ExternalRef> {
None
}
fn resolve_fragment(&self, _node: NodeId, _fragment: &str) -> Option<NodeId> {
None
}
fn ref_property(&self, _node: NodeId) -> Option<String> {
None
}
fn ref_label(&self, _node: NodeId, _property: &str) -> Option<String> {
None
}
fn link_property(
&self,
_source: NodeId,
_label: &str,
_target: NodeId,
_name: &str,
) -> Option<Value> {
None
}
fn document_by_ref(&self, _id: &str) -> Option<NodeId> {
None
}
fn reverse_resolve(
&self,
_node: NodeId,
_property: Option<&str>,
_hint: Option<&str>,
) -> Option<Vec<NodeId>> {
None
}
fn descendants_named(&self, _node: NodeId, _name: &str) -> Option<Vec<(NodeId, usize)>> {
None
}
fn descendants_where(
&self,
_node: NodeId,
_name: &str,
_property: &str,
_value: &Value,
) -> Option<Vec<(NodeId, usize)>> {
None
}
fn prefetch_links(&self, _nodes: &[NodeId], _dir: LinkDir) {}
fn quantifier_bound(&self) -> usize {
32
}
fn allow_shell(&self) -> bool {
false
}
fn invocation_instant(&self) -> Option<(i64, u32)> {
None
}
fn provenance(&self, _node: NodeId) -> Provenance {
Provenance::default()
}
fn has_node(&self, node: NodeId) -> bool {
if node == self.root() {
return true;
}
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
self.parent(node).is_some() || self.name(node).is_some()
}))
.unwrap_or(false);
std::panic::set_hook(hook);
ok
}
fn provenance_list(&self, node: NodeId) -> ProvenanceList {
ProvenanceList::single(self.provenance(node))
}
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
crate::quantity::scale_expr(expr)
}
}
#[macro_export]
macro_rules! forward_ast_adapter {
($inner:tt $(; except $($except:ident),+ $(,)?)? $(;)?) => {
$crate::forward_ast_adapter!(@each [$inner] [$($($except)+)?]
root children name parent traits property properties
walks_for_references can_refer_to reference_properties
resolve_needs_label reverse_label tree_identities
property_names resolves_references index_lookup
index_referrers non_tree_nodes child_host children_named
answers_to has_trait default_value metadata
aliased_metadata links backlinks resolve external_ref
resolve_fragment ref_property ref_label link_property
document_by_ref reverse_resolve descendants_named
descendants_where prefetch_links quantifier_bound
allow_shell invocation_instant provenance has_node
provenance_list unit_scale
);
};
(@each [$inner:tt] $ex:tt $($m:ident)*) => {
$( $crate::forward_ast_adapter!(@unless [$inner] $ex $m); )*
};
(@unless [$i:tt] [root $($r:ident)*] root) => {};
(@unless [$i:tt] [children $($r:ident)*] children) => {};
(@unless [$i:tt] [name $($r:ident)*] name) => {};
(@unless [$i:tt] [parent $($r:ident)*] parent) => {};
(@unless [$i:tt] [traits $($r:ident)*] traits) => {};
(@unless [$i:tt] [property $($r:ident)*] property) => {};
(@unless [$i:tt] [properties $($r:ident)*] properties) => {};
(@unless [$i:tt] [walks_for_references $($r:ident)*] walks_for_references) => {};
(@unless [$i:tt] [can_refer_to $($r:ident)*] can_refer_to) => {};
(@unless [$i:tt] [reference_properties $($r:ident)*] reference_properties) => {};
(@unless [$i:tt] [resolve_needs_label $($r:ident)*] resolve_needs_label) => {};
(@unless [$i:tt] [reverse_label $($r:ident)*] reverse_label) => {};
(@unless [$i:tt] [tree_identities $($r:ident)*] tree_identities) => {};
(@unless [$i:tt] [property_names $($r:ident)*] property_names) => {};
(@unless [$i:tt] [resolves_references $($r:ident)*] resolves_references) => {};
(@unless [$i:tt] [index_lookup $($r:ident)*] index_lookup) => {};
(@unless [$i:tt] [index_referrers $($r:ident)*] index_referrers) => {};
(@unless [$i:tt] [non_tree_nodes $($r:ident)*] non_tree_nodes) => {};
(@unless [$i:tt] [child_host $($r:ident)*] child_host) => {};
(@unless [$i:tt] [children_named $($r:ident)*] children_named) => {};
(@unless [$i:tt] [answers_to $($r:ident)*] answers_to) => {};
(@unless [$i:tt] [has_trait $($r:ident)*] has_trait) => {};
(@unless [$i:tt] [default_value $($r:ident)*] default_value) => {};
(@unless [$i:tt] [metadata $($r:ident)*] metadata) => {};
(@unless [$i:tt] [aliased_metadata $($r:ident)*] aliased_metadata) => {};
(@unless [$i:tt] [links $($r:ident)*] links) => {};
(@unless [$i:tt] [backlinks $($r:ident)*] backlinks) => {};
(@unless [$i:tt] [resolve $($r:ident)*] resolve) => {};
(@unless [$i:tt] [external_ref $($r:ident)*] external_ref) => {};
(@unless [$i:tt] [resolve_fragment $($r:ident)*] resolve_fragment) => {};
(@unless [$i:tt] [ref_property $($r:ident)*] ref_property) => {};
(@unless [$i:tt] [ref_label $($r:ident)*] ref_label) => {};
(@unless [$i:tt] [link_property $($r:ident)*] link_property) => {};
(@unless [$i:tt] [document_by_ref $($r:ident)*] document_by_ref) => {};
(@unless [$i:tt] [reverse_resolve $($r:ident)*] reverse_resolve) => {};
(@unless [$i:tt] [descendants_named $($r:ident)*] descendants_named) => {};
(@unless [$i:tt] [descendants_where $($r:ident)*] descendants_where) => {};
(@unless [$i:tt] [prefetch_links $($r:ident)*] prefetch_links) => {};
(@unless [$i:tt] [quantifier_bound $($r:ident)*] quantifier_bound) => {};
(@unless [$i:tt] [allow_shell $($r:ident)*] allow_shell) => {};
(@unless [$i:tt] [invocation_instant $($r:ident)*] invocation_instant) => {};
(@unless [$i:tt] [provenance $($r:ident)*] provenance) => {};
(@unless [$i:tt] [has_node $($r:ident)*] has_node) => {};
(@unless [$i:tt] [provenance_list $($r:ident)*] provenance_list) => {};
(@unless [$i:tt] [unit_scale $($r:ident)*] unit_scale) => {};
(@unless [$i:tt] [$x:ident $($r:ident)*] $m:ident) => {
$crate::forward_ast_adapter!(@unless [$i] [$($r)*] $m);
};
(@unless [$i:tt] [] $m:ident) => {
$crate::forward_ast_adapter!(@fwd [$i] $m);
};
(@target $s:ident *) => { (**$s) };
(@target $s:ident $f:tt) => { $s.$f };
(@fwd [$i:tt] root) => {
fn root(&self) -> $crate::NodeId {
$crate::forward_ast_adapter!(@target self $i).root()
}
};
(@fwd [$i:tt] children) => {
fn children(&self, node: $crate::NodeId) -> Vec<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).children(node)
}
};
(@fwd [$i:tt] name) => {
fn name(&self, node: $crate::NodeId) -> Option<String> {
$crate::forward_ast_adapter!(@target self $i).name(node)
}
};
(@fwd [$i:tt] parent) => {
fn parent(&self, node: $crate::NodeId) -> Option<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).parent(node)
}
};
(@fwd [$i:tt] traits) => {
fn traits(&self, node: $crate::NodeId) -> Vec<String> {
$crate::forward_ast_adapter!(@target self $i).traits(node)
}
};
(@fwd [$i:tt] property) => {
fn property(&self, node: $crate::NodeId, name: &str) -> Option<$crate::Value> {
$crate::forward_ast_adapter!(@target self $i).property(node, name)
}
};
(@fwd [$i:tt] properties) => {
fn properties(&self, node: $crate::NodeId) -> Vec<(String, $crate::Value)> {
$crate::forward_ast_adapter!(@target self $i).properties(node)
}
};
(@fwd [$i:tt] walks_for_references) => {
fn walks_for_references(&self) -> bool {
$crate::forward_ast_adapter!(@target self $i).walks_for_references()
}
};
(@fwd [$i:tt] can_refer_to) => {
fn can_refer_to(&self, source: $crate::NodeId, target: $crate::NodeId) -> Option<bool> {
$crate::forward_ast_adapter!(@target self $i).can_refer_to(source, target)
}
};
(@fwd [$i:tt] reference_properties) => {
fn reference_properties(
&self,
source: $crate::NodeId,
target: $crate::NodeId,
) -> Vec<String> {
$crate::forward_ast_adapter!(@target self $i).reference_properties(source, target)
}
};
(@fwd [$i:tt] resolve_needs_label) => {
fn resolve_needs_label(&self, node: $crate::NodeId, property: &str) -> bool {
$crate::forward_ast_adapter!(@target self $i).resolve_needs_label(node, property)
}
};
(@fwd [$i:tt] reverse_label) => {
fn reverse_label(&self, target: $crate::NodeId) -> Option<String> {
$crate::forward_ast_adapter!(@target self $i).reverse_label(target)
}
};
(@fwd [$i:tt] tree_identities) => {
fn tree_identities(&self) -> Vec<(String, $crate::NodeId)> {
$crate::forward_ast_adapter!(@target self $i).tree_identities()
}
};
(@fwd [$i:tt] property_names) => {
fn property_names(&self, node: $crate::NodeId) -> Vec<String> {
$crate::forward_ast_adapter!(@target self $i).property_names(node)
}
};
(@fwd [$i:tt] resolves_references) => {
fn resolves_references(&self) -> bool {
$crate::forward_ast_adapter!(@target self $i).resolves_references()
}
};
(@fwd [$i:tt] index_lookup) => {
fn index_lookup(
&self,
scope: &str,
property: &str,
value: &$crate::Value,
) -> Option<Vec<$crate::NodeId>> {
$crate::forward_ast_adapter!(@target self $i).index_lookup(scope, property, value)
}
};
(@fwd [$i:tt] index_referrers) => {
fn index_referrers(
&self,
scope: &str,
property: &str,
label: Option<&str>,
target: $crate::NodeId,
) -> Option<Vec<$crate::NodeId>> {
$crate::forward_ast_adapter!(@target self $i)
.index_referrers(scope, property, label, target)
}
};
(@fwd [$i:tt] non_tree_nodes) => {
fn non_tree_nodes(&self) -> Vec<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).non_tree_nodes()
}
};
(@fwd [$i:tt] child_host) => {
fn child_host(&self, node: $crate::NodeId) -> $crate::NodeId {
$crate::forward_ast_adapter!(@target self $i).child_host(node)
}
};
(@fwd [$i:tt] children_named) => {
fn children_named(&self, node: $crate::NodeId, name: &str) -> Vec<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).children_named(node, name)
}
};
(@fwd [$i:tt] answers_to) => {
fn answers_to(&self, node: $crate::NodeId, name: &str) -> bool {
$crate::forward_ast_adapter!(@target self $i).answers_to(node, name)
}
};
(@fwd [$i:tt] has_trait) => {
fn has_trait(&self, node: $crate::NodeId, name: &str) -> bool {
$crate::forward_ast_adapter!(@target self $i).has_trait(node, name)
}
};
(@fwd [$i:tt] default_value) => {
fn default_value(&self, node: $crate::NodeId) -> Option<$crate::Value> {
$crate::forward_ast_adapter!(@target self $i).default_value(node)
}
};
(@fwd [$i:tt] metadata) => {
fn metadata(&self, node: $crate::NodeId, key: &str) -> Option<$crate::Value> {
$crate::forward_ast_adapter!(@target self $i).metadata(node, key)
}
};
(@fwd [$i:tt] aliased_metadata) => {
fn aliased_metadata(&self, node: $crate::NodeId) -> &'static [&'static str] {
$crate::forward_ast_adapter!(@target self $i).aliased_metadata(node)
}
};
(@fwd [$i:tt] links) => {
fn links(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
$crate::forward_ast_adapter!(@target self $i).links(node)
}
};
(@fwd [$i:tt] backlinks) => {
fn backlinks(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
$crate::forward_ast_adapter!(@target self $i).backlinks(node)
}
};
(@fwd [$i:tt] resolve) => {
fn resolve(
&self,
node: $crate::NodeId,
property: &str,
hint: Option<&str>,
) -> Option<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).resolve(node, property, hint)
}
};
(@fwd [$i:tt] external_ref) => {
fn external_ref(
&self,
node: $crate::NodeId,
property: &str,
hint: Option<&str>,
) -> Option<$crate::ExternalRef> {
$crate::forward_ast_adapter!(@target self $i).external_ref(node, property, hint)
}
};
(@fwd [$i:tt] resolve_fragment) => {
fn resolve_fragment(&self, node: $crate::NodeId, fragment: &str) -> Option<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).resolve_fragment(node, fragment)
}
};
(@fwd [$i:tt] ref_property) => {
fn ref_property(&self, node: $crate::NodeId) -> Option<String> {
$crate::forward_ast_adapter!(@target self $i).ref_property(node)
}
};
(@fwd [$i:tt] ref_label) => {
fn ref_label(&self, node: $crate::NodeId, property: &str) -> Option<String> {
$crate::forward_ast_adapter!(@target self $i).ref_label(node, property)
}
};
(@fwd [$i:tt] link_property) => {
fn link_property(
&self,
source: $crate::NodeId,
label: &str,
target: $crate::NodeId,
name: &str,
) -> Option<$crate::Value> {
$crate::forward_ast_adapter!(@target self $i).link_property(source, label, target, name)
}
};
(@fwd [$i:tt] document_by_ref) => {
fn document_by_ref(&self, id: &str) -> Option<$crate::NodeId> {
$crate::forward_ast_adapter!(@target self $i).document_by_ref(id)
}
};
(@fwd [$i:tt] reverse_resolve) => {
fn reverse_resolve(
&self,
node: $crate::NodeId,
property: Option<&str>,
hint: Option<&str>,
) -> Option<Vec<$crate::NodeId>> {
$crate::forward_ast_adapter!(@target self $i).reverse_resolve(node, property, hint)
}
};
(@fwd [$i:tt] descendants_named) => {
fn descendants_named(
&self,
node: $crate::NodeId,
name: &str,
) -> Option<Vec<($crate::NodeId, usize)>> {
$crate::forward_ast_adapter!(@target self $i).descendants_named(node, name)
}
};
(@fwd [$i:tt] descendants_where) => {
fn descendants_where(
&self,
node: $crate::NodeId,
name: &str,
property: &str,
value: &$crate::Value,
) -> Option<Vec<($crate::NodeId, usize)>> {
$crate::forward_ast_adapter!(@target self $i)
.descendants_where(node, name, property, value)
}
};
(@fwd [$i:tt] prefetch_links) => {
fn prefetch_links(&self, nodes: &[$crate::NodeId], dir: $crate::LinkDir) {
$crate::forward_ast_adapter!(@target self $i).prefetch_links(nodes, dir)
}
};
(@fwd [$i:tt] quantifier_bound) => {
fn quantifier_bound(&self) -> usize {
$crate::forward_ast_adapter!(@target self $i).quantifier_bound()
}
};
(@fwd [$i:tt] allow_shell) => {
fn allow_shell(&self) -> bool {
$crate::forward_ast_adapter!(@target self $i).allow_shell()
}
};
(@fwd [$i:tt] invocation_instant) => {
fn invocation_instant(&self) -> Option<(i64, u32)> {
$crate::forward_ast_adapter!(@target self $i).invocation_instant()
}
};
(@fwd [$i:tt] provenance) => {
fn provenance(&self, node: $crate::NodeId) -> $crate::Provenance {
$crate::forward_ast_adapter!(@target self $i).provenance(node)
}
};
(@fwd [$i:tt] has_node) => {
fn has_node(&self, node: $crate::NodeId) -> bool {
$crate::forward_ast_adapter!(@target self $i).has_node(node)
}
};
(@fwd [$i:tt] provenance_list) => {
fn provenance_list(&self, node: $crate::NodeId) -> $crate::ProvenanceList {
$crate::forward_ast_adapter!(@target self $i).provenance_list(node)
}
};
(@fwd [$i:tt] unit_scale) => {
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
$crate::forward_ast_adapter!(@target self $i).unit_scale(expr)
}
};
}
pub struct QuantifierBound<'a, A: AstAdapter> {
pub inner: &'a A,
pub bound: usize,
}
impl<A: AstAdapter> AstAdapter for QuantifierBound<'_, A> {
crate::forward_ast_adapter!(inner; except quantifier_bound);
fn quantifier_bound(&self) -> usize {
self.bound
}
}
pub struct AllowShell<'a, A: AstAdapter> {
pub inner: &'a A,
}
impl<A: AstAdapter> AstAdapter for AllowShell<'_, A> {
crate::forward_ast_adapter!(inner; except allow_shell);
fn allow_shell(&self) -> bool {
true
}
}
pub struct WithNow<'a, A: AstAdapter> {
pub inner: &'a A,
pub secs: i64,
pub nanos: u32,
}
impl<A: AstAdapter> AstAdapter for WithNow<'_, A> {
crate::forward_ast_adapter!(inner; except invocation_instant);
fn invocation_instant(&self) -> Option<(i64, u32)> {
Some((self.secs, self.nanos))
}
}
impl AstAdapter for Box<dyn AstAdapter> {
crate::forward_ast_adapter!(*);
}
#[cfg(test)]
mod forwarding {
use super::*;
fn fn_names(block: &str, indent: &str) -> Vec<String> {
block
.lines()
.filter_map(|l| l.strip_prefix(indent)?.strip_prefix("fn "))
.map(|rest| {
rest.split(|c: char| !(c.is_alphanumeric() || c == '_'))
.next()
.unwrap()
.to_string()
})
.collect()
}
#[test]
fn the_macro_lists_every_trait_method() {
let src = include_str!("adapter.rs");
let trait_src = &src[src.find("pub trait AstAdapter {").unwrap()..];
let trait_src = &trait_src[..trait_src.find("\n}\n").unwrap()];
let in_trait = fn_names(trait_src, " ");
let table = &src[src
.find("coverage test reads this list from the source.")
.unwrap()..];
let table = &table[table.find('\n').unwrap()..table.find(");").unwrap()];
let listed: Vec<String> = table.split_whitespace().map(str::to_string).collect();
assert_eq!(
listed, in_trait,
"the forwarding table drifted from the trait"
);
let macro_src = &src[src.find("macro_rules! forward_ast_adapter").unwrap()..];
let macro_src = ¯o_src[..macro_src.find("\n}\n").unwrap()];
for m in &in_trait {
assert!(
macro_src.contains(&format!(
"(@unless [$i:tt] [{m} $($r:ident)*] {m}) => {{}};"
)),
"no except arm for {m}"
);
assert!(
macro_src.contains(&format!("(@fwd [$i:tt] {m}) => {{")),
"no forwarding arm for {m}"
);
}
}
struct Probe;
fn n(k: u64) -> NodeId {
NodeId(k)
}
fn int(v: &Value) -> u64 {
match v {
Value::Int(i) => *i as u64,
_ => 0,
}
}
impl AstAdapter for Probe {
fn root(&self) -> NodeId {
n(1)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
vec![n(node.0 * 10)]
}
fn name(&self, node: NodeId) -> Option<String> {
Some(format!("name{}", node.0))
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
Some(n(node.0 + 100))
}
fn traits(&self, node: NodeId) -> Vec<String> {
vec![format!("trait{}", node.0)]
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
Some(Value::Str(format!("prop:{}:{name}", node.0)))
}
fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
vec![(format!("p{}", node.0), Value::Int(node.0 as i64))]
}
fn walks_for_references(&self) -> bool {
true
}
fn can_refer_to(&self, source: NodeId, target: NodeId) -> Option<bool> {
Some(source.0 < target.0)
}
fn reference_properties(&self, source: NodeId, target: NodeId) -> Vec<String> {
vec![format!("rp{}-{}", source.0, target.0)]
}
fn resolve_needs_label(&self, node: NodeId, property: &str) -> bool {
node.0 == 7 && property == "x"
}
fn reverse_label(&self, target: NodeId) -> Option<String> {
Some(format!("rl{}", target.0))
}
fn tree_identities(&self) -> Vec<(String, NodeId)> {
vec![("tree".into(), n(42))]
}
fn property_names(&self, node: NodeId) -> Vec<String> {
vec![format!("pn{}", node.0)]
}
fn resolves_references(&self) -> bool {
false
}
fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
Some(vec![
n((scope.len() + property.len()) as u64),
n(int(value)),
])
}
fn index_referrers(
&self,
scope: &str,
property: &str,
label: Option<&str>,
target: NodeId,
) -> Option<Vec<NodeId>> {
Some(vec![n(
(scope.len() + property.len() + label.map_or(0, str::len)) as u64 + target.0,
)])
}
fn non_tree_nodes(&self) -> Vec<NodeId> {
vec![n(99)]
}
fn child_host(&self, node: NodeId) -> NodeId {
n(node.0 + 1000)
}
fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
vec![n(node.0 + name.len() as u64)]
}
fn answers_to(&self, node: NodeId, name: &str) -> bool {
node.0 == 3 && name == "alias"
}
fn has_trait(&self, node: NodeId, name: &str) -> bool {
node.0 == 4 && name == "talias"
}
fn default_value(&self, node: NodeId) -> Option<Value> {
Some(Value::Int(-(node.0 as i64)))
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
Some(Value::Str(format!("meta:{}:{key}", node.0)))
}
fn aliased_metadata(&self, node: NodeId) -> &'static [&'static str] {
if node.0 == 5 { &["size"] } else { &["other"] }
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
vec![("link".into(), n(node.0 + 2))]
}
fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
vec![("back".into(), n(node.0 + 3))]
}
fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
Some(n(node.0
+ property.len() as u64
+ hint.map_or(0, |h| h.len() as u64)))
}
fn external_ref(
&self,
node: NodeId,
property: &str,
hint: Option<&str>,
) -> Option<ExternalRef> {
Some(ExternalRef {
document: format!("doc{}:{property}", node.0),
within: hint.map(str::to_string),
})
}
fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
Some(n(node.0 + fragment.len() as u64 + 7))
}
fn ref_property(&self, node: NodeId) -> Option<String> {
Some(format!("href{}", node.0))
}
fn ref_label(&self, node: NodeId, property: &str) -> Option<String> {
Some(format!("rel{}:{property}", node.0))
}
fn link_property(
&self,
source: NodeId,
label: &str,
target: NodeId,
name: &str,
) -> Option<Value> {
Some(Value::Str(format!(
"{}-{label}-{}-{name}",
source.0, target.0
)))
}
fn document_by_ref(&self, id: &str) -> Option<NodeId> {
Some(n(id.len() as u64 + 50))
}
fn reverse_resolve(
&self,
node: NodeId,
property: Option<&str>,
hint: Option<&str>,
) -> Option<Vec<NodeId>> {
Some(vec![n(node.0
+ property.map_or(0, |p| p.len() as u64) * 2
+ hint.map_or(0, |h| h.len() as u64) * 3)])
}
fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
Some(vec![(n(node.0 + 9), name.len())])
}
fn descendants_where(
&self,
node: NodeId,
name: &str,
property: &str,
value: &Value,
) -> Option<Vec<(NodeId, usize)>> {
Some(vec![(n(node.0 + int(value)), name.len() + property.len())])
}
fn prefetch_links(&self, nodes: &[NodeId], dir: LinkDir) {
PREFETCHED.with(|p| p.set(nodes.len() * 10 + matches!(dir, LinkDir::In) as usize));
}
fn quantifier_bound(&self) -> usize {
77
}
fn allow_shell(&self) -> bool {
true
}
fn invocation_instant(&self) -> Option<(i64, u32)> {
Some((12, 34))
}
fn provenance(&self, node: NodeId) -> Provenance {
Provenance {
source: Some(format!("src{}", node.0)),
..Default::default()
}
}
fn has_node(&self, node: NodeId) -> bool {
node.0 == 8
}
fn provenance_list(&self, node: NodeId) -> ProvenanceList {
ProvenanceList {
entries: vec![self.provenance(node)],
elided: node.0 as u32,
}
}
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
Some((expr.len() as f64, format!("scale:{expr}")))
}
}
thread_local! {
static PREFETCHED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
fn assert_forwards(w: &dyn AstAdapter, except: &[&str]) {
let p = Probe;
let v = Value::Int(6);
macro_rules! same {
($m:ident $(, $a:expr)*) => {
if !except.contains(&stringify!($m)) {
assert_eq!(w.$m($($a),*), p.$m($($a),*), stringify!($m));
}
};
}
same!(root);
same!(children, n(2));
same!(name, n(2));
same!(parent, n(2));
same!(traits, n(2));
same!(property, n(2), "k");
same!(properties, n(2));
same!(walks_for_references);
same!(can_refer_to, n(2), n(3));
same!(reference_properties, n(2), n(3));
same!(resolve_needs_label, n(7), "x");
same!(reverse_label, n(2));
same!(tree_identities);
same!(property_names, n(2));
same!(resolves_references);
same!(index_lookup, "s", "pp", &v);
same!(index_referrers, "s", "pp", Some("lbl"), n(2));
same!(non_tree_nodes);
same!(child_host, n(2));
same!(children_named, n(2), "abc");
same!(answers_to, n(3), "alias");
same!(has_trait, n(4), "talias");
same!(default_value, n(2));
same!(metadata, n(2), "key");
same!(aliased_metadata, n(5));
same!(links, n(2));
same!(backlinks, n(2));
same!(resolve, n(2), "pp", Some("h"));
same!(external_ref, n(2), "pp", Some("frag"));
same!(resolve_fragment, n(2), "frag");
same!(ref_property, n(2));
same!(ref_label, n(2), "pp");
same!(link_property, n(2), "lbl", n(3), "nm");
same!(document_by_ref, "id");
same!(reverse_resolve, n(2), Some("pp"), Some("h"));
same!(descendants_named, n(2), "abc");
same!(descendants_where, n(2), "abc", "pp", &v);
if !except.contains(&"prefetch_links") {
PREFETCHED.with(|c| c.set(0));
w.prefetch_links(&[n(1), n(2)], LinkDir::In);
assert_eq!(PREFETCHED.with(|c| c.get()), 21, "prefetch_links");
}
same!(quantifier_bound);
same!(allow_shell);
same!(invocation_instant);
same!(provenance, n(2));
same!(has_node, n(8));
same!(has_node, n(9));
same!(provenance_list, n(2));
same!(unit_scale, "km");
}
#[test]
fn every_wrapper_forwards_every_method() {
let p = Probe;
let w = QuantifierBound {
inner: &p,
bound: 5,
};
assert_forwards(&w, &["quantifier_bound"]);
assert_eq!(w.quantifier_bound(), 5);
let w = AllowShell { inner: &p };
assert_forwards(&w, &["allow_shell"]);
let w = WithNow {
inner: &p,
secs: 1,
nanos: 2,
};
assert_forwards(&w, &["invocation_instant"]);
assert_eq!(w.invocation_instant(), Some((1, 2)));
let b: Box<dyn AstAdapter> = Box::new(Probe);
assert_forwards(&b, &[]);
let s = crate::source::Sourced {
inner: Probe,
encoding: "utf-8".into(),
replaced: 0,
};
assert_forwards(&s, &["metadata"]);
assert_eq!(s.metadata(n(2), "key"), p.metadata(n(2), "key"));
assert_eq!(
s.metadata(n(1), "encoding"),
Some(Value::Str("utf-8".into()))
);
}
}