upend/src/database/lang.rs

472 lines
17 KiB
Rust

use crate::addressing::Address;
use crate::database::entry::EntryValue;
use nonempty::NonEmpty;
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute(pub String);
impl From<&str> for Attribute {
fn from(str: &str) -> Self {
Self(str.to_string())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryComponent<T>
where
T: TryFrom<lexpr::Value>,
{
Exact(T),
In(Vec<T>),
Contains(String),
Variable(Option<String>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct PatternQuery {
pub entity: QueryComponent<Address>,
pub attribute: QueryComponent<Attribute>,
pub value: QueryComponent<EntryValue>,
}
impl TryFrom<lexpr::Value> for Address {
type Error = QueryParseError;
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
match value {
lexpr::Value::Symbol(str) => {
if let Some(address_str) = str.strip_prefix('@') {
address_str
.parse()
.map_err(|e: anyhow::Error| QueryParseError(e.to_string()))
} else {
Err(QueryParseError(
"Incorrect address format (use @address).".into(),
))
}
}
_ => Err(QueryParseError(
"Incorrect type for address (use @address).".into(),
)),
}
}
}
impl TryFrom<lexpr::Value> for Attribute {
type Error = QueryParseError;
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
match value {
lexpr::Value::String(str) => Ok(Attribute(str.to_string())),
_ => Err(QueryParseError(
"Can only convert to attribute from string.".into(),
)),
}
}
}
impl TryFrom<lexpr::Value> for EntryValue {
type Error = QueryParseError;
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
match value {
lexpr::Value::Number(num) => {
Ok(EntryValue::Number(num.as_f64().ok_or_else(|| {
QueryParseError(format!("Error processing number ({num:?})."))
})?))
}
lexpr::Value::Char(chr) => Ok(EntryValue::String(chr.to_string())),
lexpr::Value::String(str) => Ok(EntryValue::String(str.to_string())),
lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)),
_ => Err(QueryParseError(
"Value can only be a string, number or address.".into(),
)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryPart {
Matches(PatternQuery),
Type(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryQualifier {
And,
Or,
Not,
Join,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MultiQuery {
pub qualifier: QueryQualifier,
pub queries: NonEmpty<Box<Query>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Query {
SingleQuery(QueryPart),
MultiQuery(MultiQuery),
}
#[derive(Debug, Clone)]
pub struct QueryParseError(String);
impl std::fmt::Display for QueryParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for QueryParseError {}
impl TryFrom<&lexpr::Value> for Query {
type Error = QueryParseError;
fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
fn parse_component<T: TryFrom<lexpr::Value>>(
value: &lexpr::Value,
) -> Result<QueryComponent<T>, QueryParseError>
where
QueryParseError: From<<T as TryFrom<lexpr::Value>>::Error>,
{
match value {
lexpr::Value::Cons(cons) => {
if let lexpr::Value::Symbol(symbol) = cons.car() {
match symbol.borrow() {
"in" => {
let (cons_vec, _) = cons.clone().into_vec();
if let Some(split) = cons_vec.split_first() {
let args = split.1;
let values: Result<Vec<T>, _> = args
.iter()
.map(|value| T::try_from(value.clone()))
.collect();
Ok(QueryComponent::In(values?))
} else {
Err(QueryParseError(
"Malformed expression: Inner value cannot be empty.".into(),
))
}
}
"contains" => {
let (mut cons_vec, _) = cons.clone().into_vec();
match cons_vec.len() {
2 => {
let value = cons_vec.remove(1);
if let lexpr::Value::String(str) = value {
Ok(QueryComponent::Contains(str.into_string()))
} else {
Err(QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
}
}
_ => Err(QueryParseError(
"Malformed expression: 'Contains' requires a single argument.".into()
)),
}
}
_ => Err(QueryParseError(format!(
"Malformed expression: Unknown symbol {}",
symbol
))),
}
} else {
Err(QueryParseError(format!(
"Malformed expression: Inner value '{:?}' is not a symbol.",
value
)))
}
}
lexpr::Value::Symbol(symbol) if symbol.starts_with('?') => {
let var_name = symbol.strip_prefix('?').unwrap();
Ok(QueryComponent::Variable(if var_name.is_empty() {
None
} else {
Some(var_name.into())
}))
}
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
}
}
if let lexpr::Value::Cons(value) = expression {
if let lexpr::Value::Symbol(symbol) = value.car() {
match symbol.borrow() {
"matches" => {
let (cons_vec, _) = value.clone().into_vec();
if let [_, entity, attribute, value] = &cons_vec[..] {
let entity = parse_component::<Address>(entity)?;
let attribute = parse_component::<Attribute>(attribute)?;
let value = parse_component::<EntryValue>(value)?;
Ok(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity,
attribute,
value,
})))
} else {
Err(QueryParseError(
"Malformed expression: Wrong number of arguments to 'matches'."
.into(),
))
}
}
"type" => {
let (cons_vec, _) = value.clone().into_vec();
if let [_, type_name] = &cons_vec[..] {
if let lexpr::Value::String(type_name_str) = type_name {
Ok(Query::SingleQuery(QueryPart::Type(
type_name_str.to_string(),
)))
} else {
Err(QueryParseError(
"Malformed expression: Type must be specified as a string."
.into(),
))
}
} else {
Err(QueryParseError(
"Malformed expression: Wrong number of arguments to 'type'.".into(),
))
}
}
"and" | "or" | "join" => {
let (cons_vec, _) = value.clone().into_vec();
let sub_expressions = &cons_vec[1..];
let values = sub_expressions
.iter()
.map(|value| Ok(Box::new(Query::try_from(value)?)))
.collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;
if let Some(queries) = NonEmpty::from_vec(values) {
Ok(Query::MultiQuery(MultiQuery {
qualifier: match symbol.borrow() {
"and" => QueryQualifier::And,
"join" => QueryQualifier::Join,
_ => QueryQualifier::Or,
},
queries,
}))
} else {
Err(QueryParseError(
"Malformed expression: sub-query list cannot be empty.".into(),
))
}
}
"not" => {
let (cons_vec, _) = value.clone().into_vec();
let sub_expressions = &cons_vec[1..];
let values = sub_expressions
.iter()
.map(|value| Ok(Box::new(Query::try_from(value)?)))
.collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;
if values.len() == 1 {
Ok(Query::MultiQuery(MultiQuery {
qualifier: QueryQualifier::Not,
queries: NonEmpty::from_vec(values).unwrap(),
}))
} else {
Err(QueryParseError(
"Malformed expression: NOT takes exactly one parameter.".into(),
))
}
}
_ => Err(QueryParseError(format!(
"Malformed expression: Unknown symbol '{}'.",
symbol
))),
}
} else {
Err(QueryParseError(format!(
"Malformed expression: Value '{:?}' is not a symbol.",
value
)))
}
} else {
Err(QueryParseError("Malformed expression: Not a list.".into()))
}
}
}
impl FromStr for Query {
type Err = QueryParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())
.map_err(|e| QueryParseError(format!("failed to parse s-expression: {e}")))?;
Query::try_from(&sexp)
}
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
use url::Url;
#[test]
fn test_matches() -> Result<()> {
let query = "(matches ? ? ?)".parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
}))
);
let address = Address::Url(Url::parse("https://upend.dev").unwrap());
let query = format!("(matches @{address} ? ?)").parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Exact(address),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
}))
);
let query = r#"(matches ? "FOO" ?)"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact("FOO".into()),
value: QueryComponent::Variable(None)
}))
);
let value = EntryValue::Number(1337.93);
let query = "(matches ? ? 1337.93)".parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Exact(value)
}))
);
Ok(())
}
#[test]
fn test_joins() -> Result<()> {
let query = "(matches ?a ?b ?)".parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(Some("a".into())),
attribute: QueryComponent::Variable(Some("b".into())),
value: QueryComponent::Variable(None)
}))
);
Ok(())
}
#[test]
fn test_in_parse() -> Result<()> {
let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
value: QueryComponent::Variable(None)
}))
);
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
}))
);
let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)];
let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
}))
);
// Invalid queries
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
}))
);
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
}))
);
Ok(())
}
#[test]
fn test_contains() -> Result<()> {
let query = r#"(matches (contains "foo") ? ?)"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Contains("foo".to_string()),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
}))
);
let query = r#"(matches ? (contains "foo") ?)"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Contains("foo".to_string()),
value: QueryComponent::Variable(None),
}))
);
let query = r#"(matches ? ? (contains "foo"))"#.parse::<Query>()?;
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Contains("foo".to_string())
}))
);
Ok(())
}
}