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:
@@ -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"] }
|
||||||
|
|||||||
@@ -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
|
||||||
'}'
|
'}'
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
113
src/ast.rs
113
src/ast.rs
@@ -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
57
src/database/mod.rs
Normal 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
86
src/database/space.rs
Normal 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
314
src/executors.rs
Normal 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
15
src/expression.rs
Normal 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;
|
||||||
|
}
|
||||||
19
src/lib.rs
19
src/lib.rs
@@ -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;
|
||||||
|
|||||||
46
src/main.rs
46
src/main.rs
@@ -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
48
src/predictors.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
symbols::Shape::HyperSphere(space_id, c, r)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Point: ast::Shape = {
|
Point: symbols::Shape = {
|
||||||
"point" "{" <Position> "}" =>
|
"point" "{" <pos:Position> <rs:( "," <String> )?> "}" => {
|
||||||
Shape::Point(<>)
|
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
319
src/symbols.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
1835
src/tests.rs
1835
src/tests.rs
File diff suppressed because it is too large
Load Diff
59
src/types.rs
Normal file
59
src/types.rs
Normal 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
171
src/validators.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user