use crate::edb::{AttributeIndex, Constant, Predicate, Relation};
use crate::error::{
anonymous_variable_not_allowed, comparison_is_always_false, comparison_is_always_true,
fact_does_not_correspond_to_schema, head_variables_missing_in_body, incompatible_types,
invalid_head_atom_count, invalid_value, negative_variables_not_also_positive, Error, Result,
SourceLocation,
};
use crate::features::{FEATURE_CONSTRAINTS, FEATURE_DISJUNCTION};
use crate::syntax::{
ANONYMOUS_TERM, CHAR_LEFT_PAREN, CHAR_PERIOD, CHAR_RIGHT_PAREN, CHAR_UNDERSCORE,
CONJUNCTION_UNICODE_SYMBOL, DISJUNCTION_UNICODE_SYMBOL, EMPTY_STR, FALSE_UNICODE_SYMBOL,
IMPLICATION_UNICODE_ARROW, NEGATION_UNICODE_SYMBOL, OPERATOR_EQUAL_ASCII,
OPERATOR_GREATER_THAN_ASCII, OPERATOR_GREATER_THAN_OR_EQUAL_ASCII,
OPERATOR_GREATER_THAN_OR_EQUAL_UNICODE, OPERATOR_LESS_THAN_ASCII,
OPERATOR_LESS_THAN_OR_EQUAL_ASCII, OPERATOR_LESS_THAN_OR_EQUAL_UNICODE,
OPERATOR_NOT_EQUAL_ASCII, OPERATOR_NOT_EQUAL_ASCII_ALT, OPERATOR_NOT_EQUAL_UNICODE,
OPERATOR_STRING_MATCH_ASCII, OPERATOR_STRING_MATCH_ASCII_WORD, OPERATOR_STRING_MATCH_UNICODE,
TYPE_NAME_COMPARISON_OPERATOR, TYPE_NAME_VARIABLE,
};
use crate::{
AttributeName, Collection, FeatureSet, IndexedCollection, Labeled, MaybeAnonymous,
MaybePositive, PredicateRef,
};
use paste::paste;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::hash::Hash;
use std::rc::Rc;
use std::str::FromStr;
pub trait MaybeGround {
fn is_ground(&self) -> bool;
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RuleSet(HashSet<Rule>);
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Rule {
head: Vec<Atom>,
body: Vec<Literal>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RuleForm {
Pure,
Constraint,
Disjunctive,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Atom {
label: PredicateRef,
terms: Vec<Term>,
src_loc: Option<SourceLocation>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Literal {
negative: bool,
inner: LiteralInner,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LiteralInner {
Relational(Atom),
Arithmetic(Comparison),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Comparison {
lhs: Term,
operator: ComparisonOperator,
rhs: Term,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ComparisonOperator {
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
StringMatch,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Term {
Anonymous,
Variable(VariableRef),
Constant(Constant),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Variable(String);
pub type VariableRef = Rc<Variable>;
impl Display for RuleSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for rule in self.iter() {
writeln!(f, "{}", rule)?;
}
Ok(())
}
}
impl Collection<Rule> for RuleSet {
delegate!(is_empty -> bool);
delegate!(len -> usize);
fn iter(&self) -> Box<dyn Iterator<Item = &'_ Rule> + '_> {
Box::new(self.0.iter())
}
fn contains(&self, value: &Rule) -> bool {
self.0.contains(value)
}
}
impl RuleSet {
pub fn add(&mut self, rule: Rule) -> bool {
self.0.insert(rule)
}
}
impl Display for Rule {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}",
if self.head.is_empty() {
FALSE_UNICODE_SYMBOL.to_string()
} else if self.head.len() == 1 {
self.head.get(0).unwrap().to_string()
} else {
self.head
.iter()
.map(|atom| atom.to_string())
.collect::<Vec<String>>()
.join(&format!(" {} ", DISJUNCTION_UNICODE_SYMBOL))
},
if self.body.is_empty() {
unreachable!("Rule body is empty!")
} else {
format!(
" {} {}",
IMPLICATION_UNICODE_ARROW,
self.body
.iter()
.map(Literal::to_string)
.collect::<Vec<String>>()
.join(&format!(" {} ", CONJUNCTION_UNICODE_SYMBOL))
)
},
CHAR_PERIOD,
)
}
}
impl MaybeGround for Rule {
fn is_ground(&self) -> bool {
self.head().all(|atom| atom.is_ground()) && self.literals().all(|lit| lit.is_ground())
}
}
impl MaybePositive for Rule {
fn is_positive(&self) -> bool {
self.body.iter().all(Literal::is_positive)
}
}
impl Rule {
pub fn new<A: Into<Vec<Atom>>, B: Into<Vec<Literal>>>(head: A, body: B) -> Self {
let body = body.into();
assert!(!body.is_empty());
Self {
head: head.into(),
body,
}
}
pub fn new_pure<B: Into<Vec<Literal>>>(head: Atom, body: B) -> Self {
Self::new(vec![head], body)
}
pub fn new_constraint<B: Into<Vec<Literal>>>(body: B) -> Self {
Self::new(Vec::default(), body)
}
pub fn new_disjunctive<A: Into<Vec<Atom>>, B: Into<Vec<Literal>>>(head: A, body: B) -> Self {
let head = head.into();
assert!(head.len() > 1);
Self::new(head, body)
}
pub fn form(&self) -> RuleForm {
match self.head.len() {
0 => RuleForm::Constraint,
1 => RuleForm::Pure,
_ => RuleForm::Disjunctive,
}
}
pub fn head(&self) -> impl Iterator<Item = &Atom> {
self.head.iter()
}
pub fn has_body(&self) -> bool {
!self.body.is_empty()
}
pub fn add<L: Into<Literal>>(&mut self, literal: L) -> &mut Self {
self.body.push(literal.into());
self
}
pub fn extend<V: Into<Vec<Literal>>>(&mut self, literals: V) -> &mut Self {
self.body.append(&mut literals.into());
self
}
pub fn literals(&self) -> impl Iterator<Item = &Literal> {
self.body.iter()
}
pub fn positive_literals(&self) -> impl Iterator<Item = &Literal> {
self.body.iter().filter(|lit| lit.is_positive())
}
pub fn negative_literals(&self) -> impl Iterator<Item = &Literal> {
self.body.iter().filter(|lit| !lit.is_positive())
}
pub fn distinguished_terms(&self) -> HashSet<&Term> {
self.head().map(|atom| atom.iter()).flatten().collect()
}
pub fn distinguished_terms_in_order(&self) -> Vec<&Term> {
let terms: Vec<&Term> = self.head().map(|atom| atom.iter()).flatten().collect();
dedup_in_place(terms)
}
pub fn non_distinguished_terms(&self) -> HashSet<&Term> {
let distinguished = self.distinguished_terms();
self.terms()
.into_iter()
.filter(|term| !distinguished.contains(term))
.collect()
}
pub fn terms(&self) -> HashSet<&Term> {
self.body
.iter()
.map(|lit| lit.terms())
.flatten()
.collect::<HashSet<&Term>>()
.union(&self.distinguished_terms())
.copied()
.collect()
}
pub fn positive_terms(&self) -> HashSet<&Term> {
self.body
.iter()
.filter(|lit| lit.is_positive())
.map(|lit| lit.terms())
.flatten()
.collect()
}
pub fn negative_terms(&self) -> HashSet<&Term> {
self.body
.iter()
.filter(|lit| !lit.is_positive())
.map(|lit| lit.terms())
.flatten()
.collect()
}
pub fn head_variables(&self) -> HashSet<&VariableRef> {
self.head().map(|atom| atom.variables()).flatten().collect()
}
pub fn variables(&self) -> HashSet<&VariableRef> {
self.body
.iter()
.map(|lit| lit.variables())
.flatten()
.collect()
}
pub fn positive_variables(&self) -> HashSet<&VariableRef> {
self.body
.iter()
.filter(|lit| lit.is_positive())
.map(|lit| lit.variables())
.flatten()
.collect()
}
pub fn negative_variables(&self) -> HashSet<&VariableRef> {
self.body
.iter()
.filter(|lit| !lit.is_positive())
.map(|lit| lit.variables())
.flatten()
.collect()
}
pub fn is_guarded(&self) -> bool {
let all_variables = self.variables();
self.literals().any(|lit| {
let lit_variables: HashSet<&VariableRef> =
HashSet::from_iter(lit.variables().into_iter());
lit_variables == all_variables
})
}
pub fn is_frontier_guarded(&self) -> bool {
let frontier_variables: HashSet<&VariableRef> = self
.head_variables()
.intersection(&self.variables())
.copied()
.collect();
self.literals().any(|lit| {
let lit_variables: HashSet<&VariableRef> =
HashSet::from_iter(lit.variables().into_iter());
lit_variables == frontier_variables
})
}
pub fn safety_check(&self, features: &FeatureSet) -> Result<()> {
let (min, max) = if features.supports(&FEATURE_DISJUNCTION) {
(1, usize::MAX)
} else if features.supports(&FEATURE_CONSTRAINTS) {
(0, usize::MAX)
} else {
(1, 1)
};
let head_len = self.head.len();
if head_len < min || head_len > max {
println!("HC: {:#?}", self);
return Err(invalid_head_atom_count(
head_len,
min,
max,
match self.head.get(0) {
None => None,
Some(atom) => atom.source_location().cloned(),
},
));
}
let body_positive_terms = self.positive_terms();
for atom in self.head() {
let missing: Vec<&Term> = atom
.iter()
.into_iter()
.filter(|term| {
if term.is_variable() {
!body_positive_terms.contains(term)
} else {
false
}
})
.collect();
if !missing.is_empty() {
return Err(head_variables_missing_in_body(
atom.label_ref(),
missing
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>(),
atom.source_location().cloned(),
));
}
let missing: Vec<&Term> = self
.negative_terms()
.into_iter()
.filter(|term| {
if term.is_variable() {
!body_positive_terms.contains(term)
} else {
false
}
})
.collect();
if !missing.is_empty() {
return Err(negative_variables_not_also_positive(
atom.label_ref(),
missing
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>(),
atom.source_location().cloned(),
));
}
}
Ok(())
}
}
impl Display for Atom {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}{}",
self.label,
CHAR_LEFT_PAREN,
if self.terms.is_empty() {
unreachable!("Atom terms are empty")
} else {
self.terms
.iter()
.map(Term::to_string)
.collect::<Vec<String>>()
.join(", ")
},
CHAR_RIGHT_PAREN,
)
}
}
impl Labeled for Atom {
get!(label -> Predicate);
get_cloned!(label_ref, label -> PredicateRef);
}
impl MaybeGround for Atom {
fn is_ground(&self) -> bool {
self.terms.iter().all(Term::is_constant)
}
}
impl Collection<Term> for Atom {
delegate!(is_empty, terms -> bool);
delegate!(len, terms -> usize);
fn iter(&self) -> Box<dyn Iterator<Item = &'_ Term> + '_> {
Box::new(self.terms.iter())
}
fn contains(&self, value: &Term) -> bool {
self.terms.contains(value)
}
}
impl Atom {
pub fn new<T: Into<Vec<Term>>>(label: PredicateRef, terms: T) -> Self {
let terms = terms.into();
assert!(!terms.is_empty());
Self {
label,
terms,
src_loc: None,
}
}
pub fn new_from<T: Into<Vec<Term>>>(relation: &Relation, terms: T) -> Result<Self> {
let terms = terms.into();
let schema = relation.schema();
assert_eq!(terms.len(), schema.len());
for (i, t) in terms.iter().enumerate() {
if let Term::Constant(c) = t {
if c.kind()
!= schema
.get(&AttributeIndex::Index(i))
.unwrap()
.kind()
.unwrap()
{
return Err(fact_does_not_correspond_to_schema(
relation.label_ref(),
terms
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", "),
));
}
}
}
Ok(Self {
label: relation.label_ref(),
terms,
src_loc: None,
})
}
pub fn new_at_location(label: PredicateRef, terms: &[Term], location: SourceLocation) -> Self {
let terms: Vec<Term> = terms.into();
assert!(!terms.is_empty());
Self {
label,
terms,
src_loc: Some(location),
}
}
pub fn add<V: Into<Term>>(&mut self, argument: V) -> &mut Self {
self.terms.push(argument.into());
self
}
pub fn extend<T: Into<Vec<Term>>>(&mut self, arguments: T) -> &mut Self {
self.terms.append(&mut arguments.into());
self
}
pub fn variables(&self) -> impl Iterator<Item = &VariableRef> {
self.terms.iter().filter_map(|t| {
if let Term::Variable(v) = t {
Some(v)
} else {
None
}
})
}
pub fn variable_index(&self, variable: &VariableRef) -> Option<usize> {
self.terms
.iter()
.enumerate()
.filter_map(|(i, term)| term.as_variable().map(|v| (i, v)))
.find(|(_, var)| var == &variable)
.map(|(i, _)| i)
}
pub fn constants(&self) -> impl Iterator<Item = &Constant> {
self.terms.iter().filter_map(|t| {
if let Term::Constant(v) = t {
Some(v)
} else {
None
}
})
}
pub fn source_location(&self) -> Option<&SourceLocation> {
self.src_loc.as_ref()
}
pub fn is_existential(&self) -> bool {
self.constants().count() == self.len()
}
pub fn is_universal(&self) -> bool {
self.variables().count() == self.len()
}
}
impl Display for Literal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}",
if self.negative {
NEGATION_UNICODE_SYMBOL
} else {
EMPTY_STR
},
self.inner,
)
}
}
impl From<Atom> for Literal {
fn from(v: Atom) -> Self {
Self::relational(v)
}
}
impl From<Comparison> for Literal {
fn from(v: Comparison) -> Self {
Self::arithmetic(v)
}
}
impl MaybeGround for Literal {
fn is_ground(&self) -> bool {
self.inner.is_ground()
}
}
impl MaybePositive for Literal {
fn is_positive(&self) -> bool {
!self.negative
}
}
impl AsRef<LiteralInner> for Literal {
fn as_ref(&self) -> &LiteralInner {
&self.inner
}
}
impl Literal {
pub fn new(negative: bool, inner: LiteralInner) -> Self {
Self { negative, inner }
}
pub fn relational(atom: Atom) -> Self {
Self::new(false, atom.into())
}
pub fn negative_relational(atom: Atom) -> Self {
Self::new(true, atom.into())
}
pub fn arithmetic(comparison: Comparison) -> Self {
Self::new(false, comparison.into())
}
pub fn negative_arithmetic(comparison: Comparison) -> Self {
Self::new(true, comparison.into())
}
delegate!(pub is_relational, inner -> bool);
delegate!(pub as_relational, inner -> Option<&Atom>);
delegate!(pub is_arithmetic, inner -> bool);
delegate!(pub as_arithmetic, inner -> Option<&Comparison>);
pub fn terms(&self) -> Vec<&Term> {
match &self.inner {
LiteralInner::Relational(v) => v.iter().collect(),
LiteralInner::Arithmetic(v) => v.terms(),
}
}
pub fn variables(&self) -> Vec<&VariableRef> {
self.terms()
.iter()
.filter_map(|t| {
if let Term::Variable(v) = t {
Some(v)
} else {
None
}
})
.collect()
}
into_inner_fn!(LiteralInner, inner);
}
impl Display for LiteralInner {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Relational(v) => v.to_string(),
Self::Arithmetic(v) => v.to_string(),
}
)
}
}
impl_enum_from!(LiteralInner, Atom, Relational);
impl_enum_from!(LiteralInner, Comparison, Arithmetic);
impl MaybeGround for LiteralInner {
fn is_ground(&self) -> bool {
match self {
LiteralInner::Relational(a) => a.is_ground(),
LiteralInner::Arithmetic(c) => c.is_ground(),
}
}
}
impl LiteralInner {
self_is_as!(relational, Relational, Atom);
self_is_as!(arithmetic, Arithmetic, Comparison);
}
impl Display for Comparison {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.lhs, self.operator, self.rhs,)
}
}
impl MaybeGround for Comparison {
fn is_ground(&self) -> bool {
self.lhs.is_constant() && self.rhs.is_constant()
}
}
impl Comparison {
pub fn new<L: Into<Term>, R: Into<Term>>(
lhs: L,
operator: ComparisonOperator,
rhs: R,
) -> Result<Self> {
let new_self = Self::new_unchecked(lhs, operator, rhs);
new_self.sanity_check()?;
Ok(new_self)
}
pub fn new_unchecked<L: Into<Term>, R: Into<Term>>(
lhs: L,
operator: ComparisonOperator,
rhs: R,
) -> Self {
Self {
lhs: lhs.into(),
operator,
rhs: rhs.into(),
}
}
pub fn sanity_check(&self) -> Result<()> {
match (
&self.lhs,
self.operator == ComparisonOperator::Equal,
&self.rhs,
) {
(Term::Anonymous, _, _) | (_, _, Term::Anonymous) => {
Err(anonymous_variable_not_allowed())
}
(Term::Constant(lhs), true, Term::Constant(rhs)) => {
if lhs.kind() != rhs.kind() {
Err(incompatible_types(
lhs.kind().to_string(),
rhs.kind().to_string(),
))
} else if lhs == rhs {
Err(comparison_is_always_true(self.to_string()))
} else {
Err(comparison_is_always_false(self.to_string()))
}
}
(Term::Constant(lhs), false, Term::Constant(rhs)) => {
if lhs.kind() != rhs.kind() {
Err(incompatible_types(
lhs.kind().to_string(),
rhs.kind().to_string(),
))
} else if lhs == rhs {
Err(comparison_is_always_false(self.to_string()))
} else {
Err(comparison_is_always_true(self.to_string()))
}
}
(Term::Variable(lhs), true, Term::Variable(rhs)) => {
if lhs == rhs {
Err(comparison_is_always_true(self.to_string()))
} else {
Ok(())
}
}
(Term::Variable(lhs), false, Term::Variable(rhs)) => {
if lhs == rhs {
Err(comparison_is_always_false(self.to_string()))
} else {
Ok(())
}
}
_ => Ok(()),
}
}
pub fn eq<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::Equal, right)
}
pub fn ne<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::NotEqual, right)
}
pub fn lt<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::LessThan, right)
}
pub fn lte<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::LessThanOrEqual, right)
}
pub fn gt<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::GreaterThan, right)
}
pub fn gte<L: Into<Term>, R: Into<Term>>(left: L, right: R) -> Result<Self> {
Self::new(left, ComparisonOperator::GreaterThanOrEqual, right)
}
get!(pub lhs -> Term);
get!(pub rhs -> Term);
get!(pub operator -> ComparisonOperator);
pub fn terms(&self) -> Vec<&Term> {
vec![&self.lhs, &self.rhs]
}
}
impl Display for ComparisonOperator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Equal => OPERATOR_EQUAL_ASCII,
Self::NotEqual => OPERATOR_NOT_EQUAL_ASCII,
Self::LessThan => OPERATOR_LESS_THAN_ASCII,
Self::LessThanOrEqual => OPERATOR_LESS_THAN_OR_EQUAL_ASCII,
Self::GreaterThan => OPERATOR_GREATER_THAN_ASCII,
Self::GreaterThanOrEqual => OPERATOR_GREATER_THAN_OR_EQUAL_ASCII,
Self::StringMatch => OPERATOR_STRING_MATCH_ASCII,
}
)
}
}
impl FromStr for ComparisonOperator {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
OPERATOR_EQUAL_ASCII => Ok(Self::Equal),
OPERATOR_NOT_EQUAL_ASCII
| OPERATOR_NOT_EQUAL_ASCII_ALT
| OPERATOR_NOT_EQUAL_UNICODE => Ok(Self::NotEqual),
OPERATOR_LESS_THAN_ASCII => Ok(Self::LessThan),
OPERATOR_LESS_THAN_OR_EQUAL_ASCII | OPERATOR_LESS_THAN_OR_EQUAL_UNICODE => {
Ok(Self::LessThanOrEqual)
}
OPERATOR_GREATER_THAN_ASCII => Ok(Self::GreaterThan),
OPERATOR_GREATER_THAN_OR_EQUAL_ASCII | OPERATOR_GREATER_THAN_OR_EQUAL_UNICODE => {
Ok(Self::GreaterThanOrEqual)
}
OPERATOR_STRING_MATCH_ASCII
| OPERATOR_STRING_MATCH_UNICODE
| OPERATOR_STRING_MATCH_ASCII_WORD => Ok(Self::StringMatch),
_ => Err(invalid_value(TYPE_NAME_COMPARISON_OPERATOR, s)),
}
}
}
impl ComparisonOperator {
pub fn inverse(&self) -> Self {
match self {
Self::Equal => Self::Equal,
Self::NotEqual => Self::NotEqual,
Self::LessThan => Self::GreaterThanOrEqual,
Self::LessThanOrEqual => Self::GreaterThan,
Self::GreaterThan => Self::LessThanOrEqual,
Self::GreaterThanOrEqual => Self::LessThan,
Self::StringMatch => Self::StringMatch,
}
}
}
impl Display for Term {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Term::Anonymous => ANONYMOUS_TERM.to_string(),
Term::Variable(v) => v.to_string(),
Term::Constant(v) => v.to_string(),
}
)
}
}
impl_enum_from!(Term, VariableRef, Variable);
impl_enum_from!(Term, Constant, Constant);
impl From<&str> for Term {
fn from(s: &str) -> Self {
Constant::from(s).into()
}
}
impl From<String> for Term {
fn from(v: String) -> Self {
Constant::from(v).into()
}
}
impl From<i64> for Term {
fn from(v: i64) -> Self {
Constant::from(v).into()
}
}
impl From<bool> for Term {
fn from(v: bool) -> Self {
Constant::from(v).into()
}
}
impl MaybeAnonymous for Term {
fn anonymous() -> Self {
Self::Anonymous
}
fn is_anonymous(&self) -> bool {
matches!(self, Self::Anonymous)
}
}
impl Term {
self_is_as!(variable, Variable, VariableRef);
self_is_as!(constant, Constant);
}
impl Display for Variable {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Variable {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if Self::is_valid(s) {
Ok(Self(s.to_owned()))
} else {
invalid_value(TYPE_NAME_VARIABLE, s).into()
}
}
}
impl AsRef<str> for Variable {
fn as_ref(&self) -> &str {
&self.0
}
}
impl AttributeName for Variable {
fn is_valid(s: &str) -> bool {
let mut chars = s.chars();
s != ANONYMOUS_TERM
&& !s.is_empty()
&& chars.next().map(|c| c.is_uppercase()).unwrap()
&& chars.all(|c| c.is_alphanumeric() || c == CHAR_UNDERSCORE)
}
fn type_name() -> &'static str {
TYPE_NAME_VARIABLE
}
}
impl Variable {
into_inner_fn!(pub String);
}
fn dedup_in_place<T: Clone + Eq + Hash, V: Into<Vec<T>>>(v: V) -> Vec<T> {
let v = v.into();
if v.len() <= 1 {
v
} else {
let mut vs: HashSet<T> = Default::default();
let mut v2: Vec<T> = Default::default();
for a in v.into_iter() {
if vs.insert(a.clone()) {
v2.push(a)
}
}
v2
}
}
pub mod eval;
pub mod query;