Introduce query references in Poliqarp

Change-Id: I4d047a4635e0ff7e8fd4e70334e485a6da8e3bd1
diff --git a/Changes b/Changes
index 06e541a..dabde93 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+0.37 2020-10-23
+    - [feature] Introduced query references in Poliqarp (diewald)
+
 0.36 2020-07-24
     - [security] Upgraded version of Google Guava
       (CVE-2018-10237; diewald)
diff --git a/pom.xml b/pom.xml
index 610d340..14a93b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 
 	<groupId>de.ids_mannheim.korap</groupId>
 	<artifactId>Koral</artifactId>
-	<version>0.36</version>
+	<version>0.37</version>
 	<packaging>jar</packaging>
 	<name>Koral</name>
 	<url>https://korap.ids-mannheim.de</url>
diff --git a/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4 b/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
index 02d1ffd..420856e 100644
--- a/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
+++ b/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
@@ -13,7 +13,7 @@
  -- author: Joachim Bingel
  -- date: 14-06-27
 
- -- updated: 28-09-2018 (diewald) 
+ -- updated: 18-09-28 (diewald) 
 
 
  Poliqarp Query Language lexer
@@ -58,7 +58,7 @@
 fragment FOCC       : '{' WS* ( [0-9]* WS* ',' WS* [0-9]+ | [0-9]+ WS* ','? ) WS* '}';
 fragment NO_RE      : ~[ \t/];
 fragment ALPHABET   : ~('\t' | ' ' | '/' | '*' | '?' | '+' | '{' | '}' | '[' | ']'
-                    | '(' | ')' | '|' | '"' | ',' | ':' | '\'' | '\\' | '!' | '=' | '~' | '&' | '^' | '<' | '>' );
+                    | '(' | ')' | '|' | '"' | ',' | ':' | '\'' | '\\' | '!' | '=' | '~' | '&' | '^' | '<' | '>' | '#' );
 NUMBER              : [0-9]+;
 
 NL                  : [\r\n] -> skip;
@@ -90,6 +90,7 @@
 EMPTYREL	: '@';
 BACKSLASH	: '\\';
 SQUOTE      : '\'';
+HASH        : '#';
 
 /* Regular expressions and Regex queries */
 fragment RE_symbol     : ~('*' | '?' | '+' | '{' | '}' | '[' | ']'
diff --git a/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4 b/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
index 0ba3451..e65d8db 100644
--- a/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
+++ b/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
@@ -66,7 +66,7 @@
 value
 : (WORD | NUMBER) | regex
 ;
- 
+
 /* Fields */
 term       
 : NEG* (foundry SLASH)? layer termOp key (COLON value)? flag?
@@ -185,6 +185,18 @@
 : emptyTokenSequence
 ;
 
+user
+: WORD
+;
+
+ref
+: WORD
+;
+
+queryref
+: LBRACE HASH (user SLASH)? ref RBRACE 
+;
+
 spanclass
 : LBRACE spanclass_id? (segment|sequence) RBRACE
 ;
@@ -201,6 +213,7 @@
   | LRPAREN segment RRPAREN
   | emptyTokenSequence
   | emptyTokenSequenceClass
+  | queryref
   ) 
   repetition?
  ; 
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
index cfa0f97..e86dbc3 100644
--- a/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
@@ -12,6 +12,7 @@
     RELATION("koral:relation"), DISTANCE("koral:distance"), REFERENCE(
     "koral:reference"), DOCUMENT("koral:doc"), DOCUMENT_GROUP("koral:docGroup"),
     DOCUMENT_GROUP_REF("koral:docGroupRef"),
+    QUERY_REF("koral:queryRef"),
     COSMAS_DISTANCE("cosmas:distance");
 
     String value;
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
index 7cde9c1..6829202 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
@@ -188,6 +188,10 @@
             processSubmatch(node);
         }
 
+        if (nodeCat.equals("queryref")) {
+            processQueryref(node);
+        }
+
         if (nodeCat.equals("meta")) {
             processMeta(node);
         }
@@ -297,6 +301,26 @@
     }
 
 
+    private void processQueryref (ParseTree node) {
+
+        String queryNameStr = "";
+        
+        if (getNodeCat(node.getChild(2)).equals("user")) {
+            queryNameStr = node.getChild(2).getText();
+            queryNameStr += '/';
+            queryNameStr += node.getChild(4).getText();
+        }
+        else {
+            queryNameStr = node.getChild(2).getText();
+        }
+
+        Map<String, Object> object = KoralObjectGenerator.makeQueryRef(queryNameStr);
+        putIntoSuperObject(object);
+        objectStack.push(object);
+        stackedObjects++;
+    }
+    
+
     private void processEmptyTokenSequenceClass (ParseTree node) {
         int classId = 1;
         if (hasChild(node, "spanclass_id")) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralObjectGenerator.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralObjectGenerator.java
index 2c65416..9f2a72a 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralObjectGenerator.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralObjectGenerator.java
@@ -74,7 +74,6 @@
         return term;
     }
 
-
     public static Map<String, Object> makeDocGroup (String relation) {
         Map<String, Object> term = new HashMap<String, Object>();
         term.put("@type", KoralType.DOCUMENT_GROUP.toString());
@@ -151,6 +150,12 @@
         return group;
     }
 
+    public static Map<String, Object> makeQueryRef (String ref) {
+        Map<String, Object> term = new HashMap<String, Object>();
+        term.put("@type", KoralType.QUERY_REF.toString());
+        term.put("ref", ref);
+        return term;
+    }
 
     public static Map<String, Object> makeClassRefCheck (
             ArrayList<ClassRefCheck> checks, ArrayList<Integer> classIn) {
diff --git a/src/test/java/de/ids_mannheim/korap/query/test/poliqarpplus/PoliqarpPlusQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/test/poliqarpplus/PoliqarpPlusQueryProcessorTest.java
index f1562a0..dd4daad 100644
--- a/src/test/java/de/ids_mannheim/korap/query/test/poliqarpplus/PoliqarpPlusQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/test/poliqarpplus/PoliqarpPlusQueryProcessorTest.java
@@ -1615,6 +1615,39 @@
         assertEquals("koral:token", res.at("/query/operands/1/@type").asText());
     }
 
+    @Test
+    public void testQueryReferences () throws JsonProcessingException, IOException {
+        query = "{#test}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:queryRef", res.at("/query/@type").asText());
+        assertEquals("test", res.at("/query/ref").asText());
+
+        query = "{#admin/example}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:queryRef", res.at("/query/@type").asText());
+        assertEquals("admin/example", res.at("/query/ref").asText());
+
+        query = "Der {#admin/example} [orth=Baum]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:token", res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:queryRef", res.at("/query/operands/1/@type").asText());
+        assertEquals("admin/example", res.at("/query/operands/1/ref").asText());
+        assertEquals("koral:token", res.at("/query/operands/2/@type").asText());
+
+        query = "[orth=Der]{#admin/example}{1,}[orth=Baum]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:token", res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:group", res.at("/query/operands/1/@type").asText());
+        assertEquals("koral:queryRef", res.at("/query/operands/1/operands/0/@type").asText());
+        assertEquals("admin/example", res.at("/query/operands/1/operands/0/ref").asText());
+        assertEquals("koral:token", res.at("/query/operands/2/@type").asText());
+    }
 
     @Test
     public void testMeta () throws JsonProcessingException, IOException {