This is an automated email from the ASF dual-hosted git repository.

pkluegl pushed a commit to branch 
feature/130-Improve-support-for-feature-assignments
in repository https://gitbox.apache.org/repos/asf/uima-ruta.git


The following commit(s) were added to 
refs/heads/feature/130-Improve-support-for-feature-assignments by this push:
     new 46eb0951 Issue #130: Improve support for feature assignments
46eb0951 is described below

commit 46eb0951644c4d5b9a76b2d93fe26fb2f009e479
Author: kluegl <peter.klu...@averbis.com>
AuthorDate: Wed Oct 11 09:40:38 2023 +0200

    Issue #130: Improve support for feature assignments
    
    - support more generic string assignments
    - improved boolean assignment
---
 .../org/apache/uima/ruta/parser/RutaParser.g       |  18 +-
 .../org/apache/uima/ruta/RutaScriptFactory.java    |  22 +--
 .../main/java/org/apache/uima/ruta/RutaStream.java |  21 ++-
 .../uima/ruta/action/AbstractMarkAction.java       |   8 +-
 .../org/apache/uima/ruta/action/ColorAction.java   |   4 +-
 .../uima/ruta/action/MarkFastReloadAction.java     | 192 +++++++++++----------
 .../ruta/descriptor/RutaDescriptorBuilder.java     |  30 ++--
 .../java/org/apache/uima/ruta/engine/Ruta.java     |  14 +-
 .../org/apache/uima/ruta/engine/RutaTestUtils.java |  19 +-
 .../org/apache/uima/ruta/engine/ViewWriter.java    |   6 +-
 .../org/apache/uima/ruta/engine/XMIWriter.java     |   8 +-
 .../uima/ruta/expression/ExpressionFactory.java    |  36 ++--
 .../annotation/AbstractAnnotationExpression.java   |  16 +-
 .../expression/bool/BooleanFeatureExpression.java  |  18 +-
 .../expression/string/StringFeatureExpression.java |  15 +-
 .../uima/ruta/visitor/StatisticsVisitor.java       |  36 ++--
 .../expression/bool/BooleanExpressionTest.java     |  77 +++++++++
 .../expression/string/StringExpressionTest.java    | 174 +++++++++++++++++++
 .../ruta/expression/string/StringFeatureTest.java  |  71 --------
 .../apache/uima/ruta/ide/core/parser/RutaParser.g  |  18 +-
 20 files changed, 523 insertions(+), 280 deletions(-)

