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"
regex = "0.2.1"
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
log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] }

View File

@@ -134,33 +134,38 @@ hyperrectangle
: 'hyperrectangle' '{'
position ',' position
( ',' position ',' position )*
( ',' STRING )?
'}'
;
/* A hypersphere is defined by its center and a radius, independantly
* of the number of dimensions of the space. */
hypersphere
: 'hypersphere' '{' position ( ',' positive_number ) '}'
: 'hypersphere' '{'
position
',' positive_number
( ',' STRING )?
'}'
;
point
: 'point' '{' position '}'
: 'point' '{' position ( ',' STRING )? '}'
;
/* Define a shape as the non-zero values in a NIfTI object, defined by
* nifti{
* spaceId: string,
* lower_corner: position, // Optional, default to the origin
* rotation: [ position+ ], // Optional, no rotation by default
* bytes: uri(STRING) // uri to the NIfTI object
* bytes: uri(STRING), // uri to the NIfTI object
* spaceId: string
* }
*/
nifti
: 'nifti' '{'
STRING ','
(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. */
nifti_operator
: 'nifti' '(' ( selector ',' )? bag_expression ')'
: 'nifti' '(' ( STRING ',' )? ( selector ',' )? bag_expression ')'
;
json_operator
: 'json' '(' jslt ',' bag_expression ')'
: 'json' '(' jslt ',' bag_expression ( ',' STRING )? ')'
;
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]
extern crate lalrpop_util;
lalrpop_mod!(pub queries); // synthesized by LALRPOP
pub mod ast;
pub use ast::*;
mod database;
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)]
mod tests;

View File

