diff --git a/lib/spargebra/Cargo.toml b/lib/spargebra/Cargo.toml index e580543a..d1b8ea4b 100644 --- a/lib/spargebra/Cargo.toml +++ b/lib/spargebra/Cargo.toml @@ -18,6 +18,7 @@ default = [] rdf-star = ["oxrdf/rdf-star"] sep-0002 = [] sep-0006 = [] +rules = [] [dependencies] peg = "0.8" diff --git a/lib/spargebra/src/lib.rs b/lib/spargebra/src/lib.rs index dc0e2aa7..02314b28 100644 --- a/lib/spargebra/src/lib.rs +++ b/lib/spargebra/src/lib.rs @@ -8,9 +8,12 @@ pub mod algebra; mod parser; mod query; +mod rule; pub mod term; mod update; pub use parser::ParseError; pub use query::*; +#[cfg(feature = "rules")] +pub use rule::*; pub use update::*; diff --git a/lib/spargebra/src/parser.rs b/lib/spargebra/src/parser.rs index 689b95ce..01c4bd69 100644 --- a/lib/spargebra/src/parser.rs +++ b/lib/spargebra/src/parser.rs @@ -1,5 +1,6 @@ use crate::algebra::*; use crate::query::*; +use crate::rule::*; use crate::term::*; use crate::update::*; use oxilangtag::LanguageTag; @@ -54,7 +55,7 @@ pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result) -> Result) -> Result { + let mut state = ParserState { + base_iri: if let Some(base_iri) = base_iri { + Some(Iri::parse(base_iri.to_owned()).map_err(|e| ParseError { + inner: ParseErrorKind::InvalidBaseIri(e), + })?) + } else { + None + }, + namespaces: HashMap::default(), + used_bnodes: HashSet::default(), + currently_used_bnodes: HashSet::default(), + aggregates: Vec::new(), + }; + + parser::RuleSetUnit(&unescape_unicode_codepoints(rules), &mut state).map_err(|e| ParseError { + inner: ParseErrorKind::Parser(e), + }) +} + /// Error returned during SPARQL parsing. #[derive(Debug)] pub struct ParseError { @@ -966,7 +988,7 @@ parser! { } //[3] - pub rule UpdateInit() -> Vec = Update() + pub rule UpdateUnit() -> Vec = Update() //[4] rule Prologue() = (BaseDecl() _ / PrefixDecl() _)* {} @@ -2445,5 +2467,16 @@ parser! { Err(literal) } } + + pub rule RuleSetUnit() -> RuleSet = RuleSet() + + rule RuleSet() -> RuleSet = _ Prologue() _ rules:(Rule() ** (_ ";" _)) _ ( ";" _)? { RuleSet { rules } } + + rule Rule() -> Rule = i("IF") _ body:ConstructTemplate() _ i("THEN") _ head:ConstructTemplate() {? + Ok(Rule { + body: GraphPattern::Bgp { patterns: body }, + head: head.into_iter().map(GroundTriplePattern::try_from).collect::>().map_err(|_| "Blank nodes are not allowed in rules head")? + }) + } } } diff --git a/lib/spargebra/src/rule.rs b/lib/spargebra/src/rule.rs new file mode 100644 index 00000000..8f5f8144 --- /dev/null +++ b/lib/spargebra/src/rule.rs @@ -0,0 +1,123 @@ +#![cfg_attr(not(feature = "rules"), allow(dead_code))] +use crate::algebra::*; +use crate::parser::{parse_rule_set, ParseError}; +use crate::term::*; +use std::fmt; +use std::str::FromStr; + +/// A parsed if/then rule set. +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct RuleSet { + pub rules: Vec, +} + +impl RuleSet { + /// Parses a set of rules with an optional base IRI to resolve relative IRIs in the rules. + /// Note that this base IRI will not be used during execution. + pub fn parse(rules: &str, base_iri: Option<&str>) -> Result { + parse_rule_set(rules, base_iri) + } + + /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html). + pub fn to_sse(&self) -> String { + let mut buffer = String::new(); + self.fmt_sse(&mut buffer) + .expect("Unexpected error during SSE formatting"); + buffer + } + + /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html). + fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result { + write!(f, "(")?; + for (i, r) in self.rules.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + r.fmt_sse(f)?; + } + write!(f, ") ") + } +} + +impl fmt::Display for RuleSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for r in &self.rules { + writeln!(f, "{r} ;")?; + } + Ok(()) + } +} + +impl FromStr for RuleSet { + type Err = ParseError; + + fn from_str(rules: &str) -> Result { + Self::parse(rules, None) + } +} + +impl<'a> TryFrom<&'a str> for RuleSet { + type Error = ParseError; + + fn try_from(rules: &str) -> Result { + Self::from_str(rules) + } +} + +impl<'a> TryFrom<&'a String> for RuleSet { + type Error = ParseError; + + fn try_from(rules: &String) -> Result { + Self::from_str(rules) + } +} + +/// A parsed if/then rule. +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct Rule { + /// The construction template. + pub head: Vec, + /// The rule body graph pattern. + pub body: GraphPattern, +} + +impl Rule { + /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html). + pub fn to_sse(&self) -> String { + let mut buffer = String::new(); + self.fmt_sse(&mut buffer) + .expect("Unexpected error during SSE formatting"); + buffer + } + + /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html). + fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result { + write!(f, "(rule (")?; + for (i, t) in self.head.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + t.fmt_sse(f)?; + } + write!(f, ") ")?; + self.body.fmt_sse(f)?; + write!(f, ")") + } +} + +impl fmt::Display for Rule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "IF {{ {} }} THEN {{ ", + SparqlGraphRootPattern { + pattern: &self.body, + dataset: None + } + )?; + for triple in self.head.iter() { + write!(f, "{triple} . ")?; + } + write!(f, "}}") + } +}