#![allow(clippy::redundant_closure_call)]
use peg::{error::ParseError, str::LineCol};
pub mod ast;
#[cfg(test)]
mod tests;
peg::parser! {
grammar cypher() for str {
use ast::*;
rule kw_match() = "MATCH"
rule kw_create() = "CREATE"
rule kw_set() = "SET"
rule kw_delete() = "DELETE"
rule kw_where() = "WHERE"
rule kw_return() = "RETURN"
rule kw_true() = "TRUE"
rule kw_false() = "FALSE"
rule kw_null() = "NULL"
rule kw_and() = "AND"
rule kw_or() = "OR"
rule kw_not() = "NOT"
rule kw_id() = "ID"
rule kw_label() = "LABEL"
rule _()
= [' ']
rule __()
= [' ' | '\n' | '\t']
rule alpha()
= ['a'..='z' | 'A'..='Z']
rule num()
= ['0'..='9']
rule alpha_num()
= ['a'..='z' | 'A'..='Z' | '0'..='9' | '_']
rule integer() -> i64
= integer:$("-"?num()+) {? integer.parse().or(Err("invalid integer")) }
rule real() -> f64
= real:$("-"? num()+ "." num()+) {? real.parse().or(Err("invalid real"))}
rule boolean() -> bool
= kw_true() { true } / kw_false() { false }
rule text() -> &'input str
= "'" text:$([^ '\'' | '\n' | '\r']*) "'" { text }
rule literal() -> Literal<'input>
= r:real() { Literal::Real(r) }
/ i:integer() { Literal::Integer(i) }
/ b:boolean() { Literal::Boolean(b) }
/ t:text() { Literal::Text(t) }
/ kw_null() { Literal::Null }
rule expression() -> Expression<'input>
= "$" name:ident() { Expression::Parameter(name) }
/ l:literal() { Expression::Literal(l) }
/ kw_id() _* "(" _* n:ident() _* ")" { Expression::id_of(n) }
/ kw_label() _* "(" _* n:ident() _* ")" { Expression::label_of(n) }
/ p:property() { Expression::property(p.0, p.1) }
rule ident() -> &'input str
= ident:$(alpha()alpha_num()*) { ident }
rule annotation() -> Annotation<'input>
= name:ident()? label:( _* ":" _* k:ident() { k } )? { Annotation { name, label } }
rule property_map() -> Vec<(&'input str, Expression<'input>)>
= "{" __* entries:( (k:ident() _* ":" _* v:expression() { (k, v) }) ++ (_* "," _*) ) __* "}" { entries }
rule node() -> Node<'input>
= "(" _* a:annotation() _* p:property_map()? _* ")" {
Node::new(a, p.unwrap_or_else(Vec::new))
}
rule edge() -> Edge<'input>
= "-[" _* a:annotation() _* p:property_map()? _* "]->" {
Edge::right(a, p.unwrap_or_else(Vec::new))
}
/ "-[" _* a:annotation() _* p:property_map()? _* "]-" {
Edge::either(a, p.unwrap_or_else(Vec::new))
}
/ "<-[" _* a:annotation() _* p:property_map()? _* "]-" {
Edge::left(a, p.unwrap_or_else(Vec::new))
}
/ "<-" { Edge::left(Annotation::empty(), Vec::new()) }
/ "->" { Edge::right(Annotation::empty(), Vec::new()) }
/ "-" { Edge::either(Annotation::empty(), Vec::new()) }
rule property() -> (&'input str, &'input str)
= name:ident() "." key:ident() { (name, key) }
rule condition() -> Condition<'input>= precedence!{
a:(@) __* kw_and() __* b:@ { Condition::and(a, b) }
a:(@) __* kw_or() __* b:@ { Condition::or(a, b) }
--
kw_not() _* c:(@) { Condition::not(c) }
--
kw_id() _* "(" _* n:ident() _* ")" _* "=" _* e:expression() { Condition::IdEq(n, e) }
e:expression() _* "=" _* kw_id() _* "(" _* n:ident() _* ")" { Condition::IdEq(n, e) }
--
a:expression() _* "=" _* b:expression() { Condition::Eq(a, b) }
a:expression() _* "<>" _* b:expression() { Condition::Ne(a, b) }
a:expression() _* "<" _* b:expression() { Condition::Lt(a, b) }
a:expression() _* "<=" _* b:expression() { Condition::Le(a, b) }
a:expression() _* ">" _* b:expression() { Condition::Gt(a, b) }
a:expression() _* ">=" _* b:expression() { Condition::Ge(a, b) }
--
e:expression() { Condition::Expression(e) }
"(" __* c:condition() __* ")" { c }
}
rule match_clause() -> MatchClause<'input>
= kw_match() __+ start:node()
edges:( (__* e:edge() __* n:node() { (e, n) }) ** "" ) {
MatchClause { start, edges }
}
rule where_clause() -> Condition<'input>
= kw_where() __+ c:condition() { c }
rule create_clause() -> CreateClause<'input>
= kw_create() __+ n:node() {?
let name = n.annotation.name;
let label = n.annotation.label.ok_or("a label is required")?;
Ok(CreateClause::CreateNode { name, label, properties: n.properties })
}
/ kw_create() __+ "(" _* lhs:ident() _* ")" __* e:edge() __* "(" _* rhs:ident() _* ")" {?
let name = e.annotation.name;
let label = e.annotation.label.ok_or("a label is required")?;
let (origin, target) = match e.direction {
Direction::Left => (rhs, lhs),
Direction::Right => (lhs, rhs),
Direction::Either => return Err("edge must be directed"),
};
Ok(CreateClause::CreateEdge {
name,
label,
origin,
target,
properties: e.properties
})
}
rule set_clause() -> SetClause<'input>
= kw_set() __+ p:property() _* "=" _* e:expression() {
SetClause { name: p.0, key: p.1, value: e }
}
rule delete_clause() -> &'input str
= kw_delete() __+ name:ident() { name }
rule return_clause() -> Vec<Expression<'input>>
= kw_return() __+ items:( expression() ++ (__* "," __*) ) { items }
pub rule query() -> Query<'input>
= __*
match_clauses:( match_clause() ** (__+) )
where_clauses:( __* w:( where_clause() ** (__+) )? { w.unwrap_or_else(Vec::new) } )
create_clauses:( __* c:(create_clause() ** (__+) )? { c.unwrap_or_else(Vec::new) } )
set_clauses:( __* s:(set_clause() ** (__+) )? { s.unwrap_or_else(Vec::new) } )
delete_clauses:( __* d:(delete_clause() ** (__+) )? { d.unwrap_or_else(Vec::new) } )
return_clause:( __* r:return_clause()? { r.unwrap_or_else(Vec::new) })
__* {
Query {
match_clauses,
where_clauses,
create_clauses,
set_clauses,
delete_clauses,
return_clause,
}
}
}
}
pub fn parse(input: &str) -> Result<ast::Query<'_>, ParseError<LineCol>> {
cypher::query(input)
}