Parser implementation

* Adaptation of the grammar to make more regular w.r.t reference space
   names placement.

 * Implemented Type check

 * Implemented prediction of the result set cardinality. This assumes:
    + A space is not infinite, this is required to compute complementary
      sets as we can compute the whole space volume.
    + Density is mostly uniform, this allows us to assume the number of
      results is related with a constant factor to the volume in space
      of the selection.
    + The prediction is approximated by using the most pessimistic
      resulting cardinality for each operator, in order to keep the
      operation simple, and fast, at the expense of the precision.

 * Implemented execution, which calls into a DB abstraction layer. That
   layer is currently mostly empty. Execution is also implemented in a
   naive way, and should most likely be optimised.
This commit is contained in:
2019-06-19 14:30:08 +02:00
parent 0eaa7839cf
commit 6ed76e485e
16 changed files with 2237 additions and 1153 deletions

View File

@@ -17,6 +17,7 @@ path = "src/main.rs"
lalrpop-util = "0.17.0" lalrpop-util = "0.17.0"
regex = "0.2.1" regex = "0.2.1"
measure_time = "0.6" # To mesure parsing time, only required by binary measure_time = "0.6" # To mesure parsing time, only required by binary
lazy_static = "1.3.0" # for space.rs, might be removed once the full data model is provided by the use of the crate
# Logging macros API # Logging macros API
log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] } log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] }

View File

@@ -134,33 +134,38 @@ hyperrectangle
: 'hyperrectangle' '{' : 'hyperrectangle' '{'
position ',' position position ',' position
( ',' position ',' position )* ( ',' position ',' position )*
( ',' STRING )?
'}' '}'
; ;
/* A hypersphere is defined by its center and a radius, independantly /* A hypersphere is defined by its center and a radius, independantly
* of the number of dimensions of the space. */ * of the number of dimensions of the space. */
hypersphere hypersphere
: 'hypersphere' '{' position ( ',' positive_number ) '}' : 'hypersphere' '{'
position
',' positive_number
( ',' STRING )?
'}'
; ;
point point
: 'point' '{' position '}' : 'point' '{' position ( ',' STRING )? '}'
; ;
/* Define a shape as the non-zero values in a NIfTI object, defined by /* Define a shape as the non-zero values in a NIfTI object, defined by
* nifti{ * nifti{
* spaceId: string,
* lower_corner: position, // Optional, default to the origin * lower_corner: position, // Optional, default to the origin
* rotation: [ position+ ], // Optional, no rotation by default * rotation: [ position+ ], // Optional, no rotation by default
* bytes: uri(STRING) // uri to the NIfTI object * bytes: uri(STRING), // uri to the NIfTI object
* spaceId: string
* } * }
*/ */
nifti nifti
: 'nifti' '{' : 'nifti' '{'
STRING ','
(position ',' )? (position ',' )?
( '[' position ( ',' position )* ']' ',' )? ( '[' position ( ',' position )* ']' ',' )?
byte_provider byte_provider ','
STRING
'}' '}'
; ;

View File

@@ -18,11 +18,11 @@ projection_operators
* *
* If it is provided, it MUST resolve to a NUMBER. */ * If it is provided, it MUST resolve to a NUMBER. */
nifti_operator nifti_operator
: 'nifti' '(' ( selector ',' )? bag_expression ')' : 'nifti' '(' ( STRING ',' )? ( selector ',' )? bag_expression ')'
; ;
json_operator json_operator
: 'json' '(' jslt ',' bag_expression ')' : 'json' '(' jslt ',' bag_expression ( ',' STRING )? ')'
; ;
jslt jslt

View File

