5ec42ac16ff4d62a5ca390ba108071d3e4ea53e3
kent
  Thu Dec 9 12:58:05 2021 -0800
Supporting comparison operations.

diff --git src/lib/strex.c src/lib/strex.c
index 7c4efa1..7100492 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -112,30 +112,36 @@
     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,
+    strexOpGt,
+    strexOpLt,
+    strexOpGe,
+    strexOpLe,
+    strexOpEq,
+    strexOpNe,
     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,
@@ -206,30 +212,31 @@
 /* Build a hash of builtins keyed by name */
 {
 struct hash *hash = hashNew(0);
 int i;
 for (i=0; i<ArraySize(builtins); ++i)
     hashAdd(hash, builtins[i].name, &builtins[i]);
 return hash;
 }
 
 static struct strexIn *strexInNew(struct lineFile *lf,
     void *symbols, StrexLookup lookup)
 /* Return a new strexIn structure wrapped around lineFile */
 {
 struct tokenizer *tkz = tokenizerOnLineFile(lf);
 tkz->leaveQuotes = TRUE;
+tkz->twoCharOps = TRUE;
 struct strexIn *si;
 AllocVar(si);
 si->tkz = tkz;
 si->builtInHash = hashBuiltIns();
 si->importHash = hashNew(4);
 si->symbols = symbols;
 si->lookup = lookup;
 return si;
 }
 
 static void strexInFree(struct strexIn **pSi)
 /* Free up memory associated with strexIn structure */
 {
 struct strexIn *si = *pSi;
 if (si != NULL)
@@ -332,33 +339,47 @@
         return "strexOpBooleanToDouble";
     case strexOpIntToDouble:
         return "strexOpIntToDouble";
     case strexOpBooleanToString:
         return "strexOpBooleanToString";
     case strexOpIntToString:
         return "strexOpIntToString";
     case strexOpDoubleToString:
         return "strexOpDoubleToString";
 
     case strexOpUnaryMinusInt:
         return "strexOpUnaryMinusInt";
     case strexOpUnaryMinusDouble:
         return "strexOpUnaryMinusDouble";
 
-
     case strexOpAdd:
 	return "strexOpAdd";
+    case strexOpSubtract:
+        return "strexOpSubtract";
+    case strexOpEq:
+        return "strexOpEq";
+    case strexOpNe:
+        return "strexOpNe";
+    case strexOpGt:
+        return "strexOpGt";
+    case strexOpLt:
+        return "strexOpLt";
+    case strexOpGe:
+        return "strexOpGe";
+    case strexOpLe:
+        return "strexOpLe";
+
     case strexOpOr:
 	return "strexOpOr";
     case strexOpAnd:
 	return "strexOpAnd";
 
     case strexOpBuiltInCall:
         return "strexOpBuiltInCall";
     case strexOpPick:
         return "strexOpPick";
     case strexOpConditional:
         return "strexOpConditional";
 
     case strexOpArrayIx:
         return "strexOpArrayIx";
     case strexOpArrayRange:
@@ -1007,47 +1028,126 @@
 	}
     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;
     }
 }
 
