use crate::model::{StarTerm, StarTriple};
use crate::{StarError, StarResult};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StarFunction {
Triple,
Subject,
Predicate,
Object,
IsTriple,
}
impl StarFunction {
pub fn name(&self) -> &'static str {
match self {
StarFunction::Triple => "TRIPLE",
StarFunction::Subject => "SUBJECT",
StarFunction::Predicate => "PREDICATE",
StarFunction::Object => "OBJECT",
StarFunction::IsTriple => "isTRIPLE",
}
}
pub fn arity(&self) -> usize {
match self {
StarFunction::Triple => 3,
StarFunction::Subject => 1,
StarFunction::Predicate => 1,
StarFunction::Object => 1,
StarFunction::IsTriple => 1,
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name.to_uppercase().as_str() {
"TRIPLE" => Some(StarFunction::Triple),
"SUBJECT" => Some(StarFunction::Subject),
"PREDICATE" => Some(StarFunction::Predicate),
"OBJECT" => Some(StarFunction::Object),
"ISTRIPLE" => Some(StarFunction::IsTriple),
_ => None,
}
}
}
pub struct FunctionEvaluator;
impl FunctionEvaluator {
pub fn evaluate(function: StarFunction, args: &[StarTerm]) -> StarResult<StarTerm> {
let expected_arity = function.arity();
if args.len() != expected_arity {
return Err(StarError::QueryError {
message: format!(
"Function {} expects {} arguments, got {}",
function.name(),
expected_arity,
args.len()
),
query_fragment: None,
position: None,
suggestion: None,
});
}
match function {
StarFunction::Triple => Self::evaluate_triple(&args[0], &args[1], &args[2]),
StarFunction::Subject => Self::evaluate_subject(&args[0]),
StarFunction::Predicate => Self::evaluate_predicate(&args[0]),
StarFunction::Object => Self::evaluate_object(&args[0]),
StarFunction::IsTriple => Self::evaluate_is_triple(&args[0]),
}
}
fn evaluate_triple(
subject: &StarTerm,
predicate: &StarTerm,
object: &StarTerm,
) -> StarResult<StarTerm> {
if !subject.can_be_subject() {
return Err(StarError::QueryError {
message: format!("Invalid subject for TRIPLE function: {subject:?}"),
query_fragment: None,
position: None,
suggestion: None,
});
}
if !predicate.can_be_predicate() {
return Err(StarError::QueryError {
message: format!("Invalid predicate for TRIPLE function: {predicate:?}"),
query_fragment: None,
position: None,
suggestion: None,
});
}
if !object.can_be_object() {
return Err(StarError::QueryError {
message: format!("Invalid object for TRIPLE function: {object:?}"),
query_fragment: None,
position: None,
suggestion: None,
});
}
let triple = StarTriple::new(subject.clone(), predicate.clone(), object.clone());
Ok(StarTerm::quoted_triple(triple))
}
fn evaluate_subject(term: &StarTerm) -> StarResult<StarTerm> {
match term {
StarTerm::QuotedTriple(triple) => Ok(triple.subject.clone()),
_ => Err(StarError::QueryError {
message: format!("SUBJECT function expects a quoted triple, got: {term:?}"),
query_fragment: None,
position: None,
suggestion: None,
}),
}
}
fn evaluate_predicate(term: &StarTerm) -> StarResult<StarTerm> {
match term {
StarTerm::QuotedTriple(triple) => Ok(triple.predicate.clone()),
_ => Err(StarError::QueryError {
message: format!("PREDICATE function expects a quoted triple, got: {term:?}"),
query_fragment: None,
position: None,
suggestion: None,
}),
}
}
fn evaluate_object(term: &StarTerm) -> StarResult<StarTerm> {
match term {
StarTerm::QuotedTriple(triple) => Ok(triple.object.clone()),
_ => Err(StarError::QueryError {
message: format!("OBJECT function expects a quoted triple, got: {term:?}"),
query_fragment: None,
position: None,
suggestion: None,
}),
}
}
fn evaluate_is_triple(term: &StarTerm) -> StarResult<StarTerm> {
let result = term.is_quoted_triple();
let value = if result { "true" } else { "false" };
StarTerm::literal_with_datatype(value, "http://www.w3.org/2001/XMLSchema#boolean")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
Term(StarTerm),
Variable(String),
FunctionCall {
function: StarFunction,
args: Vec<Expression>,
},
BinaryOp {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
UnaryOp {
op: UnaryOperator,
expr: Box<Expression>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BinaryOperator {
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
And,
Or,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnaryOperator {
Not,
Minus,
}
pub struct ExpressionEvaluator;
impl ExpressionEvaluator {
pub fn evaluate(
expr: &Expression,
bindings: &std::collections::HashMap<String, StarTerm>,
) -> StarResult<StarTerm> {
match expr {
Expression::Term(term) => Ok(term.clone()),
Expression::Variable(var) => {
bindings
.get(var)
.cloned()
.ok_or_else(|| StarError::QueryError {
message: format!("Unbound variable: {var}"),
query_fragment: None,
position: None,
suggestion: None,
})
}
Expression::FunctionCall { function, args } => {
let evaluated_args: Result<Vec<_>, _> = args
.iter()
.map(|arg| Self::evaluate(arg, bindings))
.collect();
let evaluated_args = evaluated_args?;
FunctionEvaluator::evaluate(*function, &evaluated_args)
}
Expression::BinaryOp { op, left, right } => {
let left_val = Self::evaluate(left, bindings)?;
let right_val = Self::evaluate(right, bindings)?;
Self::evaluate_binary_op(*op, &left_val, &right_val)
}
Expression::UnaryOp { op, expr } => {
let val = Self::evaluate(expr, bindings)?;
Self::evaluate_unary_op(*op, &val)
}
}
}
fn evaluate_binary_op(
op: BinaryOperator,
left: &StarTerm,
right: &StarTerm,
) -> StarResult<StarTerm> {
match op {
BinaryOperator::Equal => {
let result = left == right;
StarTerm::literal_with_datatype(
if result { "true" } else { "false" },
"http://www.w3.org/2001/XMLSchema#boolean",
)
}
BinaryOperator::NotEqual => {
let result = left != right;
StarTerm::literal_with_datatype(
if result { "true" } else { "false" },
"http://www.w3.org/2001/XMLSchema#boolean",
)
}
_ => Err(StarError::QueryError {
message: format!("Binary operator {op:?} not yet implemented"),
query_fragment: None,
position: None,
suggestion: None,
}),
}
}
fn evaluate_unary_op(op: UnaryOperator, term: &StarTerm) -> StarResult<StarTerm> {
match op {
UnaryOperator::Not => {
if let Some(literal) = term.as_literal() {
if let Some(datatype) = &literal.datatype {
if datatype.iri == "http://www.w3.org/2001/XMLSchema#boolean" {
let value = literal.value.as_str();
let negated = match value {
"true" => "false",
"false" => "true",
_ => {
return Err(StarError::QueryError {
message: "Invalid boolean value".to_string(),
query_fragment: None,
position: None,
suggestion: None,
})
}
};
return StarTerm::literal_with_datatype(
negated,
"http://www.w3.org/2001/XMLSchema#boolean",
);
}
}
}
Err(StarError::QueryError {
message: "NOT operator expects a boolean literal".to_string(),
query_fragment: None,
position: None,
suggestion: None,
})
}
UnaryOperator::Minus => Err(StarError::QueryError {
message: "Arithmetic operations not yet implemented".to_string(),
query_fragment: None,
position: None,
suggestion: None,
}),
}
}
}
impl Expression {
pub fn triple(subject: Expression, predicate: Expression, object: Expression) -> Self {
Expression::FunctionCall {
function: StarFunction::Triple,
args: vec![subject, predicate, object],
}
}
pub fn subject(expr: Expression) -> Self {
Expression::FunctionCall {
function: StarFunction::Subject,
args: vec![expr],
}
}
pub fn predicate(expr: Expression) -> Self {
Expression::FunctionCall {
function: StarFunction::Predicate,
args: vec![expr],
}
}
pub fn object(expr: Expression) -> Self {
Expression::FunctionCall {
function: StarFunction::Object,
args: vec![expr],
}
}
pub fn is_triple(expr: Expression) -> Self {
Expression::FunctionCall {
function: StarFunction::IsTriple,
args: vec![expr],
}
}
pub fn equal(left: Expression, right: Expression) -> Self {
Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(left),
right: Box::new(right),
}
}
pub fn var(name: &str) -> Self {
Expression::Variable(name.to_string())
}
pub fn term(term: StarTerm) -> Self {
Expression::Term(term)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_triple_function() {
let subject = StarTerm::iri("http://example.org/alice").unwrap();
let predicate = StarTerm::iri("http://example.org/age").unwrap();
let object = StarTerm::literal("25").unwrap();
let result = FunctionEvaluator::evaluate(
StarFunction::Triple,
&[subject.clone(), predicate.clone(), object.clone()],
)
.unwrap();
assert!(result.is_quoted_triple());
if let StarTerm::QuotedTriple(triple) = result {
assert_eq!(triple.subject, subject);
assert_eq!(triple.predicate, predicate);
assert_eq!(triple.object, object);
}
}
#[test]
fn test_subject_function() {
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted = StarTerm::quoted_triple(triple);
let result = FunctionEvaluator::evaluate(StarFunction::Subject, &[quoted]).unwrap();
assert_eq!(result, StarTerm::iri("http://example.org/alice").unwrap());
}
#[test]
fn test_predicate_function() {
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted = StarTerm::quoted_triple(triple);
let result = FunctionEvaluator::evaluate(StarFunction::Predicate, &[quoted]).unwrap();
assert_eq!(result, StarTerm::iri("http://example.org/age").unwrap());
}
#[test]
fn test_object_function() {
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted = StarTerm::quoted_triple(triple);
let result = FunctionEvaluator::evaluate(StarFunction::Object, &[quoted]).unwrap();
assert_eq!(result, StarTerm::literal("25").unwrap());
}
#[test]
fn test_is_triple_function() {
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted = StarTerm::quoted_triple(triple);
let result = FunctionEvaluator::evaluate(StarFunction::IsTriple, &[quoted]).unwrap();
assert_eq!(
result,
StarTerm::literal_with_datatype("true", "http://www.w3.org/2001/XMLSchema#boolean")
.unwrap()
);
let iri = StarTerm::iri("http://example.org/alice").unwrap();
let result = FunctionEvaluator::evaluate(StarFunction::IsTriple, &[iri]).unwrap();
assert_eq!(
result,
StarTerm::literal_with_datatype("false", "http://www.w3.org/2001/XMLSchema#boolean")
.unwrap()
);
}
#[test]
fn test_expression_evaluation() {
let mut bindings = HashMap::new();
bindings.insert(
"x".to_string(),
StarTerm::iri("http://example.org/alice").unwrap(),
);
bindings.insert(
"p".to_string(),
StarTerm::iri("http://example.org/age").unwrap(),
);
bindings.insert("o".to_string(), StarTerm::literal("25").unwrap());
let expr = Expression::triple(
Expression::var("x"),
Expression::var("p"),
Expression::var("o"),
);
let result = ExpressionEvaluator::evaluate(&expr, &bindings).unwrap();
assert!(result.is_quoted_triple());
let expr = Expression::subject(Expression::triple(
Expression::var("x"),
Expression::var("p"),
Expression::var("o"),
));
let result = ExpressionEvaluator::evaluate(&expr, &bindings).unwrap();
assert_eq!(result, StarTerm::iri("http://example.org/alice").unwrap());
}
#[test]
fn test_nested_function_calls() {
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted1 = StarTerm::quoted_triple(triple1);
let triple2 = StarTriple::new(
quoted1.clone(),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
let quoted2 = StarTerm::quoted_triple(triple2);
let inner_subject =
FunctionEvaluator::evaluate(StarFunction::Subject, std::slice::from_ref("ed2))
.unwrap();
let outer_subject =
FunctionEvaluator::evaluate(StarFunction::Subject, &[inner_subject]).unwrap();
assert_eq!(
outer_subject,
StarTerm::iri("http://example.org/alice").unwrap()
);
}
#[test]
fn test_error_cases() {
let result = FunctionEvaluator::evaluate(
StarFunction::Triple,
&[StarTerm::iri("http://example.org/alice").unwrap()],
);
assert!(result.is_err());
let result = FunctionEvaluator::evaluate(
StarFunction::Triple,
&[
StarTerm::literal("invalid_subject").unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::literal("o").unwrap(),
],
);
assert!(result.is_err());
let result = FunctionEvaluator::evaluate(
StarFunction::Subject,
&[StarTerm::iri("http://example.org/notATriple").unwrap()],
);
assert!(result.is_err());
}
}