* Refactor the code, Update to the Service API REST JSON objects on disk. * Connect to the DB data model and engine to execute queries. * Remove the syntactic sugar around implicit `inside` operation on shapes, as it introduces issues.
478 lines
14 KiB
Plaintext
478 lines
14 KiB
Plaintext
use std::str::FromStr;
|
|
|
|
use mercator_db::space::Space;
|
|
|
|
use crate::symbols;
|
|
|
|
grammar;
|
|
|
|
//*********************************************************************/
|
|
// FORMATTING DATA */
|
|
//*********************************************************************/
|
|
pub Query = { Projections? };
|
|
|
|
Projections: symbols::Projection = {
|
|
NiftiOperator,
|
|
JsonOperator
|
|
};
|
|
|
|
// If selector is not provided, one (1) will be used as the values for
|
|
// each position where there is a point in bag_expression.
|
|
//
|
|
// If it is provided, it MUST resolve to a NUMBER.
|
|
NiftiOperator: symbols::Projection = {
|
|
"nifti" "("
|
|
<s:( Selector "," )?>
|
|
<b:Bags>
|
|
<rs:( "," <String> )?>
|
|
")" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
|
|
if let Some((sel, _)) = s {
|
|
symbols::Projection::Nifti(space_id, sel, b)
|
|
} else {
|
|
symbols::Projection::Nifti(space_id, symbols::LiteralSelector(Vec::new()), b)
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
JsonOperator: symbols::Projection = {
|
|
"json" "("
|
|
<f:JsonValues> ","
|
|
<b:Bags>
|
|
<rs:( "," <String> )?>
|
|
")" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
|
|
symbols::Projection::JSON(space_id, f, b)
|
|
}
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// JSON */
|
|
//*********************************************************************/
|
|
|
|
// Taken and adapted from:
|
|
// https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4
|
|
//
|
|
// Some of the parser / lexer rules are in the imported grammar as well.
|
|
JsonValues: symbols::JsonValue = {
|
|
String => symbols::JsonValue::String(<>),
|
|
JsonNumber => <>,
|
|
JsonObj => <>,
|
|
JsonArray => <>,
|
|
"true" => symbols::JsonValue::Bool(true),
|
|
"false" => symbols::JsonValue::Bool(false),
|
|
"null" => symbols::JsonValue::Null,
|
|
// Support reference to values from the selected bag.
|
|
Selector => symbols::JsonValue::Selector(<>),
|
|
Aggregations => symbols::JsonValue::Aggregation(<>)
|
|
};
|
|
|
|
JsonObj: symbols::JsonValue = {
|
|
"{" <exp:( JsonPair ( "," JsonPair )* )?> "}" => {
|
|
if let Some((elem, list)) = exp {
|
|
let mut values = vec![elem];
|
|
|
|
for v in list {
|
|
let (_, pair) = v;
|
|
values.push(pair.clone());
|
|
}
|
|
|
|
symbols::JsonValue::Object(values)
|
|
} else {
|
|
symbols::JsonValue::Object(Vec::new())
|
|
}
|
|
}
|
|
};
|
|
|
|
JsonPair: (String, symbols::JsonValue) = {
|
|
<s:String> ":" <v:JsonValues> => (s, v)
|
|
};
|
|
|
|
JsonArray: symbols::JsonValue = {
|
|
"[" <exp:( JsonValues ( "," JsonValues )* )?> "]" => {
|
|
if let Some((elem, list)) = exp {
|
|
let mut values = vec![elem];
|
|
|
|
for v in list.iter() {
|
|
let (_, val) = v;
|
|
values.push(val.clone());
|
|
}
|
|
|
|
symbols::JsonValue::Array(values)
|
|
} else {
|
|
symbols::JsonValue::Array(Vec::new())
|
|
}
|
|
}
|
|
};
|
|
|
|
// The bag expression is implicit here, as this is te
|
|
// second argument to the json operator
|
|
Aggregations: symbols::Aggregation = {
|
|
"count" "(" <d:"distinct"?> <s:Selector> ")" => {
|
|
if let Some(_) = d {
|
|
symbols::Aggregation::Count(true, s)
|
|
} else {
|
|
symbols::Aggregation::Count(false, s)
|
|
}
|
|
},
|
|
"sum" "(" <Selector> ")" =>
|
|
symbols::Aggregation::Sum(<>),
|
|
"min" "(" <Selector> ")" =>
|
|
symbols::Aggregation::Min(<>),
|
|
"max" "(" <Selector> ")" =>
|
|
symbols::Aggregation::Max(<>),
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// SELECTING / FILTERING DATA */
|
|
//*********************************************************************/
|
|
pub Filters = { Bags };
|
|
|
|
// All these expressions generate bags.
|
|
Bags: symbols::Bag = {
|
|
// Bag Operators
|
|
Distinct,
|
|
Filter,
|
|
Complement,
|
|
Intersection,
|
|
Union,
|
|
Bag,
|
|
// Spatial Operators
|
|
Inside,
|
|
Outside,
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// BAG OPERATORS */
|
|
//*********************************************************************/
|
|
Distinct: symbols::Bag = {
|
|
"distinct" "(" <Bags> ")" =>
|
|
symbols::Bag::Distinct(Box::new(<>))
|
|
};
|
|
|
|
// Returns all the points which are NOT part of the bag.
|
|
Complement: symbols::Bag = {
|
|
"complement" "(" <Bags> ")" =>
|
|
symbols::Bag::Complement(Box::new(<>))
|
|
};
|
|
|
|
// Returns points which are part of both left and right sets.
|
|
Intersection: symbols::Bag = {
|
|
"intersection" "(" <lh:Bags> "," <rh:Bags> ")" =>
|
|
symbols::Bag::Intersection(Box::new(lh), Box::new(rh))
|
|
};
|
|
|
|
// Returns points which are either part of left or right sets
|
|
// (or both).
|
|
Union: symbols::Bag = {
|
|
"union" "(" <lh:Bags> "," <rh:Bags> ")" =>
|
|
symbols::Bag::Union(Box::new(lh), Box::new(rh))
|
|
};
|
|
|
|
// Filters point so that points part of the resulting bag respect
|
|
// the predicate.
|
|
Filter: symbols::Bag = {
|
|
// "filter" "(" <p:Predicates> "," <b:Bags> ")" =>
|
|
"filter" "(" <b:Bags> ")" =>
|
|
symbols::Bag::Filter(None, Box::new(b)),
|
|
"filter" "(" <p:Predicates> <b:("," <Bags> )?> ")" =>
|
|
symbols::get_filter(p, b)
|
|
};
|
|
|
|
Predicates: symbols::Predicate = {
|
|
Less,
|
|
Greater,
|
|
Equal,
|
|
Not,
|
|
And,
|
|
Or
|
|
};
|
|
|
|
Less: symbols::Predicate = {
|
|
"<" "(" <v:Positions> "," <literal:Position> ")" => {
|
|
symbols::Predicate::Less(v, literal)
|
|
}
|
|
};
|
|
|
|
Greater: symbols::Predicate = {
|
|
">" "(" <v:Positions> "," <literal:Position> ")" => {
|
|
symbols::Predicate::Greater(v, literal)
|
|
}
|
|
};
|
|
|
|
Equal: symbols::Predicate = {
|
|
"=" "(" <v:Positions> "," <literal:Position> ")" => {
|
|
symbols::Predicate::Equal(v, literal)
|
|
}
|
|
};
|
|
|
|
Not: symbols::Predicate = {
|
|
"!" "(" <p:Predicates> ")" =>
|
|
symbols::Predicate::Not(Box::new(p))
|
|
};
|
|
|
|
And: symbols::Predicate = {
|
|
"&" "(" <lh:Predicates> "," <rh:Predicates> ")" =>
|
|
symbols::Predicate::And(Box::new(lh), Box::new(rh))
|
|
};
|
|
|
|
Or: symbols::Predicate = {
|
|
"|" "(" <lh:Predicates> "," <rh:Predicates> ")" =>
|
|
symbols::Predicate::Or(Box::new(lh), Box::new(rh))
|
|
};
|
|
|
|
// Arbitrary bag of positions.
|
|
Bag: symbols::Bag = {
|
|
"bag" "{" <elem:Bags> <list:("," Bags )*> "}" => {
|
|
let mut bags = vec![elem];
|
|
|
|
for (_, b) in list {
|
|
bags.push(b);
|
|
}
|
|
|
|
symbols::Bag::Bag(bags)
|
|
}
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// SPATIAL OPERATORS */
|
|
//*********************************************************************/
|
|
|
|
// Faces | vertices are included to allow selection on a pure plane or
|
|
// boundary.
|
|
//
|
|
// For example:
|
|
// intersection(outside(hyperrectangle{[0,0], [1,1]},
|
|
// inside(hyperrectangle{[0,0], [1,1]})
|
|
// will be true for any point lying EXACTLY on a face, corner or edge
|
|
// of the cube [0,0], [1,1].
|
|
|
|
// Returns the set of points outside the shape, (face included)
|
|
Outside: symbols::Bag = {
|
|
"outside" "(" <Shapes> ")" =>
|
|
symbols::Bag::Outside(<>)
|
|
};
|
|
|
|
// Returns the set of points inside the shape, (face included)
|
|
Inside: symbols::Bag = {
|
|
"inside" "(" <Shapes> ")" =>
|
|
symbols::Bag::Inside(<>)
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// SHAPES */
|
|
//*********************************************************************/
|
|
|
|
// Shapes are defined in terms of POSITION, a.k.a a LiteralPosition
|
|
// value, which is not a POSITIONS, which might be a filter for example.
|
|
Shapes: symbols::Shape = {
|
|
Point,
|
|
HyperRectangle,
|
|
HyperSphere,
|
|
Nifti
|
|
};
|
|
|
|
// If the hyperrectangle is aligned with the axes, then two points are
|
|
// enough, if not we need all the points to be specified.
|
|
HyperRectangle: symbols::Shape = {
|
|
"hyperrectangle" "{"
|
|
<l:Position> "," <h:Position>
|
|
<list:( "," Position "," Position )*>
|
|
<rs:( "," <String> )?>
|
|
"}" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
let mut pos = vec![l, h];
|
|
for (_, lh, _, rh) in list.iter() {
|
|
pos.push(lh.clone());
|
|
pos.push(rh.clone());
|
|
}
|
|
symbols::Shape::HyperRectangle(space_id, pos)
|
|
}
|
|
};
|
|
|
|
// A hypersphere is defined by its center and a radius, independantly
|
|
// of the number of dimensions of the space.
|
|
HyperSphere: symbols::Shape = {
|
|
"hypersphere" "{"
|
|
<c:Position> "," <r:PositiveNumber>
|
|
<rs:( "," <String> )?>
|
|
"}" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
|
|
symbols::Shape::HyperSphere(space_id, c, r)
|
|
}
|
|
};
|
|
|
|
Point: symbols::Shape = {
|
|
"point" "{" <pos:Position> <rs:( "," <String> )?> "}" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
|
|
symbols::Shape::Point(space_id, pos)
|
|
}
|
|
};
|
|
|
|
// Define a shape as the non-zero values in a NIfTI object, defined by
|
|
// nifti{
|
|
// spaceId: string,
|
|
// lower_corner: position, // Optional, default to the origin
|
|
// rotation: [ position+ ], // Optional, no rotation by default
|
|
// bytes: uri(STRING) // uri to the NIfTI object
|
|
// }
|
|
Nifti: symbols::Shape = {
|
|
"nifti" "{"
|
|
<o:( Position "," )?>
|
|
<rotation:( "[" Position ( "," Position)* "]" "," )?>
|
|
<data:ByteProvider>
|
|
<rs:( "," <String> )?>
|
|
"}" => {
|
|
let space_id = match rs {
|
|
Some(id) => id,
|
|
None => Space::universe().name().clone(),
|
|
};
|
|
symbols::Shape::Nifti(space_id)
|
|
}
|
|
};
|
|
|
|
// FIXME: STRING is assumed to be a well-formed URI, fully specify here?
|
|
//
|
|
// FIXME: Add a provider for in-line raw-byte stream.
|
|
ByteProvider = { "uri" "(" String ")" };
|
|
|
|
//*********************************************************************/
|
|
// POSITIONS */
|
|
//*********************************************************************/
|
|
|
|
// Always returns a vector of numbers, a.k.a a position (a scalar will
|
|
// be represented as a vector of one element)
|
|
Positions: symbols::Position = {
|
|
StrCmp,
|
|
Selector => symbols::Position::Selector(<>),
|
|
Position => symbols::Position::Literal(<>)
|
|
};
|
|
|
|
// Compare lexicographically two strings, and returns a `position`:
|
|
// [-1] : String is lexicographically before,
|
|
// [ 0] : is equal,
|
|
// [ 1] : is after.
|
|
StrCmp: symbols::Position = {
|
|
"str_cmp" "(" <s:Selector> "," <v:String> ")" => {
|
|
symbols::Position::StrCmp(s, v)
|
|
}
|
|
};
|
|
|
|
// FIXME: FIELDS are expected to be exisiting in the data model. Root Object is assumed to be the type of the ressource on which the POST call was done.
|
|
Selector: symbols::LiteralSelector = {
|
|
( <Field> )+ => symbols::LiteralSelector(<>)
|
|
};
|
|
|
|
Position: symbols::LiteralPosition = {
|
|
"[" <element:Number> <list:( "," <Number>)*> "]" => {
|
|
let mut pos = vec![element];
|
|
|
|
for e in list.iter() {
|
|
pos.push(e.clone());
|
|
}
|
|
|
|
symbols::LiteralPosition(pos)
|
|
}
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// TOKENS - STRINGS */
|
|
//*********************************************************************/
|
|
|
|
// Accept field descriptor which
|
|
// 1. start with a dot ('.')
|
|
// 2. optionnally followed by a field name consisting of a letter or
|
|
// underscore, followed by letters, numbers or underscore,
|
|
// 3. optionnally followed by brakets enclosing a natural number
|
|
// denoting an offset in a list or array.
|
|
Field: symbols::Field = {
|
|
<n:r"[.]([a-zA-Z_][[a-zA-Z_0-9]]*)?([\[](0|[1-9][0-9]*)[\]])?"> => {
|
|
if let Some(pos) = n.rfind('[') {
|
|
let name = &n[1..pos];
|
|
let index = &n[(pos+1)..(n.len()-1)];
|
|
let index = usize::from_str(index).unwrap();
|
|
symbols::Field(String::from(name), Some(index))
|
|
} else {
|
|
let name = &n[1..];
|
|
symbols::Field(String::from(name), None)
|
|
}
|
|
}
|
|
};
|
|
|
|
String: String = {
|
|
r#"["]([\\](["\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\u0000-\u001F])*["]"# => {
|
|
let s = <>;
|
|
let l = s.len() - 1;
|
|
s[1..l].to_string()
|
|
}
|
|
};
|
|
|
|
//*********************************************************************/
|
|
// TOKENS - NUMBERS */
|
|
//*********************************************************************/
|
|
// We define 3 kinds of number, to avoid ambiguities in the rules.
|
|
JsonNumber: symbols::JsonValue = {
|
|
<s:"-"?> <v:Num> => match s {
|
|
None => symbols::JsonValue::JsonNumber(v),
|
|
Some(_) => match v {
|
|
symbols::LiteralNumber::Int(x) => symbols::JsonValue::JsonNumber(symbols::LiteralNumber::Int(-x)),
|
|
symbols::LiteralNumber::Float(x) => symbols::JsonValue::JsonNumber(symbols::LiteralNumber::Float(-x))
|
|
}
|
|
}
|
|
};
|
|
|
|
PositiveNumber: symbols::LiteralNumber = { "+"? <v:Num> => v };
|
|
|
|
Number: symbols::LiteralNumber = {
|
|
"+" <v:Num> => v,
|
|
"-" <v:Num> => match v {
|
|
symbols::LiteralNumber::Int(x) => symbols::LiteralNumber::Int(-x),
|
|
symbols::LiteralNumber::Float(x) => symbols::LiteralNumber::Float(-x)
|
|
},
|
|
<v:Num> => v
|
|
|
|
};
|
|
|
|
Num: symbols::LiteralNumber = {
|
|
r"0([.][0-9]+([eE][+\-]?(0|[1-9][0-9]*))?)?"
|
|
=> {
|
|
if let Ok(v) = i64::from_str(<>) {
|
|
symbols::LiteralNumber::Int(v)
|
|
} else {
|
|
// Either parsing as a float succeed or we pass along
|
|
// the error
|
|
symbols::LiteralNumber::Float(f64::from_str(<>).unwrap())
|
|
}
|
|
},
|
|
r"[1-9][0-9]*([.][0-9]+)?([eE][+\-]?(0|[1-9][0-9]*))?"
|
|
=> {
|
|
if let Ok(v) = i64::from_str(<>) {
|
|
symbols::LiteralNumber::Int(v)
|
|
} else {
|
|
// Either parsing as a float succeed or we pass along
|
|
// the error
|
|
symbols::LiteralNumber::Float(f64::from_str(<>).unwrap())
|
|
}
|
|
}
|
|
};
|