multiple operators, e.g. #1 . #2 . #3 or #1 > #2 > #3
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
index a6e4686..473e80d 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
@@ -58,7 +58,7 @@
 	/**
 	 * Keeps track of explicitly (by #-var definition) or implicitly (number as reference) introduced entities (for later reference by #-operator)
 	 */
-	Map<String, Object> variableReferences = new LinkedHashMap<String, Object>(); 
+	Map<String, LinkedHashMap<String,Object>> variableReferences = new LinkedHashMap<String, LinkedHashMap<String,Object>>(); 
 	/**
 	 * Counter for variable definitions.
 	 */
@@ -151,6 +151,7 @@
 			throw new NullPointerException("Parser has not been instantiated!"); 
 		}
 		log.info("Processing Annis query.");
+		log.info("AST is: "+tree.toStringTree(parser));
 		System.out.println("Processing Annis QL");
 		if (verbose) System.out.println(tree.toStringTree(parser));
 		processNode(tree);
@@ -200,7 +201,7 @@
 			List<ParseTree> globalLingTermNodes = new ArrayList<ParseTree>();
 			for (ParseTree exprNode : getChildrenWithCat(node,"expr")) {
 				List<ParseTree> lingTermNodes = new ArrayList<ParseTree>();
-				lingTermNodes.addAll(getChildrenWithCat(exprNode, "unary_linguistic_term"));
+//				lingTermNodes.addAll(getChildrenWithCat(exprNode, "unary_linguistic_term"));
 				lingTermNodes.addAll(getChildrenWithCat(exprNode, "n_ary_linguistic_term"));
 				globalLingTermNodes.addAll(lingTermNodes);
 				// Traverse refOrNode nodes under *ary_linguistic_term nodes and extract references
@@ -255,62 +256,65 @@
 		}
 		
 		if (nodeCat.equals("unary_linguistic_term")) {
-			
+			LinkedHashMap<String, Object> unaryOperator = parseUnaryOperator(node);
+			String reference = node.getChild(0).toStringTree(parser).substring(1);
+			LinkedHashMap<String, Object> object = variableReferences.get(reference);
+			object.putAll(unaryOperator);
 		}
 		
 		if (nodeCat.equals("n_ary_linguistic_term")) {
 			// get referenced operands
-			// TODO generalize operator
-			// TODO capture variableExprs
 			
 			// get operator and determine type of group (sequence/treeRelation/relation/...)
-			LinkedHashMap<String, Object> operatorTree = parseOperatorNode(node.getChild(1).getChild(0));
-			String groupType;
-			try {
-				groupType = (String) operatorTree.get("groupType");
-			} catch (ClassCastException | NullPointerException n) {
-				groupType = "relation";
-			}
-			LinkedHashMap<String, Object> group = makeGroup(groupType);
-			if (groupType.equals("relation") || groupType.equals("treeRelation")) {
-				LinkedHashMap<String, Object> relationGroup = new LinkedHashMap<String, Object>();
-				putAllButGroupType(relationGroup, operatorTree);
-				group.put("relation", relationGroup);
-			} else if (groupType.equals("sequence")) {
-				putAllButGroupType(group, operatorTree);
-			} else if (groupType.equals("position")) {
-				String frame = (String) operatorTree.get("frame");
-//				if (!mirroredPositionFrames.contains(frame.substring(6))) { //remove leading "frame:"
+			List<ParseTree> operatorNodes = getChildrenWithCat(node, "operator");
+			int i = 1;
+			for (ParseTree operatorNode : operatorNodes) {
+				ParseTree operand1 = node.getChild(i-1);
+				ParseTree operand2 = node.getChild(i+1);
+				LinkedHashMap<String, Object> operatorTree = parseOperatorNode(node.getChild(i).getChild(0));
+				String groupType;
+				try {
+					groupType = (String) operatorTree.get("groupType");
+				} catch (ClassCastException | NullPointerException n) {
+					groupType = "relation";
+				}
+				LinkedHashMap<String, Object> group = makeGroup(groupType);
+				if (groupType.equals("relation") || groupType.equals("treeRelation")) {
+					LinkedHashMap<String, Object> relationGroup = new LinkedHashMap<String, Object>();
+					putAllButGroupType(relationGroup, operatorTree);
+					group.put("relation", relationGroup);
+				} else if (groupType.equals("sequence")) {
 					putAllButGroupType(group, operatorTree);
-//				} else {
-//					group = makeGroup("or");
-//					LinkedHashMap<String, Object> group1 = makeGroup(groupType);
-//					LinkedHashMap<String, Object> group2 = makeGroup(groupType);
-//					putAllButGroupType(group1, operatorTree);
-//					putAllButGroupType(group2, operatorTree);
-//					ArrayList<Object> groupOperands = (ArrayList<Object>) group.get("operands");
-//					groupOperands.add(group1);
-//					groupOperands.add(group2);
-//					ArrayList<ArrayList<Object>> distOperandsList = new ArrayList<ArrayList<Object>>();
-//					distOperandsList.add((ArrayList<Object>) group1.get("operands"));
-//					distOperandsList.add((ArrayList<Object>) group2.get("operands"));
-//					invertedOperandsLists.push((ArrayList<Object>) group2.get("operands"));
-//					distributedOperandsLists.push(distOperandsList);
-//				}
-			}
-			// insert referenced nodes into operands list
-			List<Object> operands = (List<Object>) group.get("operands");
-			for (ParseTree refOrNode : getChildrenWithCat(node, "refOrNode")) {
-				if (!getNodeCat(refOrNode.getChild(0)).equals("variableExpr")) {
-					String ref = refOrNode.getChild(0).toStringTree(parser).substring(1);
+				} else if (groupType.equals("position")) {
+					putAllButGroupType(group, operatorTree);
+				}
+				// insert referenced nodes into operands list
+				List<Object> operands = (List<Object>) group.get("operands");
+				if (!getNodeCat(operand1.getChild(0)).equals("variableExpr")) {
+					String ref = operand1.getChild(0).toStringTree(parser).substring(1);
 					operands.add(variableReferences.get(ref));
 				} else {
 					
 				}
+				// also process second operand, but only if there is no subsequent operator
+				// that will claim the second operand as its first!
+				if (i == node.getChildCount()-2) {
+					if (!getNodeCat(operand2.getChild(0)).equals("variableExpr")) {
+						String ref = operand2.getChild(0).toStringTree(parser).substring(1);
+						operands.add(variableReferences.get(ref));
+					} else {
+						
+					}
+				}
+				putIntoSuperObject(group);
+				objectStack.push(group);
+				stackedObjects++;
+				
+				i += 2;
 			}
-			putIntoSuperObject(group);
-			objectStack.push(group);
-			stackedObjects++;
+			
+			
+
 		}
 		
 		if (nodeCat.equals("variableExpr")) {
@@ -322,6 +326,7 @@
 			} else if (firstChildNodeCat.equals("tok")) {
 				object = makeToken();
 				LinkedHashMap<String, Object> term = makeTerm();
+				term.put("layer", "orth");
 				object.put("wrap", term);
 			} else if (firstChildNodeCat.equals("qName")) {	// only (foundry/)?layer specified
 				// may be token or span, depending on indicated layer! (e.g. cnx/cat=NP or mate/pos=NN)
@@ -391,6 +396,27 @@
 	}
 
 
+	/**
+	 * Parses a unary_linguistic_operator node. Possible operators are: root, arity, tokenarity.
+	 * Operators are embedded into a korap:term, in turn wrapped by an 'attr' property in a korap:span.
+	 * @param node The unary_linguistic_operator node
+	 * @return A map containing the attr key, to be inserted into korap:span 
+	 */
+	private LinkedHashMap<String, Object> parseUnaryOperator(ParseTree node) {
+		LinkedHashMap<String, Object> attr = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> term = makeTerm();
+		String op = node.getChild(1).toStringTree(parser).substring(1);
+		if (op.equals("arity") || op.equals("tokenarity")) {
+			LinkedHashMap<String, Object> boundary = boundaryFromRangeSpec(node.getChild(3), false);
+			term.put(op, boundary);
+		} else {
+			term.put(op, true);
+		}
+		
+		attr.put("attr", term);
+		return attr;
+	}
+
 	private LinkedHashMap<String, Object> parseOperatorNode(ParseTree operatorNode) {
 		LinkedHashMap<String, Object> relation = null;
 		String operator = getNodeCat(operatorNode);
@@ -522,8 +548,13 @@
 	}
 
 	private LinkedHashMap<String, Object> boundaryFromRangeSpec(ParseTree rangeSpec) {
+		return boundaryFromRangeSpec(rangeSpec, true); 
+	}
+	
+	private LinkedHashMap<String, Object> boundaryFromRangeSpec(ParseTree rangeSpec, boolean expandToMax) {
 		Integer min = Integer.parseInt(rangeSpec.getChild(0).toStringTree(parser));
-		Integer max = MAXIMUM_DISTANCE;
+		Integer max = min;
+		if (expandToMax) max = MAXIMUM_DISTANCE;
 		if (rangeSpec.getChildCount()==3) 
 			max = Integer.parseInt(rangeSpec.getChild(2).toStringTree(parser));
 		return makeBoundary(min, max);
@@ -671,13 +702,19 @@
 		 */
 		String[] queries = new String[] {
 			 "node & #1:root",
-			 "pos=\"N\" & pos=\"V\" & pos=\"N\" & #1 . #2 & #2 . #3"
+			 "pos=\"N\" & pos=\"V\" & pos=\"N\" & #1 . #2 & #2 . #3",
+			 "cat=\"NP\" & #1:tokenarity=2",
+			 "node & node & node & #1 . #2 . #3"
+//			 "cnx/cat=\"NP\" > node",
+//			 "node > node",
+//			 "cat=/NP/ > node",
+//			 "/Mann/",
+//			 "node > tok=\"foo\"",
 			};
-		AqlTree.verbose=true;
+//		AqlTree.verbose=true;
 		for (String q : queries) {
 			try {
 				System.out.println(q);
-//				System.out.println(AqlTree.parseAnnisQuery(q).toStringTree(AqlTree.parser));
 				AqlTree at = new AqlTree(q);
 				System.out.println(at.parseAnnisQuery(q).toStringTree(at.parser));
 				System.out.println();
diff --git a/src/test/java/AqlTreeTest.java b/src/test/java/AqlTreeTest.java
index a39ccc4..4ad1a35 100644
--- a/src/test/java/AqlTreeTest.java
+++ b/src/test/java/AqlTreeTest.java
@@ -34,7 +34,7 @@
 		
 		// [orth!=Frau]
 		query = "tok!=\"Frau\"";
-		String token2 = "{@type=korap:token, wrap={@type=korap:term, key=Frau, match=match:ne}}";
+		String token2 = "{@type=korap:token, wrap={@type=korap:term, layer=orth, key=Frau, match=match:ne}}";
 		assertTrue(equalsQueryContent(token2, query));
 		
 		// Mann
@@ -163,6 +163,23 @@
 	}
 	
 	@Test
+	public void testMultipleDominance() throws QueryException {
+		query = "cat=\"NP\" & cat=\"VP\" & cat=\"NP\" & #1 > #2 > #3";
+		String dom1 = 
+				"{@type=korap:group, operation=operation:relation, operands=[" +
+						"{@type=korap:span, layer=cat, key=NP, match=match:eq}," +
+						"{@type=korap:group, operation=operation:relation, operands=[" +
+							"{@type=korap:span, layer=cat, key=VP, match=match:eq}," +
+							"{@type=korap:span, layer=cat, key=NP, match=match:eq}" +
+						"], relation={@type=korap:treeRelation, reltype=dominance}}" +
+				"], relation={@type=korap:treeRelation, reltype=dominance}" +
+				"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(dom1.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	}
+	
+	@Test
 	public void testPointingRelations() throws QueryException {
 		query = "node & node & #2 ->label[coref=\"true\"] #1";
 		String dom1 = 
@@ -190,7 +207,7 @@
 	@Test
 	public void testSequence() throws QueryException {
 		query = "node & node & #1 . #2";
-		String dom1 = 
+		String seq1 = 
 				"{@type=korap:group, operation=operation:sequence, " +
 					"operands=[" +
 						"{@type=korap:span}," +
@@ -199,10 +216,10 @@
 				"}";
 		aqlt = new AqlTree(query);
 		map = aqlt.getRequestMap().get("query").toString();
-		assertEquals(dom1.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		assertEquals(seq1.replaceAll(" ", ""), map.replaceAll(" ", ""));
 		
 		query = "node & node & #1 .* #2";
-		String dom2 = 
+		String seq2 = 
 				"{@type=korap:group, operation=operation:sequence, operands=[" +
 						"{@type=korap:span}," +
 						"{@type=korap:span}" +
@@ -212,7 +229,59 @@
 				"}";
 		aqlt = new AqlTree(query);
 		map = aqlt.getRequestMap().get("query").toString();
-		assertEquals(dom2.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		assertEquals(seq2.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "node & node & #1 .2,3 #2";
+		String seq3 = 
+				"{@type=korap:group, operation=operation:sequence, operands=[" +
+						"{@type=korap:span}," +
+						"{@type=korap:span}" +
+					"], distances=[" +
+						"{@type=korap:distance, key=w, min=2, max=3}" +
+					"], inOrder=true" +
+				"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(seq3.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+	}
+	
+	@Test
+	public void testMultipleSequence() throws QueryException {
+		query = "tok=\"Sonne\" & tok=\"Mond\" & tok=\"Sterne\" & #1 .0,2 #2 .0,4 #3";
+		String seq4 = 
+				"{@type=korap:group, operation=operation:sequence," +
+						"operands=[" +
+							"{@type=korap:token, wrap={@type=korap:term, layer=orth, key=Sonne, match=match:eq}}," +
+							"{@type=korap:group, operation=operation:sequence, operands=[" +
+									"{@type=korap:token, wrap={@type=korap:term, layer=orth, key=Mond, match=match:eq}}," +
+									"{@type=korap:token, wrap={@type=korap:term, layer=orth, key=Sterne, match=match:eq}}" +
+								"], distances=[" +
+									"{@type=korap:distance, key=w, min=0, max=4}" +
+								"], inOrder=true}" +
+						"],distances=[" +
+							"{@type=korap:distance, key=w, min=0, max=2}" +
+						"], inOrder=true" +
+					"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(seq4.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "node & node & node & #1 . #2 .1,3 #3";
+		String seq5 = 
+				"{@type=korap:group, operation=operation:sequence, operands=[" +
+						"{@type=korap:span}," +
+						"{@type=korap:group, operation=operation:sequence, operands=[" +
+							"{@type=korap:span}," +
+							"{@type=korap:span}" +
+						"], distances=[" +
+							"{@type=korap:distance, key=w, min=1, max=3}" +
+						"], inOrder=true}" +
+					"], inOrder=true" +
+				"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(seq5.replaceAll(" ", ""), map.replaceAll(" ", ""));
 	}
 	
 	@Test
@@ -228,48 +297,110 @@
 		map = aqlt.getRequestMap().get("query").toString();
 		assertEquals(pos1.replaceAll(" ", ""), map.replaceAll(" ", ""));
 		
-//		query = "node & node & #2 _i_ #1";
-//		String pos2 = 
-//				"{@type=korap:group, operation=operation:position, operands=[" +
-//						"{@type=korap:span}," +
-//						"{@type=korap:span}" +
-//				"], frame=frame:contains" +
-//				"}";
-//		aqlt = new AqlTree(query);
-//		map = aqlt.getRequestMap().get("query").toString();
-//		assertEquals(pos2.replaceAll(" ", ""), map.replaceAll(" ", ""));
-//		
-//		query = "node & node & #2 _l_ #1";
-//		String pos3 = 
-//				"{@type=korap:group, operation=operation:position, operands=[" +
-//						"{@type=korap:span}," +
-//						"{@type=korap:span}" +
-//				"], frame=frame:startswith" +
-//				"}";
-//		aqlt = new AqlTree(query);
-//		map = aqlt.getRequestMap().get("query").toString();
-//		assertEquals(pos3.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		query = "node & node & #2 _i_ #1";
+		String pos2 = 
+				"{@type=korap:group, operation=operation:position, operands=[" +
+						"{@type=korap:span}," +
+						"{@type=korap:span}" +
+				"], frame=frame:contains" +
+				"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(pos2.replaceAll(" ", ""), map.replaceAll(" ", ""));
 		
-		query = "node & \"Mann\" & #2 _r_ #1";
+		query = "node & node & #2 _l_ #1";
+		String pos3 = 
+				"{@type=korap:group, operation=operation:position, operands=[" +
+						"{@type=korap:span}," +
+						"{@type=korap:span}" +
+				"], inOrder=false, frame=frame:startswith" +
+				"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(pos3.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "node & \"Mann\" & #1 _r_ #2";
 		String pos4 = 
-				"{@type=korap:group, operation=operation:or, operands=[" +
 					"{@type=korap:group, operation=operation:position, operands=[" +
 						"{@type=korap:span}," +
-						"{@type=korap:token, wrap={@type=korap:term, type=type:regex, key=Mann, match=match:eq}}" +
-						"], frame=frame:endswith" +
-					"}," +
-					"{@type=korap:group, operation=operation:position, operands=[" +
-						"{@type=korap:token, wrap={@type=korap:term, type=type:regex, key=Mann, match=match:eq}}," +
-						"{@type=korap:span}" +
-						"], frame=frame:endswith" +
-					"}" +
-				"]}";
+						"{@type=korap:token, wrap={@type=korap:term, key=Mann, match=match:eq}}" +
+						"], inOrder=false, frame=frame:endswith" +
+					"}";
 		aqlt = new AqlTree(query);
 		map = aqlt.getRequestMap().get("query").toString();
 		assertEquals(pos4.replaceAll(" ", ""), map.replaceAll(" ", ""));
 		
+		query = "node & \"Mann\" & #2 _r_ #1";
+		String pos5 = 
+					"{@type=korap:group, operation=operation:position, operands=[" +
+						"{@type=korap:token, wrap={@type=korap:term, key=Mann, match=match:eq}}," +
+						"{@type=korap:span}" +
+						"], inOrder=false, frame=frame:endswith" +
+					"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(pos5.replaceAll(" ", ""), map.replaceAll(" ", ""));
 	}
 	
+	@Test
+	public void testMultiplePredications() throws QueryException {
+		// a noun before a verb before a noun
+		// XXX Warning incorrect serialisation! Need to ensure that the two Vs are identical! 
+		// Embed in one sequence!
+		query = "pos=\"N\" & pos=\"V\" & pos=\"N\" & #1 . #2 & #2 . #3"; 
+		String mult1 = 
+				"{@type=korap:group, operation=operation:sequence, operands=[" +
+					"{@type=korap:group, operation=operation:sequence, operands=[" +
+						"{@type=korap:token, wrap={@type=korap:term, layer=pos, key=N, match=match:eq}}," +
+						"{@type=korap:token, wrap={@type=korap:term, layer=pos, key=V, match=match:eq}}" +
+					"], inOrder=true}," +
+					"{@type=korap:group, operation=operation:sequence, operands=[" +
+						"{@type=korap:token, wrap={@type=korap:term, layer=pos, key=V, match=match:eq}}," +
+						"{@type=korap:token, wrap={@type=korap:term, layer=pos, key=N, match=match:eq}}" +
+					"], inOrder=true}" +
+				"], distances=[" +
+					"{@type=korap:distance, key=t, min=0, max=0}" +
+				"]}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(mult1.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	}	
 	
-}
-
+	@Test
+	public void testUnaryRelations() throws QueryException {
+		query = "node & #1:tokenarity=2";
+		String unary1 = 
+				"{@type=korap:span, attr={@type=korap:term, tokenarity={@type=korap:boundary,min=2,max=2}}}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(unary1.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "cnx/cat=\"NP\" & #1:tokenarity=2";
+		String unary2 = 
+				"{@type=korap:span, foundry=cnx, layer=cat, key=NP, match=match:eq, attr={@type=korap:term, tokenarity={@type=korap:boundary,min=2,max=2}}}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(unary2.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "cnx/cat=\"NP\" & #1:root";
+		String unary3 = 
+				"{@type=korap:span, foundry=cnx, layer=cat, key=NP, match=match:eq, attr={@type=korap:term, root=true}}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(unary3.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "cnx/cat=\"NP\" & node & #1>#2 & #1:tokenarity=2";
+		String unary4 = 
+					"{@type=korap:group, operation=operation:relation, operands=[" +
+						"{@type=korap:span, foundry=cnx, layer=cat, key=NP, match=match:eq, attr={@type=korap:term, tokenarity={@type=korap:boundary,min=2,max=2}}}," +
+						"{@type=korap:span}" +
+					"], relation={@type=korap:treeRelation, reltype=dominance}" +
+					"}";
+		aqlt = new AqlTree(query);
+		map = aqlt.getRequestMap().get("query").toString();
+		assertEquals(unary4.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	}	
+	
+	// TODO commonparent, commonancestor
+	
+}
\ No newline at end of file