99c8ed08eb3655325a40db805e9ba44db0302bd6
kent
  Fri Aug 9 23:39:05 2019 -0700
Fixed off-by-one problem on empty end ranges.

diff --git src/lib/strex.c src/lib/strex.c
index 3d20516..16d8de2 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -625,67 +625,64 @@
 if (tok == NULL)
     tokenizerReuse(tkz);
 else if (tok[0] == '[')
     {
     array = strexParseCoerce(array, strexTypeString);
     tok = tokenizerMustHaveNext(tkz);
     if (tok[0] == ':')  // Case where is a range with empty beginning.  We can even compute index.
         {
 	tok = tokenizerMustHaveNext(tkz);
 	if (tok[0] == ']')
 	    {
 	    tokenizerReuse(tkz);    // Range is just whole array, do nothing really
 	    }
 	else
 	    {
-	    tokenizerReuse(tkz);    // Range is just whole array, do nothing really
+	    tokenizerReuse(tkz);    
 	    struct strexParse *firstIndex = strexParseNew(strexOpLiteral, strexTypeInt);
 	    struct strexParse *secondIndex = strexParseCoerce(strexParseExpression(in), strexTypeInt);
 	    p = arrayRangeTree(array, firstIndex, secondIndex);
 	    }
         }
     else
 	{
 	tokenizerReuse(tkz);
 	struct strexParse *firstIndex = strexParseCoerce(strexParseExpression(in), strexTypeInt);
 	tok = tokenizerMustHaveNext(tkz);
 	if (tok[0] == ':')
 	    {
 	    struct strexParse *secondIndex;
 	    tok = tokenizerMustHaveNext(tkz);
 	    if (tok[0] == ']')  // Case where second half of rang is empty
 		{
 	        tokenizerReuse(tkz);
-		secondIndex = strexParseNew(strexOpLiteral, strexTypeInt);
-		secondIndex->val.i = -1;
+		secondIndex = strexParseNew(strexOpStrlen, strexTypeInt);
+		secondIndex->children = array;
 		}
 	    else
 	        {
 	        tokenizerReuse(tkz);
 		secondIndex = strexParseCoerce(strexParseExpression(in), strexTypeInt);
 		}
 	    p = arrayRangeTree(array, firstIndex, secondIndex);
 	    }
 	else
 	    {
 	    // Simple no range case
 	    tokenizerReuse(tkz);
-	    AllocVar(p);
-	    p->op = strexOpArrayIx;
-	    p->type = strexTypeString;
+	    p = strexParseNew(strexOpArrayIx, strexTypeString);
 	    p->children = array;
-	    p->val.s = cloneString("");
 	    array->next = firstIndex;
 	    }
 	}
     skipOverRequired(in, "]");
     }
 else
     tokenizerReuse(tkz);
 return p;
 }
 
 
 static struct strexParse *strexParseUnaryMinus(struct strexIn *in)
 /* Return unary minus sort of parse tree if there's a leading '-' */
 {
 struct tokenizer *tkz = in->tkz;
@@ -890,31 +887,31 @@
  * has just been turned into two integer values. */
 {
 struct strexParse *array = p->children;
 struct strexParse *index1 = array->next;
 struct strexParse *index2 = index1->next;
 struct strexEval arrayVal = strexLocalEval(array, record, lookup, lm);
 struct strexEval rangeStart = strexLocalEval(index1, record, lookup, lm);
 struct strexEval rangeEnd = strexLocalEval(index2, record, lookup, lm);
 char *arraySource = arrayVal.val.s;
 int start = rangeStart.val.i;
 int end = rangeEnd.val.i;
 int len = strlen(arraySource); 
 if (start < 0)
     start = strlen(arraySource) + start;
 if (end < 0)
-    end = strlen(arraySource) + end + 1;
+    end = strlen(arraySource) + end;
 if (start < 0)
    start = 0;
 if (end > len)
     end = len;
 if (end < start) end = start;   // errors apparently just get truncated in this language.  hmm
 struct strexEval res;
 res.val.s = lmCloneStringZ(lm, arrayVal.val.s + start, end-start);
 res.type = strexTypeString;
 return res;
 }
 
 static char *splitString(char *words,  int ix,  struct lm *lm)
 /* Return the space-delimited word of index ix as clone into lm */
 {
 char *s = words;
@@ -1138,30 +1135,35 @@
     case strexOpUnaryMinusInt:
         res = strexLocalEval(p->children, record, lookup, lm);
 	res.val.i = -res.val.i;
 	break;
     case strexOpUnaryMinusDouble:
         res = strexLocalEval(p->children, record, lookup, lm);
 	res.val.x = -res.val.x;
 	break;
 
     case strexOpArrayIx:
        res = strexEvalArrayIx(p, record, lookup, lm);
        break;
     case strexOpArrayRange:
        res = strexEvalArrayRange(p, record, lookup, lm);
        break;
+    case strexOpStrlen:
+       res = strexLocalEval(p->children, record, lookup, lm);
+       res.type = strexTypeInt;
+       res.val.i = strlen(res.val.s);
+       break;
 
     case strexOpBuiltInCall:
        res = strexEvalCallBuiltIn(p, record, lookup, lm);
        break;
 
     /* Mathematical ops, simple binary type */
     case strexOpAdd:
        res = strexEvalAdd(p, record, lookup, lm);
        break;
 
     default:
         errAbort("Unknown op %s\n", strexOpToString(p->op));
 	res.type = strexTypeInt;	// Keep compiler from complaining.
 	res.val.i = 0;	// Keep compiler from complaining.
 	break;