diff --git a/nemo/src/execution/tracing/trace.rs b/nemo/src/execution/tracing/trace.rs index 433cbdc8..78ecad34 100644 --- a/nemo/src/execution/tracing/trace.rs +++ b/nemo/src/execution/tracing/trace.rs @@ -207,24 +207,17 @@ pub struct TraceTreeRuleApplication { } impl TraceTreeRuleApplication { - /// Instantiate the given rule with its assignment producing a [Rule] with only ground terms. - fn to_instantiated_rule(&self) -> Rule { - let mut rule = self.rule.clone(); - self.assignment.apply(&mut rule); - - rule - } - /// Get the [Fact] that was produced by this rule application. fn to_derived_atom(&self) -> Fact { - let rule = self.to_instantiated_rule(); - let derived_atom = rule.head()[self._position].clone(); - Fact::from(derived_atom) + let mut fact = self.rule.head()[self._position].clone(); + self.assignment.apply(&mut fact); + + Fact::from(fact) } /// Get a string representation of the Instantiated rule. fn to_instantiated_string(&self) -> String { - self.to_instantiated_rule().to_string() + self.rule.display_instantiated(&self.assignment) } } diff --git a/nemo/src/rule_model/components/rule.rs b/nemo/src/rule_model/components/rule.rs index 30b4051e..6dd67a86 100644 --- a/nemo/src/rule_model/components/rule.rs +++ b/nemo/src/rule_model/components/rule.rs @@ -2,6 +2,8 @@ use std::{collections::HashSet, fmt::Display, hash::Hash}; +use nemo_physical::datavalues::DataValue; + use crate::{ parse_component, parser::ast::ProgramAST, @@ -11,6 +13,7 @@ use crate::{ ValidationErrorBuilder, }, origin::Origin, + substitution::Substitution, translation::ASTProgramTranslation, }, }; @@ -39,6 +42,8 @@ pub struct Rule { /// Name of the rule name: Option, + /// How an instantiated version of this rule should be displayed + display: Option, /// Head of the rule head: Vec, @@ -57,6 +62,7 @@ impl Rule { Self { origin: Origin::Created, name: None, + display: None, head, body, } @@ -68,6 +74,38 @@ impl Rule { self } + /// Set how an instantiated version of the rule should be displayed. + pub fn set_display(mut self, display: Term) -> Self { + self.display = Some(display); + self + } + + /// Return a string representation of the rule instantiated with the given [Substitution]. + /// This will either return + /// * The content of the display attribute for this rule + /// * a canonical string representation of the rule (i.e. [Display] representation) + /// whichever is the first defined in this list. + pub fn display_instantiated(&self, substitution: &Substitution) -> String { + if let Some(mut display) = self.display.clone() { + substitution.apply(&mut display); + if let Term::Primitive(Primitive::Ground(ground)) = display.reduce() { + if let Some(result) = ground.value().to_plain_string() { + return result; + } + } + } + + let mut rule_name_prefix = String::from(""); + if let Some(name) = &self.name { + rule_name_prefix = format!("{}: ", name.clone()); + } + + let mut rule_instantiated = self.clone(); + substitution.apply(&mut rule_instantiated); + + format!("{}{}", rule_name_prefix, rule_instantiated) + } + /// Return a reference to the body of the rule. pub fn body(&self) -> &Vec { &self.body @@ -352,6 +390,22 @@ impl ProgramComponent for Rule { .flat_map(|atom| atom.variables()) .any(|variable| variable.is_existential()); + if let Some(display) = &self.display { + display.validate(builder)?; + + for variable in display.variables() { + if !safe_variables.contains(variable) { + builder.report_error( + *variable.origin(), + ValidationErrorKind::AttributeRuleUnsafe { + variable: variable.to_string(), + }, + ); + return None; + } + } + } + for atom in self.head() { atom.validate(builder)?; @@ -520,6 +574,8 @@ pub struct RuleBuilder { /// Name of the rule name: Option, + /// How an instantiated version of this rule should be displayed + display: Option, /// Head of the rule head: Vec, @@ -529,11 +585,17 @@ pub struct RuleBuilder { impl RuleBuilder { /// Set the name of the built rule. - pub fn name(mut self, name: &str) -> Self { + pub fn name_mut(&mut self, name: &str) -> &mut Self { self.name = Some(name.to_string()); self } + /// Set the display property of the built rule. + pub fn display_mut(&mut self, display: Term) -> &mut Self { + self.display = Some(display); + self + } + /// Set the [Origin] of the built rule. pub fn origin(mut self, origin: Origin) -> Self { self.origin = origin; @@ -602,11 +664,16 @@ impl RuleBuilder { /// Finish building and return a [Rule]. pub fn finalize(self) -> Rule { - let rule = Rule::new(self.head, self.body).set_origin(self.origin); + let mut rule = Rule::new(self.head, self.body).set_origin(self.origin); - match &self.name { - Some(name) => rule.set_name(name), - None => rule, + if let Some(name) = &self.name { + rule = rule.set_name(name); } + + if let Some(display) = self.display { + rule = rule.set_display(display); + } + + rule } } diff --git a/nemo/src/rule_model/components/term/operation.rs b/nemo/src/rule_model/components/term/operation.rs index 576c195d..21cd1527 100644 --- a/nemo/src/rule_model/components/term/operation.rs +++ b/nemo/src/rule_model/components/term/operation.rs @@ -115,9 +115,11 @@ impl Operation { &HashMap::default(), None, ); - let result = stack_program.evaluate_data(&[]).expect("term is ground"); - Term::from(GroundTerm::new(result)) + match stack_program.evaluate_data(&[]) { + Some(result) => Term::from(GroundTerm::new(result)), + None => Term::Operation(self.clone()), + } } } @@ -175,6 +177,10 @@ impl Operation { OperationKind::NumericDivision => "/", OperationKind::Equal => "=", OperationKind::Unequals => "!=", + OperationKind::NumericGreaterthan => ">", + OperationKind::NumericGreaterthaneq => ">=", + OperationKind::NumericLessthan => "<", + OperationKind::NumericLessthaneq => "<=", _ => return None, }) } diff --git a/nemo/src/rule_model/error/hint/similar.rs b/nemo/src/rule_model/error/hint/similar.rs index d9248856..571b69a4 100644 --- a/nemo/src/rule_model/error/hint/similar.rs +++ b/nemo/src/rule_model/error/hint/similar.rs @@ -4,7 +4,10 @@ use similar_string::find_best_similarity; use strum::IntoEnumIterator; -use crate::rule_model::components::term::operation::operation_kind::OperationKind; +use crate::rule_model::{ + components::term::operation::operation_kind::OperationKind, + translation::attribute::KnownAttributes, +}; use super::Hint; @@ -46,4 +49,12 @@ impl Hint { Self::similar("operation", target, options) } + + /// Checks whether a similar string exists within the known attributes + /// and returns the most similar one, if it meets the threshold. + pub fn similar_attribute(target: impl AsRef) -> Option { + let options = KnownAttributes::iter().map(|kind| kind.name()); + + Self::similar("attribute", target, options) + } } diff --git a/nemo/src/rule_model/error/translation_error.rs b/nemo/src/rule_model/error/translation_error.rs index 0f611ab8..8b635ce4 100644 --- a/nemo/src/rule_model/error/translation_error.rs +++ b/nemo/src/rule_model/error/translation_error.rs @@ -95,6 +95,30 @@ pub enum TranslationErrorKind { #[assoc(note = "rdf imports/exports must have file extension nt, nq, ttl, trig, or rdf.")] #[assoc(code = 118)] RdfUnspecifiedUnknownExtension(String), + /// Unkown attribute + #[error("unknown attribute: `{0}`")] + #[assoc(code = 119)] + AttributeUnknown(String), + /// Unexpected attribute + #[error("unexpected attribute: `{0}`")] + #[assoc(code = 120)] + AttributeUnexpected(String), + /// Attributed defined multiple times + #[error("")] + #[assoc(code = 121)] + AttributeRedefined, + /// Attribute contains wrong number of parameters + #[error("attribute expected {expected} parameters, found {found}")] + #[assoc(code = 122)] + AttributeInvalidParameterCount { expected: usize, found: usize }, + /// Invalid attribute parameter: Wrong type + #[error("attribute parameter of type {found}, expected {expected}")] + #[assoc(code = 123)] + AttributeParameterWrongType { expected: String, found: String }, + /// Invalid attribute parameter: Wrong component + #[error("attribute parameter is {found}, expected {expected}")] + #[assoc(code = 124)] + AttributeParameterWrongComponent { expected: String, found: String }, /// Unsupported: Declare statements #[error(r#"declare statements are currently unsupported"#)] diff --git a/nemo/src/rule_model/error/validation_error.rs b/nemo/src/rule_model/error/validation_error.rs index 12f6e5fb..77d5cc09 100644 --- a/nemo/src/rule_model/error/validation_error.rs +++ b/nemo/src/rule_model/error/validation_error.rs @@ -135,6 +135,10 @@ pub enum ValidationErrorKind { #[error(r#"unknown compression format `{format}`"#)] #[assoc(code = 226)] ImportExportUnknownCompression { format: String }, + /// Attribute in rule is unsafe + #[error(r#"display attribute uses unsafe variable: `{variable}`"#)] + #[assoc(code = 227)] + AttributeRuleUnsafe { variable: String }, /// Unsupported feature: Multiple aggregates in one rule #[error(r#"multiple aggregates in one rule is currently unsupported"#)] diff --git a/nemo/src/rule_model/translation.rs b/nemo/src/rule_model/translation.rs index 65394ce6..11703d60 100644 --- a/nemo/src/rule_model/translation.rs +++ b/nemo/src/rule_model/translation.rs @@ -1,5 +1,6 @@ //! This module defines [ASTProgramTranslation]. +pub(crate) mod attribute; pub(crate) mod basic; pub(crate) mod complex; pub(crate) mod directive; diff --git a/nemo/src/rule_model/translation/attribute.rs b/nemo/src/rule_model/translation/attribute.rs new file mode 100644 index 00000000..911107dc --- /dev/null +++ b/nemo/src/rule_model/translation/attribute.rs @@ -0,0 +1,146 @@ +//! This module contains functions +//! for processing [Attribute][crate::parser::ast::attribute::Attribute]s. + +use std::collections::HashMap; + +use enum_assoc::Assoc; +use strum_macros::EnumIter; + +use crate::{ + parser::{ + ast::{self, ProgramAST}, + span::Span, + }, + rule_model::{ + components::{ + term::{value_type::ValueType, Term}, + ProgramComponent, ProgramComponentKind, + }, + error::{ + hint::Hint, info::Info, translation_error::TranslationErrorKind, ComplexErrorLabelKind, + TranslationError, + }, + }, +}; + +use super::ASTProgramTranslation; + +/// All recognized [Attribute][crate::parser::ast::attribute::Attribute]s +#[derive(Assoc, EnumIter, Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[func(pub fn name(&self) -> &'static str)] +#[func(pub fn from_name(name: &str) -> Option)] +#[func(pub fn schema(&self) -> Vec<(Option, Option)>)] +pub(crate) enum KnownAttributes { + /// Name associated with a component + #[assoc(name = "name")] + #[assoc(from_name = "name")] + #[assoc(schema = vec![(Some(ProgramComponentKind::PlainString), None)])] + Name, + /// User-defined way of displaying the component + #[assoc(name = "display")] + #[assoc(from_name = "display")] + #[assoc(schema = vec![(None, Some(ValueType::String))])] + Display, +} + +impl<'a> ASTProgramTranslation<'a> { + /// Evaluates a list of attributes, checking for errors, + /// and returns a map from an attribute to a list of parameters. + pub(crate) fn process_attributes( + &mut self, + attributes: impl Iterator>, + expected: &[KnownAttributes], + ) -> Result>, TranslationError> { + let mut result = HashMap::new(); + let mut previous_attributes = HashMap::>::new(); + + for attribute in attributes { + let tag = attribute.content().tag(); + let name = tag.to_string(); + + if let Some(attribute_kind) = KnownAttributes::from_name(&name) { + if !expected.contains(&attribute_kind) { + return Err(TranslationError::new( + tag.span(), + TranslationErrorKind::AttributeUnexpected(name.clone()), + )); + } + + if let Some(previous_span) = previous_attributes.get(&attribute_kind) { + let error = TranslationError::new( + attribute.span(), + TranslationErrorKind::AttributeRedefined, + ) + .add_label( + ComplexErrorLabelKind::Information, + previous_span.range(), + Info::FirstDefinition, + ); + + return Err(error); + } + + let mut terms = Vec::::new(); + + let count_expected = attribute_kind.schema().len(); + let count_found = attribute.content().expressions().count(); + if count_found != count_expected { + return Err(TranslationError::new( + attribute.span(), + TranslationErrorKind::AttributeInvalidParameterCount { + expected: count_expected, + found: count_found, + }, + )); + } + + for (expression, schema) in attribute + .content() + .expressions() + .zip(attribute_kind.schema()) + { + let term = self.build_inner_term(expression)?; + + if let Some(expected_component) = schema.0 { + if term.kind() != expected_component { + return Err(TranslationError::new( + expression.span(), + TranslationErrorKind::AttributeParameterWrongComponent { + expected: expected_component.name().to_string(), + found: term.kind().name().to_string(), + }, + )); + } + } + + if let Some(expected_type) = schema.1 { + if term.value_type() != expected_type { + return Err(TranslationError::new( + expression.span(), + TranslationErrorKind::AttributeParameterWrongType { + expected: expected_type.name().to_string(), + found: term.value_type().name().to_string(), + }, + )); + } + } + + terms.push(term); + } + + result.insert(attribute_kind, terms); + previous_attributes.insert(attribute_kind, attribute.span()); + } else { + let mut error = TranslationError::new( + tag.span(), + TranslationErrorKind::AttributeUnknown(name.clone()), + ); + error.add_hint_option(Hint::similar_attribute(&name)); + + return Err(error); + } + } + + Ok(result) + } +} diff --git a/nemo/src/rule_model/translation/rule.rs b/nemo/src/rule_model/translation/rule.rs index 70482014..da60d9a2 100644 --- a/nemo/src/rule_model/translation/rule.rs +++ b/nemo/src/rule_model/translation/rule.rs @@ -1,5 +1,7 @@ //! This module contains functions for creating a [Rule] from the corresponding ast node. +use nemo_physical::datavalues::DataValue; + use crate::{ parser::ast::{self, ProgramAST}, rule_model::{ @@ -8,14 +10,14 @@ use crate::{ literal::Literal, rule::{Rule, RuleBuilder}, tag::Tag, - term::Term, + term::{primitive::Primitive, Term}, ProgramComponent, }, error::{translation_error::TranslationErrorKind, TranslationError}, }, }; -use super::ASTProgramTranslation; +use super::{attribute::KnownAttributes, ASTProgramTranslation}; impl<'a> ASTProgramTranslation<'a> { /// Create a [Rule] from the corresponding AST node. @@ -25,6 +27,21 @@ impl<'a> ASTProgramTranslation<'a> { ) -> Result { let mut rule_builder = RuleBuilder::default().origin(self.register_node(rule)); + let expected_attributes = vec![KnownAttributes::Name, KnownAttributes::Display]; + let attributes = self.process_attributes(rule.attributes(), &expected_attributes)?; + + if let Some(rule_name) = attributes.get(&KnownAttributes::Name) { + if let Term::Primitive(Primitive::Ground(ground)) = &rule_name[0] { + if let Some(name) = ground.value().to_plain_string() { + rule_builder.name_mut(&name); + } + } + } + + if let Some(rule_display) = attributes.get(&KnownAttributes::Display) { + rule_builder.display_mut(rule_display[0].clone()); + } + for expression in rule.head() { rule_builder.add_head_atom_mut(self.build_head_atom(expression)?); }