graphannis 0.10.1

This is a prototype for a new backend implementation of the ANNIS linguistic search and visualization system.
Documentation
use std::*;
use super::ast;
use std::rc::Rc;
use annis::operator::EdgeAnnoSearchSpec;
use annis::db::exec::nodesearch::NodeSearchSpec;
use annis::db::aql::operators::{
    OverlapSpec, 
    IdenticalCoverageSpec,
    PrecedenceSpec,
    DominanceSpec,
    PointingSpec,
    PartOfSubCorpusSpec,
    InclusionSpec,
    IdenticalNodeSpec,
};

grammar;

match {
    "tok" => TOK,
    "node" => NODE,
    "_=_" => IDENT_COV,
    "_ident_" => IDENT_NODE,
    "_o_" => OVERLAP,
    "_i_" => INCLUSION,
} else {
    r"[a-zA-Z_%][a-zA-Z0-9_\-%]*" => ID,
    r##"#[0-9]+"## => NODE_REF,
    r##"#[a-zA-Z][a-zA-Z0-9]*"## => VARIABLE_NODE_REF,
    r##"[a-zA-Z][a-zA-Z0-9]*#"## => VARIABLE_DEF,
    r"[0-9]+" => DIGITS,
    _
}

pub Disjunction : ast::Disjunction  = {
    <head:Conjunction> <tail:("|" Conjunction)*> => {
        let mut result = ast::Disjunction::new();
        for t in tail.into_iter() {
            result.push_front(t.1);
        }
        result.push_front(head);
        return result;
    },
}

Conjunction : ast::Conjunction = {
    <head:Factor> <tail:("&" Factor)*> => {
        let mut r = ast::Conjunction::new();
        for t in tail.into_iter() {
            for f in t.1.into_iter() {
                r.push_front(f);
            }
        }
        for f in head.into_iter() {
            r.push_front(f);
        }
        return r;
    },
}

Factor : Vec<ast::Factor> = {
    Literal => {
        let mut result = Vec::new();
        for l in <>.into_iter() {
            result.push(ast::Factor::Literal(l));
        }
        result
    },
    "(" <d:Disjunction> ")" => vec![ast::Factor::Disjunction(d)],
}

Literal : Vec<ast::Literal> = {
    // any node annotation search
    <start: @L> <var:(VARIABLE_DEF)?> <spec:NodeSearch> <end: @R> => {
        let pos = Some(ast::Pos{start, end});
        vec![ast::Literal::NodeSearch{pos, spec, variable: var.and_then(|s| Some(s[0..s.len()-1].to_string()))}]
    },
    // binary operator
    <start: @L> <lhs:Operand> <op:BinaryOpSpec> <rhs:Operand> <tail:(BinaryOpSpec Operand)*> <end: @R> => {
        let mut result : Vec<ast::Literal> = Vec::new();
        // TODO: can we get the position for each individual binary operator?
        let pos = ast::Pos {
            start, end
        };
        result.push(ast::Literal::BinaryOp{lhs: lhs.clone(), op, rhs: rhs.clone(), pos: Some(pos.clone())});
        
        let mut last_operand = rhs.clone();
        for t in tail.into_iter() {
            result.push(ast::Literal::BinaryOp{
                lhs: last_operand.clone(), 
                op: t.0, 
                rhs: t.1.clone(), 
                pos: Some(pos.clone())
            });
            last_operand = t.1;
        }

        return result;
    },
    // legacy meta-data query `meta::doc="..."`
    <start: @L> "meta::" <name:QName> "=" <text:TextSearch> <end: @R> => {
        let pos = ast::Pos {start, end};
        let spec = match text.1 { 
            ast::StringMatchType::Exact => {
                NodeSearchSpec::ExactValue {
                    ns: name.0,
                    name: name.1,
                    val: Some(text.0),
                    is_meta: true,
                }
            },
            ast::StringMatchType::Regex => {
                NodeSearchSpec::RegexValue {
                    ns: name.0,
                    name: name.1,
                    val: text.0,
                    is_meta: true,
                }
            },
        };
        vec![ast::Literal::LegacyMetaSearch{spec, pos}]
    },
}

