Added FCS ExpressionGroup.

Change-Id: I31680e81ff454fd20192ca14f0503adf8caa1535
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
index ca789a9..abdde0b 100644
--- a/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
@@ -1,5 +1,9 @@
 package de.ids_mannheim.korap.query.object;
 
+/**
+ * @author margaretha
+ * 
+ */
 public enum KoralContext {
 	SENTENCE("s"), PARAGRAPH("p"), TEXT("t");
 	
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
index 2a25202..bd5f6ea 100644
--- a/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
@@ -1,5 +1,9 @@
 package de.ids_mannheim.korap.query.object;
 
+/**
+ * @author margaretha
+ * 
+ */
 public enum KoralMatchOperator {
     EQUALS("eq"), NOT_EQUALS("ne");
 
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
index e9eba9f..eaae803 100644
--- a/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
@@ -5,10 +5,8 @@
 import java.util.List;
 import java.util.Map;
 
-import de.ids_mannheim.korap.query.parse.fcsql.ExpressionParser;
 import de.ids_mannheim.korap.query.serialize.MapBuilder;
 import de.ids_mannheim.korap.query.serialize.util.KoralException;
-import eu.clarin.sru.server.fcs.parser.QueryNode;
 
 /**
  * @author margaretha
@@ -19,18 +17,12 @@
     private static final KoralType type = KoralType.TERMGROUP;
 
     private String relation;
-    private List<Object> operands = new ArrayList<Object>();
+    private List<KoralObject> operands = new ArrayList<KoralObject>();
 
-    public KoralTermGroup () {
-
-    }
-
-    public KoralTermGroup (ExpressionParser parser, KoralRelation relation,
-            List<QueryNode> nodes) throws KoralException {
+    public KoralTermGroup (KoralRelation relation, List<KoralObject> operands)
+            throws KoralException {
         this.relation = relation.toString();
-        for (QueryNode node : nodes) {
-            operands.add(parser.parseExpression(node, false, false));
-        }
+        this.operands = operands;
     }
 
     public String getRelation() {
@@ -41,11 +33,11 @@
         this.relation = relation;
     }
 
-    public List<Object> getOperands() {
+    public List<KoralObject> getOperands() {
         return operands;
     }
 
-    public void setOperands(List<Object> operands) {
+    public void setOperands(List<KoralObject> operands) {
         this.operands = operands;
     }
 
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
index d62f4a7..488b4c8 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
@@ -1,20 +1,23 @@
 package de.ids_mannheim.korap.query.parse.fcsql;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
-import de.ids_mannheim.korap.query.serialize.util.KoralException;
-import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import de.ids_mannheim.korap.query.object.KoralMatchOperator;
 import de.ids_mannheim.korap.query.object.KoralObject;
 import de.ids_mannheim.korap.query.object.KoralRelation;
 import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralTerm.KoralTermType;
 import de.ids_mannheim.korap.query.object.KoralTermGroup;
 import de.ids_mannheim.korap.query.object.KoralToken;
-import de.ids_mannheim.korap.query.object.KoralTerm.KoralTermType;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import eu.clarin.sru.server.fcs.parser.Expression;
 import eu.clarin.sru.server.fcs.parser.ExpressionAnd;
+import eu.clarin.sru.server.fcs.parser.ExpressionGroup;
 import eu.clarin.sru.server.fcs.parser.ExpressionNot;
 import eu.clarin.sru.server.fcs.parser.ExpressionOr;
 import eu.clarin.sru.server.fcs.parser.Operator;
@@ -56,9 +59,10 @@
                 return parseBooleanExpression(operands, KoralRelation.AND);
             }
         }
-        // else if (queryNode instanceof ExpressionGroup) {
-        //
-        // }
+        else if (queryNode instanceof ExpressionGroup) {
+            // Ignore the group
+            return parseExpression(queryNode.getFirstChild());
+        }
         else if (queryNode instanceof ExpressionNot) {
             boolean negation = isNot ? false : true;
             return parseExpression(queryNode.getChild(0), negation, isToken);
@@ -81,9 +85,13 @@
         }
     }
 
-    private KoralObject parseBooleanExpression(List<QueryNode> operands,
+    private KoralToken parseBooleanExpression(List<QueryNode> operands,
             KoralRelation relation) throws KoralException {
-        KoralTermGroup termGroup = new KoralTermGroup(this, relation, operands);
+        List<KoralObject> terms = new ArrayList<>();
+        for (QueryNode node : operands) {
+            terms.add(parseExpression(node, false, false));
+        }
+        KoralTermGroup termGroup = new KoralTermGroup(relation, terms);
         return new KoralToken(termGroup);
     }
 
@@ -179,20 +187,32 @@
 
     private void parseRegexFlags(KoralTerm koralTerm, Set<RegexFlag> set) throws KoralException {
         // default case sensitive
-        if (set != null) {
-            for (RegexFlag f : set) {
-                if (f == RegexFlag.CASE_SENSITVE) {
-                    koralTerm.setCaseSensitive(true);
-                }
-                else if (f == RegexFlag.CASE_INSENSITVE) {
-                    koralTerm.setCaseSensitive(false);
-                }
-                else {
-                	throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
-                            "SRU diagnostic 48:" + f.name()
-                                    + " is unsupported.");
-                }
+        if (set == null) return;
+        
+        ArrayList<String> names = new ArrayList<String>();
+        Iterator<RegexFlag> i = set.iterator();
+        while (i.hasNext()) {
+            RegexFlag f = i.next();
+            if (f == RegexFlag.CASE_SENSITVE) {
+                koralTerm.setCaseSensitive(true);
             }
+            else if (f == RegexFlag.CASE_INSENSITVE) {
+                koralTerm.setCaseSensitive(false);
+            }
+            else {
+                names.add(f.name());
+            }
+        }
+
+        if (names.size() == 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflag: " + names.get(0)
+                            + " is unsupported.");
+        }
+        else if (names.size() > 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflags: " + names.toString()
+                            + " are unsupported.");
         }
     }
 
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
index 2c83c5d..85be144 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
@@ -49,9 +49,11 @@
         else if (queryNode instanceof QueryDisjunction) {
             return parseGroupQuery(queryNode.getChildren(),
                     KoralOperation.DISJUNCTION);
-        } else if (queryNode instanceof QueryWithWithin) {
+        }
+        else if (queryNode instanceof QueryWithWithin) {
         	return parseWithinQuery((QueryWithWithin)queryNode);
-	    } else if (queryNode instanceof SimpleWithin) {
+        }
+        else if (queryNode instanceof SimpleWithin) {
 	    	SimpleWithin withinNode = (SimpleWithin) queryNode;
 	    	return parseWithinScope(withinNode.getScope());
 	    }
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
new file mode 100644
index 0000000..3973aea
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
@@ -0,0 +1,173 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLComplexTest {
+
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    // | implicit-query
+    // | segment-query
+    // -------------------------------------------------------------------------
+    // implicit-query ::= flagged-regexp
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    @Test
+    public void testGroupQuery() throws IOException {
+        String query = "(\"blaue\"|\"grüne\")";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";;
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // group and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"]|[text=\"Mann\"])";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+        // sequence and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"])[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:[{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // group and sequence
+        query = "([text=\"blaue\"][pos=\"NN\"])";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // main-query ::= simple-query
+    // | simple-query "|" main-query /* or */
+    // | simple-query main-query /* sequence */
+    // | simple-query quantifier /* quatification */
+    // -------------------------------------------------------------------------
+
+    // | simple-query "|" main-query /* or */
+    @Test
+    public void testOrQuery() throws IOException {
+        String query = "\"man\"|\"Mann\"";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:man,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos=\"NN\"]|\"Mann\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        // group with two segment queries
+        query = "[pos=\"NN\"]|[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos=\"NN\"]&[text=\"Mann\"]";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(399, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("FCS diagnostic 10"));
+    }
+
+    // | simple-query main-query /* sequence */
+    @Test
+    public void testSequenceQuery() throws IOException {
+        String query = "\"blaue|grüne\" [pos = \"NN\"]";
+        String jsonLd = "{@type:koral:group, "
+                + "operation:operation:sequence, "
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term, key:blaue|grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, type:type:regex, match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[text=\"blaue|grüne\"][pos = \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"blaue\" \"grüne\" [pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+    }
+
+    // | simple-query quantifier /* quatification */
+    @Test
+    public void testQueryWithQuantifier() throws IOException {
+
+    }
+
+    // -------------------------------------------------------------------------
+    // query ::= main-query within-part?
+    // -------------------------------------------------------------------------
+    // within-part ::= simple-within-part
+    // simple-within-part ::= "within" simple-within-scope
+
+    @Test
+    public void testWithinQuery() throws IOException {
+        String query = "[cnx:pos=\"VVFIN\"] within s";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "operands:["
+                + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:VVFIN,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within sentence";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within p";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:p,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within text";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:t,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within u";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(310, error.get(0));
+        assertEquals(
+                "FCS diagnostic 11: Within scope UTTERANCE is currently unsupported.",
+                (String) error.get(1));
+    }
+    
+}
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
index 475606a..7fa90d0 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
@@ -17,11 +17,11 @@
  */
 public class FCSQLQueryProcessorTest {
 
-    QuerySerializer qs = new QuerySerializer();
-    ObjectMapper mapper = new ObjectMapper();
-    JsonNode node;
+    static QuerySerializer qs = new QuerySerializer();
+    static ObjectMapper mapper = new ObjectMapper();
+    static JsonNode node;
 
-    private void runAndValidate(String query, String jsonLD)
+    public static void runAndValidate(String query, String jsonLD)
             throws JsonProcessingException {
         FCSQLQueryProcessor processor = new FCSQLQueryProcessor(query, "2.0");
         String serializedQuery = mapper.writeValueAsString(processor
@@ -29,7 +29,7 @@
         assertEquals(jsonLD.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-    private void validateNode(String query, String path, String jsonLd)
+    public static void validateNode(String query, String path, String jsonLd)
             throws JsonProcessingException, IOException {
         qs.setQuery(query, "fcsql", "2.0");
         node = mapper.readTree(qs.toJSON());
@@ -37,7 +37,7 @@
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-    private List<Object> getError(FCSQLQueryProcessor processor) {
+    public static List<Object> getError(FCSQLQueryProcessor processor) {
         List<Object> errors = (List<Object>) processor.requestMap.get("errors");
         return (List<Object>) errors.get(0);
     }
@@ -56,6 +56,7 @@
                 error.get(1));
     }
 
+    // regexp ::= quoted-string
     @Test
     public void testTermQuery() throws JsonProcessingException {
         String query = "\"Sonne\"";
@@ -65,31 +66,102 @@
     }
 
     @Test
-    public void testTermQueryWithRegexFlag() throws JsonProcessingException {
+    public void testRegex() throws JsonProcessingException {
+        String query = "[text=\"M(a|ä)nn(er)?\"]";
+        String jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
+                + "key:M(a|ä)nn(er)?,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\".*?Mann.*?\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:.*?Mann.*?,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"z.B.\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:z.B.,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"Sonne&scheint\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:Sonne&scheint,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        // Not possible
+        // query = "\"a\\.\"";
+    }
+
+    // flagged-regexp ::= regexp
+    // | regexp "/" regexp-flag+
+    @Test
+    public void testTermQueryWithRegexFlag() throws IOException {
         String query = "\"Fliegen\" /c";
         String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
                 + "key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Fliegen\" /i";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Fliegen\" /C";
+        jsonLd = "{@type:koral:term, key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /I";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /l";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("SRU diagnostic 48: Regexflags"));
+
+        query = "\"Fliegen\" /d";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Regexflag: IGNORE_DIACRITICS is unsupported.",
+                (String) error.get(1));
+    }
+
+    // operator ::= "=" /* equals */
+    // | "!=" /* non-equals */
+    @Test
+    public void testOperator() throws IOException {
+        String query = "[cnx:pos != \"N\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, type:type:regex, match:match:ne}}";
         runAndValidate(query, jsonLd);
     }
 
+
+    // attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+    // attribute ::= simple-attribute | qualified-attribute
+    // -------------------------------------------------------------------------
+
+    // simple-attribute ::= identifier
     @Test
     public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
         String query = "[text = \"Sonne\"]";
         String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
                 + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
         query = "[lemma = \"sein\"]";
         jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
                 + "foundry:tt, layer:l, type:type:regex, match:match:eq}}";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
         query = "[pos = \"NN\"]";
         jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
                 + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
     }
 
+    // qualified-attribute ::= identifier ":" identifier
     @Test
     public void testTermQueryWithQualifier() throws JsonProcessingException {
         String query = "[mate:lemma = \"sein\"]";
@@ -126,47 +198,76 @@
 
     }
 
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // expression ::= basic-expression
+    // | expression "|" expression /* or */
+    // | expression "&" expression /* and */
+    // -------------------------------------------------------------------------
+
+    // | expression "|" expression /* or */
     @Test
-    public void testRegex() throws JsonProcessingException {
-        String query = "[text=\"M(a|ä)nn(er)?\"]";
-        String jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
-                + "key:M(a|ä)nn(er)?,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
-        runAndValidate(query, jsonLd);
+    public void testExpressionOr() throws IOException {
+        String query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        String jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:or,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
-        query = "\".*?Mann.*?\"";
-        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:.*?Mann.*?,"
-                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
-        runAndValidate(query, jsonLd);
+        query = "[cnx:lemma=\"sein\" | mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:term, key: sein, foundry: cnx, layer: l, type:type:regex, match: match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap/operands/0",
+                jsonLd);
 
-        query = "\"z.B.\"";
-        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:z.B.,"
-                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
-        runAndValidate(query, jsonLd);
-
-        query = "\"Sonne&scheint\"";
-        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:Sonne&scheint,"
-                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
-        runAndValidate(query, jsonLd);
-
-        // Not possible
-        // query = "\"a\\.\"";
     }
 
+    // | expression "&" expression /* and */
     @Test
-    public void testNot() throws IOException {
-        String query = "[cnx:pos != \"N\"]";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
-                + "foundry:cnx, layer:p, type:type:regex, match:match:ne}}";
-        runAndValidate(query, jsonLd);
+    public void testExpressionAnd() throws IOException {
+        String query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
+        String jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:and,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
 
-        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+    // -------------------------------------------------------------------------
+    // basic-expression ::= '(' expression ')' /* grouping */
+    // | "!" expression /* not */
+    // | attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+
+    // basic-expression ::= '(' expression ')' /* grouping */
+
+    @Test
+    public void testExpressionGroup() throws JsonProcessingException {
+        String query = "[(text=\"blau\"|pos=\"ADJ\")]";
+        String jsonLd = "{@type: koral:token,"
+                + "wrap: {@type: koral:termGroup,"
+                + "relation: relation:or,"
+                + "operands: ["
+                + "{@type: koral:term, key: blau, foundry: opennlp, layer: orth, type:type:regex,match: match:eq},"
+                + "{@type: koral:term, key: ADJ, foundry: tt, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // "!" expression /* not */
+    @Test
+    public void testExpressionNot() throws IOException {
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
                 + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
-        query = "[!pos != \"NN\"]";
-        runAndValidate(query, jsonLd);
+        String query = "[!pos != \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
         query = "[!!pos = \"NN\"]";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
         query = "[!!!pos != \"NN\"]";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
         query = "[mate:lemma=\"sein\" & !mate:pos=\"PPOSS\"]";
         jsonLd = "{@type: koral:token,"
@@ -176,9 +277,10 @@
                 + " operands:["
                 + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
                 + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:ne}]}}";
-        runAndValidate(query, jsonLd);
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
     }
 
+
     @Test
     public void testWrongQuery() throws IOException {
         String query = "!(mate:lemma=\"sein\" | mate:pos=\"PPOSS\")";
@@ -198,109 +300,4 @@
                 error.get(1).toString().startsWith("FCS diagnostic 10"));
     }
 
-    @Test
-    public void testSequenceQuery() throws IOException {
-        String query = "\"blaue|grüne\" [pos = \"NN\"]";
-        String jsonLd = "{@type:koral:group, "
-                + "operation:operation:sequence, "
-                + "operands:["
-                + "{@type:koral:token, wrap:{@type:koral:term, key:blaue|grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}},"
-                + "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, type:type:regex, match:match:eq}}"
-                + "]}";
-        runAndValidate(query, jsonLd);
-
-        query = "[text=\"blaue|grüne\"][pos = \"NN\"]";
-        runAndValidate(query, jsonLd);
-
-        query = "\"blaue\" \"grüne\" [pos = \"NN\"]";
-        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
-        validateNode(query, "/query/operands/1", jsonLd);
-
-    }
-
-    @Test
-    public void testBooleanQuery() throws IOException {
-        String query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
-        String jsonLd = "{@type: koral:token,"
-                + " wrap: { @type: koral:termGroup,"
-                + "relation: relation:and,"
-                + " operands:["
-                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
-                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
-        runAndValidate(query, jsonLd);
-
-        query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
-        validateNode(query, "/query/wrap/relation", "relation:or");
-
-        query = "[cnx:lemma=\"sein\" | mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
-        jsonLd = "{@type: koral:term, key: sein, foundry: cnx, layer: l, type:type:regex, match: match:eq}";
-        validateNode(query, "/query/wrap/operands/0", jsonLd);
-
-        // group with two tokens
-        query = "[pos=\"NN\"]|[text=\"Mann\"]";
-        jsonLd = "{@type:koral:group,"
-                + "operation:operation:disjunction,"
-                + "operands:["
-                + "{@type:koral:token, wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
-                + "{@type:koral:token, wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
-        runAndValidate(query, jsonLd);
-
-        query = "[pos=\"NN\"]&[text=\"Mann\"]";
-        List<Object> error = getError(new FCSQLQueryProcessor(query, "2.0"));
-        assertEquals(399, error.get(0));
-        String msg = (String) error.get(1);
-        assertEquals(true, msg.startsWith("FCS diagnostic 10"));
-    }
-
-    @Test
-    public void testGroupQuery() throws IOException {
-        String query = "(\"blaue\"|\"grüne\")";
-        String jsonLd = "{@type:koral:group,"
-                + "operation:operation:disjunction,"
-                + "operands:["
-                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
-                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";;
-        runAndValidate(query, jsonLd);
-
-        // group and disjunction
-        query = "([pos=\"NN\"]|[cnx:pos=\"N\"]|[text=\"Mann\"])";
-        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}";
-        validateNode(query, "/query/operands/1", jsonLd);
-
-        // sequence and disjunction
-        query = "([pos=\"NN\"]|[cnx:pos=\"N\"])[text=\"Mann\"]";
-        jsonLd = "{@type:koral:group,"
-                + "operation:operation:sequence,"
-                + "operands:["
-                + "{@type:koral:group,"
-                + "operation:operation:disjunction,"
-                + "operands:[{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
-                + "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
-                + "]},"
-                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
-                + "]}";
-        runAndValidate(query, jsonLd);
-
-        // group and sequence
-        query = "([text=\"blaue\"][pos=\"NN\"])";
-        jsonLd = "{@type:koral:group,"
-                + "operation:operation:sequence,"
-                + "operands:["
-                + "{@type:koral:token,wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
-                + "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}"
-                + "]}";
-        runAndValidate(query, jsonLd);
-    }
-    
-    @Test
-    public void testWithinQuery() throws JsonProcessingException {
-    	String query = "[cnx:pos=\"VVFIN\"] within s";
-    	String jsonLd = "{@type:koral:group,"
-    			+ "operation:operation:position,"
-    			+ "operands:["
-    			+ "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
-    			+ "{@type:koral:token,wrap:{@type:koral:term,key:VVFIN,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
-    			+ "]}";
-    	runAndValidate(query, jsonLd);
-	}
 }