3b8231d6039e2a9ffc012414a7881ebf72eee458
kent
  Mon Jan 11 16:50:51 2021 -0800
Implementing subtraction, since it was in the docs, as Jonathon pointed out.  #ref 26745

diff --git src/lib/strex.c src/lib/strex.c
index 38c0933..7c4efa1 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -111,30 +111,31 @@
     strexOpPick,	/* Similar to built in but pick deserves it's own op. */
     strexOpConditional,	/* Conditional trinary operation */
 
     strexOpArrayIx,	/* An array with an index. */
     strexOpArrayRange,	/* An array with a range. */
 
     strexOpStrlen,	/* Length of a string */
 
     /* Unary minus for numbers, logical not */
     strexOpUnaryMinusInt,
     strexOpUnaryMinusDouble,
     strexOpNot,
 
     /* Binary operations. */
     strexOpAdd,
+    strexOpSubtract,
     strexOpOr,
     strexOpAnd,
 
     /* Type conversions - possibly a few more than we actually need at the moment. */
     strexOpStringToBoolean,
     strexOpIntToBoolean,
     strexOpDoubleToBoolean,
     strexOpStringToInt,
     strexOpDoubleToInt,
     strexOpBooleanToInt,
     strexOpStringToDouble,
     strexOpBooleanToDouble,
     strexOpIntToDouble,
     strexOpBooleanToString,
     strexOpIntToString,
@@ -971,42 +972,51 @@
 else
     {
     tokenizerReuse(tkz);
     return strexParseIndex(in);
     }
 }
 
 static struct strexParse *strexParseSum(struct strexIn *in)
 /* Parse out plus or minus. */
 {
 struct tokenizer *tkz = in->tkz;
 struct strexParse *p = strexParseUnaryNeg(in);
 for (;;)
     {
     char *tok = tokenizerNext(tkz);
-    if (tok == NULL || differentString(tok, "+"))
+    if (tok == NULL || (differentString(tok, "+") && differentString(tok, "-")))
 	{
 	tokenizerReuse(tkz);
 	return p;
 	}
+    enum strexOp op = (tok[0] == '+' ? strexOpAdd : strexOpSubtract);
 
     /* What we've parsed so far becomes left side of binary op, next term ends up on right. */
     struct strexParse *l = p;
     struct strexParse *r = strexParseUnaryNeg(in);
 
     /* Make left and right side into a common type */
     enum strexType childType = commonTypeForMathBop(l->type, r->type);
+    if (op == strexOpSubtract)
+        {
+	if (childType == strexTypeString)
+	    {
+	    errAbort("Expecting numbers around '-' sign line %d of %s",
+		in->tkz->lf->lineIx, in->tkz->lf->fileName);
+	    }
+	}
     l = strexParseCoerce(l, childType);
     r = strexParseCoerce(r, childType);
 
     /* Create the binary operation */
     AllocVar(p);
     p->op = strexOpAdd;
     p->type = childType;
 
     /* Now hang children onto node. */
     p->children = l;
     l->next = r;
     }
 }
 
 static struct strexParse *strexParseAnd(struct strexIn *in)
@@ -1222,30 +1232,56 @@
 	r.val.s = buf;
 	break;
     case strexTypeDouble:
 	safef(buf, bufSize, "%g", r.val.x);
 	r.val.s = buf;
 	break;
     default:
 	internalErr();
 	r.val.s = NULL;
 	break;
     }
 r.type = strexTypeString;
 return r;
 }
 
+static struct strexEval strexEvalSubtract(struct strexParse *p, struct strexRun *run)
+/* Return a - b. */
+{
+struct strexParse *lp = p->children;
+struct strexParse *rp = lp->next;
+struct strexEval lv = strexLocalEval(lp, run);
+struct strexEval rv = strexLocalEval(rp, run);
+struct strexEval res;
+assert(lv.type == rv.type);   // Is our type automatic casting working?
+switch (lv.type)
+    {
+    case strexTypeInt:
+	res.val.i = (lv.val.i - rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.x = (lv.val.x - rv.val.x);
+	break;
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = lv.type;
+return res;
+}
+
 static struct strexEval strexEvalAdd(struct strexParse *p, struct strexRun *run)
 /* Return a + b. */
 {
 struct strexParse *lp = p->children;
 struct strexParse *rp = lp->next;
 struct strexEval lv = strexLocalEval(lp, run);
 struct strexEval rv = strexLocalEval(rp, run);
 struct strexEval res;
 assert(lv.type == rv.type);   // Is our type automatic casting working?
 switch (lv.type)
     {
     case strexTypeInt:
 	res.val.i = (lv.val.i + rv.val.i);
 	break;
     case strexTypeDouble:
@@ -1263,30 +1299,31 @@
 	char *s = lmAlloc(run->lm, lLen + rLen + 1);
 	memcpy(s, lv.val.s, lLen);
 	memcpy(s+lLen, rv.val.s, rLen);
 	res.val.s = s;
 	break;
 	}
     default:
 	internalErr();
 	res.val.b = FALSE;
 	break;
     }
 res.type = lv.type;
 return res;
 }
 
+
 static struct strexEval strexEvalOr(struct strexParse *p, struct strexRun *run)
 /* Return a or b. */
 {
 struct strexParse *lp = p->children;
 struct strexParse *rp = lp->next;
 struct strexEval res = strexLocalEval(lp, run);
 boolean gotIt = TRUE;
 switch (res.type)
     {
     case strexTypeBoolean:
 	gotIt = res.val.b != 0;
 	break;
     case strexTypeInt:
 	gotIt = (res.val.i != 0);
 	break;
@@ -2047,30 +2084,33 @@
     case strexOpBuiltInCall:
        res = strexEvalCallBuiltIn(p, run);
        break;
     case strexOpPick:
        res = strexEvalPick(p, run);
        break;
     case strexOpConditional:
        res = strexEvalConditional(p, run);
        break;
 
 
     /* Mathematical ops, simple binary type */
     case strexOpAdd:
        res = strexEvalAdd(p, run);
        break;
+    case strexOpSubtract:
+       res = strexEvalSubtract(p, run);
+       break;
 
     /* Logical ops, simple binary type and not*/
     case strexOpOr:
         res = strexEvalOr(p, run);
         break;
     case strexOpAnd:
         res = strexEvalAnd(p, run);
         break;
     case strexOpNot:
         res = strexLocalEval(p->children, run);
 	res.val.b = !res.val.b;
 	break;
 
     default:
         errAbort("Unknown op %s\n", strexOpToString(p->op));