Operand : ast::Operand = {
    NodeRef =>  ast::Operand::NodeRef(<>),
    <start:@L> <var:(VARIABLE_DEF)?> <spec:NodeSearch> <end:@R> => {
        let pos = ast::Pos {start, end};
        let spec = Rc::from(spec);
        let variable = var.and_then(|s| Some(s[0..s.len()-1].to_string()));
        ast::Operand::Literal{spec, pos, variable, }
    },
}

/// General search for annotation nodes
NodeSearch : NodeSearchSpec = {
    // searching for nodes with `node`
    NODE => NodeSearchSpec::AnyNode,
    // searching for tokens with `tok`
    TOK => NodeSearchSpec::AnyToken,
    // searching for a token value, e.g. tok="abc" or "abc"
    <tok_def:(TOK "=")?> <val:TextSearch>=> {
        // TODO: negation
        let spec = match val.1 { 
            ast::StringMatchType::Exact => {
                NodeSearchSpec::ExactTokenValue {
                    val: val.0,
                    leafs_only: tok_def.is_some(),
                }
            },
            ast::StringMatchType::Regex => {
                NodeSearchSpec::RegexTokenValue {
                    val: val.0,
                    leafs_only: tok_def.is_some(),
                }
            },
        };
        spec
    },
    // named annotation search with value, e.g. pos="NN"
    <name:QName> "=" <text:TextSearch> => {
        // TODO: negation
        let spec = match text.1 { 
            ast::StringMatchType::Exact => {
                NodeSearchSpec::ExactValue {
                    ns: name.0,
                    name: name.1,
                    val: Some(text.0),
                    is_meta: false,
                }
            },
            ast::StringMatchType::Regex => {
                NodeSearchSpec::RegexValue {
                    ns: name.0,
                    name: name.1,
                    val: text.0,
                    is_meta: false,
                }
            },
        };
        spec
    },
    // search for annotation name without value, e.g. pos
    <name:QName> => {
        let spec = NodeSearchSpec::ExactValue {
            ns: name.0,
            name: name.1,
            val: None,
            is_meta: false,
        };
        spec
    },
}

/// Node references like `#1` (using the index of the node) or `#abc`
/// (using explicit name for variable)
NodeRef : ast::NodeRef = {
    <v:NODE_REF> => ast::NodeRef::ID(v[1..].parse::<usize>().unwrap()),
    <v:VARIABLE_NODE_REF> => ast::NodeRef::Name(v[1..].to_string()),
}