+boolean isCmpOp(char *tok)
+/* Return TRUE if tok is one of t > < >= <= = and !=. */
+{
+char t0 = tok[0];
+char t1 = tok[1];
+switch (t0)
+   {
+   case '>':
+   case '<':
+        return t1 == 0 || t1 == '=';
+   case '=':
+        return t1 == 0;
+   case '!':
+        return t1 == '=';
+   default:
+       return FALSE;
+   }
+}
+
+enum strexOp strexOpForCmpTok(char *tok)
+/* Return TRUE if tok is one of t > < >= <= = and !=. */
+{
+if (sameString(tok, "="))
+    return strexOpEq;
+else if (sameString(tok, "!="))
+    return strexOpNe;
+else if (sameString(tok, ">"))
+    return strexOpGt;
+else if (sameString(tok, ">="))
+    return strexOpGe;
+else if (sameString(tok, "<"))
+    return strexOpLt;
+else if (sameString(tok, "<="))
+    return strexOpLe;
+else
+    {
+    warn("unrecognized cmp op token %s", tok);
+    internalErr();
+    return strexOpUnknown;
+    }
+}
+
+
+static struct strexParse *strexParseCompareOp(struct strexIn *in)
+/* Parse out > < >= <= = and !=. */
+{
+struct tokenizer *tkz = in->tkz;
+struct strexParse *p = strexParseSum(in);
+char *tok = tokenizerNext(tkz);
+if (tok == NULL || !isCmpOp(tok))
+    {
+    tokenizerReuse(tkz);
+    return p;
+    }
+enum strexOp cmpOp = strexOpForCmpTok(tok);
+
+/* 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 = strexParseSum(in);
+
+/* Make left and right side into a common type */
+enum strexType commonType = commonTypeForMathBop(l->type, r->type);
+if (commonType == strexTypeBoolean)
+    commonType = strexTypeInt;
+
+l = strexParseCoerce(l, commonType);
+r = strexParseCoerce(r, commonType);
+
+/* Create the binary operation */
+AllocVar(p);
+p->op = cmpOp;
+p->type = strexTypeBoolean;
+
+/* Now hang children onto node and return it. */
+p->children = l;
+l->next = r;
+return p;
+}
+
 static struct strexParse *strexParseAnd(struct strexIn *in)
 /* Parse out plus or minus. */
 {
 struct tokenizer *tkz = in->tkz;
-struct strexParse *p = strexParseSum(in);
+struct strexParse *p = strexParseCompareOp(in);
 for (;;)
     {
     char *tok = tokenizerNext(tkz);
     if (tok == NULL || differentString(tok, "and"))
 	{
 	tokenizerReuse(tkz);
 	return p;
 	}
 
     /* 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 = strexParseSum(in);
+    struct strexParse *r = strexParseCompareOp(in);
 
     /* Make left and right side into a common type */
     enum strexType childType = commonTypeForLogicBop(l->type, r->type);
     l = strexParseCoerce(l, childType);
     r = strexParseCoerce(r, childType);
 
     /* Create the binary operation */
     AllocVar(p);
     p->op = strexOpAnd;
     p->type = childType;
 
     /* Now hang children onto node. */
     p->children = l;
     l->next = r;
     }
@@ -1299,30 +1399,217 @@
 	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 strexEvalEq(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.b = (lv.val.i == rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x == rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (strcasecmp(lv.val.s, rv.val.s) == 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+return res;
+}
+
+static struct strexEval strexEvalNe(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.b = (lv.val.i != rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x != rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (strcasecmp(lv.val.s, rv.val.s) != 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+return res;
+}
+
+
+static struct strexEval strexEvalGt(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.b = (lv.val.i > rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x > rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (cmpWordsWithEmbeddedNumbers(lv.val.s, rv.val.s) > 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+return res;
+}
+
+static struct strexEval strexEvalLt(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.b = (lv.val.i < rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x < rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (cmpWordsWithEmbeddedNumbers(lv.val.s, rv.val.s) < 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+return res;
+}
+
+static struct strexEval strexEvalGe(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.b = (lv.val.i >= rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x >= rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (cmpWordsWithEmbeddedNumbers(lv.val.s, rv.val.s) >= 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+return res;
+}
+
+static struct strexEval strexEvalLe(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.b = (lv.val.i <= rv.val.i);
+	break;
+    case strexTypeDouble:
+	res.val.b = (lv.val.x <= rv.val.x);
+	break;
+    case strexTypeString:
+	{
+	res.val.b = (cmpWordsWithEmbeddedNumbers(lv.val.s, rv.val.s) <= 0);
+	break;
+	}
+    default:
+	internalErr();
+	res.val.b = FALSE;
+	break;
+    }
+res.type = strexTypeBoolean;
+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);
@@ -2088,30 +2375,50 @@
        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;
 
+    /* Comparison ops, simple binary type */
+    case strexOpEq:
+       res = strexEvalEq(p, run);
+       break;
+    case strexOpNe:
+       res = strexEvalNe(p, run);
+       break;
+    case strexOpGt:
+       res = strexEvalGt(p, run);
+       break;
+    case strexOpLt:
+       res = strexEvalLt(p, run);
+       break;
+    case strexOpGe:
+       res = strexEvalGe(p, run);
+       break;
+    case strexOpLe:
+       res = strexEvalLe(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));
 	res.type = strexTypeInt;	// Keep compiler from complaining.