Skip to content

Commit

Permalink
Relay
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor256 committed Jan 11, 2023
1 parent cd3e370 commit 0b6903f
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 166 deletions.
188 changes: 188 additions & 0 deletions src/find.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (c) 2022 Yegor Bugayenko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use crate::Sodg;
use crate::{DeadRelay, LambdaRelay, Relay};
use anyhow::{anyhow, Context, Result};
use log::trace;
use std::collections::VecDeque;
use std::str::FromStr;

impl Relay for DeadRelay {
fn re(&self, v: u32, a: &str, b: &str) -> Result<String> {
Err(anyhow!("Can't find {a}/{b} at ν{v}"))
}
}

impl LambdaRelay {
/// Makes a new instance of `LambdaRelay` with the encapsulated
/// function.
#[allow(dead_code)]
pub fn new(lambda: fn(u32, &str, &str) -> Result<String>) -> Self {
LambdaRelay { lambda }
}
}

impl Relay for LambdaRelay {
fn re(&self, v: u32, a: &str, b: &str) -> Result<String> {
(self.lambda)(v, a, b)
}
}

impl Sodg {
/// Find a vertex in the Sodg by its locator using a closure to provide alternative edge names.
///
/// ```
/// use sodg::Sodg;
/// use sodg::DeadRelay;
/// use sodg::LambdaRelay;
/// let mut g = Sodg::empty();
/// g.add(0).unwrap();
/// g.add(1).unwrap();
/// g.bind(0, 1, "foo").unwrap();
/// assert!(g.find(0, "bar", DeadRelay {}).is_err());
/// let v = g.find(0, "bar", LambdaRelay::new(|v, a, b| {
/// assert_eq!(a, "bar");
/// assert_eq!(b, "");
/// Ok("foo".to_string())
/// })).unwrap();
/// assert_eq!(1, v);
/// ```
///
/// If target vertex is not found or `v1` is absent,
/// an `Err` will be returned.
pub fn find<T: Relay>(&self, v1: u32, loc: &str, relay: T) -> Result<u32> {
let mut v = v1;
let mut locator: VecDeque<String> = VecDeque::new();
loc.split('.')
.filter(|k| !k.is_empty())
.for_each(|k| locator.push_back(k.to_string()));
loop {
let next = locator.pop_front();
if next.is_none() {
trace!("#find_with_closure: end of locator, we are at ν{v}");
break;
}
let k = next.unwrap().to_string();
if k.is_empty() {
return Err(anyhow!("System error, the locator is empty"));
}
if k.starts_with('ν') {
let num: String = k.chars().skip(1).collect::<Vec<_>>().into_iter().collect();
v = u32::from_str(num.as_str())?;
trace!("#find_with_closure: jumping directly to ν{v}");
continue;
}
if let Some(to) = self.kid(v, k.as_str()) {
trace!("#find_with_closure: ν{v}.{k} -> ν{to}");
v = to;
continue;
};
let (head, tail) = Self::split_a(&k);
let redirect = relay.re(v, &head, &tail);
let failure = if let Ok(re) = redirect {
if let Ok(to) = self.find(v, re.as_str(), DeadRelay {}) {
trace!("#find_with_closure: ν{v}.{k} -> ν{to} (redirect to .{re})");
v = to;
continue;
}
format!("redirect to .{re} didn't help")
} else {
redirect.err().unwrap().to_string()
};
let others: Vec<String> = self
.vertices
.get(&v)
.context(format!("Can't find ν{v}"))
.unwrap()
.edges
.iter()
.map(|e| e.a.clone())
.collect();
return Err(anyhow!(
"Can't find .{} in ν{} among other {} attribute{}: {} ({failure})",
k,
v,
others.len(),
if others.len() == 1 { "" } else { "s" },
others.join(", ")
));
}
trace!("#find_with_closure: found ν{v1} by '{loc}'");
Ok(v)
}
}

