4d9a3cd9ed4c6f8dff6aaa45c751ba5e6c56440f
kent
  Wed Aug 14 22:36:21 2019 -0700
Adding the ternary conditional operator, as well as built in boolean functions in() same() starts() and ends()

diff --git src/lib/strex.c src/lib/strex.c
index c80c8cc..a86b81b 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -51,30 +51,34 @@
     strexBuiltInTrim,
     strexBuiltInBetween,
     strexBuiltInSplit,
     strexBuiltInNow,
     strexBuiltInMd5,
     strexBuiltInSeparate,
     strexBuiltInUncsv,
     strexBuiltInUntsv,
     strexBuiltInReplace,
     strexBuiltInFix,
     strexBuiltInStrip,
     strexBuiltInLen,
     strexBuiltInSymbol,
     strexBuiltInLower,
     strexBuiltInUpper,
+    strexBuiltInIn, 
+    strexBuiltInStarts,
+    strexBuiltInEnds,
+    strexBuiltInSame,
     };
 
 struct strexBuiltIn
 /* Information to describe a built in function */
     {
     char *name;		/* Name in strex language:  trim, split, etc */
     enum strexBuiltInFunc func;  /* enum version: strexBuiltInTrim strexBuiltInSplit etc. */
     enum strexType returnType;	 /* Type of return value */
     int paramCount;	/* Number of parameters, not flexible in this language! */
     enum strexType *paramTypes;  /* Array of types, one for each parameter */
     };
 
 union strexVal
 /* Some value of arbirary type that can be of any type corresponding to strexType */
     {
@@ -89,30 +93,31 @@
 /* Result of evaluation of parse tree. */
     {
     enum strexType type;
     union strexVal val;
     };
 
 enum strexOp
 /* An operation in the parse tree. */
     {
     strexOpUnknown,	/* Should not occur */
     strexOpLiteral,        /* Literal string or number. */
     strexOpSymbol,	/* A symbol name. */
 
     strexOpBuiltInCall,	/* Call a built in function */
     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 */
     strexOpUnaryMinusInt,
     strexOpUnaryMinusDouble,
 
     /* Binary operations. */
     strexOpAdd,
     strexOpOr,
     strexOpAnd,
 
@@ -163,30 +168,34 @@
     { "trim", strexBuiltInTrim, strexTypeString, 1, oneString, },
     { "between", strexBuiltInBetween, strexTypeString, 3, threeStrings },
     { "split", strexBuiltInSplit, strexTypeString, 2, stringInt },
     { "now", strexBuiltInNow, strexTypeString, 0, NULL },
     { "md5", strexBuiltInMd5, strexTypeString, 1, oneString },
     { "separate", strexBuiltInSeparate, strexTypeString, 3, stringStringInt },
     { "uncsv", strexBuiltInUncsv, strexTypeString, 2, stringInt },
     { "untsv", strexBuiltInUntsv, strexTypeString, 2, stringInt },
     { "replace", strexBuiltInReplace, strexTypeString, 3, threeStrings },
     { "fix", strexBuiltInFix, strexTypeString, 3, threeStrings },
     { "strip", strexBuiltInStrip, strexTypeString, 2, twoStrings },
     { "len", strexBuiltInLen, strexTypeInt, 1, oneString},
     { "symbol", strexBuiltInSymbol, strexTypeString, 2, twoStrings },
     { "upper", strexBuiltInUpper, strexTypeString, 1, oneString },
     { "lower", strexBuiltInLower, strexTypeString, 1, oneString },
+    { "in", strexBuiltInIn, strexTypeBoolean, 2, twoStrings },
+    { "starts", strexBuiltInStarts, strexTypeBoolean, 2, twoStrings}, 
+    { "ends", strexBuiltInEnds, strexTypeBoolean, 2, twoStrings}, 
+    { "same", strexBuiltInSame, strexTypeBoolean, 2, twoStrings}, 
 };
 
 static struct hash *hashBuiltIns()
 /* 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(char *expression, char *fileName, int fileLineNumber,
     void *symbols, StrexLookup lookup)
 /* Return a new strexIn structure wrapped around expression */
@@ -262,31 +271,30 @@
 switch (type)
     {
     case strexTypeBoolean:
 	return "boolean";
 	break;
     case strexTypeString:
 	return "string";
 	break;
     case strexTypeInt:
 	return "integer";
 	break;
     case strexTypeDouble:
 	return "floating point";
 	break;
     default:
-	uglyf("Weird, type is %d\n", (int)type);
         internalErr();
 	return NULL;
     }
 }
 
 static char *strexOpToString(enum strexOp op)
 /* Return string representation of parse op. */
 {
 switch (op)
     {
     case strexOpLiteral:
 	return "strexOpLiteral";
     case strexOpSymbol:
 	return "strexOpSymbol";
     
@@ -320,30 +328,32 @@
     case strexOpUnaryMinusDouble:
         return "strexOpUnaryMinusDouble";
 
 
     case strexOpAdd:
 	return "strexOpAdd";
     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:
         return "strexOpArrayRange";
 
     case strexOpStrlen:
         return "strexOpStrlen";
     default:
 	return "strexOpUnknown";
     }
 }
 
 void strexParseDump(struct strexParse *p, int depth, FILE *f)
 /* Dump out strexParse tree and children. */