@@ -3,7 +3,8 @@ extern crate measure_time;
extern crate parser;
use parser::queries;
use parser::QueryParser;
use parser::{Executor, Predictor, Validator};
use std::io;
@@ -15,13 +16,13 @@ fn main() {
pretty_env_logger::init();
//let parser = queries::FiltersParser::new();
let parser = queries::QueryParser::new();
let parser = QueryParser::new();
loop {
println!();
info!("Expression to parse (type `quit` to exit): ");
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(0) => break, // Catch ^D
Ok(1) => continue, // Catch \n
@@ -34,11 +35,42 @@ fn main() {
}
let input = input.as_str();
let mut out;
{
debug_time!("Parsing");
out = parser.parse(input);
debug_time!("Interpretation");
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 crate::ast;
use crate::ast::*;
use crate::symbols;
use crate::database::space;
grammar;
@@ -10,7 +10,7 @@ grammar;
//*********************************************************************/
pub Query = { Projections? };
Projections: ast::Projection = {
Projections: symbols::Projection = {
NiftiOperator,
JsonOperator
};
@@ -19,18 +19,39 @@ Projections: ast::Projection = {
// each position where there is a point in bag_expression.
//
// If it is provided, it MUST resolve to a NUMBER.
NiftiOperator: ast::Projection = {
"nifti" "(" <s:( Selector "," )?> <b:Bags> ")" =>
NiftiOperator: symbols::Projection = {
"nifti" "("
<s:( Selector "," )?>
<b:Bags>
<rs:( "," <String> )?>
")" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
if let Some((sel, _)) = s {
Projection::Nifti(sel, b)
symbols::Projection::Nifti(space_id, sel, b)
} else {
Projection::Nifti(Vec::new(), b)
symbols::Projection::Nifti(space_id, symbols::LiteralSelector(Vec::new()), b)
}
}
};
JsonOperator: ast::Projection = {
"json" "(" <f:JsonValues> "," <b:Bags> ")" =>
Projection::JSON(f, b)
JsonOperator: symbols::Projection = {
"json" "("
<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
//
// Some of the parser / lexer rules are in the imported grammar as well.
JsonValues: ast::JsonValue = {
String => JsonValue::String(<>),
JsonValues: symbols::JsonValue = {
String => symbols::JsonValue::String(<>),
JsonNumber => <>,
JsonObj => <>,
JsonArray => <>,
"true" => JsonValue::Bool(true),
"false" => JsonValue::Bool(false),
"null" => JsonValue::Null,
"true" => symbols::JsonValue::Bool(true),
"false" => symbols::JsonValue::Bool(false),
"null" => symbols::JsonValue::Null,
// Support reference to values from the selected bag.
Selector => JsonValue::Selector(<>),
Aggregations => JsonValue::Aggregation(<>)
Selector => symbols::JsonValue::Selector(<>),
Aggregations => symbols::JsonValue::Aggregation(<>)
};
JsonObj: ast::JsonValue = {
JsonObj: symbols::JsonValue = {
"{" <exp:( JsonPair ( "," JsonPair )* )?> "}" => {
if let Some((elem, list)) = exp {
let mut values: Vec<(String, JsonValue)> = vec![elem];
let mut values = vec![elem];
for v in list {
let (_, pair) = v;
values.push(pair.clone());
}
JsonValue::Object(values)
symbols::JsonValue::Object(values)
} else {
JsonValue::Object(Vec::new())
symbols::JsonValue::Object(Vec::new())
}
}
};
JsonPair: (String, ast::JsonValue) = {
JsonPair: (String, symbols::JsonValue) = {
<s:String> ":" <v:JsonValues> => (s, v)
};
JsonArray: ast::JsonValue = {
JsonArray: symbols::JsonValue = {
"[" <exp:( JsonValues ( "," JsonValues )* )?> "]" => {
if let Some((elem, list)) = exp {
let mut values: Vec<JsonValue> = vec![elem];
let mut values = vec![elem];
for v in list.iter() {
let (_, val) = v;
values.push(val.clone());
}
JsonValue::Array(values)
symbols::JsonValue::Array(values)
} else {
JsonValue::Array(Vec::new())
symbols::JsonValue::Array(Vec::new())
}
}
};
// The bag expression is implicit here, as this is te
// second argument to the json operator
Aggregations: ast::Aggregation = {
Aggregations: symbols::Aggregation = {
"count" "(" <d:"distinct"?> <s:Selector> ")" => {
if let Some(_) = d {
Aggregation::Count(true, s)
symbols::Aggregation::Count(true, s)
} else {
Aggregation::Count(false, s)
symbols::Aggregation::Count(false, s)
}
},
"sum" "(" <Selector> ")" =>
Aggregation::Sum(<>),
symbols::Aggregation::Sum(<>),
"min" "(" <Selector> ")" =>
Aggregation::Min(<>),
symbols::Aggregation::Min(<>),
"max" "(" <Selector> ")" =>
Aggregation::Max(<>),
symbols::Aggregation::Max(<>),
};
//*********************************************************************/
@@ -116,7 +137,7 @@ Aggregations: ast::Aggregation = {
pub Filters = { Bags };
// All these expressions generate bags.
Bags: ast::Bag = {
Bags: symbols::Bag = {
// Bag Operators
Distinct,
Filter,
@@ -129,49 +150,65 @@ Bags: ast::Bag = {
Outside,
// When used directly here, the inside() operation on the shape is
// implied.
Shapes => Bag::Inside(<>)
Shapes => symbols::Bag::Inside(<>)
};
//*********************************************************************/
// BAG OPERATORS */
//*********************************************************************/
Distinct: ast::Bag = {
Distinct: symbols::Bag = {
"distinct" "(" <Bags> ")" =>
Bag::Distinct(Box::new(<>))
symbols::Bag::Distinct(Box::new(<>))
};
// Returns all the points which are NOT part of the bag.
Complement: ast::Bag = {
Complement: symbols::Bag = {
"complement" "(" <Bags> ")" =>
Bag::Complement(Box::new(<>))
symbols::Bag::Complement(Box::new(<>))
};
// Returns points which are part of both left and right sets.
Intersection: ast::Bag = {
Intersection: symbols::Bag = {
"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
// (or both).
Union: ast::Bag = {
Union: symbols::Bag = {
"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
// the predicate.
Filter: ast::Bag = {
Filter: symbols::Bag = {
// "filter" "(" <p:Predicates> "," <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 {
Some(b) => Bag::Filter(Some(p), Some(Box::new(b))),
None => Bag::Filter(Some(p), None),
Some(b) => symbols::Bag::Filter(Some(p), Box::new(b)),
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,
Greater,
Equal,
@@ -180,41 +217,41 @@ Predicates: ast::Predicate = {
Or
};
Less: ast::Predicate = {
Less: symbols::Predicate = {
"<" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Less(v, literal)
symbols::Predicate::Less(v, literal)
}
};
Greater: ast::Predicate = {
Greater: symbols::Predicate = {
">" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Greater(v, literal)
symbols::Predicate::Greater(v, literal)
}
};
Equal: ast::Predicate = {
Equal: symbols::Predicate = {
"=" "(" <v:Positions> "," <literal:Position> ")" => {
Predicate::Equal(v, literal)
symbols::Predicate::Equal(v, literal)
}
};
Not: ast::Predicate = {
Not: symbols::Predicate = {
"!" "(" <p:Predicates> ")" =>
Predicate::Not(Box::new(p))
symbols::Predicate::Not(Box::new(p))
};
And: ast::Predicate = {
And: symbols::Predicate = {
"&" "(" <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> ")" =>
Predicate::Or(Box::new(lh), Box::new(rh))
symbols::Predicate::Or(Box::new(lh), Box::new(rh))
};
// Arbitrary bag of positions.
Bag: ast::Bag = {
Bag: symbols::Bag = {
"bag" "{" <elem:Bags> <list:("," Bags )*> "}" => {
let mut bags = vec![elem];
@@ -222,7 +259,7 @@ Bag: ast::Bag = {
bags.push(b);
}
Bag::Bag(bags)
symbols::Bag::Bag(bags)
}
};
@@ -240,15 +277,15 @@ Bag: ast::Bag = {
// of the cube [0,0], [1,1].
// Returns the set of points outside the shape, (face included)
Outside: ast::Bag = {
Outside: symbols::Bag = {
"outside" "(" <Shapes> ")" =>
Bag::Outside(<>)
symbols::Bag::Outside(<>)
};
// Returns the set of points inside the shape, (face included)
Inside: ast::Bag = {
Inside: symbols::Bag = {
"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
// value, which is not a POSITIONS, which might be a filter for example.
Shapes: ast::Shape = {
Shapes: symbols::Shape = {
Point,
HyperRectangle,
HyperSphere,
@@ -266,30 +303,50 @@ Shapes: ast::Shape = {
// If the hyperrectangle is aligned with the axes, then two points are
// enough, if not we need all the points to be specified.
HyperRectangle: ast::Shape = {
HyperRectangle: symbols::Shape = {
"hyperrectangle" "{"
<l:Position> "," <h:Position>
<list:( "," Position "," Position )*>
<rs:( "," <String> )?>
"}" => {
let space_id = match rs {
Some(id) => id,
None => space::name().clone(),
};
let mut pos = vec![l, h];
for (_, lh, _, rh) in list.iter() {
pos.push(lh.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
// of the number of dimensions of the space.
HyperSphere: ast::Shape = {
"hypersphere" "{" <c:Position> "," <r:PositiveNumber> "}" =>
Shape::HyperSphere(c, r)
HyperSphere: symbols::Shape = {
"hypersphere" "{"
<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" "{" <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
@@ -299,14 +356,18 @@ Point: ast::Shape = {
// rotation: [ position+ ], // Optional, no rotation by default
// bytes: uri(STRING) // uri to the NIfTI object
// }
Nifti: ast::Shape = {
Nifti: symbols::Shape = {
"nifti" "{"
String ","
( Position "," )?
( "[" Position ( "," Position)* "]" "," )?
ByteProvider
<o:( Position "," )?>
<rotation:( "[" Position ( "," Position)* "]" "," )?>
<data: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
// be represented as a vector of one element)
Positions: ast::Position = {
Positions: symbols::Position = {
StrCmpICase,
StrCmp,
Selector => Position::Selector(<>),
Position => Position::LiteralPosition(<>)
Selector => symbols::Position::Selector(<>),
Position => symbols::Position::Literal(<>)
};
// Compare lexicographically two strings, and returns a `position`:
// [-1] : String is lexicographically before,
// [ 0] : is equal,
// [ 1] : is after.
StrCmp: ast::Position = {
StrCmp: symbols::Position = {
"str_cmp" "(" <s:Selector> "," <v:String> ")" => {
Position::StrCmp(s, v)
symbols::Position::StrCmp(s, v)
}
};
// Same, but case insensitive.
StrCmpICase: ast::Position = {
StrCmpICase: symbols::Position = {
"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.
Selector = {
( <Field> )+
Selector: symbols::LiteralSelector = {
( <Field> )+ => symbols::LiteralSelector(<>)
};
Position: ast::LiteralPosition = {
Position: symbols::LiteralPosition = {
"[" <element:Number> <list:( "," <Number>)*> "]" => {
let mut pos: LiteralPosition = vec![element];
let mut pos = vec![element];
for e in list.iter() {
pos.push(e.clone());
}
pos
symbols::LiteralPosition(pos)
}
};
@@ -370,72 +431,75 @@ Position: ast::LiteralPosition = {
// 1. start with a dot ('.')
// 2. optionnally followed by a field name consisting of a letter or
// underscore, followed by letters, numbers or underscore,
// 3. optionnally followed by brakets enclosing an natural number
// 3. optionnally followed by brakets enclosing a natural number
// 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]*)[\]])?"> => {
if let Some(pos) = n.rfind('[') {
let name = &n[1..pos];
let index = &n[(pos+1)..(n.len()-1)];
let index = usize::from_str(index).unwrap();
Field(String::from(name), Some(index))
symbols::Field(String::from(name), Some(index))
} else {
let name = &n[1..];
Field(String::from(name), None)
symbols::Field(String::from(name), None)
}
}
};
String: String = {
r#"["]([\\](["\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\u0000-\u001F])*["]"# =>
String::from(<>)
r#"["]([\\](["\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\u0000-\u001F])*["]"# => {
let s = <>;
let l = s.len() - 1;
s[1..l].to_string()
}
};
//*********************************************************************/
// TOKENS - NUMBERS */
//*********************************************************************/
// We define 3 kinds of number, to avoid ambiguities in the rules.
JsonNumber: ast::JsonValue = {
JsonNumber: symbols::JsonValue = {
<s:"-"?> <v:Num> => match s {
None => JsonValue::JsonNumber(v),
None => symbols::JsonValue::JsonNumber(v),
Some(_) => match v {
LiteralNumber::Int(x) => JsonValue::JsonNumber(LiteralNumber::Int(-x)),
LiteralNumber::Float(x) => JsonValue::JsonNumber(LiteralNumber::Float(-x))
symbols::LiteralNumber::Int(x) => symbols::JsonValue::JsonNumber(symbols::LiteralNumber::Int(-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> => match v {
LiteralNumber::Int(x) => LiteralNumber::Int(-x),
LiteralNumber::Float(x) => LiteralNumber::Float(-x)
symbols::LiteralNumber::Int(x) => symbols::LiteralNumber::Int(-x),
symbols::LiteralNumber::Float(x) => symbols::LiteralNumber::Float(-x)
},
<v:Num> => v
};
Num: ast::LiteralNumber = {
Num: symbols::LiteralNumber = {
r"0([.][0-9]+([eE][+\-]?(0|[1-9][0-9]*))?)?"
=> {
if let Ok(v) = i64::from_str(<>) {
LiteralNumber::Int(v)
symbols::LiteralNumber::Int(v)
} else {
// Either parsing as a float succeed or we pass along
// 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]*))?"
=> {
if let Ok(v) = i64::from_str(<>) {
LiteralNumber::Int(v)
symbols::LiteralNumber::Int(v)
} else {
// Either parsing as a float succeed or we pass along
// 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
}
}

File diff suppressed because it is too large Load Diff

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())
}
}