#[test]
fn finds_with_closure() -> Result<()> {
let mut g = Sodg::empty();
g.add(1)?;
g.add(2)?;
g.add(3)?;
g.bind(1, 2, "first")?;
g.bind(2, 3, "something_else")?;
assert_eq!(
3,
g.find(
1,
"first.second/abc",
LambdaRelay::new(|v, head, tail| {
if v == 1 && !tail.is_empty() {
panic!();
}
if v == 2 && head == "second" && tail == "abc" {
Ok("something_else".to_string())
} else {
Ok("".to_string())
}
})
)?
);
Ok(())
}

#[test]
fn finds_root() -> Result<()> {
let mut g = Sodg::empty();
g.add(0)?;
assert_eq!(0, g.find(0, "", DeadRelay {})?);
Ok(())
}

#[test]
fn closure_return_absolute_vertex() {
let mut g = Sodg::empty();
g.add(0).unwrap();
g.add(1).unwrap();
g.bind(0, 1, "foo").unwrap();
assert!(g.find(0, "bar", DeadRelay {}).is_err());
let v = g
.find(
0,
"bar",
LambdaRelay::new(|_v, a, b| {
assert_eq!(a, "bar");
assert_eq!(b, "");
Ok("ν1".to_string())
}),
)
.unwrap();
assert_eq!(1, v);
}
5 changes: 4 additions & 1 deletion src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use crate::DeadRelay;
use crate::Sodg;
use anyhow::{Context, Result};
use itertools::Itertools;
Expand All @@ -27,7 +28,9 @@ impl Sodg {
/// Finds an object by the provided locator and prints its tree
/// of sub-objects and edges. Mostly used for testing.
pub fn inspect(&self, loc: &str) -> Result<String> {
let v = self.find(0, loc).context(format!("Can't locate '{loc}'"))?;
let v = self
.find(0, loc, DeadRelay {})
.context(format!("Can't locate '{loc}'"))?;
let mut seen = HashSet::new();
Ok(format!(
"{}/ν{}\n{}",
Expand Down
27 changes: 26 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod alerts;
mod ctors;
mod debug;
mod edge;
mod find;
mod gc;
mod hex;
mod inspect;
Expand All @@ -54,6 +55,7 @@ mod slice;
mod vertex;
mod xml;

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

Expand All @@ -69,13 +71,14 @@ pub(crate) use crate::vertex::Vertex;
///
/// ```
/// use sodg::Sodg;
/// use sodg::DeadRelay;
/// let mut sodg = Sodg::empty();
/// sodg.add(0).unwrap();
/// sodg.add(1).unwrap();
/// sodg.bind(0, 1, "a").unwrap();
/// sodg.add(2).unwrap();
/// sodg.bind(1, 2, "b").unwrap();
/// assert_eq!(2, sodg.find(0, "a.b").unwrap());
/// assert_eq!(2, sodg.find(0, "a.b", DeadRelay {}).unwrap());
/// ```
///
/// This package is used in [reo](https://github.com/objectionary/reo)
Expand All @@ -91,6 +94,28 @@ pub struct Sodg {
alerts_active: bool,
}

/// A relay that is used with `Sodg::find()` can't find an attribute.
/// It asks the relay for the name of the attribute to use instead
/// of the not found one, which is provided in the `a` parameter. The
/// `v` parameter is the ID of the vertex where the attribute `a` is not
/// found. The `b` is the locator of the attribute.
///
/// A relay may return a new vertex ID as a string `"ν42"`, for example.
/// Pretty much anything that the relay will return will be used
/// as a new search string, starting from the `v` vertex.
pub trait Relay {
fn re(&self, v: u32, a: &str, b: &str) -> Result<String>;
}

/// This `Relay` doesn't even try to find anything, but returns
/// an error.
pub struct DeadRelay {}

/// This `Relay` can be made of a lambda function.
pub struct LambdaRelay {
lambda: fn(u32, &str, &str) -> Result<String>,
}

#[cfg(test)]
use simple_logger::SimpleLogger;

Expand Down
Loading

0 comments on commit 0b6903f

Please sign in to comment.