@@ -604,31 +614,31 @@
     if (function->op != strexOpSymbol)
         errAbort("Unexpected '(' line %d of %s", tkz->lf->lineIx, tkz->lf->fileName);
 
     /* Look up function to call and complain if it doesn't exist */
     char *functionName = function->val.s;
 
     /* Deal with special named ops like pick */
     if (sameString(functionName, "pick"))
         {
 	/* Yay, the pick operation.  It looks like
 	 *    pick( keyExp,  key1, val1, key2, val2, ..., keyN, valN)
 	 * the logic is to evaluate keyExp, and then pick one of the valN's to return,
 	 * the one where the keyN is the same as keyExp */
 	struct strexParse *keyExp = strexParseExpression(in);
 	slAddHead(&function->children, keyExp);
-	skipOverRequired(in, ",");
+	skipOverRequired(in, "?");
 
 	struct strexParse *firstVal = NULL;
 	for (;;)
 	    {
 	    struct strexParse *key = strexParseCoerce(strexParseExpression(in), keyExp->type);
 	    slAddHead(&function->children, key);
 	    skipOverRequired(in, ":");
 	    struct strexParse *val = strexParseExpression(in);
 	    if (firstVal == NULL)
 	        firstVal = val;
 	    else
 		{
 		if (firstVal->type != val->type)
 		    {
 		    errAbort("Mixed value types %s and %s in pick() expression line %d of %s",
@@ -909,35 +919,71 @@
     enum strexType childType = commonTypeForLogicBop(l->type, r->type);
     l = strexParseCoerce(l, childType);
     r = strexParseCoerce(r, childType);
 
     /* Create the binary operation */
     AllocVar(p);
     p->op = strexOpOr;
     p->type = childType;
 
     /* Now hang children onto node. */
     p->children = l;
     l->next = r;
     }
 }
 
+static struct strexParse *strexParseConditional(struct strexIn *in)
+/* Handle the ternary operator ?:  usually written as ( boolean ? trueExp : falseExp)
+ * because of it's ridiculously low precedence.  Makes this parse tree:
+ *         strexOpConditional
+ *            boolean exp  (always boolean type)
+ *            true exp     (same type as false exp)
+ *            false exp    (same type as true exp)      */
+{
+struct tokenizer *tkz = in->tkz;
+struct strexParse *p = strexParseOr(in);
+char *tok = tokenizerNext(tkz);
+if (tok == NULL)
+    tokenizerReuse(tkz);
+else if (tok[0] == '?')
+    {
+    struct strexParse *booleanExp = strexParseCoerce(p, strexTypeBoolean);
+    struct strexParse *trueExp = strexParseExpression(in);
+    skipOverRequired(in, ":");
+    struct strexParse *falseExp = strexParseExpression(in);
+    if (trueExp->type != falseExp->type)
+	errAbort("Mixed value types %s and %s in conditional expression (?:) line %d of %s",
+	    strexTypeToString(trueExp->type), strexTypeToString(falseExp->type),
+	    tkz->lf->lineIx, tkz->lf->fileName);
+
+    /* Make conditional expression and hook up it's three children. */
+    struct strexParse *conditional = strexParseNew(strexOpConditional, trueExp->type);
+    conditional->children = booleanExp;
+    booleanExp->next = trueExp;
+    trueExp->next = falseExp;
+    p = conditional;
+    }
+else
+    tokenizerReuse(tkz);
+return p;
+}
+
 
 static struct strexParse *strexParseExpression(struct strexIn *in)
 /* Parse out an expression. Leaves input at next expression. */
 {
-return strexParseOr(in);
+return strexParseConditional(in);
 }
 
 static void ensureAtEnd(struct strexIn *in)
 /* Make sure that we are at end of input. */
 {
 struct tokenizer *tkz = in->tkz;
 char *leftover = tokenizerNext(tkz);
 if (leftover != NULL)
     errAbort("Extra input starting with '%s' line %d of %s", leftover, tkz->lf->lineIx,
 	tkz->lf->fileName);
 }
 
 struct strexParse *strexParseString(char *s, char *fileName, int fileLineNumber,
     void *symbols, StrexLookup lookup)
 /* Parse out string expression in s and return root of tree. */
@@ -965,31 +1011,30 @@
     {
     case strexTypeBoolean:
         r.val.s = (r.val.b ? "true" : "false");
 	break;
     case strexTypeString:
 	break;	/* It's already done. */
     case strexTypeInt:
 	safef(buf, bufSize, "%lld", r.val.i);
 	r.val.s = buf;
 	break;
     case strexTypeDouble:
 	safef(buf, bufSize, "%g", r.val.x);
 	r.val.s = buf;
 	break;
     default:
-	uglyf("Weird, r.type is %s\n", strexTypeToString(r.type));
 	internalErr();
 	r.val.s = NULL;
 	break;
     }
 r.type = strexTypeString;
 return r;
 }
 
 static struct strexEval strexEvalAdd(struct strexParse *p, void *record, StrexLookup lookup,
 	struct lm *lm)
 /* Return a + b. */
 {
 struct strexParse *lp = p->children;
 struct strexParse *rp = lp->next;
 struct strexEval lv = strexLocalEval(lp, record, lookup, lm);
@@ -1392,30 +1437,58 @@
 	}
     case strexBuiltInLower:
         {
         struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
 	res.val.s = lmCloneString(lm, a.val.s);
 	tolowers(res.val.s);
 	break;
 	}
     case strexBuiltInUpper:
         {
         struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
 	res.val.s = lmCloneString(lm, a.val.s);
 	touppers(res.val.s);
 	break;
 	}
+    case strexBuiltInIn: 
+        {
+        struct strexEval string = strexLocalEval(p->children, record, lookup, lm);
+        struct strexEval query = strexLocalEval(p->children->next, record, lookup, lm);
+	res.val.b = (strstr(string.val.s, query.val.s) != NULL);
+	break;
+	}
+    case strexBuiltInStarts:
+        {
+        struct strexEval starts = strexLocalEval(p->children, record, lookup, lm);
+        struct strexEval string = strexLocalEval(p->children->next, record, lookup, lm);
+	res.val.b = startsWith(starts.val.s, string.val.s);
+	break;
+	}
+    case strexBuiltInEnds:
+        {
+        struct strexEval string = strexLocalEval(p->children, record, lookup, lm);
+        struct strexEval end = strexLocalEval(p->children->next, record, lookup, lm);
+	res.val.b = endsWith(string.val.s, end.val.s);
+	break;
+	}
+    case strexBuiltInSame:
+        {
+        struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
+        struct strexEval b = strexLocalEval(p->children->next, record, lookup, lm);
+	res.val.b = (strcmp(a.val.s, b.val.s) == 0);
+	break;
+	}
     }
 return res;
 }
 
 static struct strexEval nullValForType(enum strexType type)
 /* Return 0, "", 0.0 depending */
 {
 struct strexEval res = {.type=type};
 switch (type)
     {
      case strexTypeInt:
 	  res.val.i = 0;
 	  break;
      case strexTypeDouble:
 	  res.val.x = 0.0;
@@ -1459,30 +1532,44 @@
 	      gotMatch = (keyVal.val.b = key.val.b);
 	      break;
 	 case strexTypeString:
 	      gotMatch = sameString(keyVal.val.s, key.val.s);
 	      break;
 	 }
     if (gotMatch)
 	 {
          return strexLocalEval(valExp, record, lookup, lm);
 	 }
     }
 res = nullValForType(pick->type);
 return res;
 }
 