diff --git a/ruta-core/src/main/antlr3/org/apache/uima/ruta/parser/RutaParser.g 
b/ruta-core/src/main/antlr3/org/apache/uima/ruta/parser/RutaParser.g
index 4e5f7fb7..4627ba17 100644
--- a/ruta-core/src/main/antlr3/org/apache/uima/ruta/parser/RutaParser.g
+++ b/ruta-core/src/main/antlr3/org/apache/uima/ruta/parser/RutaParser.g
@@ -2431,7 +2431,7 @@ options {
        | match = dottedIdWithIndex2 (comp = LESS | comp = GREATER | comp = 
GREATEREQUAL | comp = LESSEQUAL |comp =  EQUAL | comp = NOTEQUAL) arg = argument
        {MatchReference mr = expressionFactory.createMatchReference(match, 
comp, arg);
        expr = expressionFactory.createAnnotationTypeExpression(mr);}
-
+        | (complexStringExpression) => cse = complexStringExpression {expr = 
cse;}
        | (featureExpression)=> fe = featureExpression {expr = 
expressionFactory.createGenericFeatureExpression(fe);}
        | a2 = booleanExpression {expr = a2;}
        | a3 = numberExpression {expr = a3;}
@@ -2455,8 +2455,8 @@ options {
 }
        :
        (featureExpression)=> fe = featureExpression {expr = 
expressionFactory.createGenericFeatureExpression(fe);}
-       | a2 = booleanExpression {expr = a2;}
-       | a3 = numberExpression {expr = a3;}
+       | a2 = simpleBooleanExpression {expr = a2;}
+       | a3 = simpleNumberExpression {expr = a3;}
        | a4 = stringExpression {expr = a4;}
        | (listExpression)=> l = listExpression {expr = l;}
        | a5 = nullExpression {expr = a5;}
@@ -2778,6 +2778,18 @@ List<IStringExpression> exprs = new 
ArrayList<IStringExpression>();
        |(e = stringFunction)=> e = stringFunction{expr = e;} 
        ;
 
+complexStringExpression returns [IStringExpression expr = null]
+options {
+       backtrack = true;
+}
+@init {List<IRutaExpression> list = new ArrayList<IRutaExpression>();}
+       :
+       a1 = simpleArgument {list.add(a1);}
+       ((PLUS)=>PLUS an = simpleArgument {list.add(an);})+
+       {expr = expressionFactory.createGenericComposedStringExpression(list);}
+       ;
+
+
 // not checked
 stringFunction returns [IStringExpression expr = null]
 @init {List<IStringExpression> list = new ArrayList<IStringExpression>();}
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/RutaScriptFactory.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/RutaScriptFactory.java
index 8f56d83d..0a731087 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/RutaScriptFactory.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/RutaScriptFactory.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -106,7 +106,7 @@ public class RutaScriptFactory {
     if (ruleElement != null) {
       rule = createRule(ruleElement, parent);
     }
-    List<RutaStatement> elements = new ArrayList<RutaStatement>();
+    List<RutaStatement> elements = new ArrayList<>();
     if (body != null) {
       for (RutaStatement each : body) {
         if (each != null) {
@@ -121,7 +121,7 @@ public class RutaScriptFactory {
     String defaultNamespace = getDefaultNamespace(module, pack);
 
     RutaScriptBlock result = createScriptBlock(module, null, null, null, 
defaultNamespace);
-    List<RuleElement> ruleElements = new ArrayList<RuleElement>();
+    List<RuleElement> ruleElements = new ArrayList<>();
     RuleElementIsolator container = new RuleElementIsolator();
     ITypeExpression documentExpression = expressionFactory
             .createSimpleTypeExpression(CAS.TYPE_NAME_DOCUMENT_ANNOTATION, 
null);
@@ -167,13 +167,13 @@ public class RutaScriptFactory {
   }
 
   public RutaRule createRule(RuleElement element, RutaBlock parent) {
-    List<RuleElement> elements = new ArrayList<RuleElement>();
+    List<RuleElement> elements = new ArrayList<>();
     elements.add(element);
     return createRule(elements, parent);
   }
 
   public RutaStatement createImplicitRule(List<AbstractRutaAction> actions, 
RutaBlock parent) {
-    List<RuleElement> elements = new ArrayList<RuleElement>();
+    List<RuleElement> elements = new ArrayList<>();
     ITypeExpression documentExpression = 
expressionFactory.createSimpleTypeExpression("Document",
             parent);
     MatchReference mr = 
expressionFactory.createMatchReference(documentExpression);
@@ -293,15 +293,15 @@ public class RutaScriptFactory {
     if (!isConjunct) {
       return reList;
     }
-    Map<Integer, List<RuleElement>> map = new TreeMap<Integer, 
List<RuleElement>>();
-    List<String> connectors = new ArrayList<String>();
+    Map<Integer, List<RuleElement>> map = new TreeMap<>();
+    List<String> connectors = new ArrayList<>();
     int reCounter = 0;
     int conCounter = 0;
     for (Token token : conList) {
       if (token == null) {
         List<RuleElement> list = map.get(conCounter);
         if (list == null) {
-          list = new ArrayList<RuleElement>();
+          list = new ArrayList<>();
           map.put(conCounter, list);
         }
         RuleElement e = reList.get(reCounter);
@@ -312,7 +312,7 @@ public class RutaScriptFactory {
         conCounter++;
       }
     }
-    List<RuleElement> elements = new ArrayList<RuleElement>();
+    List<RuleElement> elements = new ArrayList<>();
 
     ConjunctRulesRuleElement cr = new ConjunctRulesRuleElement(null, 
container, env);
     for (List<RuleElement> each : map.values()) {
@@ -324,7 +324,7 @@ public class RutaScriptFactory {
     }
     cr.setElements(elements);
     cr.setContainer(null);
-    List<RuleElement> result = new ArrayList<RuleElement>();
+    List<RuleElement> result = new ArrayList<>();
     result.add(cr);
     return result;
   }
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
index 7a5c6126..bd2ec85a 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
@@ -204,11 +204,10 @@ public class RutaStream {
           AnnotationFS additionalWindow) {
     if (additionalWindow != null) {
       // TODO UIMA-6281 replace select
-      this.basicIt = 
cas.getAnnotationIndex(basicType).select().coveredBy(additionalWindow)
-              .fsIterator();
+      basicIt = 
cas.getAnnotationIndex(basicType).select().coveredBy(additionalWindow).fsIterator();
       // was: this.basicIt = 
cas.getAnnotationIndex(basicType).subiterator(additionalWindow);
     } else {
-      this.basicIt = cas.getAnnotationIndex(basicType).iterator();
+      basicIt = cas.getAnnotationIndex(basicType).iterator();
     }
     currentIt = filter.createFilteredIterator(cas, basicType);
   }
@@ -346,8 +345,9 @@ public class RutaStream {
       createRutaBasic(0, 0);
     } else if (anchors.size() == 1) {
       Integer first = anchors.get(0);
-      if (first >= 0 && first <= cas.getDocumentText().length())
+      if (first >= 0 && first <= cas.getDocumentText().length()) {
         createRutaBasic(first, first);
+      }
     } else {
       for (int i = 0; i < anchors.size() - 1; i++) {
         Integer first = anchors.get(i);
@@ -1231,7 +1231,7 @@ public class RutaStream {
       } else if (value instanceof IStringExpression) {
         IStringExpression stringExpr = (IStringExpression) value;
         String string = stringExpr.getStringValue(context, this);
-        StringArrayFS array = FSCollectionFactory.createStringArrayFS(cas, new 
String[] { string });
+        StringArrayFS array = FSCollectionFactory.createStringArrayFS(cas, 
string);
         annotation.setFeatureValue(feature, array);
       }
     } else if (rangeName.equals(CAS.TYPE_NAME_INTEGER) || 
rangeName.equals(CAS.TYPE_NAME_LONG)
@@ -1253,7 +1253,7 @@ public class RutaStream {
       if (value instanceof INumberExpression) {
         INumberExpression numberExpr = (INumberExpression) value;
         int v = numberExpr.getIntegerValue(context, this);
-        IntArrayFS array = FSCollectionFactory.createIntArrayFS(cas, new int[] 
{ v });
+        IntArrayFS array = FSCollectionFactory.createIntArrayFS(cas, v);
         annotation.setFeatureValue(feature, array);
       } else if (value instanceof INumberListExpression) {
         INumberListExpression expr = (INumberListExpression) value;
@@ -1272,7 +1272,7 @@ public class RutaStream {
       if (value instanceof INumberExpression) {
         INumberExpression numberExpr = (INumberExpression) value;
         double v = numberExpr.getDoubleValue(context, this);
-        DoubleArrayFS array = FSCollectionFactory.createDoubleArrayFS(cas, new 
double[] { v });
+        DoubleArrayFS array = FSCollectionFactory.createDoubleArrayFS(cas, v);
         annotation.setFeatureValue(feature, array);
       } else if (value instanceof INumberListExpression) {
         INumberListExpression expr = (INumberListExpression) value;
@@ -1291,7 +1291,7 @@ public class RutaStream {
       if (value instanceof INumberExpression) {
         INumberExpression numberExpr = (INumberExpression) value;
         float v = numberExpr.getFloatValue(context, this);
-        FloatArrayFS array = FSCollectionFactory.createFloatArrayFS(cas, new 
float[] { v });
+        FloatArrayFS array = FSCollectionFactory.createFloatArrayFS(cas, v);
         annotation.setFeatureValue(feature, array);
       } else if (value instanceof INumberListExpression) {
         INumberListExpression expr = (INumberListExpression) value;
@@ -1305,6 +1305,9 @@ public class RutaStream {
         IBooleanExpression expr = (IBooleanExpression) value;
         Boolean v = expr.getBooleanValue(context, this);
         annotation.setBooleanValue(feature, v);
+      } else if (value instanceof IStringExpression) {
+        String stringValue = ((IStringExpression) 
value).getStringValue(context, this);
+        annotation.setBooleanValue(feature, Boolean.parseBoolean(stringValue));
       }
     } else if (rangeName.equals(CAS.TYPE_NAME_BOOLEAN_ARRAY)) {
       if (value instanceof IBooleanListExpression) {
@@ -1315,7 +1318,7 @@ public class RutaStream {
       } else if (value instanceof IBooleanExpression) {
         IBooleanExpression expr = (IBooleanExpression) value;
         Boolean v = expr.getBooleanValue(context, this);
-        BooleanArrayFS array = FSCollectionFactory.createBooleanArrayFS(cas, 
new boolean[] { v });
+        BooleanArrayFS array = FSCollectionFactory.createBooleanArrayFS(cas, 
v);
         annotation.setFeatureValue(feature, array);
       }
     } else if (value instanceof AnnotationTypeExpression && 
!range.isPrimitive()) {
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/action/AbstractMarkAction.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/action/AbstractMarkAction.java
index 7ec80235..4cfc7e69 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/action/AbstractMarkAction.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/action/AbstractMarkAction.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -66,7 +66,7 @@ public abstract class AbstractMarkAction extends 
TypeSensitiveAction {
 
   boolean getDictWSParamValue(MatchContext context) {
     Object configParameterValue = context.getParent().getContext()
-                                         
.getConfigParameterValue(RutaEngine.PARAM_DICT_REMOVE_WS);
+            .getConfigParameterValue(RutaEngine.PARAM_DICT_REMOVE_WS);
     if (configParameterValue instanceof Boolean) {
       return (Boolean) configParameterValue;
     }
@@ -76,7 +76,7 @@ public abstract class AbstractMarkAction extends 
TypeSensitiveAction {
   protected List<Integer> getIndexList(MatchContext context, 
List<INumberExpression> list,
           RutaStream stream) {
     RuleElement element = context.getElement();
-    List<Integer> indexList = new ArrayList<Integer>();
+    List<Integer> indexList = new ArrayList<>();
     if (list == null || list.isEmpty()) {
       int self = element.getContainer().getRuleElements().indexOf(element) + 1;
       indexList.add(self);
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/action/ColorAction.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/action/ColorAction.java
index 33045c5c..c6c2bb2c 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/action/ColorAction.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/action/ColorAction.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/action/MarkFastReloadAction.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/action/MarkFastReloadAction.java
index 1f90f190..b2a93e53 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/action/MarkFastReloadAction.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/action/MarkFastReloadAction.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -49,113 +49,123 @@ import org.springframework.core.io.ResourceLoader;
 
 public class MarkFastReloadAction extends AbstractMarkAction {
 
-    private static ConcurrentHashMap<String, TwlCacheEntry> twlCache = new 
ConcurrentHashMap<String, TwlCacheEntry>();
+  private static ConcurrentHashMap<String, TwlCacheEntry> twlCache = new 
ConcurrentHashMap<>();
 
-    private IStringExpression listName;
+  private IStringExpression listName;
 
-    private IStringListExpression stringList;
+  private IStringListExpression stringList;
 
-    private IBooleanExpression ignore;
+  private IBooleanExpression ignore;
 
-    private INumberExpression ignoreLength;
+  private INumberExpression ignoreLength;
 
-    private IBooleanExpression ignoreWS;
+  private IBooleanExpression ignoreWS;
 
-    private class TwlCacheEntry {
-        private TreeWordList twl;
-        private long lastModified;
+  private class TwlCacheEntry {
+    private TreeWordList twl;
 
-        public TwlCacheEntry(TreeWordList twl, long lastModified) {
-            this.lastModified = lastModified;
-            this.twl = twl;
-        }
-    }
+    private long lastModified;
 
-    public MarkFastReloadAction(ITypeExpression type, IStringExpression 
listName, IBooleanExpression ignore, INumberExpression ignoreLength, 
IBooleanExpression ignoreWS) {
-        super(type);
-        this.listName = listName;
-        this.ignore = ignore == null ? new SimpleBooleanExpression(false) : 
ignore;
-        this.ignoreLength = ignoreLength == null ? new 
SimpleNumberExpression(Integer.valueOf(0)) : ignoreLength;
-        this.ignoreWS = ignoreWS == null ? new SimpleBooleanExpression(true) : 
ignoreWS;
+    public TwlCacheEntry(TreeWordList twl, long lastModified) {
+      this.lastModified = lastModified;
+      this.twl = twl;
     }
-
-    @Override
-    public void execute(MatchContext context, RutaStream stream, 
InferenceCrowd crowd) {
-      
-        TreeWordList wl = null;
-
-        RuleMatch match = context.getRuleMatch();
-        RuleElement element = context.getElement();
-        String listNameValue = listName.getStringValue(context, stream);
-
-        ResourceLoader resourceLoader = new 
RutaResourceLoader(element.getParent().getEnvironment().getResourcePaths());
-        Resource resource = resourceLoader.getResource(listNameValue);
-        if (resource.exists()) {
-            File resourceFile = null;
-            try {
-                resourceFile = resource.getFile();
-            } catch (IOException e1) {
-                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, 
"Unable to obtain file from resource: " + listNameValue, e1);
-            }
-
-            TwlCacheEntry cacheEntry = twlCache.get(listNameValue);
-            if (cacheEntry == null || cacheEntry != null && 
resourceFile.lastModified() > cacheEntry.lastModified) {
-                Logger.getLogger(this.getClass().getName()).info("Creating 
Tree Word List from resource: " + listNameValue);
-
-                UimaContext uimaContext = element.getParent().getContext();
-                Boolean dictRemoveWS = false;
-                if (uimaContext != null) {
-                    dictRemoveWS = (Boolean) 
uimaContext.getConfigParameterValue(RutaEngine.PARAM_DICT_REMOVE_WS);
-                    if (dictRemoveWS == null) {
-                        dictRemoveWS = false;
-                    }
-                }
-
-                try {
-                    wl = new TreeWordList(resource, dictRemoveWS);
-                } catch (IOException e) {
-                    
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Unable to create 
TWL: " + listNameValue, e);
-                }
-
-                twlCache.put(listNameValue, new TwlCacheEntry(wl, 
resourceFile.lastModified()));
-            } else {
-                wl = cacheEntry.twl;
-            }
-
-        } else {
-            Logger.getLogger(this.getClass().getName()).severe("Can't find 
resource: " + listNameValue);
+  }
+
+  public MarkFastReloadAction(ITypeExpression type, IStringExpression listName,
+          IBooleanExpression ignore, INumberExpression ignoreLength, 
IBooleanExpression ignoreWS) {
+    super(type);
+    this.listName = listName;
+    this.ignore = ignore == null ? new SimpleBooleanExpression(false) : ignore;
+    this.ignoreLength = ignoreLength == null ? new 
SimpleNumberExpression(Integer.valueOf(0))
+            : ignoreLength;
+    this.ignoreWS = ignoreWS == null ? new SimpleBooleanExpression(true) : 
ignoreWS;
+  }
+
+  @Override
+  public void execute(MatchContext context, RutaStream stream, InferenceCrowd 
crowd) {
+
+    TreeWordList wl = null;
+
+    RuleMatch match = context.getRuleMatch();
+    RuleElement element = context.getElement();
+    String listNameValue = listName.getStringValue(context, stream);
+
+    ResourceLoader resourceLoader = new RutaResourceLoader(
+            element.getParent().getEnvironment().getResourcePaths());
+    Resource resource = resourceLoader.getResource(listNameValue);
+    if (resource.exists()) {
+      File resourceFile = null;
+      try {
+        resourceFile = resource.getFile();
+      } catch (IOException e1) {
+        Logger.getLogger(this.getClass().getName()).log(Level.SEVERE,
+                "Unable to obtain file from resource: " + listNameValue, e1);
+      }
+
+      TwlCacheEntry cacheEntry = twlCache.get(listNameValue);
+      if (cacheEntry == null
+              || cacheEntry != null && resourceFile.lastModified() > 
cacheEntry.lastModified) {
+        Logger.getLogger(this.getClass().getName())
+                .info("Creating Tree Word List from resource: " + 
listNameValue);
+
+        UimaContext uimaContext = element.getParent().getContext();
+        Boolean dictRemoveWS = false;
+        if (uimaContext != null) {
+          dictRemoveWS = (Boolean) uimaContext
+                  .getConfigParameterValue(RutaEngine.PARAM_DICT_REMOVE_WS);
+          if (dictRemoveWS == null) {
+            dictRemoveWS = false;
+          }
         }
 
-        List<AnnotationFS> matchedAnnotationsOf = 
match.getMatchedAnnotationsOfElement(element);
-        for (AnnotationFS annotationFS : matchedAnnotationsOf) {
-            RutaStream windowStream = stream.getWindowStream(annotationFS, 
annotationFS.getType());
-            Collection<AnnotationFS> found = wl.find(windowStream, 
this.getIgnore().getBooleanValue(context, stream),
-                    this.getIgnoreLength().getIntegerValue(context, stream), 
null, 0, this.getIgnoreWS().getBooleanValue(context, stream));
-            for (AnnotationFS annotation : found) {
-                createAnnotation(annotation, context, windowStream);
-            }
+        try {
+          wl = new TreeWordList(resource, dictRemoveWS);
+        } catch (IOException e) {
+          Logger.getLogger(this.getClass().getName()).log(Level.SEVERE,
+                  "Unable to create TWL: " + listNameValue, e);
         }
-    }
 
-    public IStringExpression getListName() {
-        return listName;
-    }
+        twlCache.put(listNameValue, new TwlCacheEntry(wl, 
resourceFile.lastModified()));
+      } else {
+        wl = cacheEntry.twl;
+      }
 
-    public IStringListExpression getStringList() {
-        return stringList;
+    } else {
+      Logger.getLogger(this.getClass().getName()).severe("Can't find resource: 
" + listNameValue);
     }
 
-    public IBooleanExpression getIgnore() {
-        return ignore;
+    List<AnnotationFS> matchedAnnotationsOf = 
match.getMatchedAnnotationsOfElement(element);
+    for (AnnotationFS annotationFS : matchedAnnotationsOf) {
+      RutaStream windowStream = stream.getWindowStream(annotationFS, 
annotationFS.getType());
+      Collection<AnnotationFS> found = wl.find(windowStream,
+              getIgnore().getBooleanValue(context, stream),
+              getIgnoreLength().getIntegerValue(context, stream), null, 0,
+              getIgnoreWS().getBooleanValue(context, stream));
+      for (AnnotationFS annotation : found) {
+        createAnnotation(annotation, context, windowStream);
+      }
     }
+  }
 
-    public INumberExpression getIgnoreLength() {
-        return ignoreLength;
-    }
+  public IStringExpression getListName() {
+    return listName;
+  }
 
-    public IBooleanExpression getIgnoreWS() {
-        return ignoreWS;
-    }
+  public IStringListExpression getStringList() {
+    return stringList;
+  }
+
+  public IBooleanExpression getIgnore() {
+    return ignore;
+  }
+
+  public INumberExpression getIgnoreLength() {
+    return ignoreLength;
+  }
+
+  public IBooleanExpression getIgnoreWS() {
+    return ignoreWS;
+  }
 
-    
 }
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/descriptor/RutaDescriptorBuilder.java
 
b/ruta-core/src/main/java/org/apache/uima/ruta/descriptor/RutaDescriptorBuilder.java
index 58e73d56..320a32e8 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/descriptor/RutaDescriptorBuilder.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/descriptor/RutaDescriptorBuilder.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -97,14 +97,14 @@ public class RutaDescriptorBuilder {
       }
       rm.setDataPath(dataPath);
     }
-    Map<String, String> typeNameMap = new HashMap<String, String>();
+    Map<String, String> typeNameMap = new HashMap<>();
     TypeSystemDescription initialTypeSystem = UIMAFramework.getXMLParser()
             .parseTypeSystemDescription(new XMLInputSource(defaultTypeSystem));
     CAS cas = CasCreationUtils.createCas(initialTypeSystem, null, new 
FsIndexDescription[0]);
     fillTypeNameMap(typeNameMap, cas.getTypeSystem());
     cas.release();
-    List<TypeSystemDescription> toInclude = new 
ArrayList<TypeSystemDescription>();
-    List<Import> importList = new ArrayList<Import>();
+    List<TypeSystemDescription> toInclude = new ArrayList<>();
+    List<Import> importList = new ArrayList<>();
     Import_impl import_impl = new Import_impl();
     if (options.isImportByName()) {
       String name = initialTypeSystem.getName();
@@ -229,12 +229,12 @@ public class RutaDescriptorBuilder {
     }
 
     // TODO hotfixes: where do I get the final types??
-    Set<String> finalTypes = new HashSet<String>();
-    finalTypes.addAll(Arrays.asList(new String[] { "uima.cas.Boolean", 
"uima.cas.Byte",
-        "uima.cas.Short", "uima.cas.Integer", "uima.cas.Long", 
"uima.cas.Float", "uima.cas.Double",
-        "uima.cas.BooleanArray", "uima.cas.ByteArray", "uima.cas.ShortArray",
-        "uima.cas.IntegerArray", "uima.cas.LongArray", "uima.cas.FloatArray",
-        "uima.cas.DoubleArray", "uima.cas.StringArray", "uima.cas.FSArray" }));
+    Set<String> finalTypes = new HashSet<>();
+    finalTypes.addAll(Arrays.asList("uima.cas.Boolean", "uima.cas.Byte", 
"uima.cas.Short",
+            "uima.cas.Integer", "uima.cas.Long", "uima.cas.Float", 
"uima.cas.Double",
+            "uima.cas.BooleanArray", "uima.cas.ByteArray", 
"uima.cas.ShortArray",
+            "uima.cas.IntegerArray", "uima.cas.LongArray", 
"uima.cas.FloatArray",
+            "uima.cas.DoubleArray", "uima.cas.StringArray", 
"uima.cas.FSArray"));
 
     int typeIndex = 0;
     for (String eachType : desc.getTypeShortNames()) {
@@ -261,8 +261,8 @@ public class RutaDescriptorBuilder {
       typeIndex++;
     }
 
-    Set<String> names = new HashSet<String>();
-    Collection<TypeDescription> types = new HashSet<TypeDescription>();
+    Set<String> names = new HashSet<>();
+    Collection<TypeDescription> types = new HashSet<>();
     for (TypeDescription each : typeSystemDescription.getTypes()) {
       String name = each.getName();
       if (!names.contains(name)) {
@@ -489,7 +489,7 @@ public class RutaDescriptorBuilder {
     }
     String[] parameterValue = (String[]) 
analysisEngineDescription.getAnalysisEngineMetaData()
             
.getConfigurationParameterSettings().getParameterValue(RutaEngine.PARAM_RESOURCE_PATHS);
-    Set<String> resourceLocations = new HashSet<String>();
+    Set<String> resourceLocations = new HashSet<>();
 
     if (parameterValue != null && parameterValue.length != 0) {
       resourceLocations.addAll(Arrays.asList(parameterValue));
@@ -557,7 +557,7 @@ public class RutaDescriptorBuilder {
     String[] extensions = (String[]) configurationParameterSettings
             .getParameterValue(RutaEngine.PARAM_ADDITIONAL_EXTENSIONS);
 
-    List<String> es = new ArrayList<String>();
+    List<String> es = new ArrayList<>();
     if (extensions != null) {
       es.addAll(Arrays.asList(extensions));
     }
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/engine/Ruta.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/engine/Ruta.java
index cefd5775..f697ec31 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/engine/Ruta.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/engine/Ruta.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -68,7 +68,7 @@ public class Ruta {
 
   /**
    * This applies the given rule on the given JCas object
-   * 
+   *
    * @param jcas
    *          - the current document
    * @param rule
@@ -181,7 +181,7 @@ public class Ruta {
 
   /**
    * Removes all debug annotations from the index.
-   * 
+   *
    * @param jcas
    *          - the current document
    */
@@ -230,7 +230,7 @@ public class Ruta {
 
   /**
    * This method returns the spans of successful rule applies.
-   * 
+   *
    * @param jcas
    *          - the current document
    * @param rule
@@ -255,7 +255,7 @@ public class Ruta {
 
   /**
    * This method returns true if the rule (or one of the rules) was able to 
match.
-   * 
+   *
    * @param jcas
    *          - the current document
    * @param rule
@@ -341,7 +341,7 @@ public class Ruta {
     String name = scriptFile.getName().substring(0, 
scriptFile.getName().length() - 5);
     settings.setParameterValue(RutaEngine.PARAM_MAIN_SCRIPT, name);
     if (tsds != null) {
-      List<TypeSystemDescription> tsdList = new 
ArrayList<TypeSystemDescription>();
+      List<TypeSystemDescription> tsdList = new ArrayList<>();
       tsdList.add(metaData.getTypeSystem());
       tsdList.addAll(Arrays.asList(tsds));
       TypeSystemDescription typeSystemDescription = 
CasCreationUtils.mergeTypeSystems(tsdList);
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
index d5230a4f..c5f4c9bf 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -134,7 +134,7 @@ public class RutaTestUtils {
           String resourceDirName, CAS cas) throws URISyntaxException, 
IOException,
           InvalidXMLException, ResourceInitializationException, 
AnalysisEngineProcessException,
           ResourceConfigurationException {
-    final Map<String, Object> parameters = new HashMap<String, Object>();
+    final Map<String, Object> parameters = new HashMap<>();
     parameters.put(RutaEngine.PARAM_DYNAMIC_ANCHORING, dynamicAnchoring);
     parameters.put(RutaEngine.PARAM_SIMPLE_GREEDY_FOR_COMPOSED, 
simpleGreedyForComposed);
 
@@ -172,7 +172,7 @@ public class RutaTestUtils {
     addTestTypes(basicTypeSystem);
     addAdditionalTypes(complexTypes, features, basicTypeSystem);
 
-    Collection<TypeSystemDescription> tsds = new 
ArrayList<TypeSystemDescription>();
+    Collection<TypeSystemDescription> tsds = new ArrayList<>();
     tsds.add(basicTypeSystem);
     TypeSystemDescription mergeTypeSystems = 
CasCreationUtils.mergeTypeSystems(tsds);
     aed.getAnalysisEngineMetaData().setTypeSystem(mergeTypeSystems);
@@ -211,7 +211,7 @@ public class RutaTestUtils {
 
   /**
    * Helper to get the test type, e.g. org.apache.uima.T1, org.apache.uima.T2, 
...
-   * 
+   *
    * @param cas
    *          - The CAS object containing the type system
    * @param i
@@ -219,8 +219,9 @@ public class RutaTestUtils {
    * @return the test type object with the given counter
    */
   public static Type getTestType(CAS cas, int i) {
-    if (cas == null)
+    if (cas == null) {
       return null;
+    }
     return cas.getTypeSystem().getType(TYPE + i);
   }
 
@@ -252,7 +253,7 @@ public class RutaTestUtils {
     TypeSystemDescription basicTypeSystem = 
aed.getAnalysisEngineMetaData().getTypeSystem();
     addTestTypes(basicTypeSystem);
     addAdditionalTypes(complexTypes, features, basicTypeSystem);
-    Collection<TypeSystemDescription> tsds = new 
ArrayList<TypeSystemDescription>();
+    Collection<TypeSystemDescription> tsds = new ArrayList<>();
     tsds.add(basicTypeSystem);
     TypeSystemDescription mergeTypeSystems = 
CasCreationUtils.mergeTypeSystems(tsds);
 
@@ -304,7 +305,7 @@ public class RutaTestUtils {
 
   /**
    * Helper for common assertion in JUnit tests
-   * 
+   *
    * @param cas
    *          - The CAS object containing the type system
    * @param typeId
@@ -336,7 +337,7 @@ public class RutaTestUtils {
 
   /**
    * Helper to run Ruta on a tests script
-   * 
+   *
    * @param testClass
    *          - the class of the unit test
    * @return the annotated {@link CAS}
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/engine/ViewWriter.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/engine/ViewWriter.java
index cc437cf2..22f1be76 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/engine/ViewWriter.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/engine/ViewWriter.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -42,7 +42,7 @@ import org.apache.uima.util.XMLSerializer;
  * This Analysis Engine is able to serialize the processed CAS to an XMI file 
whereas the the source
  * and destination view can be specified A descriptor file for this Analysis 
Engine is located in
  * the folder <code>descriptor/utils</code> of a UIMA Ruta project.
- * 
+ *
  */
 public class ViewWriter extends JCasMultiplier_ImplBase {
 
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/engine/XMIWriter.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/engine/XMIWriter.java
index 4ec446d0..d2fe1f09 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/engine/XMIWriter.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/engine/XMIWriter.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -42,7 +42,7 @@ import org.apache.uima.util.XMLSerializer;
  * folder, dependent on the execution of the rules, e.g., whether a pattern of 
annotations occurs or
  * not. A descriptor file for this Analysis Engine is located in the folder
  * <code>descriptor/utils</code> of a UIMA Ruta project.
- * 
+ *
  */
 public class XMIWriter extends JCasAnnotator_ImplBase {
 
@@ -70,7 +70,7 @@ public class XMIWriter extends JCasAnnotator_ImplBase {
     }
     if (aContext != null) {
       output = (String) aContext.getConfigParameterValue(PARAM_OUTPUT);
-      this.context = aContext;
+      context = aContext;
     }
   }
 
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/ExpressionFactory.java
 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/ExpressionFactory.java
index de85b03a..cf752cfc 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/ExpressionFactory.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/ExpressionFactory.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -116,9 +116,8 @@ public class ExpressionFactory {
     SimpleNumberExpression simpleNumberExpression = new 
SimpleNumberExpression(valueOf);
     if (minus != null) {
       return new NegativeNumberExpression(simpleNumberExpression);
-    } else {
-      return simpleNumberExpression;
     }
+    return simpleNumberExpression;
   }
 
   public INumberExpression createDoubleExpression(Token number, Token minus) {
@@ -126,23 +125,21 @@ public class ExpressionFactory {
     SimpleNumberExpression simpleNumberExpression = new 
SimpleNumberExpression(valueOf);
     if (minus != null) {
       return new NegativeNumberExpression(simpleNumberExpression);
-    } else {
-      return simpleNumberExpression;
     }
+    return simpleNumberExpression;
   }
 
   public INumberExpression createReferenceNumberExpression(Token var, Token 
minus) {
     NumberVariableExpression simpleNumberExpression = new 
NumberVariableExpression(var.getText());
     if (minus != null) {
       return new NegativeNumberExpression(simpleNumberExpression);
-    } else {
-      return simpleNumberExpression;
     }
+    return simpleNumberExpression;
   }
 
   public INumberExpression 
createComposedNumberExpression(List<INumberExpression> expressions,
           List<Token> opTokens) {
-    List<String> ops = new ArrayList<String>();
+    List<String> ops = new ArrayList<>();
     for (Token token : opTokens) {
       ops.add(token.getText());
     }
@@ -151,8 +148,8 @@ public class ExpressionFactory {
 
   public INumberExpression createComposedNumberExpression(INumberExpression 
expression,
           Token opToken) {
-    List<String> ops = new ArrayList<String>();
-    List<INumberExpression> exprList = new ArrayList<INumberExpression>();
+    List<String> ops = new ArrayList<>();
+    List<INumberExpression> exprList = new ArrayList<>();
     ops.add(opToken.getText());
     exprList.add(expression);
     return new ComposedNumberExpression(exprList, ops);
@@ -160,8 +157,8 @@ public class ExpressionFactory {
 
   public INumberExpression createComposedNumberExpression(INumberExpression 
expression1,
           Token opToken, INumberExpression expression2) {
-    List<String> ops = new ArrayList<String>();
-    List<INumberExpression> exprList = new ArrayList<INumberExpression>();
+    List<String> ops = new ArrayList<>();
+    List<INumberExpression> exprList = new ArrayList<>();
     ops.add(opToken.getText());
     exprList.add(expression1);
     exprList.add(expression2);
@@ -178,6 +175,19 @@ public class ExpressionFactory {
     return new ComposedStringExpression(expressions);
   }
 
+  public IStringExpression createGenericComposedStringExpression(
+          List<IRutaExpression> expressions) {
+    List<IStringExpression> stringExpression = new ArrayList<>();
+    for (IRutaExpression each : expressions) {
+      if (each instanceof IStringExpression) {
+        stringExpression.add((IStringExpression) each);
+      } else {
+        System.out.println();
+      }
+    }
+    return new ComposedStringExpression(stringExpression);
+  }
+
   public AbstractStringExpression createReferenceStringExpression(Token var) {
     return new StringVariableExpression(var.getText());
   }
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/annotation/AbstractAnnotationExpression.java
 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/annotation/AbstractAnnotationExpression.java
index 04a8535f..d9c992b7 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/annotation/AbstractAnnotationExpression.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/annotation/AbstractAnnotationExpression.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -25,21 +25,21 @@ import org.apache.uima.ruta.RutaStream;
 import org.apache.uima.ruta.expression.RutaExpression;
 import org.apache.uima.ruta.rule.MatchContext;
 
-public abstract class AbstractAnnotationExpression extends RutaExpression 
implements IAnnotationExpression {
+public abstract class AbstractAnnotationExpression extends RutaExpression
+        implements IAnnotationExpression {
 
   @Override
   public FeatureStructure getFeatureStructure(MatchContext context, RutaStream 
stream) {
     return getAnnotation(context, stream);
   }
-  
+
   @Override
   public String getStringValue(MatchContext context, RutaStream stream) {
     AnnotationFS annotation = getAnnotation(context, stream);
-    if(annotation != null) {
+    if (annotation != null) {
       return annotation.getCoveredText();
-    } else {
-      return "null";
     }
+    return "null";
   }
-  
+
 }
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/bool/BooleanFeatureExpression.java
 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/bool/BooleanFeatureExpression.java
index 415a634d..627f79ad 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/bool/BooleanFeatureExpression.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/bool/BooleanFeatureExpression.java
@@ -22,6 +22,7 @@ package org.apache.uima.ruta.expression.bool;
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.Feature;
 import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.text.AnnotationFS;
@@ -43,10 +44,10 @@ public class BooleanFeatureExpression extends 
AbstractBooleanExpression {
   public boolean getBooleanValue(MatchContext context, RutaStream stream) {
 
     AnnotationFS annotation = context.getAnnotation();
-    Feature feature = this.fe.getFeature(context, stream);
-    List<AnnotationFS> list = this.getTargetAnnotation(annotation, this.fe, 
context, stream);
-    Collection<? extends FeatureStructure> featureStructures = 
this.fe.getFeatureStructures(list,
-            false, context, stream);
+    Feature feature = fe.getFeature(context, stream);
+    List<AnnotationFS> list = getTargetAnnotation(annotation, fe, context, 
stream);
+    Collection<? extends FeatureStructure> featureStructures = 
fe.getFeatureStructures(list, false,
+            context, stream);
     if (!featureStructures.isEmpty()) {
       FeatureStructure next = featureStructures.iterator().next();
 //      if (next instanceof AnnotationFS && next != annotation) {
@@ -57,6 +58,11 @@ public class BooleanFeatureExpression extends 
AbstractBooleanExpression {
         LazyFeature lazyFeature = (LazyFeature) feature;
         feature = lazyFeature.initialize(next);
       }
+      if (feature != null && 
feature.getRange().getName().equals(CAS.TYPE_NAME_STRING)) {
+        String stringValue = next.getStringValue(feature);
+        return Boolean.parseBoolean(stringValue);
+      }
+
       return next.getBooleanValue(feature);
     }
     return false;
@@ -65,12 +71,12 @@ public class BooleanFeatureExpression extends 
AbstractBooleanExpression {
   @Override
   public String getStringValue(MatchContext context, RutaStream stream) {
 
-    return String.valueOf(this.getBooleanValue(context, stream));
+    return String.valueOf(getBooleanValue(context, stream));
   }
 
   public FeatureExpression getFe() {
 
-    return this.fe;
+    return fe;
   }
 
   public void setFe(FeatureExpression fe) {
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/string/StringFeatureExpression.java
 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/string/StringFeatureExpression.java
index b838a59f..82f49e09 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/expression/string/StringFeatureExpression.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/expression/string/StringFeatureExpression.java
@@ -22,6 +22,7 @@ package org.apache.uima.ruta.expression.string;
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.Feature;
 import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.text.AnnotationFS;
@@ -45,10 +46,10 @@ public class StringFeatureExpression extends 
AbstractStringExpression {
   public String getStringValue(MatchContext context, RutaStream stream) {
 
     AnnotationFS annotation = context.getAnnotation();
-    Feature feature = this.fe.getFeature(context, stream);
-    List<AnnotationFS> list = this.getTargetAnnotation(annotation, this.fe, 
context, stream);
-    Collection<? extends FeatureStructure> featureStructures = 
this.fe.getFeatureStructures(list,
-            false, context, stream);
+    Feature feature = fe.getFeature(context, stream);
+    List<AnnotationFS> list = getTargetAnnotation(annotation, fe, context, 
stream);
+    Collection<? extends FeatureStructure> featureStructures = 
fe.getFeatureStructures(list, false,
+            context, stream);
     if (!featureStructures.isEmpty()) {
       FeatureStructure next = featureStructures.iterator().next();
       // if (next instanceof AnnotationFS && 
!next.getType().equals(annotation.getType())) {
@@ -61,16 +62,18 @@ public class StringFeatureExpression extends 
AbstractStringExpression {
       }
       if (next instanceof AnnotationFS && feature instanceof 
CoveredTextFeature) {
         return ((AnnotationFS) next).getCoveredText();
-      } else {
+      }
+      if (feature.getRange().getName().equals(CAS.TYPE_NAME_STRING)) {
         return next.getStringValue(feature);
       }
+      return next.getFeatureValueAsString(feature);
     }
     return null;
   }
 
   public FeatureExpression getFe() {
 
-    return this.fe;
+    return fe;
   }
 
   public void setFe(FeatureExpression fe) {
diff --git 
a/ruta-core/src/main/java/org/apache/uima/ruta/visitor/StatisticsVisitor.java 
b/ruta-core/src/main/java/org/apache/uima/ruta/visitor/StatisticsVisitor.java
index 141a327f..aa3a57c4 100644
--- 
a/ruta-core/src/main/java/org/apache/uima/ruta/visitor/StatisticsVisitor.java
+++ 
b/ruta-core/src/main/java/org/apache/uima/ruta/visitor/StatisticsVisitor.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -62,12 +62,12 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
   public StatisticsVisitor(RutaVerbalizer verbalizer) {
     super();
     this.verbalizer = verbalizer;
-    conditionTime = new HashMap<String, Long>();
-    actionTime = new HashMap<String, Long>();
-    conditionAmount = new HashMap<String, Integer>();
-    actionAmount = new HashMap<String, Integer>();
-    conditionDelta = new HashMap<String, Long>();
-    actionDelta = new HashMap<String, Long>();
+    conditionTime = new HashMap<>();
+    actionTime = new HashMap<>();
+    conditionAmount = new HashMap<>();
+    actionAmount = new HashMap<>();
+    conditionDelta = new HashMap<>();
+    actionDelta = new HashMap<>();
   }
 
   @Override
@@ -76,8 +76,9 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
       AbstractRutaCondition c = (AbstractRutaCondition) element;
       String name = verbalizer.verbalizeName(c);
       Integer amount = conditionAmount.get(name);
-      if (amount == null)
+      if (amount == null) {
         amount = 0;
+      }
       amount++;
       conditionAmount.put(name, amount);
       conditionDelta.put(name, System.currentTimeMillis());
@@ -85,8 +86,9 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
       AbstractRutaAction a = (AbstractRutaAction) element;
       String name = verbalizer.verbalizeName(a);
       Integer amount = actionAmount.get(name);
-      if (amount == null)
+      if (amount == null) {
         amount = 0;
+      }
       amount++;
       actionAmount.put(name, amount);
       actionDelta.put(name, System.currentTimeMillis());
@@ -101,8 +103,9 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
       Long start = conditionDelta.get(name);
       long delta = System.currentTimeMillis() - start;
       Long total = conditionTime.get(name);
-      if (total == null)
+      if (total == null) {
         total = 0L;
+      }
       total += delta;
       conditionTime.put(name, total);
     } else if (element instanceof AbstractRutaAction) {
@@ -111,8 +114,9 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
       Long start = actionDelta.get(name);
       long delta = System.currentTimeMillis() - start;
       Long total = actionTime.get(name);
-      if (total == null)
+      if (total == null) {
         total = 0L;
+      }
       total += delta;
       actionTime.put(name, total);
     }
@@ -120,10 +124,10 @@ public class StatisticsVisitor implements 
RutaInferenceVisitor {
 
   @Override
   public void finished(RutaStream stream, List<RutaInferenceVisitor> visitors) 
{
-    List<String> names = new ArrayList<String>();
-    List<Double> totals = new ArrayList<Double>();
-    List<Integer> amounts = new ArrayList<Integer>();
-    List<Double> parts = new ArrayList<Double>();
+    List<String> names = new ArrayList<>();
+    List<Double> totals = new ArrayList<>();
+    List<Integer> amounts = new ArrayList<>();
+    List<Double> parts = new ArrayList<>();
     for (String each : conditionTime.keySet()) {
       double total = conditionTime.get(each);
       double amount = conditionAmount.get(each);
diff --git 
a/ruta-core/src/test/java/org/apache/uima/ruta/expression/bool/BooleanExpressionTest.java
 
b/ruta-core/src/test/java/org/apache/uima/ruta/expression/bool/BooleanExpressionTest.java
new file mode 100644
index 00000000..5a16d727
--- /dev/null
+++ 
b/ruta-core/src/test/java/org/apache/uima/ruta/expression/bool/BooleanExpressionTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.uima.ruta.expression.bool;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.ruta.engine.Ruta;
+import org.apache.uima.ruta.engine.RutaTestUtils;
+import org.apache.uima.ruta.engine.RutaTestUtils.TestFeature;
+import org.junit.jupiter.api.Test;
+
+public class BooleanExpressionTest {
+
+  @Test
+  public void testBooleanAssignment() throws Exception {
+
+    Map<String, String> typeMap = new TreeMap<>();
+    typeMap.put("Struct", "uima.tcas.Annotation");
+    typeMap.put("Temp", "uima.tcas.Annotation");
+
+    Map<String, List<TestFeature>> featureMap = new TreeMap<>();
+    featureMap.put("Struct", Arrays.asList(new TestFeature("bf", "", 
"uima.cas.Boolean")));
+    featureMap.put("Temp", Arrays.asList(new TestFeature("sf", "", 
"uima.cas.String")));
+
+    String document = "This is a test.";
+
+    String script = "";
+    script += "STRING vsTrue = \"true\";\n";
+    script += "STRING vsFalse = \"FALSE\";\n";
+    script += "Document{->Struct, t:Temp, t.sf=\"true\"};\n";
+
+    script += "s:Struct{-> s.bf = false};\n";
+    script += "s:Struct{-> s.bf = true};\n";
+    script += "s:Struct{s.bf-> T1};\n";
+
+    script += "s:Struct{-> s.bf = false};\n";
+    script += "s:Struct{-> s.bf = vsTrue};\n";
+    script += "s:Struct{s.bf-> T2};\n";
+
+    script += "s:Struct{-> s.bf = false};\n";
+    script += "s:Struct{-> s.bf = Temp.sf};\n";
+    script += "s:Struct{s.bf-> T3};\n";
+
+    script += "s:Struct{-> s.bf = false};\n";
+    script += "s:Struct{-> s.bf = \"tr\" + \"ue\"};\n";
+    script += "s:Struct{s.bf-> T4};\n";
+
+    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 4, 1, "This is a test.");
+  }
+
+}
diff --git 
a/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringExpressionTest.java
 
b/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringExpressionTest.java
new file mode 100644
index 00000000..8855493e
--- /dev/null
+++ 
b/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringExpressionTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.uima.ruta.expression.string;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.ruta.engine.Ruta;
+import org.apache.uima.ruta.engine.RutaTestUtils;
+import org.apache.uima.ruta.engine.RutaTestUtils.TestFeature;
+import org.junit.jupiter.api.Test;
+
+public class StringExpressionTest {
+
+  @Test
+  public void testValidValue() throws Exception {
+
+    String document = "Any - 0 test.";
+
+    String script = "DOUBLE d = 5; BOOLEAN b = true; TYPE t = 
org.apache.uima.ruta.type.TruePositive;INT i = 4;\n";
+    script += "CW{-> CREATE(Struct, \"s\" = d)};";
+    script += "SPECIAL{-> CREATE(Struct, \"s\" = b)};";
+    script += "NUM{-> CREATE(Struct, \"s\" = t)};";
+    script += "SW{-> Struct};";
+    script += "s:Struct{PARTOF(SW)-> s.s = i};";
+
+    script += "Struct.s==\"5.0\"{-> T1};";
+    script += "Struct.s==\"true\"{-> T2};";
+    script += "Struct.s==\"org.apache.uima.ruta.type.TruePositive\"{-> T3};";
+    script += "Struct.s==\"4\"{-> T4};";
+
+    Map<String, String> typeMap = new TreeMap<>();
+    String typeName = "Struct";
+    typeMap.put(typeName, "uima.tcas.Annotation");
+
+    Map<String, List<TestFeature>> featureMap = new TreeMap<>();
+    List<TestFeature> list = new ArrayList<>();
+    featureMap.put(typeName, list);
+    String fn = "s";
+    list.add(new TestFeature(fn, "", "uima.cas.String"));
+
+    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "Any");
+    RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "-");
+    RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "0");
+    RutaTestUtils.assertAnnotationsEquals(cas, 4, 1, "test");
+
+  }
+
+  @Test
+  public void testStringFeatureAssignment() throws Exception {
+
+    Map<String, String> typeMap = new TreeMap<>();
+    typeMap.put("Struct", "uima.tcas.Annotation");
+    typeMap.put("Temp", "uima.tcas.Annotation");
+
+    Map<String, List<TestFeature>> featureMap = new TreeMap<>();
+    featureMap.put("Struct", Arrays.asList(new TestFeature("sf", "", 
"uima.cas.String")));
+    featureMap.put("Temp", Arrays.asList(new TestFeature("if", "", 
"uima.cas.Integer")));
+
+    String document = "This is a test.";
+
+    String script = "";
+    script += "INT vi = 2;\n";
+    script += "STRING vs = \"T\";\n";
+    script += "Document{->Struct, Temp};\n";
+
+    script += "s:Struct{-> s.sf = \"T\" + 1};\n";
+    script += "s:Struct{s.sf == \"T1\"-> T1};\n";
+
+    script += "s:Struct{-> s.sf = \"T\" + vi};\n";
+    script += "s:Struct{s.sf == \"T2\"-> T2};\n";
+
+    script += "s:Struct{->s.sf = \"T\", s.sf = s.sf + 3};\n";
+    script += "s:Struct{s.sf == \"T3\"-> T3};\n";
+
+    script += "Temp{->t:Temp, t.if = 4};\n";
+    script += "s:Struct{->s.sf = \"T\", s.sf = \"T\" + Temp.if};\n";
+    script += "s:Struct{s.sf == \"T4\"-> T4};\n";
+
+    script += "Temp{->t:Temp, t.if = 5};\n";
+    script += "s:Struct{->s.sf = \"T\" + t.if}<-{t:Temp;};\n";
+    script += "s:Struct{s.sf == \"T5\"-> T5};\n";
+
+    script += "Temp{->t:Temp, t.if = 6};\n";
+    script += "s:Struct{->s.sf = vs + t.if}<-{t:Temp;};\n";
+    script += "s:Struct{s.sf == \"T6\"-> T6};\n";
+
+    script += "s:Struct{->s.sf = vi};\n";
+    script += "s:Struct{s.sf == \"2\"-> T7};\n";
+
+    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 4, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 5, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 6, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 7, 1, "This is a test.");
+  }
+
+  @Test
+  public void testStringVariableAssignment() throws Exception {
+
+    Map<String, String> typeMap = new TreeMap<>();
+    typeMap.put("Struct", "uima.tcas.Annotation");
+    typeMap.put("Temp", "uima.tcas.Annotation");
+
+    Map<String, List<TestFeature>> featureMap = new TreeMap<>();
+    featureMap.put("Struct", Arrays.asList(new TestFeature("sf", "", 
"uima.cas.String")));
+    featureMap.put("Temp", Arrays.asList(new TestFeature("if", "", 
"uima.cas.Integer")));
+
+    String document = "This is a test.";
+
+    String script = "";
+    script += "INT vi = 2;\n";
+    script += "STRING result = \"\";\n";
+    script += "STRING st = \"T\";\n";
+    script += "Document{->s:Struct, t:Temp, s.sf=\"a\", t.if=9};\n";
+
+    script += "Document{-> result = \"T\" + 1};\n";
+    script += "Document{result == \"T1\"-> T1};\n";
+
+    script += "Document{-> result = \"T\" + vi};\n";
+    script += "Document{result == \"T2\"-> T2};\n";
+
+    script += "Document{-> result = st +3};\n";
+    script += "Document{result == \"T3\"-> T3};\n";
+
+    script += "Document{-> result = 4};\n";
+    script += "Document{result == \"4\"-> T4};\n";
+
+    script += "Document{-> result = false};\n";
+    script += "Document{result == \"false\"-> T5};\n";
+
+    script += "Document{-> result = Struct.sf + Temp.if};\n";
+    script += "Document{result == \"a9\"-> T6};\n";
+
+    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 4, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 5, 1, "This is a test.");
+    RutaTestUtils.assertAnnotationsEquals(cas, 6, 1, "This is a test.");
+  }
+
+}
diff --git 
a/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringFeatureTest.java
 
b/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringFeatureTest.java
deleted file mode 100644
index 98c3a979..00000000
--- 
a/ruta-core/src/test/java/org/apache/uima/ruta/expression/string/StringFeatureTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.uima.ruta.expression.string;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.uima.cas.CAS;
-import org.apache.uima.ruta.engine.Ruta;
-import org.apache.uima.ruta.engine.RutaTestUtils;
-import org.apache.uima.ruta.engine.RutaTestUtils.TestFeature;
-import org.junit.jupiter.api.Test;
-
-public class StringFeatureTest {
-
-  @Test
-  public void testValidValue() throws Exception {
-
-    String document = "Any - 0 test.";
-
-    String script = "DOUBLE d = 5; BOOLEAN b = true; TYPE t = 
org.apache.uima.ruta.type.TruePositive;INT i = 4;\n";
-    script += "CW{-> CREATE(Struct, \"s\" = d)};";
-    script += "SPECIAL{-> CREATE(Struct, \"s\" = b)};";
-    script += "NUM{-> CREATE(Struct, \"s\" = t)};";
-    script += "SW{-> Struct};";
-    script += "s:Struct{PARTOF(SW)-> s.s = i};";
-
-    script += "Struct.s==\"5.0\"{-> T1};";
-    script += "Struct.s==\"true\"{-> T2};";
-    script += "Struct.s==\"org.apache.uima.ruta.type.TruePositive\"{-> T3};";
-    script += "Struct.s==\"4\"{-> T4};";
-
-    Map<String, String> typeMap = new TreeMap<String, String>();
-    String typeName = "Struct";
-    typeMap.put(typeName, "uima.tcas.Annotation");
-
-    Map<String, List<TestFeature>> featureMap = new TreeMap<String, 
List<TestFeature>>();
-    List<TestFeature> list = new ArrayList<RutaTestUtils.TestFeature>();
-    featureMap.put(typeName, list);
-    String fn = "s";
-    list.add(new TestFeature(fn, "", "uima.cas.String"));
-
-    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);
-    Ruta.apply(cas, script);
-
-    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "Any");
-    RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "-");
-    RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "0");
-    RutaTestUtils.assertAnnotationsEquals(cas, 4, 1, "test");
-
-  }
-
-}
diff --git 
a/ruta-ep-ide/src/main/antlr3/org/apache/uima/ruta/ide/core/parser/RutaParser.g 
b/ruta-ep-ide/src/main/antlr3/org/apache/uima/ruta/ide/core/parser/RutaParser.g
index 040114db..83db6a50 100644
--- 
a/ruta-ep-ide/src/main/antlr3/org/apache/uima/ruta/ide/core/parser/RutaParser.g
+++ 
b/ruta-ep-ide/src/main/antlr3/org/apache/uima/ruta/ide/core/parser/RutaParser.g
@@ -2284,6 +2284,7 @@ argument returns [Expression expr = null]
        :
        (conditionedAnnotationType)=>cat = conditionedAnnotationType{expr = 
cat;                }
        | (nullExpression) => a5 = nullExpression {expr = a5;}
+       | (complexStringExpression) => cse = complexStringExpression {expr = 
cse;}
        | (featureExpression)=> fe = featureExpression {expr = fe;}
        | (booleanExpression)=> a2 = booleanExpression {expr = a2;}
        | (numberExpression)=> a3 = numberExpression {expr = a3;}
@@ -2310,8 +2311,8 @@ simpleArgument returns [Expression expr = null]
        :
         (nullExpression) => a5 = nullExpression {expr = a5;}
        | (featureExpression)=> fe = featureExpression {expr = fe;}
-       | (booleanExpression)=> a2 = booleanExpression {expr = a2;}
-       | (numberExpression)=> a3 = numberExpression {expr = a3;}
+       | (simpleBooleanExpression)=> a2 = simpleBooleanExpression {expr = a2;}
+       | (simpleNumberExpression)=> a3 = simpleNumberExpression {expr = a3;}
        | (stringExpression)=> a4 = stringExpression {expr = a4;}
        | (listExpression)=> l = listExpression {expr = l;}
        | a1 = typeExpression {expr = a1;}
@@ -2616,6 +2617,19 @@ List<Expression> exprList = new ArrayList<Expression>();
        {expr = ExpressionFactory.createStringExpression(exprList);}
        ;
 
+complexStringExpression returns [Expression expr = null]
+options {
+       backtrack = true;
+}
+@init {
+List<Expression> exprList = new ArrayList<Expression>();
+{expr = ExpressionFactory.createEmptyStringExpression(input.LT(1));}
+}
+       :
+       a1 = simpleArgument {exprList.add(a1);}
+       ((PLUS)=>PLUS an = simpleArgument {exprList.add(an);})+
+       {expr = ExpressionFactory.createStringExpression(exprList);}
+       ;
 
 // not checked
 stringFunction returns [Expression expr = null]

Reply via email to