use super::functions::uuid_simple;
use crate::{Rule, RuleAtom, Term};
use anyhow::{anyhow, Result};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct RifRule {
pub id: Option<String>,
pub body: RifFormula,
pub head: RifFormula,
pub variables: Vec<RifVar>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub enum RifAtom {
Positional(RifPositionalAtom),
Named(RifNamedAtom),
Equal(RifTerm, RifTerm),
External(RifExternal),
}
#[derive(Debug, Clone)]
pub struct RifDocument {
pub dialect: RifDialect,
pub prefixes: HashMap<String, String>,
pub imports: Vec<RifImport>,
pub groups: Vec<RifGroup>,
pub base: Option<String>,
pub metadata: HashMap<String, String>,
}
impl RifDocument {
pub fn new(dialect: RifDialect) -> Self {
Self {
dialect,
prefixes: HashMap::new(),
imports: Vec::new(),
groups: Vec::new(),
base: None,
metadata: HashMap::new(),
}
}
pub fn add_prefix(&mut self, prefix: &str, iri: &str) {
self.prefixes.insert(prefix.to_string(), iri.to_string());
}
pub fn add_group(&mut self, group: RifGroup) {
self.groups.push(group);
}
pub fn to_oxirs_rules(&self) -> Result<Vec<Rule>> {
let converter = RifConverter::new(self.prefixes.clone());
let mut rules = Vec::new();
for group in &self.groups {
for sentence in &group.sentences {
if let RifSentence::Rule(rif_rule) = sentence {
rules.push(converter.convert_rule(rif_rule)?);
}
}
}
Ok(rules)
}
pub fn from_oxirs_rules(rules: &[Rule], dialect: RifDialect) -> Self {
let converter = RifConverter::new(HashMap::new());
let mut doc = Self::new(dialect);
let sentences: Vec<RifSentence> = rules
.iter()
.map(|r| RifSentence::Rule(Box::new(converter.rule_to_rif(r))))
.collect();
doc.add_group(RifGroup {
id: None,
sentences,
metadata: HashMap::new(),
});
doc
}
pub fn expand_iri(&self, prefixed: &str) -> String {
if let Some((prefix, local)) = prefixed.split_once(':') {
if let Some(base_iri) = self.prefixes.get(prefix) {
return format!("{}{}", base_iri, local);
}
}
prefixed.to_string()
}
}
#[derive(Debug, Clone)]
pub struct RifFrame {
pub object: RifTerm,
pub slots: Vec<(RifTerm, RifTerm)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RifVar {
pub name: String,
pub var_type: Option<String>,
}
impl RifVar {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
var_type: None,
}
}
pub fn typed(name: &str, var_type: &str) -> Self {
Self {
name: name.to_string(),
var_type: Some(var_type.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct RifFunc {
pub name: Box<RifTerm>,
pub args: Vec<RifTerm>,
}
#[derive(Debug, Clone)]
pub enum RifFormula {
Atom(RifAtom),
And(Vec<RifFormula>),
Or(Vec<RifFormula>),
Naf(Box<RifFormula>),
Neg(Box<RifFormula>),
Exists(Vec<RifVar>, Box<RifFormula>),
External(RifExternal),
Equal(RifTerm, RifTerm),
Frame(RifFrame),
Member(RifTerm, RifTerm),
Subclass(RifTerm, RifTerm),
}
impl RifFormula {
pub fn atom(predicate: &str, args: Vec<RifTerm>) -> Self {
Self::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: RifTerm::Const(RifConst::Iri(predicate.to_string())),
args,
}))
}
pub fn and(formulas: Vec<RifFormula>) -> Self {
Self::And(formulas)
}
pub fn naf(formula: RifFormula) -> Self {
Self::Naf(Box::new(formula))
}
}
#[derive(Debug, Clone)]
pub enum RifTerm {
Var(RifVar),
Const(RifConst),
Func(Box<RifFunc>),
List(Vec<RifTerm>),
External(Box<RifExternal>),
}
impl RifTerm {
pub fn var(name: &str) -> Self {
Self::Var(RifVar::new(name))
}
pub fn iri(iri: &str) -> Self {
Self::Const(RifConst::Iri(iri.to_string()))
}
pub fn string(s: &str) -> Self {
Self::Const(RifConst::Literal(RifLiteral {
value: s.to_string(),
datatype: Some("xsd:string".to_string()),
lang: None,
}))
}
pub fn integer(n: i64) -> Self {
Self::Const(RifConst::Literal(RifLiteral {
value: n.to_string(),
datatype: Some("xsd:integer".to_string()),
lang: None,
}))
}
}
#[derive(Debug, Clone)]
pub struct RifGroup {
pub id: Option<String>,
pub sentences: Vec<RifSentence>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RifDialect {
Core,
#[default]
Bld,
Prd,
}
#[derive(Debug, Clone)]
pub struct RifForall {
pub variables: Vec<RifVar>,
pub formula: Box<RifSentence>,
}
#[derive(Debug, Clone)]
pub struct RifNamedAtom {
pub predicate: RifTerm,
pub args: HashMap<String, RifTerm>,
}
#[derive(Debug, Clone)]
pub enum RifSentence {
Rule(Box<RifRule>),
Fact(RifFact),
Group(RifGroup),
Forall(RifForall),
}
#[derive(Debug, Clone)]
pub struct RifImport {
pub location: String,
pub profile: Option<String>,
}
#[derive(Debug)]
pub struct RifSerializer {
#[allow(dead_code)]
dialect: RifDialect,
#[allow(dead_code)]
indent: usize,
}
impl RifSerializer {
pub fn new(dialect: RifDialect) -> Self {
Self { dialect, indent: 0 }
}
pub fn serialize(&self, doc: &RifDocument) -> Result<String> {
let mut output = String::new();
output.push_str(&format!(
"(* RIF-{} Document *)\n\n",
match doc.dialect {
RifDialect::Core => "Core",
RifDialect::Bld => "BLD",
RifDialect::Prd => "PRD",
}
));
if let Some(base) = &doc.base {
output.push_str(&format!("Base(<{}>)\n", base));
}
for (prefix, iri) in &doc.prefixes {
output.push_str(&format!("Prefix({} <{}>)\n", prefix, iri));
}
if !doc.prefixes.is_empty() {
output.push('\n');
}
for import in &doc.imports {
if let Some(profile) = &import.profile {
output.push_str(&format!("Import(<{}> <{}>)\n", import.location, profile));
} else {
output.push_str(&format!("Import(<{}>)\n", import.location));
}
}
if !doc.imports.is_empty() {
output.push('\n');
}
for group in &doc.groups {
output.push_str(&self.serialize_group(group)?);
output.push('\n');
}
Ok(output)
}
fn serialize_group(&self, group: &RifGroup) -> Result<String> {
let mut output = String::new();
if let Some(id) = &group.id {
output.push_str(&format!("Group {} (\n", id));
} else {
output.push_str("Group (\n");
}
for sentence in &group.sentences {
output.push_str(" ");
output.push_str(&self.serialize_sentence(sentence)?);
output.push('\n');
}
output.push_str(")\n");
Ok(output)
}
fn serialize_sentence(&self, sentence: &RifSentence) -> Result<String> {
match sentence {
RifSentence::Rule(rule) => self.serialize_rule(rule),
RifSentence::Fact(fact) => self.serialize_atom(&fact.atom),
RifSentence::Group(group) => self.serialize_group(group),
RifSentence::Forall(forall) => self.serialize_forall(forall),
}
}
fn serialize_forall(&self, forall: &RifForall) -> Result<String> {
let vars: Vec<String> = forall
.variables
.iter()
.map(|v| format!("?{}", v.name))
.collect();
let inner = self.serialize_sentence(&forall.formula)?;
Ok(format!("Forall {} ({})", vars.join(" "), inner))
}
fn serialize_rule(&self, rule: &RifRule) -> Result<String> {
let head = self.serialize_formula(&rule.head)?;
let body = self.serialize_formula(&rule.body)?;
let rule_str = if body.is_empty() || body == "And()" {
head
} else {
format!("{} :- {}", head, body)
};
if let Some(id) = &rule.id {
Ok(format!("(* {} *) {}", id, rule_str))
} else {
Ok(rule_str)
}
}
fn serialize_formula(&self, formula: &RifFormula) -> Result<String> {
match formula {
RifFormula::Atom(atom) => self.serialize_atom(atom),
RifFormula::And(formulas) => {
if formulas.is_empty() {
return Ok("And()".to_string());
}
let parts: Result<Vec<String>> =
formulas.iter().map(|f| self.serialize_formula(f)).collect();
Ok(format!("And({})", parts?.join(" ")))
}
RifFormula::Or(formulas) => {
let parts: Result<Vec<String>> =
formulas.iter().map(|f| self.serialize_formula(f)).collect();
Ok(format!("Or({})", parts?.join(" ")))
}
RifFormula::Naf(inner) => Ok(format!("Naf({})", self.serialize_formula(inner)?)),
RifFormula::Neg(inner) => Ok(format!("Neg({})", self.serialize_formula(inner)?)),
RifFormula::Exists(vars, inner) => {
let var_strs: Vec<String> = vars.iter().map(|v| format!("?{}", v.name)).collect();
Ok(format!(
"Exists {} ({})",
var_strs.join(" "),
self.serialize_formula(inner)?
))
}
RifFormula::External(ext) => {
Ok(format!("External({})", self.serialize_atom(&ext.content)?))
}
RifFormula::Equal(left, right) => Ok(format!(
"{} = {}",
self.serialize_term(left)?,
self.serialize_term(right)?
)),
RifFormula::Frame(frame) => self.serialize_frame(frame),
RifFormula::Member(term, class) => Ok(format!(
"{}#{}",
self.serialize_term(term)?,
self.serialize_term(class)?
)),
RifFormula::Subclass(sub, sup) => Ok(format!(
"{}##{}",
self.serialize_term(sub)?,
self.serialize_term(sup)?
)),
}
}
fn serialize_atom(&self, atom: &RifAtom) -> Result<String> {
match atom {
RifAtom::Positional(pos) => {
let pred = self.serialize_term(&pos.predicate)?;
if pos.args.is_empty() {
Ok(pred)
} else {
let args: Result<Vec<String>> =
pos.args.iter().map(|t| self.serialize_term(t)).collect();
Ok(format!("{}({})", pred, args?.join(" ")))
}
}
RifAtom::Named(named) => {
let pred = self.serialize_term(&named.predicate)?;
let args: Vec<String> = named
.args
.iter()
.map(|(k, v)| format!("{}->{}", k, self.serialize_term(v).unwrap_or_default()))
.collect();
Ok(format!("{}({})", pred, args.join(" ")))
}
RifAtom::Equal(left, right) => Ok(format!(
"{} = {}",
self.serialize_term(left)?,
self.serialize_term(right)?
)),
RifAtom::External(ext) => {
Ok(format!("External({})", self.serialize_atom(&ext.content)?))
}
}
}
fn serialize_term(&self, term: &RifTerm) -> Result<String> {
match term {
RifTerm::Var(v) => Ok(format!("?{}", v.name)),
RifTerm::Const(c) => match c {
RifConst::Iri(iri) => Ok(format!("<{}>", iri)),
RifConst::Local(name) => Ok(name.clone()),
RifConst::Literal(lit) => {
let mut s = format!("\"{}\"", lit.value);
if let Some(dt) = &lit.datatype {
s.push_str(&format!("^^{}", dt));
} else if let Some(lang) = &lit.lang {
s.push_str(&format!("@{}", lang));
}
Ok(s)
}
},
RifTerm::Func(f) => {
let name = self.serialize_term(&f.name)?;
let args: Result<Vec<String>> =
f.args.iter().map(|t| self.serialize_term(t)).collect();
Ok(format!("{}({})", name, args?.join(" ")))
}
RifTerm::List(items) => {
let parts: Result<Vec<String>> =
items.iter().map(|t| self.serialize_term(t)).collect();
Ok(format!("List({})", parts?.join(" ")))
}
RifTerm::External(ext) => {
Ok(format!("External({})", self.serialize_atom(&ext.content)?))
}
}
}
fn serialize_frame(&self, frame: &RifFrame) -> Result<String> {
let obj = self.serialize_term(&frame.object)?;
let slots: Vec<String> = frame
.slots
.iter()
.map(|(k, v)| {
format!(
"{}->{}",
self.serialize_term(k).unwrap_or_default(),
self.serialize_term(v).unwrap_or_default()
)
})
.collect();
Ok(format!("{}[{}]", obj, slots.join(" ")))
}
}
#[derive(Debug, Clone)]
pub enum RifConst {
Iri(String),
Local(String),
Literal(RifLiteral),
}
#[derive(Debug, Clone)]
pub struct RifFact {
pub atom: RifAtom,
pub id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RifLiteral {
pub value: String,
pub datatype: Option<String>,
pub lang: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RifExternal {
pub content: Box<RifAtom>,
}
#[derive(Debug)]
pub struct RifConverter {
#[allow(dead_code)]
prefixes: HashMap<String, String>,
}
impl RifConverter {
pub fn new(prefixes: HashMap<String, String>) -> Self {
Self { prefixes }
}
pub fn convert_rule(&self, rif_rule: &RifRule) -> Result<Rule> {
let name = rif_rule
.id
.clone()
.unwrap_or_else(|| format!("rule_{}", uuid_simple()));
let body = self.convert_formula_to_atoms(&rif_rule.body)?;
let head = self.convert_formula_to_atoms(&rif_rule.head)?;
Ok(Rule { name, body, head })
}
fn convert_formula_to_atoms(&self, formula: &RifFormula) -> Result<Vec<RuleAtom>> {
match formula {
RifFormula::Atom(atom) => Ok(vec![Self::convert_atom(atom)?]),
RifFormula::And(formulas) => {
let mut atoms = Vec::new();
for f in formulas {
atoms.extend(self.convert_formula_to_atoms(f)?);
}
Ok(atoms)
}
RifFormula::Or(_) => Err(anyhow!("Disjunction in rule body not supported")),
RifFormula::Naf(inner) => {
let inner_atoms = self.convert_formula_to_atoms(inner)?;
if inner_atoms.len() == 1 {
if let RuleAtom::Triple {
subject,
predicate,
object,
} = &inner_atoms[0]
{
Ok(vec![RuleAtom::Builtin {
name: "not".to_string(),
args: vec![subject.clone(), predicate.clone(), object.clone()],
}])
} else {
Err(anyhow!("NAF only supported for triple patterns"))
}
} else {
Err(anyhow!("NAF of complex formula not supported"))
}
}
RifFormula::Neg(_) => Err(anyhow!("Classical negation not supported")),
RifFormula::Exists(_, inner) => self.convert_formula_to_atoms(inner),
RifFormula::External(ext) => Ok(vec![Self::convert_atom(&ext.content)?]),
RifFormula::Equal(left, right) => Ok(vec![RuleAtom::Builtin {
name: "equal".to_string(),
args: vec![Self::convert_term(left), Self::convert_term(right)],
}]),
RifFormula::Frame(frame) => self.convert_frame(frame),
RifFormula::Member(term, class) => Ok(vec![RuleAtom::Triple {
subject: Self::convert_term(term),
predicate: Term::Constant("rdf:type".to_string()),
object: Self::convert_term(class),
}]),
RifFormula::Subclass(sub, sup) => Ok(vec![RuleAtom::Triple {
subject: Self::convert_term(sub),
predicate: Term::Constant("rdfs:subClassOf".to_string()),
object: Self::convert_term(sup),
}]),
}
}
fn convert_atom(atom: &RifAtom) -> Result<RuleAtom> {
match atom {
RifAtom::Positional(pos) => {
let predicate = Self::convert_term(&pos.predicate);
if pos.args.len() == 2 {
Ok(RuleAtom::Triple {
subject: Self::convert_term(&pos.args[0]),
predicate,
object: Self::convert_term(&pos.args[1]),
})
} else {
let pred_name = match &predicate {
Term::Constant(c) => c.clone(),
Term::Variable(v) => v.clone(),
_ => "unknown".to_string(),
};
Ok(RuleAtom::Builtin {
name: pred_name,
args: pos.args.iter().map(Self::convert_term).collect(),
})
}
}
RifAtom::Named(named) => {
let pred_name = match &Self::convert_term(&named.predicate) {
Term::Constant(c) => c.clone(),
_ => "unnamed".to_string(),
};
let args: Vec<Term> = named.args.values().map(Self::convert_term).collect();
Ok(RuleAtom::Builtin {
name: pred_name,
args,
})
}
RifAtom::Equal(left, right) => Ok(RuleAtom::Builtin {
name: "equal".to_string(),
args: vec![Self::convert_term(left), Self::convert_term(right)],
}),
RifAtom::External(ext) => Self::convert_atom(&ext.content),
}
}
fn convert_term(term: &RifTerm) -> Term {
match term {
RifTerm::Var(v) => Term::Variable(v.name.clone()),
RifTerm::Const(c) => match c {
RifConst::Iri(iri) => Term::Constant(iri.clone()),
RifConst::Local(name) => Term::Constant(name.clone()),
RifConst::Literal(lit) => Term::Literal(lit.value.clone()),
},
RifTerm::Func(f) => {
let func_name = match &*f.name {
RifTerm::Const(RifConst::Local(n)) => n.clone(),
RifTerm::Const(RifConst::Iri(iri)) => iri.clone(),
_ => "func".to_string(),
};
Term::Function {
name: func_name,
args: f.args.iter().map(Self::convert_term).collect(),
}
}
RifTerm::List(items) => Term::Function {
name: "list".to_string(),
args: items.iter().map(Self::convert_term).collect(),
},
RifTerm::External(ext) => {
if let RifAtom::Positional(pos) = &*ext.content {
let name = match &pos.predicate {
RifTerm::Const(RifConst::Local(n)) => n.clone(),
_ => "external".to_string(),
};
Term::Function {
name,
args: pos.args.iter().map(Self::convert_term).collect(),
}
} else {
Term::Constant("external".to_string())
}
}
}
}
fn convert_frame(&self, frame: &RifFrame) -> Result<Vec<RuleAtom>> {
let subject = Self::convert_term(&frame.object);
let mut atoms = Vec::new();
for (slot, value) in &frame.slots {
atoms.push(RuleAtom::Triple {
subject: subject.clone(),
predicate: Self::convert_term(slot),
object: Self::convert_term(value),
});
}
Ok(atoms)
}
pub fn rule_to_rif(&self, rule: &Rule) -> RifRule {
let head = self.atoms_to_formula(&rule.head);
let body = self.atoms_to_formula(&rule.body);
RifRule {
id: Some(rule.name.clone()),
head,
body,
variables: Vec::new(),
metadata: HashMap::new(),
}
}
fn atoms_to_formula(&self, atoms: &[RuleAtom]) -> RifFormula {
if atoms.is_empty() {
return RifFormula::And(vec![]);
}
let formulas: Vec<RifFormula> = atoms.iter().map(|a| self.atom_to_rif(a)).collect();
if formulas.len() == 1 {
formulas
.into_iter()
.next()
.expect("formulas validated to have exactly one element")
} else {
RifFormula::And(formulas)
}
}
fn atom_to_rif(&self, atom: &RuleAtom) -> RifFormula {
match atom {
RuleAtom::Triple {
subject,
predicate,
object,
} => RifFormula::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: Self::term_to_rif(predicate),
args: vec![Self::term_to_rif(subject), Self::term_to_rif(object)],
})),
RuleAtom::Builtin { name, args } => {
RifFormula::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: RifTerm::Const(RifConst::Local(name.clone())),
args: args.iter().map(Self::term_to_rif).collect(),
}))
}
RuleAtom::NotEqual { left, right } => {
RifFormula::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: RifTerm::Const(RifConst::Local("notEqual".to_string())),
args: vec![Self::term_to_rif(left), Self::term_to_rif(right)],
}))
}
RuleAtom::GreaterThan { left, right } => {
RifFormula::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: RifTerm::Const(RifConst::Local("greaterThan".to_string())),
args: vec![Self::term_to_rif(left), Self::term_to_rif(right)],
}))
}
RuleAtom::LessThan { left, right } => {
RifFormula::Atom(RifAtom::Positional(RifPositionalAtom {
predicate: RifTerm::Const(RifConst::Local("lessThan".to_string())),
args: vec![Self::term_to_rif(left), Self::term_to_rif(right)],
}))
}
}
}
fn term_to_rif(term: &Term) -> RifTerm {
match term {
Term::Variable(v) => RifTerm::Var(RifVar::new(v)),
Term::Constant(c) => {
if c.starts_with("http://") || c.starts_with("https://") {
RifTerm::Const(RifConst::Iri(c.clone()))
} else {
RifTerm::Const(RifConst::Local(c.clone()))
}
}
Term::Literal(l) => RifTerm::Const(RifConst::Literal(RifLiteral {
value: l.clone(),
datatype: None,
lang: None,
})),
Term::Function { name, args } => RifTerm::Func(Box::new(RifFunc {
name: Box::new(RifTerm::Const(RifConst::Local(name.clone()))),
args: args.iter().map(Self::term_to_rif).collect(),
})),
}
}
}
#[derive(Debug, Clone)]
pub struct RifPositionalAtom {
pub predicate: RifTerm,
pub args: Vec<RifTerm>,
}