@@ -1,113 +0,0 @@
trait Expression {
fn check_type();
fn predict();
fn execute();
}
/**********************************************************************/
/* FORMATTING DATA */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Projection {
Nifti(LiteralSelector, Bag),
JSON(JsonValue, Bag),
}
// JSON FORMAT
#[derive(Clone, Debug)]
pub enum JsonValue {
String(String),
JsonNumber(LiteralNumber),
Bool(bool),
Null,
Object(Vec<(String, JsonValue)>),
Array(Vec<JsonValue>),
Selector(LiteralSelector),
Aggregation(Aggregation),
}
#[derive(Clone, Debug)]
pub enum Aggregation {
Count(bool, LiteralSelector),
Sum(LiteralSelector),
Min(LiteralSelector),
Max(LiteralSelector),
}
// NIFTI
#[derive(Clone, Debug)]
struct Transform {
reference: String,
offset: Vec<LiteralNumber>,
rotation: Vec<Vec<LiteralNumber>>,
}
/**********************************************************************/
/* SELECTING / FILTERING DATA */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Bag {
Distinct(Box<Bag>),
Filter(Option<Predicate>, Option<Box<Bag>>),
Complement(Box<Bag>),
Intersection(Box<Bag>, Box<Bag>),
Union(Box<Bag>, Box<Bag>),
Bag(Vec<Bag>),
Inside(Shape),
Outside(Shape),
}
/**********************************************************************/
/* BAG OPERATORS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Predicate {
Less(Position, LiteralPosition),
Greater(Position, LiteralPosition),
Equal(Position, LiteralPosition),
Not(Box<Predicate>),
And(Box<Predicate>, Box<Predicate>),
Or(Box<Predicate>, Box<Predicate>),
}
/**********************************************************************/
/* SPATIAL OPERATORS */
/**********************************************************************/
/**********************************************************************/
/* SHAPES */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Shape {
Point(LiteralPosition),
HyperRectangle(Vec<LiteralPosition>),
HyperSphere(LiteralPosition, LiteralNumber),
Nifti(),
}
/**********************************************************************/
/* POSITIONS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Position {
StrCmpICase(LiteralSelector, String),
StrCmp(LiteralSelector, String),
Selector(LiteralSelector),
LiteralPosition(LiteralPosition),
}
/**********************************************************************/
/* Literals / TOKENS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub struct Field(pub String, pub Option<usize>);
#[derive(Clone, Debug)]
pub enum LiteralNumber {
Int(i64),
Float(f64),
}
pub type LiteralPosition = Vec<LiteralNumber>;
pub type LiteralSelector = Vec<Field>;

57
src/database/mod.rs Normal file
View File

@@ -0,0 +1,57 @@
use crate::executors::ResultSet;
use crate::symbols::*;
//use ironsea_index::*;
pub fn get_all(_space_id: &String) -> ResultSet {
//space::get_all(space_id)
Err("not yet implemented".to_string())
}
pub fn get_by_bounding_box(_space_id: &String, _bounding_box: &Vec<LiteralPosition>) -> ResultSet {
Err("not yet implemented".to_string())
}
pub fn get_by_position(_space_id: &String, _position: &LiteralPosition) -> ResultSet {
Err("not yet implemented".to_string())
}
pub fn get(_point: &Object, _fields: &Vec<Field>) -> Result<LiteralPosition, String> {
Err("not yet implemented".to_string())
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Object {
id: u32,
}
impl Object {
pub fn length(&self) -> f64 {
0.0
}
pub fn eval(&self, _predicate: &Predicate) -> bool {
false
}
}
impl Position {
pub fn eval(&self, point: &Object) -> LiteralPosition {
match self {
Position::StrCmpICase(_selector, _string) => LiteralPosition(vec![]), //TODO
Position::StrCmp(_selector, _string) => LiteralPosition(vec![]), //TODO
Position::Selector(selector) => selector.eval(point),
Position::Literal(position) => position.clone(),
}
}
}
impl LiteralSelector {
pub fn eval(&self, point: &Object) -> LiteralPosition {
let LiteralSelector(fields) = self;
match get(point, fields) {
Err(_) => LiteralPosition(vec![]),
Ok(p @ LiteralPosition(_)) => p,
}
}
}

86
src/database/space.rs Normal file
View File

@@ -0,0 +1,86 @@
use std::collections::HashMap;
use crate::types::LiteralTypes;
//FIXME: Improve, as this should not be static, but coming from DB.
lazy_static! {
static ref UNIVERSE: String = "Universe".to_string();
static ref SPACES: HashMap<&'static str, Space> = {
let mut m = HashMap::new();
m.insert(
"Universe",
Space {
space_type: LiteralTypes::Vector(vec![
LiteralTypes::Float,
LiteralTypes::Float,
LiteralTypes::Float,
]),
bounding_box: vec![
vec![0, 0, 0],
vec![
std::u32::MAX as i64,
std::u32::MAX as i64,
std::u32::MAX as i64,
],
],
},
);
m
};
}
struct Space {
space_type: LiteralTypes,
bounding_box: Vec<Vec<i64>>,
}
impl Space {
pub fn max_volume(&self) -> f64 {
let mut volume = 1.0;
for max in &self.bounding_box[1] {
volume *= *max as f64;
}
volume
}
}
pub fn name() -> &'static String {
lazy_static! {
static ref UNIVERSE: String = "Universe".to_string();
};
&UNIVERSE
}
#[inline]
pub fn get_type(space_id: &String) -> &LiteralTypes {
lazy_static! {
static ref EMPTY_TYPE: LiteralTypes = LiteralTypes::Vector(Vec::new());
};
match SPACES.get(space_id.as_str()) {
None => &EMPTY_TYPE,
Some(space) => &space.space_type,
}
}
#[inline]
pub fn bounding_box(space_id: &String) -> &Vec<Vec<i64>> {
lazy_static! {
static ref EMPTY_BOX: Vec<Vec<i64>> = Vec::new();
};
match SPACES.get(space_id.as_str()) {
None => &EMPTY_BOX,
Some(space) => &space.bounding_box,
}
}
#[inline]
pub fn max_volume(space_id: &String) -> f64 {
match SPACES.get(space_id.as_str()) {
None => 0.0,
Some(space) => space.max_volume(),
}
}

314
src/executors.rs Normal file
View File

@@ -0,0 +1,314 @@
use std::collections::HashSet;
use super::database;
use super::expression::*;
use super::symbols::*;
pub type ResultSet = Result<Vec<database::Object>, String>;
impl Executor for Projection {
type ResultSet = self::ResultSet;
fn execute(&self) -> ResultSet {
match self {
Projection::Nifti(_, _, _bag) => Err("not yet implemented".to_string()),
Projection::JSON(_, _format, bag) => bag.execute(), // FIXME: Add projections here
}
}
}
impl Executor for Bag {
type ResultSet = self::ResultSet;
fn execute(&self) -> ResultSet {
fn get_bounding_box(
position: &LiteralPosition,
radius: &LiteralNumber,
) -> Result<Vec<LiteralPosition>, String> {
let LiteralPosition(position) = position;
let mut low = vec![];
let mut high = vec![];
match radius {
LiteralNumber::Int(r) => {
for x in position {
match x {
LiteralNumber::Int(x) => {
low.push(LiteralNumber::Int(x - r));
high.push(LiteralNumber::Int(x + r));
}
LiteralNumber::Float(x) => {
low.push(LiteralNumber::Float(x - (*r as f64)));
high.push(LiteralNumber::Float(x + (*r as f64)));
}
};
}
}
LiteralNumber::Float(r) => {
for x in position {
match x {
LiteralNumber::Int(_) => {
return Err(format!("The radius provided is a floating point value, which is incompatible with integer coordinates components: radius {:?}, coordinates {:?}", radius, position));
}
LiteralNumber::Float(x) => {
low.push(LiteralNumber::Float(x - r));
high.push(LiteralNumber::Float(x + r));
}
};
}
}
}
Ok(vec![LiteralPosition(low), LiteralPosition(high)])
};
match self {
Bag::Distinct(bag) => match bag.execute() {
e @ Err(_) => e,
Ok(mut v) => {
let set: HashSet<_> = v.drain(..).collect(); // dedup
v.extend(set.into_iter());
Ok(v)
}
},
Bag::Filter(predicate, bag) => match predicate {
None => bag.execute(),
Some(predicate) => match bag.execute() {
e @ Err(_) => e,
Ok(source) => {
let mut filtered = Vec::new();
for point in source {
if point.eval(predicate) {
filtered.push(point);
}
}
Ok(filtered)
}
},
},
Bag::Complement(bag) => match bag.execute() {
// The complement of a set is computed within its definition space.
e @ Err(_) => e,
Ok(inside) => {
let mut outside = Vec::new();
match database::get_all(bag.space()) {
e @ Err(_) => e,
Ok(points) => {
for point in points {
if !inside.contains(&point) {
outside.push(point)
}
}
Ok(outside)
}
}
}
},
Bag::Intersection(lh, rh) => {
let l = lh.execute();
if let Ok(l) = l {
let r = rh.execute();
if let Ok(r) = r {
let mut v = vec![];
if rh.predict() < lh.predict() {
for o in r {
if l.contains(&o) {
v.push(o);
}
}
} else {
for o in l {
if r.contains(&o) {
v.push(o);
}
}
}
Ok(v)
} else {
r
}
} else {
l
}
}
Bag::Union(lh, rh) => {
let l = lh.execute();
if let Ok(mut l) = l {
let r = rh.execute();
if let Ok(mut r) = r {
if rh.predict() < lh.predict() {
l.append(&mut r);
Ok(l)
} else {
r.append(&mut l);
Ok(r)
}
} else {
r
}
} else {
l
}
}
Bag::Bag(bags) => {
let mut v = vec![];
for bag in bags {
let b = bag.execute();
match b {
e @ Err(_) => {
return e;
}
Ok(mut b) => {
//TODO: SPACE CONVERSIONS IF NOT THE SAME SPACES?
v.append(&mut b);
}
}
}
Ok(v)
}
Bag::Inside(shape) => match shape {
Shape::Point(space_id, position) => database::get_by_position(space_id, position),
Shape::HyperRectangle(space_id, bounding_box) => {
database::get_by_bounding_box(space_id, bounding_box)
}
Shape::HyperSphere(space_id, position, radius) => {
let length = match radius {
LiteralNumber::Int(x) => *x as f64,
LiteralNumber::Float(x) => *x,
};
match get_bounding_box(position, radius) {
Err(e) => Err(e),
Ok(inside) => match database::get_by_bounding_box(space_id, &inside) {
e @ Err(_) => e,
Ok(source) => {
let mut filtered = vec![];
for point in source {
// Include the surface of the sphere
if point.length() <= length {
filtered.push(point);
}
}
Ok(filtered)
}
},
}
}
Shape::Nifti(_space_id) => Err("not yet implemented".to_string()),
},
Bag::Outside(shape) => {
fn outside_set(space_id: &String, inside: Vec<database::Object>) -> ResultSet {
let mut outside = Vec::new();
match database::get_all(space_id) {
e @ Err(_) => e,
Ok(points) => {
for point in points {
if !inside.contains(&point) {
outside.push(point)
}
}
Ok(outside)
}
}
}
match shape {
Shape::Point(space_id, position) => {
match database::get_by_position(space_id, position) {
e @ Err(_) => e,
Ok(inside) => outside_set(space_id, inside),
}
}
Shape::HyperRectangle(space_id, bounding_box) => {
// We need to adapt the bounding_box to ensure the
// surface will not hit as part of the inside set, so we
// compute the biggest bounding box contained within the
// given box.
// Smallest increment possible
let mut low: Vec<LiteralNumber> = vec![];
let LiteralPosition(coordinates) = &bounding_box[0];
for coordinate in coordinates {
match coordinate {
LiteralNumber::Int(x) => low.push(LiteralNumber::Int(x + 1)),
LiteralNumber::Float(x) => {
low.push(LiteralNumber::Float(x + std::f64::EPSILON))
}
};
}
let low = LiteralPosition(low);
// Smallest decrement possible
let mut high: Vec<LiteralNumber> = vec![];
let LiteralPosition(coordinates) = &bounding_box[1];
for coordinate in coordinates {
match coordinate {
LiteralNumber::Int(x) => high.push(LiteralNumber::Int(x - 1)),
LiteralNumber::Float(x) => {
high.push(LiteralNumber::Float(x - std::f64::EPSILON))
}
};
}
let high = LiteralPosition(high);
match database::get_by_bounding_box(space_id, &vec![low, high]) {
e @ Err(_) => e,
Ok(inside) => outside_set(space_id, inside),
}
}
Shape::HyperSphere(space_id, position, radius) => {
let length = match radius {
LiteralNumber::Int(x) => *x as f64,
LiteralNumber::Float(x) => *x,
};
match get_bounding_box(position, radius) {
Err(e) => Err(e),
Ok(inside) => match database::get_by_bounding_box(space_id, &inside) {
Err(e) => Err(e),
Ok(source) => {
let mut filtered = vec![];
for point in source {
// Exclude the surface of the sphere, so
// that it is included in the
// complement.
if point.length() < length {
filtered.push(point);
}
}
outside_set(space_id, filtered)
}
},
}
}
Shape::Nifti(_space_id) => Err("not yet implemented".to_string()),
}
}
}
}
}
impl Predicate {
pub fn eval(&self, point: &database::Object) -> bool {
match self {
Predicate::Not(predicate) => !predicate.eval(point),
Predicate::And(lh, rh) => lh.eval(point) && rh.eval(point),
Predicate::Or(lh, rh) => lh.eval(point) || rh.eval(point),
// I don't know how to evaluate these at this point, so let the DB object take care of that.
// Predicate::Less(selector, literal) => &selector.eval(point) < literal,
// Predicate::Greater(selector, literal) => &selector.eval(point) > literal,
// Predicate::Equal(selector, literal) => &selector.eval(point) == literal,
// Redirect to the DB Objet the evaluation of the remaining predicate operators
predicate => point.eval(predicate),
}
}
}

15
src/expression.rs Normal file
View File

@@ -0,0 +1,15 @@
pub trait Validator {
type ValidationResult;
fn validate(&self) -> Self::ValidationResult;
}
pub trait Predictor {
fn predict(&self) -> f64;
}
pub trait Executor {
type ResultSet;
fn execute(&self) -> Self::ResultSet;
}

View File

@@ -1,10 +1,25 @@
#[macro_use]
extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate lalrpop_util; extern crate lalrpop_util;
lalrpop_mod!(pub queries); // synthesized by LALRPOP lalrpop_mod!(pub queries); // synthesized by LALRPOP
pub mod ast; mod database;
pub use ast::*; mod executors;
mod expression;
mod predictors;
mod validators;
mod symbols;
mod types;
pub use expression::Executor;
pub use expression::Predictor;
pub use expression::Validator;
pub use queries::FiltersParser;
pub use queries::QueryParser;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@@ -3,7 +3,8 @@ extern crate measure_time;
extern crate parser; extern crate parser;
use parser::queries; use parser::QueryParser;
use parser::{Executor, Predictor, Validator};
use std::io; use std::io;
@@ -15,13 +16,13 @@ fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
//let parser = queries::FiltersParser::new(); //let parser = queries::FiltersParser::new();
let parser = queries::QueryParser::new(); let parser = QueryParser::new();
loop { loop {
println!();
info!("Expression to parse (type `quit` to exit): "); info!("Expression to parse (type `quit` to exit): ");
let mut input = String::new(); let mut input = String::new();
match io::stdin().read_line(&mut input) { match io::stdin().read_line(&mut input) {
Ok(0) => break, // Catch ^D Ok(0) => break, // Catch ^D
Ok(1) => continue, // Catch \n Ok(1) => continue, // Catch \n
@@ -34,11 +35,42 @@ fn main() {
} }
let input = input.as_str(); let input = input.as_str();
let mut out;
{ {
debug_time!("Parsing"); debug_time!("Interpretation");
out = parser.parse(input); let mut parse;
{
trace_time!("Parsing");
parse = parser.parse(input);
}
trace!("Tree: \n{:?}", parse);
match parse {
Ok(Some(t)) => {
let validate;
{
trace_time!("Type check");
validate = t.validate();
}
info!("Type: \n{:?}", validate);
if let Ok(_) = validate {
let predict;
{
trace_time!("Prediction");
predict = t.predict();
}
info!("Predict: \n{:?}", predict);
let execute;
{
trace_time!("Exectution");
execute = t.execute();
}
info!("Execution: \n{:?}", execute);
}
}
_ => (),
}
} }
info!("Tree: \n{:?}", out);
} }
} }

48
src/predictors.rs Normal file
View File

@@ -0,0 +1,48 @@
use super::expression::Predictor;
use super::symbols::*;
use super::database::space;
impl Predictor for Projection {
fn predict(&self) -> f64 {
match self {
Projection::Nifti(_, _, bag) => bag.predict(),
Projection::JSON(_, _, bag) => bag.predict(),
}
}
}
impl Predictor for Bag {
fn predict(&self) -> f64 {
match self {
Bag::Distinct(bag) => bag.predict(),
Bag::Filter(_, bag) => bag.predict(),
Bag::Complement(bag) => space::max_volume(bag.space()) - bag.predict(),
Bag::Intersection(lh, rh) => {
let l = lh.predict();
let r = rh.predict();
if l < r {
l
} else {
r
}
}
Bag::Union(lh, rh) => lh.predict() + rh.predict(),
Bag::Bag(bags) => {
let mut s = 0.0;
for bag in bags {
s += bag.predict();
}
s
}
Bag::Inside(shape) => shape.predict(),
Bag::Outside(shape) => space::max_volume(shape.space()) - shape.predict(),
}
}
}
impl Predictor for Shape {
fn predict(&self) -> f64 {
self.volume()
}
}

View File

@@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use crate::ast; use crate::symbols;
use crate::ast::*; use crate::database::space;
grammar; grammar;
@@ -10,7 +10,7 @@ grammar;
//*********************************************************************/ //*********************************************************************/
pub Query = { Projections? }; pub Query = { Projections? };
Projections: ast::Projection = { Projections: symbols::Projection = {
NiftiOperator, NiftiOperator,
JsonOperator JsonOperator
}; };
@@ -19,18 +19,39 @@ Projections: ast::Projection = {
// each position where there is a point in bag_expression. // each position where there is a point in bag_expression.
// //
// If it is provided, it MUST resolve to a NUMBER. // If it is provided, it MUST resolve to a NUMBER.
NiftiOperator: ast::Projection = { NiftiOperator: symbols::Projection = {
"nifti" "(" <s:( Selector "," )?> <b:Bags> ")" => "nifti" "("
<s:( Selector "," )?>
<b:Bags>
<rs:( "," <String> )?>
")" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
if let Some((sel, _)) = s { if let Some((sel, _)) = s {
Projection::Nifti(sel, b) symbols::Projection::Nifti(space_id, sel, b)
} else { } else {
Projection::Nifti(Vec::new(), b) symbols::Projection::Nifti(space_id, symbols::LiteralSelector(Vec::new()), b)
}
} }
}; };
JsonOperator: ast::Projection = { JsonOperator: symbols::Projection = {
"json" "(" <f:JsonValues> "," <b:Bags> ")" => "json" "("
Projection::JSON(f, b) <f:JsonValues> ","
<b:Bags>
<rs:( "," <String> )?>
")" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
symbols::Projection::JSON(space_id, f, b)
}
}; };
//*********************************************************************/ //*********************************************************************/
@@ -41,73 +62,73 @@ JsonOperator: ast::Projection = {
// https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4 // https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4
// //
// Some of the parser / lexer rules are in the imported grammar as well. // Some of the parser / lexer rules are in the imported grammar as well.
JsonValues: ast::JsonValue = { JsonValues: symbols::JsonValue = {
String => JsonValue::String(<>), String => symbols::JsonValue::String(<>),
JsonNumber => <>, JsonNumber => <>,
JsonObj => <>, JsonObj => <>,
JsonArray => <>, JsonArray => <>,
"true" => JsonValue::Bool(true), "true" => symbols::JsonValue::Bool(true),
"false" => JsonValue::Bool(false), "false" => symbols::JsonValue::Bool(false),
"null" => JsonValue::Null, "null" => symbols::JsonValue::Null,
// Support reference to values from the selected bag. // Support reference to values from the selected bag.
Selector => JsonValue::Selector(<>), Selector => symbols::JsonValue::Selector(<>),
Aggregations => JsonValue::Aggregation(<>) Aggregations => symbols::JsonValue::Aggregation(<>)
}; };
JsonObj: ast::JsonValue = { JsonObj: symbols::JsonValue = {
"{" <exp:( JsonPair ( "," JsonPair )* )?> "}" => { "{" <exp:( JsonPair ( "," JsonPair )* )?> "}" => {
if let Some((elem, list)) = exp { if let Some((elem, list)) = exp {
let mut values: Vec<(String, JsonValue)> = vec![elem]; let mut values = vec![elem];
for v in list { for v in list {
let (_, pair) = v; let (_, pair) = v;
values.push(pair.clone()); values.push(pair.clone());
} }
JsonValue::Object(values) symbols::JsonValue::Object(values)
} else { } else {
JsonValue::Object(Vec::new()) symbols::JsonValue::Object(Vec::new())
} }
} }
}; };
JsonPair: (String, ast::JsonValue) = { JsonPair: (String, symbols::JsonValue) = {
<s:String> ":" <v:JsonValues> => (s, v) <s:String> ":" <v:JsonValues> => (s, v)
}; };
JsonArray: ast::JsonValue = { JsonArray: symbols::JsonValue = {
"[" <exp:( JsonValues ( "," JsonValues )* )?> "]" => { "[" <exp:( JsonValues ( "," JsonValues )* )?> "]" => {
if let Some((elem, list)) = exp { if let Some((elem, list)) = exp {
let mut values: Vec<JsonValue> = vec![elem]; let mut values = vec![elem];
for v in list.iter() { for v in list.iter() {
let (_, val) = v; let (_, val) = v;
values.push(val.clone()); values.push(val.clone());
} }
JsonValue::Array(values) symbols::JsonValue::Array(values)
} else { } else {
JsonValue::Array(Vec::new()) symbols::JsonValue::Array(Vec::new())
} }
} }
}; };
// The bag expression is implicit here, as this is te // The bag expression is implicit here, as this is te
// second argument to the json operator // second argument to the json operator
Aggregations: ast::Aggregation = { Aggregations: symbols::Aggregation = {
"count" "(" <d:"distinct"?> <s:Selector> ")" => { "count" "(" <d:"distinct"?> <s:Selector> ")" => {
if let Some(_) = d { if let Some(_) = d {
Aggregation::Count(true, s) symbols::Aggregation::Count(true, s)
} else { } else {
Aggregation::Count(false, s) symbols::Aggregation::Count(false, s)
} }
}, },
"sum" "(" <Selector> ")" => "sum" "(" <Selector> ")" =>
Aggregation::Sum(<>), symbols::Aggregation::Sum(<>),
"min" "(" <Selector> ")" => "min" "(" <Selector> ")" =>
Aggregation::Min(<>), symbols::Aggregation::Min(<>),
"max" "(" <Selector> ")" => "max" "(" <Selector> ")" =>
Aggregation::Max(<>), symbols::Aggregation::Max(<>),
}; };
//*********************************************************************/ //*********************************************************************/
@@ -116,7 +137,7 @@ Aggregations: ast::Aggregation = {
pub Filters = { Bags }; pub Filters = { Bags };
// All these expressions generate bags. // All these expressions generate bags.
Bags: ast::Bag = { Bags: symbols::Bag = {
// Bag Operators // Bag Operators
Distinct, Distinct,
Filter, Filter,
@@ -129,49 +150,65 @@ Bags: ast::Bag = {
Outside, Outside,
// When used directly here, the inside() operation on the shape is // When used directly here, the inside() operation on the shape is
// implied. // implied.
Shapes => Bag::Inside(<>) Shapes => symbols::Bag::Inside(<>)
}; };
//*********************************************************************/ //*********************************************************************/
// BAG OPERATORS */ // BAG OPERATORS */
//*********************************************************************/ //*********************************************************************/
Distinct: ast::Bag = { Distinct: symbols::Bag = {
"distinct" "(" <Bags> ")" => "distinct" "(" <Bags> ")" =>
Bag::Distinct(Box::new(<>)) symbols::Bag::Distinct(Box::new(<>))
}; };
// Returns all the points which are NOT part of the bag. // Returns all the points which are NOT part of the bag.
Complement: ast::Bag = { Complement: symbols::Bag = {
"complement" "(" <Bags> ")" => "complement" "(" <Bags> ")" =>
Bag::Complement(Box::new(<>)) symbols::Bag::Complement(Box::new(<>))
}; };
// Returns points which are part of both left and right sets. // Returns points which are part of both left and right sets.
Intersection: ast::Bag = { Intersection: symbols::Bag = {
"intersection" "(" <lh:Bags> "," <rh:Bags> ")" => "intersection" "(" <lh:Bags> "," <rh:Bags> ")" =>
Bag::Intersection(Box::new(lh), Box::new(rh)) symbols::Bag::Intersection(Box::new(lh), Box::new(rh))
}; };
// Returns points which are either part of left or right sets // Returns points which are either part of left or right sets
// (or both). // (or both).
Union: ast::Bag = { Union: symbols::Bag = {
"union" "(" <lh:Bags> "," <rh:Bags> ")" => "union" "(" <lh:Bags> "," <rh:Bags> ")" =>
Bag::Union(Box::new(lh), Box::new(rh)) symbols::Bag::Union(Box::new(lh), Box::new(rh))
}; };
// Filters point so that points part of the resulting bag respect // Filters point so that points part of the resulting bag respect
// the predicate. // the predicate.
Filter: ast::Bag = { Filter: symbols::Bag = {
// "filter" "(" <p:Predicates> "," <b:Bags> ")" => // "filter" "(" <p:Predicates> "," <b:Bags> ")" =>
"filter" "(" <b:Bags> ")" => "filter" "(" <b:Bags> ")" =>
Bag::Filter(None, Some(Box::new(b))), symbols::Bag::Filter(None, Box::new(b)),
"filter" "(" <p:Predicates> <b:("," <Bags> )?> ")" => match b { "filter" "(" <p:Predicates> <b:("," <Bags> )?> ")" => match b {
Some(b) => Bag::Filter(Some(p), Some(Box::new(b))), Some(b) => symbols::Bag::Filter(Some(p), Box::new(b)),
None => Bag::Filter(Some(p), None), None => {
let v = space::bounding_box(space::name());
let mut positions = Vec::new();
for point in v {
let mut position = Vec::new();
for c in point {
position.push(symbols::LiteralNumber::Int(*c))
}
positions.push(symbols::LiteralPosition(position));
}
let bb = symbols::Shape::HyperRectangle(
space::name().clone(),
positions
);
symbols::Bag::Filter(Some(p), Box::new(symbols::Bag::Inside(bb)))
}
} }
}; };
Predicates: ast::Predicate = { Predicates: symbols::Predicate = {
Less, Less,
Greater, Greater,
Equal, Equal,
@@ -180,41 +217,41 @@ Predicates: ast::Predicate = {
Or Or
}; };
Less: ast::Predicate = { Less: symbols::Predicate = {
"<" "(" <v:Positions> "," <literal:Position> ")" => { "<" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Less(v, literal) symbols::Predicate::Less(v, literal)
} }
}; };
Greater: ast::Predicate = { Greater: symbols::Predicate = {
">" "(" <v:Positions> "," <literal:Position> ")" => { ">" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Greater(v, literal) symbols::Predicate::Greater(v, literal)
} }
}; };
Equal: ast::Predicate = { Equal: symbols::Predicate = {
"=" "(" <v:Positions> "," <literal:Position> ")" => { "=" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Equal(v, literal) symbols::Predicate::Equal(v, literal)
} }
}; };
Not: ast::Predicate = { Not: symbols::Predicate = {
"!" "(" <p:Predicates> ")" => "!" "(" <p:Predicates> ")" =>
Predicate::Not(Box::new(p)) symbols::Predicate::Not(Box::new(p))
}; };
And: ast::Predicate = { And: symbols::Predicate = {
"&" "(" <lh:Predicates> "," <rh:Predicates> ")" => "&" "(" <lh:Predicates> "," <rh:Predicates> ")" =>
Predicate::And(Box::new(lh), Box::new(rh)) symbols::Predicate::And(Box::new(lh), Box::new(rh))
}; };
Or: ast::Predicate = { Or: symbols::Predicate = {
"|" "(" <lh:Predicates> "," <rh:Predicates> ")" => "|" "(" <lh:Predicates> "," <rh:Predicates> ")" =>
Predicate::Or(Box::new(lh), Box::new(rh)) symbols::Predicate::Or(Box::new(lh), Box::new(rh))
}; };
// Arbitrary bag of positions. // Arbitrary bag of positions.
Bag: ast::Bag = { Bag: symbols::Bag = {
"bag" "{" <elem:Bags> <list:("," Bags )*> "}" => { "bag" "{" <elem:Bags> <list:("," Bags )*> "}" => {
let mut bags = vec![elem]; let mut bags = vec![elem];
@@ -222,7 +259,7 @@ Bag: ast::Bag = {
bags.push(b); bags.push(b);
} }
Bag::Bag(bags) symbols::Bag::Bag(bags)
} }
}; };
@@ -240,15 +277,15 @@ Bag: ast::Bag = {
// of the cube [0,0], [1,1]. // of the cube [0,0], [1,1].
// Returns the set of points outside the shape, (face included) // Returns the set of points outside the shape, (face included)
Outside: ast::Bag = { Outside: symbols::Bag = {
"outside" "(" <Shapes> ")" => "outside" "(" <Shapes> ")" =>
Bag::Outside(<>) symbols::Bag::Outside(<>)
}; };
// Returns the set of points inside the shape, (face included) // Returns the set of points inside the shape, (face included)
Inside: ast::Bag = { Inside: symbols::Bag = {
"inside" "(" <Shapes> ")" => "inside" "(" <Shapes> ")" =>
Bag::Inside(<>) symbols::Bag::Inside(<>)
}; };
//*********************************************************************/ //*********************************************************************/
@@ -257,7 +294,7 @@ Inside: ast::Bag = {
// Shapes are defined in terms of POSITION, a.k.a a LiteralPosition // 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. // value, which is not a POSITIONS, which might be a filter for example.
Shapes: ast::Shape = { Shapes: symbols::Shape = {
Point, Point,
HyperRectangle, HyperRectangle,
HyperSphere, HyperSphere,
@@ -266,30 +303,50 @@ Shapes: ast::Shape = {
// If the hyperrectangle is aligned with the axes, then two points are // If the hyperrectangle is aligned with the axes, then two points are
// enough, if not we need all the points to be specified. // enough, if not we need all the points to be specified.
HyperRectangle: ast::Shape = { HyperRectangle: symbols::Shape = {
"hyperrectangle" "{" "hyperrectangle" "{"
<l:Position> "," <h:Position> <l:Position> "," <h:Position>
<list:( "," Position "," Position )*> <list:( "," Position "," Position )*>
<rs:( "," <String> )?>
"}" => { "}" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
let mut pos = vec![l, h]; let mut pos = vec![l, h];
for (_, lh, _, rh) in list.iter() { for (_, lh, _, rh) in list.iter() {
pos.push(lh.clone()); pos.push(lh.clone());
pos.push(rh.clone()); pos.push(rh.clone());
} }
Shape::HyperRectangle(pos) symbols::Shape::HyperRectangle(space_id, pos)
} }
}; };
// A hypersphere is defined by its center and a radius, independantly // A hypersphere is defined by its center and a radius, independantly
// of the number of dimensions of the space. // of the number of dimensions of the space.
HyperSphere: ast::Shape = { HyperSphere: symbols::Shape = {
"hypersphere" "{" <c:Position> "," <r:PositiveNumber> "}" => "hypersphere" "{"
Shape::HyperSphere(c, r) <c:Position> "," <r:PositiveNumber>
<rs:( "," <String> )?>
"}" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
}; };
Point: ast::Shape = { symbols::Shape::HyperSphere(space_id, c, r)
"point" "{" <Position> "}" => }
Shape::Point(<>) };
Point: symbols::Shape = {
"point" "{" <pos:Position> <rs:( "," <String> )?> "}" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
symbols::Shape::Point(space_id, pos)
}
}; };
// Define a shape as the non-zero values in a NIfTI object, defined by // Define a shape as the non-zero values in a NIfTI object, defined by
@@ -299,14 +356,18 @@ Point: ast::Shape = {
// rotation: [ position+ ], // Optional, no rotation by default // rotation: [ position+ ], // Optional, no rotation by default
// bytes: uri(STRING) // uri to the NIfTI object // bytes: uri(STRING) // uri to the NIfTI object
// } // }
Nifti: ast::Shape = { Nifti: symbols::Shape = {
"nifti" "{" "nifti" "{"
String "," <o:( Position "," )?>
( Position "," )? <rotation:( "[" Position ( "," Position)* "]" "," )?>
( "[" Position ( "," Position)* "]" "," )? <data:ByteProvider>
ByteProvider <rs:( "," <String> )?>
"}" => { "}" => {
Shape::Nifti() let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
symbols::Shape::Nifti(space_id)
} }
}; };
@@ -321,44 +382,44 @@ ByteProvider = { "uri" "(" String ")" };
// Always returns a vector of numbers, a.k.a a position (a scalar will // Always returns a vector of numbers, a.k.a a position (a scalar will
// be represented as a vector of one element) // be represented as a vector of one element)
Positions: ast::Position = { Positions: symbols::Position = {
StrCmpICase, StrCmpICase,
StrCmp, StrCmp,
Selector => Position::Selector(<>), Selector => symbols::Position::Selector(<>),
Position => Position::LiteralPosition(<>) Position => symbols::Position::Literal(<>)
}; };
// Compare lexicographically two strings, and returns a `position`: // Compare lexicographically two strings, and returns a `position`:
// [-1] : String is lexicographically before, // [-1] : String is lexicographically before,
// [ 0] : is equal, // [ 0] : is equal,
// [ 1] : is after. // [ 1] : is after.
StrCmp: ast::Position = { StrCmp: symbols::Position = {
"str_cmp" "(" <s:Selector> "," <v:String> ")" => { "str_cmp" "(" <s:Selector> "," <v:String> ")" => {
Position::StrCmp(s, v) symbols::Position::StrCmp(s, v)
} }
}; };
// Same, but case insensitive. // Same, but case insensitive.
StrCmpICase: ast::Position = { StrCmpICase: symbols::Position = {
"str_cmp_ignore_case" "(" <s:Selector> "," <v:String> ")" => { "str_cmp_ignore_case" "(" <s:Selector> "," <v:String> ")" => {
Position::StrCmpICase(s, v) symbols::Position::StrCmpICase(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. // 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 = { Selector: symbols::LiteralSelector = {
( <Field> )+ ( <Field> )+ => symbols::LiteralSelector(<>)
}; };
Position: ast::LiteralPosition = { Position: symbols::LiteralPosition = {
"[" <element:Number> <list:( "," <Number>)*> "]" => { "[" <element:Number> <list:( "," <Number>)*> "]" => {
let mut pos: LiteralPosition = vec![element]; let mut pos = vec![element];
for e in list.iter() { for e in list.iter() {
pos.push(e.clone()); pos.push(e.clone());
} }
pos symbols::LiteralPosition(pos)
} }
}; };
@@ -370,72 +431,75 @@ Position: ast::LiteralPosition = {
// 1. start with a dot ('.') // 1. start with a dot ('.')
// 2. optionnally followed by a field name consisting of a letter or // 2. optionnally followed by a field name consisting of a letter or
// underscore, followed by letters, numbers or underscore, // underscore, followed by letters, numbers or underscore,
// 3. optionnally followed by brakets enclosing an natural number // 3. optionnally followed by brakets enclosing a natural number
// denoting an offset in a list or array. // denoting an offset in a list or array.
Field: Field = { Field: symbols::Field = {
<n:r"[.]([a-zA-Z_][[a-zA-Z_0-9]]*)?([\[](0|[1-9][0-9]*)[\]])?"> => { <n:r"[.]([a-zA-Z_][[a-zA-Z_0-9]]*)?([\[](0|[1-9][0-9]*)[\]])?"> => {
if let Some(pos) = n.rfind('[') { if let Some(pos) = n.rfind('[') {
let name = &n[1..pos]; let name = &n[1..pos];
let index = &n[(pos+1)..(n.len()-1)]; let index = &n[(pos+1)..(n.len()-1)];
let index = usize::from_str(index).unwrap(); let index = usize::from_str(index).unwrap();
Field(String::from(name), Some(index)) symbols::Field(String::from(name), Some(index))
} else { } else {
let name = &n[1..]; let name = &n[1..];
Field(String::from(name), None) symbols::Field(String::from(name), None)
} }
} }
}; };
String: String = { String: String = {
r#"["]([\\](["\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\u0000-\u001F])*["]"# => r#"["]([\\](["\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\u0000-\u001F])*["]"# => {
String::from(<>) let s = <>;
let l = s.len() - 1;
s[1..l].to_string()
}
}; };
//*********************************************************************/ //*********************************************************************/
// TOKENS - NUMBERS */ // TOKENS - NUMBERS */
//*********************************************************************/ //*********************************************************************/
// We define 3 kinds of number, to avoid ambiguities in the rules. // We define 3 kinds of number, to avoid ambiguities in the rules.
JsonNumber: ast::JsonValue = { JsonNumber: symbols::JsonValue = {
<s:"-"?> <v:Num> => match s { <s:"-"?> <v:Num> => match s {
None => JsonValue::JsonNumber(v), None => symbols::JsonValue::JsonNumber(v),
Some(_) => match v { Some(_) => match v {
LiteralNumber::Int(x) => JsonValue::JsonNumber(LiteralNumber::Int(-x)), symbols::LiteralNumber::Int(x) => symbols::JsonValue::JsonNumber(symbols::LiteralNumber::Int(-x)),
LiteralNumber::Float(x) => JsonValue::JsonNumber(LiteralNumber::Float(-x)) symbols::LiteralNumber::Float(x) => symbols::JsonValue::JsonNumber(symbols::LiteralNumber::Float(-x))
} }
} }
}; };
PositiveNumber: ast::LiteralNumber = { "+"? <v:Num> => v }; PositiveNumber: symbols::LiteralNumber = { "+"? <v:Num> => v };
Number: ast::LiteralNumber = { Number: symbols::LiteralNumber = {
"+" <v:Num> => v, "+" <v:Num> => v,
"-" <v:Num> => match v { "-" <v:Num> => match v {
LiteralNumber::Int(x) => LiteralNumber::Int(-x), symbols::LiteralNumber::Int(x) => symbols::LiteralNumber::Int(-x),
LiteralNumber::Float(x) => LiteralNumber::Float(-x) symbols::LiteralNumber::Float(x) => symbols::LiteralNumber::Float(-x)
}, },
<v:Num> => v <v:Num> => v
}; };
Num: ast::LiteralNumber = { Num: symbols::LiteralNumber = {
r"0([.][0-9]+([eE][+\-]?(0|[1-9][0-9]*))?)?" r"0([.][0-9]+([eE][+\-]?(0|[1-9][0-9]*))?)?"
=> { => {
if let Ok(v) = i64::from_str(<>) { if let Ok(v) = i64::from_str(<>) {
LiteralNumber::Int(v) symbols::LiteralNumber::Int(v)
} else { } else {
// Either parsing as a float succeed or we pass along // Either parsing as a float succeed or we pass along
// the error // the error
LiteralNumber::Float(f64::from_str(<>).unwrap()) symbols::LiteralNumber::Float(f64::from_str(<>).unwrap())
} }
}, },
r"[1-9][0-9]*([.][0-9]+)?([eE][+\-]?(0|[1-9][0-9]*))?" r"[1-9][0-9]*([.][0-9]+)?([eE][+\-]?(0|[1-9][0-9]*))?"
=> { => {
if let Ok(v) = i64::from_str(<>) { if let Ok(v) = i64::from_str(<>) {
LiteralNumber::Int(v) symbols::LiteralNumber::Int(v)
} else { } else {
// Either parsing as a float succeed or we pass along // Either parsing as a float succeed or we pass along
// the error // the error
LiteralNumber::Float(f64::from_str(<>).unwrap()) symbols::LiteralNumber::Float(f64::from_str(<>).unwrap())
} }
} }
}; };

319
src/symbols.rs Normal file
View File

@@ -0,0 +1,319 @@
use std::cmp::Ordering;
use super::database::space;
pub use super::types::*;
/**********************************************************************/
/* FORMATTING DATA */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Projection {
Nifti(String, LiteralSelector, Bag),
JSON(String, JsonValue, Bag),
}
impl Projection {
pub fn space(&self) -> &String {
match self {
Projection::Nifti(space, _, _) => &space,
Projection::JSON(space, _, _) => &space,
}
}
}
// JSON FORMAT
#[derive(Clone, Debug)]
pub enum JsonValue {
String(String),
JsonNumber(LiteralNumber),
Bool(bool),
Null,
Object(Vec<(String, JsonValue)>),
Array(Vec<JsonValue>),
Selector(LiteralSelector),
Aggregation(Aggregation),
}
#[derive(Clone, Debug)]
pub enum Aggregation {
Count(bool, LiteralSelector),
Sum(LiteralSelector),
Min(LiteralSelector),
Max(LiteralSelector),
}
// NIFTI
#[derive(Clone, Debug)]
struct Transform {
reference: String,
offset: Vec<LiteralNumber>,
rotation: Vec<Vec<LiteralNumber>>,
}
/**********************************************************************/
/* SELECTING / FILTERING DATA */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Bag {
Distinct(Box<Bag>),
Filter(Option<Predicate>, Box<Bag>),
Complement(Box<Bag>),
Intersection(Box<Bag>, Box<Bag>),
Union(Box<Bag>, Box<Bag>),
Bag(Vec<Bag>),
Inside(Shape),
Outside(Shape),
}
impl Bag {
pub fn space(&self) -> &String {
match self {
Bag::Distinct(bag) => bag.space(),
Bag::Filter(_, bag) => bag.space(),
Bag::Complement(bag) => bag.space(),
Bag::Intersection(lh, _) => {
// We are assuming lh and rh are in the same space.
// Checked as part of the validation.
lh.space()
}
Bag::Union(lh, _) => {
// We are assuming lh and rh are in the same space.
// Checked as part of the validation.
lh.space()
}
Bag::Bag(_) => {
// Bags can be defined in different spaces, thus the output is
// always in the universe space.
space::name()
}
Bag::Inside(shape) => shape.space(),
Bag::Outside(shape) => shape.space(),
}
}
}
/**********************************************************************/
/* BAG OPERATORS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Predicate {
Less(Position, LiteralPosition),
Greater(Position, LiteralPosition),
Equal(Position, LiteralPosition),
Not(Box<Predicate>),
And(Box<Predicate>, Box<Predicate>),
Or(Box<Predicate>, Box<Predicate>),
}
/**********************************************************************/
/* SPATIAL OPERATORS */
/**********************************************************************/
/**********************************************************************/
/* SHAPES */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Shape {
Point(String, LiteralPosition),
HyperRectangle(String, Vec<LiteralPosition>),
HyperSphere(String, LiteralPosition, LiteralNumber),
Nifti(String),
}
impl Shape {
pub fn space(&self) -> &String {
match self {
Shape::Point(space, _) => space,
Shape::HyperRectangle(space, _) => space,
Shape::HyperSphere(space, _, _) => space,
Shape::Nifti(space) => space,
}
}
pub fn volume(&self) -> f64 {
match self {
Shape::Point(_, _) => 1.0, // This is the smallest non-zero volume possible //TODO DOUBLE CHECK IT IS TRUE
Shape::HyperRectangle(_space, pos) => {
//TODO: At this time, only aligned to the axes, defined by two points, hyperrectangles are supported.
assert_eq!(pos.len(), 2);
// We assume the first position is the low point, the second is
// the high point, this being true for each dimension. As we add
// an even number of points per extra dimension, we assume the
// last group is the high end, and the last position is the
// highest point.
let mut volume = 1.0;
let LiteralPosition(low) = &pos[0];
let LiteralPosition(high) = &pos[pos.len() - 1];
// For each dimension, multiply by the length in that dimension
for i in 0..low.len() {
let l = match low[i] {
LiteralNumber::Int(x) => x as f64,
LiteralNumber::Float(x) => x,
};
let h = match high[i] {
LiteralNumber::Int(x) => x as f64,
LiteralNumber::Float(x) => x,
};
let length = if h > l { h - l } else { l - h };
volume *= length;
}
volume
}
Shape::HyperSphere(_space, pos, r) => {
// Formula from https://en.wikipedia.org/wiki/N-sphere#/media/File:N_SpheresVolumeAndSurfaceArea.png
let LiteralPosition(p) = pos;
let k = p.len(); // Number of dimensions.
let r = match *r {
LiteralNumber::Int(x) => x as f64,
LiteralNumber::Float(x) => x,
};
let pi = std::f64::consts::PI;
let factor = 2.0 * pi;
// Set starting values for the coefficient
let mut a = 2.0;
let mut i = 1;
if (k % 2) == 0 {
a = pi;
i = 2;
}
while i < k {
i += 2;
a *= factor;
a /= i as f64;
}
a * r.powi(i as i32)
}
Shape::Nifti(_) => unimplemented!(),
}
}
}
/**********************************************************************/
/* POSITIONS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub enum Position {
StrCmpICase(LiteralSelector, String),
StrCmp(LiteralSelector, String),
Selector(LiteralSelector),
Literal(LiteralPosition),
}
/**********************************************************************/
/* Literals / TOKENS */
/**********************************************************************/
#[derive(Clone, Debug)]
pub struct Field(pub String, pub Option<usize>);
#[derive(Clone, Debug)]
pub enum LiteralNumber {
Int(i64),
Float(f64),
}
impl PartialEq for LiteralNumber {
fn eq(&self, other: &LiteralNumber) -> bool {
match self {
LiteralNumber::Int(l) => match other {
LiteralNumber::Int(r) => l == r,
LiteralNumber::Float(_) => false,
},
LiteralNumber::Float(l) => match other {
LiteralNumber::Int(r) => l == &(*r as f64),
LiteralNumber::Float(r) => l == r,
},
}
}
}
#[derive(Clone, Debug)]
pub struct LiteralPosition(pub Vec<LiteralNumber>);
impl LiteralPosition {
pub fn get_type(&self) -> LiteralTypes {
let Self(v) = self;
let mut t = Vec::new();
for n in v {
t.push(match n {
LiteralNumber::Int(_) => LiteralTypes::Int,
LiteralNumber::Float(_) => LiteralTypes::Float,
});
}
LiteralTypes::Vector(t)
}
pub fn length(&self) -> f64 {
let LiteralPosition(v) = self;
let mut a = 0.0;
for x in v {
let x = match x {
LiteralNumber::Int(x) => (*x) as f64,
LiteralNumber::Float(x) => *x,
};
a += x * x;
}
a
}
}
impl PartialOrd for LiteralPosition {
fn partial_cmp(&self, other: &LiteralPosition) -> Option<Ordering> {
let LiteralPosition(lh) = self;
let LiteralPosition(rh) = other;
if lh.len() != rh.len() {
None
} else {
// Order is defined by the geometric length of the vector between the Origin and the point.
let l = self.length();
let r = other.length();
l.partial_cmp(&r)
}
}
}
impl PartialEq for LiteralPosition {
fn eq(&self, other: &LiteralPosition) -> bool {
let LiteralPosition(lh) = self;
let LiteralPosition(rh) = other;
if lh.len() == rh.len() {
for i in 0..lh.len() {
if lh[i] != rh[i] {
return false;
}
}
true
} else {
false
}
}
}
#[derive(Clone, Debug)]
pub struct LiteralSelector(pub Vec<Field>);
impl LiteralSelector {
pub fn get_type(&self) -> LiteralTypes {
// FIXME: Pretend for now that everything is a number, needs to be actually looked up in data model.
LiteralTypes::Int
}
}

View File

@@ -1,4 +1,5 @@
//use super::ast; #[cfg(test)]
mod parsing {
/******************************************************************/ /******************************************************************/
/* FORMATTING DATA */ /* FORMATTING DATA */
@@ -49,12 +50,14 @@ mod query {
assert!(p.parse("nifti(point{[0]})").is_ok()); assert!(p.parse("nifti(point{[0]})").is_ok());
assert!(p.parse("nifti(.properties.id, point{[0]})").is_ok()); assert!(p.parse("nifti(.properties.id, point{[0]})").is_ok());
unimplemented!(); // TO REMEMBER SOME WORK IS DUE HERE.
//FIXME: THIS SHOULD BE ALLOWED //FIXME: THIS SHOULD BE ALLOWED
assert!(p.parse("nifti(2, point{[0]})").is_ok()); assert!(p.parse("nifti(2, point{[0]})").is_ok());
assert!(p.parse("nifti(2.23, point{[0]})").is_ok()); assert!(p.parse("nifti(2.23, point{[0]})").is_ok());
//FIXME: SYNTAX OK, TYPE NOT //FIXME: SYNTAX OK, TYPE NOT
assert!(p.parse("nifti(\"asd\", point{[0]})").is_err()); assert!(p.parse("nifti(point{[0], \"space\"})").is_err());
} }
#[test] #[test]
@@ -175,7 +178,9 @@ mod query {
.parse(format!("json({}, point{{[0]}})", "[0, 1]").as_str()) .parse(format!("json({}, point{{[0]}})", "[0, 1]").as_str())
.is_ok()); .is_ok());
assert!(p assert!(p
.parse(format!("json({}, point{{[0]}})", "[{\"field\": 0}, {\"field\": 1}]").as_str()) .parse(
format!("json({}, point{{[0]}})", "[{\"field\": 0}, {\"field\": 1}]").as_str()
)
.is_ok()); .is_ok());
} }
@@ -183,8 +188,6 @@ mod query {
fn aggregations() { fn aggregations() {
let p = query_parser(); let p = query_parser();
//FIXME: ADD STUFF
// count () // count ()
assert!(p assert!(p
.parse(format!("json({}, point{{[0]}})", "count()").as_str()) .parse(format!("json({}, point{{[0]}})", "count()").as_str())
@@ -522,10 +525,15 @@ mod filters {
assert!(p.parse("hyperrectangle{[0], [1], [2], [3], [4]}").is_err()); assert!(p.parse("hyperrectangle{[0], [1], [2], [3], [4]}").is_err());
assert!(p.parse("hyperrectangle{[0], [1]}").is_ok()); assert!(p.parse("hyperrectangle{[0], [1]}").is_ok());
assert!(p.parse("hyperrectangle{[0], [1], \"space\"}").is_ok());
assert!(p.parse("hyperrectangle{[0], [1], [2], [3]}").is_ok());
assert!(p.parse("hyperrectangle{[0], [1], [2], [3]}").is_ok()); assert!(p.parse("hyperrectangle{[0], [1], [2], [3]}").is_ok());
assert!(p assert!(p
.parse("hyperrectangle{[0], [1], [2], [3], [4], [5]}") .parse("hyperrectangle{[0], [1], [2], [3], [4], [5]}")
.is_ok()); .is_ok());
assert!(p
.parse("hyperrectangle{[0], [1], [2], [3], [4], [5], \"space\"}")
.is_ok());
} }
#[test] #[test]
@@ -537,6 +545,7 @@ mod filters {
assert!(p.parse("hypersphere{[0]}").is_err()); assert!(p.parse("hypersphere{[0]}").is_err());
assert!(p.parse("hypersphere{[0], 23}").is_ok()); assert!(p.parse("hypersphere{[0], 23}").is_ok());
assert!(p.parse("hypersphere{[0], 23, \"space\"}").is_ok());
} }
#[test] #[test]
@@ -547,20 +556,19 @@ mod filters {
assert!(p.parse("point{[]}").is_err()); assert!(p.parse("point{[]}").is_err());
assert!(p.parse("point{[0]}").is_ok()); assert!(p.parse("point{[0]}").is_ok());
assert!(p.parse("point{[0], \"space\"}").is_ok());
} }
#[test] #[test]
fn nifti() { fn nifti() {
let p = filters_parser(); let _p = filters_parser();
//FIXME: ADD STUFF unimplemented!();
assert!(false);
} }
#[test] #[test]
fn byte_provider() { fn byte_provider() {
let p = filters_parser(); let _p = filters_parser();
//FIXME: ADD STUFF unimplemented!();
assert!(false);
} }
/* Not useful to test this rule /* Not useful to test this rule
@@ -603,7 +611,9 @@ mod filters {
let p = filters_parser(); let p = filters_parser();
assert!(p assert!(p
.parse(format!("filter(=({}, [1]), point{{[0]}})", "str_cmp(.field, \"\")").as_str()) .parse(
format!("filter(=({}, [1]), point{{[0]}})", "str_cmp(.field, \"\")").as_str()
)
.is_ok()); .is_ok());
assert!(p assert!(p
@@ -771,7 +781,7 @@ mod filters {
fn test_str_ok(p: &queries::FiltersParser, string: &str) { fn test_str_ok(p: &queries::FiltersParser, string: &str) {
let n = format!( let n = format!(
"{}{}{}", "{}{}{}",
"nifti{", string, ", uri(\"http://a.nifti.file\") }" "nifti{uri(\"http://a.nifti.file\"), ", string, " }"
); );
let n = n.as_str(); let n = n.as_str();
@@ -942,3 +952,4 @@ mod filters {
.is_err()); .is_err());
} }
} }
}

59
src/types.rs Normal file
View File

@@ -0,0 +1,59 @@
#[derive(Clone, Debug)]
pub enum LiteralTypes {
String,
Int,
Float,
Bag(Vec<LiteralTypes>), // List of types (heterogeneous)
Vector(Vec<LiteralTypes>), // List of coordinates types (heterogeneous)
Array(usize, Box<LiteralTypes>), // Length, homogeneous type
}
impl PartialEq for LiteralTypes {
fn eq(&self, other: &Self) -> bool {
match self {
LiteralTypes::String => match other {
LiteralTypes::String => true,
_ => false,
},
LiteralTypes::Int => match other {
LiteralTypes::Int => true,
_ => false,
},
LiteralTypes::Float => match other {
LiteralTypes::Float => true,
LiteralTypes::Int => true,
_ => false,
},
LiteralTypes::Bag(_) => match other {
LiteralTypes::Bag(_) => true,
_ => false,
},
LiteralTypes::Vector(v) => match other {
LiteralTypes::Vector(ov) => {
let n = v.len();
if n != ov.len() {
false
} else {
for i in 0..n {
if v[i] != ov[i] {
return false;
}
}
true
}
}
_ => false,
},
LiteralTypes::Array(n, t) => match other {
LiteralTypes::Array(on, ot) => {
if on == n {
t == ot
} else {
false
}
}
_ => false,
},
}
}
}

171
src/validators.rs Normal file
View File

@@ -0,0 +1,171 @@
use super::database::space;
use super::expression::Validator;
use super::symbols::*;
pub type ValidationResult = Result<LiteralTypes, String>;
impl Validator for Projection {
type ValidationResult = self::ValidationResult;
fn validate(&self) -> ValidationResult {
match self {
Projection::Nifti(_, _, _) => Err("not yet implemented".to_string()),
Projection::JSON(_, _format, bag) => bag.validate(),
//FIXME: Add support for projections
/* match format.validate() {
Ok(_) => bag.validate(),
Err(_) => Err(()),
}*/
}
}
}
impl Validator for Bag {
type ValidationResult = self::ValidationResult;
fn validate(&self) -> ValidationResult {
fn compare_bag_types(lh: &Box<Bag>, rh: &Box<Bag>) -> ValidationResult {
if lh.space().cmp(rh.space()) != std::cmp::Ordering::Equal {
return Err(format!(
"left and right sets are defined in different reference spaces: '{}' vs '{}'.",
lh.space(),
rh.space()
));
}
let l = lh.validate();
let r = rh.validate();
match &l {
Err(_) => l,
Ok(tl) => match r {
e @ Err(_) => e,
Ok(tr) => {
if tl != &tr {
Err(format!(
"Incoherent types between left and right sets: '{:?}' vs '{:?}'",
tl, &tr
))
} else {
l
}
}
},
}
}
match self {
Bag::Distinct(bag) => bag.validate(),
Bag::Filter(_, bag) => bag.validate(),
Bag::Complement(bag) => bag.validate(),
Bag::Intersection(lh, rh) => compare_bag_types(lh, rh),
Bag::Union(lh, rh) => compare_bag_types(lh, rh),
Bag::Bag(bags) => {
for b in bags {
let t = b.validate();
if t.is_err() {
return t;
}
}
Ok(space::get_type(space::name()).clone())
}
Bag::Inside(shape) => shape.validate(),
Bag::Outside(shape) => shape.validate(),
}
}
}
impl Validator for Shape {
type ValidationResult = self::ValidationResult;
fn validate(&self) -> ValidationResult {
match self {
Shape::Point(_, v) => v.validate(),
Shape::HyperRectangle(_space, pos) => {
let first = pos[0].get_type();
match pos.len() {
2 => {
if first != pos[1].get_type() {
Err(format!(
"HyperRectangle: Incompatible types in points definitions: '{:?}' vs '{:?}'",
first,
pos[1].get_type()
))
} else {
Ok(first)
}
}
_ => {
//FIXME: Implement arbitrary hypercube definition support. For now reject.
Err("not yet implemented".to_string())
/*
fn check_orthogonal(pos: &Vec<LiteralPosition>) -> bool {
let k = pos.len();
let mut raw_pos = vec![];
for s in pos {
match s {
LiteralTypes::Vector(n) => {
let mut v = vec![];
for c in n {
v.push(match c {
LiteralNumber::Int(x) => x as f64,
LiteralNumber::Float(x) => x,
});
}
raw_pos.push(v)
}
_ => pass,
}
}
for p in raw_pos {}
true
}
match &first {
LiteralTypes::Vector(n) => {
if 2usize.pow(n.len() as u32) == pos.len() {
// Check we have coherent types for the coordinates.
for point in pos {
if first != point.get_type() {
return Err(());
}
}
// We need to check the points define a shape with orthogonal faces.
if check_orthogonal(pos) {
Ok(first)
} else {
Err(())
}
} else {
Err(())
}
}
_ => Err(()),
}*/
}
}
}
Shape::HyperSphere(_, pos, _) => pos.validate(),
Shape::Nifti(_) => Err("not yet implemented".to_string()),
}
}
}
impl Validator for LiteralPosition {
type ValidationResult = self::ValidationResult;
fn validate(&self) -> ValidationResult {
Ok(self.get_type())
}
}
impl Validator for LiteralSelector {
type ValidationResult = self::ValidationResult;
fn validate(&self) -> ValidationResult {
Ok(self.get_type())
}
}