7ef20404a23038b61099344295dd592d1435850d
kent
  Thu Aug 15 09:58:04 2019 -0700
Adding default to pick.

diff --git src/lib/strex.c src/lib/strex.c
index ffe3e39..36d6a71 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -585,30 +585,61 @@
 	    break;
 	case strexTypeInt:
 	    cast->op = intCastOp(p->type);
 	    break;
 	case strexTypeDouble:
 	    cast->op = doubleCastOp(p->type);
 	    break;
 	default:
 	    internalErr();
 	    break;
 	}
     return cast;
     }
 }
 
+static union strexVal strexValEmptyForType(enum strexType type)
+/* Create default empty/zero value - from empty string to 0.0 */
+{
+union strexVal ret;
+switch (type)
+    {
+    case strexTypeBoolean:
+	ret.b = FALSE;
+	break;
+    case strexTypeString:
+        ret.s = "";
+	break;
+    case strexTypeInt:
+        ret.i = 0;
+	break;
+    case strexTypeDouble:
+        ret.x = 0;
+	break;
+    }
+return ret;
+}
+
+#ifdef OLD
+static struct strexEval nullValForType(enum strexType type)
+/* Return 0, "", 0.0 depending */
+{
+struct strexEval res = {.type=type, .val=strexValEmptyForType(type)};
+return res;
+}
+#endif /* OLD */
+
 static struct strexParse *strexParseFunction(struct strexIn *in)
 /* Handle the (a,b,c) in funcCall(a,b,c).  Convert it into tree:
 *         strexOpBuiltInCall
 *            strexParse(a)
 *            strexParse(b)
 *            strexParse(c)
 * or something like that.  With no parameters 
 *        strexOpBuiltInCall */
 {
 struct tokenizer *tkz = in->tkz;
 struct strexParse *function = strexParseAtom(in);
 char *tok = tokenizerNext(tkz);
 if (tok == NULL)
     tokenizerReuse(tkz);
 else if (tok[0] == '(')
@@ -616,62 +647,101 @@
     /* Check that the current op, is a pure symbol. */
     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 = strexParseOr(in);
-	slAddHead(&function->children, keyExp);
 	skipOverRequired(in, "?");
 
+	/* Loop through parsing key/val pairs.  We'll save away the first value
+	 * and the default value if we see one. */
 	struct strexParse *firstVal = NULL;
+	struct strexParse *defaultVal = NULL;
 	for (;;)
 	    {
+	    /* Peek at next token to see if it is "default" */
+	    char *tok = tokenizerNext(tkz);
+	    boolean isDefault = FALSE;
+	    struct strexParse *val;
+	    if (sameString(tok, "default"))
+	        {
+		if (defaultVal != NULL)
+		    {
+		    errAbort("multiple defaults in pick, line %d of %s",
+		        tkz->lf->lineIx, tkz->lf->fileName);
+		    }
+		skipOverRequired(in, ":");
+		defaultVal = val = strexParseExpression(in);
+		isDefault = TRUE;
+		}
+	    else
+		{
+		/* Nope not default, back up input stream and evaluate key expression */
+		tokenizerReuse(tkz);
 		struct strexParse *key = strexParseCoerce(strexParseExpression(in), keyExp->type);
 		slAddHead(&function->children, key);
 		skipOverRequired(in, ":");
-	    struct strexParse *val = strexParseExpression(in);
+		val = strexParseExpression(in);
+		}
+
+	    /* Keep track of overall expression type by storing the first value we see and
+	     * making sure subsequent types agree with first one. */
 	    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",
 			strexTypeToString(firstVal->type), strexTypeToString(val->type),
 			tkz->lf->lineIx, tkz->lf->fileName);
 		    }
 		val = strexParseCoerce(val, firstVal->type);
 		}
+	    if (!isDefault)
 		slAddHead(&function->children, val);
+
 	    tok = tokenizerMustHaveNext(tkz);
 	    if (tok[0] == ')')
 		break;
 	    else if (tok[0] != ',')
 		errAbort("Error in pick parameter list line %d of %s", 
 		    tkz->lf->lineIx, tkz->lf->fileName);
 	    }
 	slReverse(&function->children);
 
+	/* Now deal with adding default value as first child */
+	if (defaultVal == NULL)
+	    {
+	    /* Need to make up empty default */
+	    defaultVal = strexParseNew(strexOpLiteral, firstVal->type);
+	    defaultVal->val = strexValEmptyForType(firstVal->type);
+	    }
+	slAddHead(&function->children, defaultVal);
+
+	/* Finally put the key expression at the very head */
+	slAddHead(&function->children, keyExp);
+
 	/* Going to reuse current op, turn it into pick */
 	function->op = strexOpPick;
 	function->type = firstVal->type;
 	}
     else
 	{
 	/* It's a builtin function as opposed to a special op.  Figure out which one.*/
 	struct strexBuiltIn *builtIn = hashFindVal(in->builtInHash, functionName);
 	if (builtIn == NULL)
 	    errAbort("No built in function %s exists line %d of %s", functionName, tkz->lf->lineIx,
 		tkz->lf->fileName);
 
 	/* We're going to reuse this current op */
 	function->op = strexOpBuiltInCall;
 	function->type = builtIn->returnType;
@@ -1476,91 +1546,72 @@
         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;
-	  break;
-     case strexTypeBoolean:
-	  res.val.b = FALSE;
-	  break;
-     case strexTypeString:
-	  res.val.s = "";
-	  break;
-    }
-return res;
-}
-
 static struct strexEval strexEvalPick(struct strexParse *pick, void *record, StrexLookup lookup,
     struct lm *lm)
 /* Evaluate a pick operator. */
 {
 /* Evaluate the keyValue */
 struct strexParse *p = pick->children;
 struct strexEval keyVal = strexLocalEval(p, record, lookup, lm);
 p = p->next;
 
-struct strexEval res;
+/* Get pointer to default expression but don't evaluate it yet */
+struct strexParse *defaultExp = p;
+p = p->next;
+
+/* Loop through all the key/val pairs until find first that matches. */
 boolean gotMatch = FALSE;
 while (p != NULL)
     {
     struct strexEval key = strexLocalEval(p, record, lookup, lm);
     p = p->next;  // Parser guarantees this non-null
     struct strexParse *valExp = p;
     p = p->next;
     switch (key.type)
          {
 	 case strexTypeInt:
 	      gotMatch = (keyVal.val.i == key.val.i);
 	      break;
 	 case strexTypeDouble:
 	      gotMatch = (keyVal.val.x == key.val.x);
 	      break;
 	 case strexTypeBoolean:
 	      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;
+return strexLocalEval(defaultExp, record, lookup, lm);
 }
 
 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;
 }