/// Binary operators that take a LHS and RHS as argument, e.g. `#1 ->dep #2`
BinaryOpSpec : ast::BinaryOpSpec = {
    // Dominance (direct edge annotation)
    <type_def:r">([a-zA-Z_%][a-zA-Z0-9_\-%]*)?"> <anno:EdgeAnno> => {
        let name = type_def[">".len()..].to_string();
        ast::BinaryOpSpec::Dominance(DominanceSpec {
            name,
            min_dist: 1,
            max_dist: 1,
            edge_anno: Some(anno),
        })
    },
    // Dominance (without edge annotation)
    <type_def:r">([a-zA-Z_%][a-zA-Z0-9_\-%]*)?"> <range:(RangeSpec)?> => {
        let name = type_def[">".len()..].to_string();
        if let Some(range) = range {
            ast::BinaryOpSpec::Dominance(DominanceSpec {
                name,
                min_dist: range.min_dist,
                max_dist: range.max_dist,
                edge_anno: None,
            })
        } else {
            ast::BinaryOpSpec::Dominance(DominanceSpec {
                name,
                min_dist: 1,
                max_dist: 1,
                edge_anno: None,
            })
        }
    },
    // Pointing (direct with edge annotation)
    <type_def:r"->[a-zA-Z_%][a-zA-Z0-9_\-%]*"> <anno:EdgeAnno> => {
        let name = type_def["->".len()..].to_string();
        ast::BinaryOpSpec::Pointing(PointingSpec {
            name,
            min_dist: 1,
            max_dist: 1,
            edge_anno: Some(anno),
        })
    },
    // Pointing (without edge annotation)
    <type_def:r"->[a-zA-Z_%][a-zA-Z0-9_\-%]*"> <range:(RangeSpec)?> => {
        let name = type_def["->".len()..].to_string();
        if let Some(range) = range {
            ast::BinaryOpSpec::Pointing(PointingSpec {
                name,
                min_dist: range.min_dist,
                max_dist: range.max_dist,
                edge_anno: None,
            })
        } else {
            ast::BinaryOpSpec::Pointing(PointingSpec {
                name,
                min_dist: 1,
                max_dist: 1,
                edge_anno: None,
            })
        }
    },
    //Precedence
    <prec_def:r"\.([a-zA-Z_%][a-zA-Z0-9_\-%]*)?"> <range:(RangeSpec)?>  => { 
        let seg_name = prec_def[".".len()..].to_string();
        let segmentation = if seg_name.is_empty() {
            None
        } else {
            Some(seg_name)
        };
        if let Some(range) = range {
            ast::BinaryOpSpec::Precedence(PrecedenceSpec {
                segmentation,
                min_dist: range.min_dist,
                max_dist: range.max_dist,
            })
        } else {
            ast::BinaryOpSpec::Precedence(PrecedenceSpec {
                segmentation,
                min_dist: 1,
                max_dist: 1,
            })
        }
    },
    // Part of subcorpus
    "@" <range:(RangeSpec)?> => {
        if let Some(range) = range {
            ast::BinaryOpSpec::PartOfSubCorpus(PartOfSubCorpusSpec {
                min_dist: range.min_dist,
                max_dist: range.max_dist,
            })
        } else {
            ast::BinaryOpSpec::PartOfSubCorpus(PartOfSubCorpusSpec {
                min_dist: 1,
                max_dist: 1,
            })
        }
    },
    // Overlap
    OVERLAP => ast::BinaryOpSpec::Overlap(OverlapSpec {}),
    // Identical coverage
    IDENT_COV => ast::BinaryOpSpec::IdenticalCoverage(IdenticalCoverageSpec {}),
    // Inclusion
    INCLUSION => ast::BinaryOpSpec::Inclusion(InclusionSpec {}),
    // Identical node
    IDENT_NODE => ast::BinaryOpSpec::IdenticalNode(IdenticalNodeSpec {}),
    // TODO: add more operators
}

TextSearch: ast::TextSearch = {
    <v:r#""[^"]*""#> => ast::TextSearch(String::from(&v[1..v.len()-1]), ast::StringMatchType::Exact),
    // see https://stackoverflow.com/questions/37032620/regex-for-matching-a-string-literal-in-java 
    // for a example how to match escaped quotation characters
    <v:r#"/[^/\\]*(\\.[^/\\]*)*/"#> => ast::TextSearch(String::from(&v[1..v.len()-1]), ast::StringMatchType::Regex),
};

EdgeAnno: EdgeAnnoSearchSpec = {
    "[" <name:QName> "=" <val:TextSearch> "]"  => {
        // TODO: negation
        // TODO: multiple edge annotations
        
        let spec = match val.1 { 
            ast::StringMatchType::Exact => {
                EdgeAnnoSearchSpec::ExactValue {
                    ns: name.0,
                    name: name.1,
                    val: Some(val.0),
                }
            },
            ast::StringMatchType::Regex => {
                EdgeAnnoSearchSpec::RegexValue {
                    ns: name.0,
                    name: name.1,
                    val: val.0,
                }
            },
        };
        spec
    },
}

RangeSpec: ast::RangeSpec = {
    (",")? <min:DIGITS> "," <max:DIGITS> => ast::RangeSpec {
        min_dist: min.parse().unwrap_or_default(), 
        max_dist: max.parse().unwrap_or_default(),
    },
    (",")? <exact:DIGITS> => ast::RangeSpec {
        min_dist: exact.parse().unwrap_or_default(), 
        max_dist: exact.parse().unwrap_or_default(),
    },
    "*" => ast::RangeSpec {
        min_dist: 1, 
        max_dist: usize::max_value(),
    }  
    
}

QName: ast::QName = {
    <ns:ID> ":" <name:ID> => ast::QName(Some(String::from(ns)), String::from(name)),
    <name:ID> => ast::QName(None, String::from(name)),
};