+static struct strexEval strexEvalConditional(struct strexParse *conditional, 
+    void *record, StrexLookup lookup, struct lm *lm)
+/* Evaluate a conditional trinary ? :  operator. */
+{
+struct strexParse *child = conditional->children;
+struct strexEval b = strexLocalEval(child, record, lookup, lm);
+struct strexEval res;
+if (b.val.b)
+    res = strexLocalEval(child->next, record, lookup, lm); 
+else
+    res = strexLocalEval(child->next->next, record, lookup, lm); 
+return res;
+}
+
 
 
 static struct strexEval strexLocalEval(struct strexParse *p, void *record, StrexLookup lookup, 
 	struct lm *lm)
 /* Evaluate self on parse tree, allocating memory if needed from lm. */
 {
 struct strexEval res;
 switch (p->op)
     {
     case strexOpLiteral:
 	res.val = p->val;
 	res.type = p->type;
 	break;
     case strexOpSymbol:
 	res.type = strexTypeString;
@@ -1582,30 +1669,33 @@
        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;
 
     /* More complicated ops. */
     case strexOpBuiltInCall:
        res = strexEvalCallBuiltIn(p, record, lookup, lm);
        break;
     case strexOpPick:
        res = strexEvalPick(p, record, lookup, lm);
        break;
+    case strexOpConditional:
+       res = strexEvalConditional(p, record, lookup, lm);
+       break;
 
 
     /* Mathematical ops, simple binary type */
     case strexOpAdd:
        res = strexEvalAdd(p, record, lookup, lm);
        break;
 
     /* Logical ops, simple binary type */
     case strexOpOr:
        res = strexEvalOr(p, record, lookup, lm);
        break;
     case strexOpAnd:
        res = strexEvalAnd(p, record, lookup, lm);
        break;