use alloc::borrow::Cow;
#[allow(unused_imports)]
use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec};
use core::fmt;
#[derive(Debug, Clone)]
pub struct Provenance {
pub name: OntologyName,
pub description: Label,
pub citation: Citation,
pub module_path: ModulePath,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LanguageCode(Cow<'static, str>);
impl LanguageCode {
pub const fn new_static(s: &'static str) -> Self {
Self(Cow::Borrowed(s))
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub const ENGLISH: Self = Self::new_static("en");
}
impl From<&'static str> for LanguageCode {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for LanguageCode {
fn from(s: String) -> Self {
Self(Cow::Owned(s))
}
}
impl AsRef<str> for LanguageCode {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for LanguageCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Label(Cow<'static, str>);
impl Label {
pub const fn new_static(s: &'static str) -> Self {
Self(Cow::Borrowed(s))
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl LanguageCode {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<&'static str> for Label {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for Label {
fn from(s: String) -> Self {
Self(Cow::Owned(s))
}
}
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl PartialEq<&str> for Label {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Definition(Cow<'static, str>);
impl Definition {
pub const fn new_static(s: &'static str) -> Self {
Self(Cow::Borrowed(s))
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<&'static str> for Definition {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for Definition {
fn from(s: String) -> Self {
Self(Cow::Owned(s))
}
}
impl fmt::Display for Definition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Lexical {
pub label: Label,
pub definition: Definition,
pub language: LanguageCode,
}
impl Lexical {
pub fn new(
label: impl Into<Label>,
definition: impl Into<Definition>,
language: impl Into<LanguageCode>,
) -> Self {
Self {
label: label.into(),
definition: definition.into(),
language: language.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConceptName {
identifier: Cow<'static, str>,
lexical: Option<Lexical>,
}
impl ConceptName {
pub const fn new_static(s: &'static str) -> Self {
Self {
identifier: Cow::Borrowed(s),
lexical: None,
}
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self {
identifier: s.into(),
lexical: None,
}
}
pub fn with_lexical(mut self, lexical: Lexical) -> Self {
self.lexical = Some(lexical);
self
}
pub fn as_str(&self) -> &str {
&self.identifier
}
pub fn lexical(&self) -> Option<&Lexical> {
self.lexical.as_ref()
}
}
impl From<&'static str> for ConceptName {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for ConceptName {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl AsRef<str> for ConceptName {
fn as_ref(&self) -> &str {
&self.identifier
}
}
impl fmt::Display for ConceptName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.identifier.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MorphismKind {
Identity,
Subsumption,
Parthood,
Causation,
Opposition,
Equivalence,
Custom(Cow<'static, str>),
}
impl MorphismKind {
pub fn as_str(&self) -> &str {
match self {
MorphismKind::Identity => "Identity",
MorphismKind::Subsumption => "Subsumption",
MorphismKind::Parthood => "Parthood",
MorphismKind::Causation => "Causation",
MorphismKind::Opposition => "Opposition",
MorphismKind::Equivalence => "Equivalence",
MorphismKind::Custom(s) => s,
}
}
pub fn from_name(name: &str) -> Self {
match name {
"Identity" => MorphismKind::Identity,
"Subsumption" => MorphismKind::Subsumption,
"Parthood" => MorphismKind::Parthood,
"Causation" => MorphismKind::Causation,
"Opposition" => MorphismKind::Opposition,
"Equivalence" => MorphismKind::Equivalence,
other => MorphismKind::Custom(Cow::Owned(other.to_string())),
}
}
}
impl fmt::Display for MorphismKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Morphism {
pub from: ConceptName,
pub to: ConceptName,
pub kind: MorphismKind,
lexical: Option<Lexical>,
}
impl Morphism {
pub fn new(
from: impl Into<ConceptName>,
to: impl Into<ConceptName>,
kind: MorphismKind,
) -> Self {
Self {
from: from.into(),
to: to.into(),
kind,
lexical: None,
}
}
pub fn with_lexical(mut self, lexical: Lexical) -> Self {
self.lexical = Some(lexical);
self
}
pub fn lexical(&self) -> Option<&Lexical> {
self.lexical.as_ref()
}
}
impl fmt::Display for Morphism {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}--{}-->{}", self.from, self.kind, self.to)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OntologyName(Cow<'static, str>);
impl OntologyName {
pub const fn new_static(s: &'static str) -> Self {
Self(Cow::Borrowed(s))
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&'static str> for OntologyName {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for OntologyName {
fn from(s: String) -> Self {
Self(Cow::Owned(s))
}
}
impl AsRef<str> for OntologyName {
fn as_ref(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for OntologyName {
fn eq(&self, other: &str) -> bool {
self.0.as_ref() == other
}
}
impl PartialEq<&str> for OntologyName {
fn eq(&self, other: &&str) -> bool {
self.0.as_ref() == *other
}
}
impl fmt::Display for OntologyName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ModulePath(Cow<'static, str>);
impl ModulePath {
pub const fn new_static(s: &'static str) -> Self {
Self(Cow::Borrowed(s))
}
pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn domain(&self) -> String {
let s = self.0.as_ref();
let s = s.strip_prefix("pr4xis_domains::").unwrap_or(s);
let s = s.strip_suffix("::ontology").unwrap_or(s);
s.replace("::", ".")
}
}
impl From<&'static str> for ModulePath {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
impl From<String> for ModulePath {
fn from(s: String) -> Self {
Self(Cow::Owned(s))
}
}
impl AsRef<str> for ModulePath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for ModulePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Year(u32);
impl Year {
pub const fn new(n: u32) -> Self {
Self(n)
}
pub fn parse(s: &str) -> Option<Self> {
s.parse::<u32>().ok().map(Self)
}
pub fn value(&self) -> u32 {
self.0
}
}
impl fmt::Display for Year {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SynkolationLevel(usize);
impl SynkolationLevel {
pub const ZERO: Self = Self(0);
pub const fn new(n: usize) -> Self {
Self(n)
}
pub fn value(&self) -> usize {
self.0
}
pub fn next(self) -> Self {
Self(self.0 + 1)
}
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
}
impl fmt::Display for SynkolationLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Grade(usize);
impl Grade {
pub const fn new(n: usize) -> Self {
Self(n)
}
pub fn value(&self) -> usize {
self.0
}
}
impl From<SynkolationLevel> for Grade {
fn from(l: SynkolationLevel) -> Self {
Self(l.0)
}
}
impl fmt::Display for Grade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Citation {
entries: Vec<CitationEntry>,
raw: Cow<'static, str>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CitationEntry {
pub authors: Cow<'static, str>,
pub year: Option<u32>,
}
impl Citation {
pub const EMPTY: Self = Self {
entries: Vec::new(),
raw: Cow::Borrowed(""),
};
pub fn parse_static(s: &'static str) -> Self {
let entries = parse_entries(s);
Self {
entries,
raw: Cow::Borrowed(s),
}
}
pub fn parse(s: impl Into<String>) -> Self {
let s = s.into();
let entries = parse_entries(&s);
Self {
entries,
raw: Cow::Owned(s),
}
}
pub fn entries(&self) -> &[CitationEntry] {
&self.entries
}
pub fn as_str(&self) -> &str {
&self.raw
}
pub fn is_empty(&self) -> bool {
self.raw.is_empty()
}
}
fn parse_entries(s: &str) -> Vec<CitationEntry> {
if s.is_empty() {
return Vec::new();
}
s.split(';')
.map(str::trim)
.filter(|p| !p.is_empty())
.map(|part| {
if let (Some(open), Some(close)) = (part.rfind('('), part.rfind(')'))
&& close > open
{
let year_str = &part[open + 1..close];
let year = year_str.parse::<u32>().ok();
let authors = part[..open].trim().to_string();
return CitationEntry {
authors: Cow::Owned(authors),
year,
};
}
CitationEntry {
authors: Cow::Owned(part.to_string()),
year: None,
}
})
.collect()
}
impl From<&'static str> for Citation {
fn from(s: &'static str) -> Self {
Self::parse_static(s)
}
}
impl From<String> for Citation {
fn from(s: String) -> Self {
Self::parse(s)
}
}
impl AsRef<str> for Citation {
fn as_ref(&self) -> &str {
&self.raw
}
}
impl fmt::Display for Citation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.raw.fmt(f)
}
}
#[derive(Debug, Clone)]
pub struct Vocabulary {
pub ontology_name: OntologyName,
pub module_path: ModulePath,
pub source: Citation,
source_of_truth: Source,
}
#[derive(Debug, Clone)]
enum Source {
Static {
concepts: fn() -> Vec<ConceptName>,
morphisms: fn() -> Vec<Morphism>,
},
Captured {
concepts: Vec<ConceptName>,
morphisms: Vec<Morphism>,
},
}
impl Vocabulary {
pub fn domain(&self) -> String {
self.module_path.domain()
}
pub fn name(&self) -> &str {
self.ontology_name.as_str()
}
pub fn concepts(&self) -> Vec<ConceptName> {
match &self.source_of_truth {
Source::Static { concepts, .. } => concepts(),
Source::Captured { concepts, .. } => concepts.clone(),
}
}
pub fn morphisms(&self) -> Vec<Morphism> {
match &self.source_of_truth {
Source::Static { morphisms, .. } => morphisms(),
Source::Captured { morphisms, .. } => morphisms.clone(),
}
}
pub fn concept_count(&self) -> usize {
match &self.source_of_truth {
Source::Static { concepts, .. } => concepts().len(),
Source::Captured { concepts, .. } => concepts.len(),
}
}
pub fn morphism_count(&self) -> usize {
match &self.source_of_truth {
Source::Static { morphisms, .. } => morphisms().len(),
Source::Captured { morphisms, .. } => morphisms.len(),
}
}
pub fn from_static<
C: crate::category::Category,
E: crate::category::entity::FinitelyGenerated,
>(
name: impl Into<OntologyName>,
module_path: impl Into<ModulePath>,
source: impl Into<Citation>,
) -> Self {
Self {
ontology_name: name.into(),
module_path: module_path.into(),
source: source.into(),
source_of_truth: Source::Static {
concepts: || {
use crate::category::FinitelyGenerated;
<E as FinitelyGenerated>::variants()
.iter()
.map(|v| {
let name = v.name();
if name.is_empty() {
ConceptName::new(format!("{v:?}"))
} else {
ConceptName::new(name.to_string())
}
})
.collect()
},
morphisms: || {
use crate::category::{Arrow, Concept};
let name_of = |e: &<C as crate::category::Category>::Object| -> String {
let n = e.name();
if n.is_empty() {
format!("{e:?}")
} else {
n.to_string()
}
};
<C as crate::category::Category>::morphisms()
.iter()
.map(|m| {
let kind_id = format!("{:?}", m.kind());
let kind = match kind_id.as_str() {
"Identity" => MorphismKind::Identity,
"Subsumption" => MorphismKind::Subsumption,
"Parthood" => MorphismKind::Parthood,
"Causation" => MorphismKind::Causation,
"Opposition" => MorphismKind::Opposition,
"Equivalence" => MorphismKind::Equivalence,
_ => MorphismKind::Custom(Cow::Owned(kind_id)),
};
Morphism::new(
ConceptName::new(name_of(&m.source())),
ConceptName::new(name_of(&m.target())),
kind,
)
})
.collect()
},
},
}
}
pub fn from_captured(
name: impl Into<OntologyName>,
module_path: impl Into<ModulePath>,
source: impl Into<Citation>,
concepts: Vec<ConceptName>,
morphisms: Vec<Morphism>,
) -> Self {
Self {
ontology_name: name.into(),
module_path: module_path.into(),
source: source.into(),
source_of_truth: Source::Captured {
concepts,
morphisms,
},
}
}
pub fn from_ontology<
C: crate::category::Category,
E: crate::category::entity::FinitelyGenerated,
>(
name: impl Into<OntologyName>,
module_path: impl Into<ModulePath>,
source: impl Into<Citation>,
) -> Self {
Self::from_static::<C, E>(name, module_path, source)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[crate::praxis_value(Verifiable)]
#[test]
fn ontology_name_from_static_is_borrowed() {
let name = OntologyName::new_static("Biology");
assert_eq!(name.as_str(), "Biology");
assert!(matches!(name.0, Cow::Borrowed(_)));
}
#[crate::praxis_value(Verifiable)]
#[test]
fn ontology_name_from_owned_is_owned() {
let name = OntologyName::new(String::from("Runtime"));
assert_eq!(name.as_str(), "Runtime");
assert!(matches!(name.0, Cow::Owned(_)));
}
#[crate::praxis_value(Verifiable)]
#[test]
fn citation_parses_single_entry() {
let c = Citation::parse_static("Shannon (1948)");
assert_eq!(c.entries().len(), 1);
assert_eq!(c.entries()[0].authors, "Shannon");
assert_eq!(c.entries()[0].year, Some(1948));
}
#[crate::praxis_value(Verifiable)]
#[test]
fn citation_parses_multiple_entries() {
let c = Citation::parse_static("Shannon (1948); Jakobson (1960); Wiener (1948)");
assert_eq!(c.entries().len(), 3);
assert_eq!(c.entries()[0].authors, "Shannon");
assert_eq!(c.entries()[1].authors, "Jakobson");
assert_eq!(c.entries()[2].authors, "Wiener");
}
#[crate::praxis_value(Verifiable)]
#[test]
fn citation_parses_et_al() {
let c = Citation::parse_static("McCrae et al. (2012, 2017)");
assert_eq!(c.entries().len(), 1);
assert_eq!(c.entries()[0].authors, "McCrae et al.");
}
#[crate::praxis_value(Verifiable)]
#[test]
fn citation_empty_string() {
let c = Citation::parse_static("");
assert!(c.is_empty());
assert_eq!(c.entries().len(), 0);
}
#[crate::praxis_value(Verifiable)]
#[test]
fn citation_roundtrips_through_display() {
let c = Citation::parse_static("Shannon (1948); Jakobson (1960)");
assert_eq!(format!("{c}"), "Shannon (1948); Jakobson (1960)");
}
#[crate::praxis_value(Explainable, Verifiable)]
#[test]
fn module_path_domain_strips_prefixes() {
let p = ModulePath::new_static("pr4xis_domains::formal::math::ontology");
assert_eq!(p.domain(), "formal.math");
}
#[crate::praxis_value(Verifiable)]
#[test]
fn wrappers_accept_static_str_and_string() {
let _: OntologyName = "literal".into();
let _: OntologyName = String::from("owned").into();
let _: ModulePath = "a::b".into();
let _: Citation = String::from("Author (2024)").into();
}
}