835a903827681f7295594708dccaec0cbf2e20fa
kent
  Mon Aug 12 21:37:48 2019 -0700
Adding new built-in function fix() for doing constant/constant substitutions. Allowing negative indexes in split() and separate() that work like negative array indexes do.

diff --git src/lib/strex.c src/lib/strex.c
index c470a7a..51d5e55 100644
--- src/lib/strex.c
+++ src/lib/strex.c
@@ -45,30 +45,31 @@
 
 enum strexBuiltInFunc
 /* One of these for each builtIn.  We'll just do a switch to implement 
  * Each built in function needs a value here, to keep it simple there's
  * aa correspondence between these names and the built in function name */
     {
     strexBuiltInTrim,
     strexBuiltInBetween,
     strexBuiltInSplit,
     strexBuiltInNow,
     strexBuiltInMd5,
     strexBuiltInSeparate,
     strexBuiltInUncsv,
     strexBuiltInUntsv,
     strexBuiltInReplace,
+    strexBuiltInFix,
     strexBuiltInStrip,
     };
 
 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. */
     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 */
     {
@@ -146,30 +147,31 @@
 static enum strexType stringInt[] = {strexTypeString, strexTypeInt};
 static enum strexType stringStringInt[] = {strexTypeString, strexTypeString, strexTypeInt};
 
 /* There's one element here for each built in function.  There's also a few switches you'll need to
  * fill in if you add a new built in function. */
 static struct strexBuiltIn builtins[] = {
     { "trim", strexBuiltInTrim, 1, oneString, },
     { "between", strexBuiltInBetween, 3, threeStrings },
     { "split", strexBuiltInSplit, 2, stringInt },
     { "now", strexBuiltInNow, 0, NULL },
     { "md5", strexBuiltInMd5, 1, oneString },
     { "separate", strexBuiltInSeparate, 3, stringStringInt },
     { "uncsv", strexBuiltInUncsv, 2, stringInt },
     { "untsv", strexBuiltInUntsv, 2, stringInt },
     { "replace", strexBuiltInReplace, 3, threeStrings },
+    { "fix", strexBuiltInFix, 3, threeStrings },
     { "strip", strexBuiltInStrip, 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)
 /* Return a new strexIn structure wrapped around expression */
@@ -906,68 +908,81 @@
     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 */
 {
+if (ix < 0)	// Negative index.  We got to count, dang
+    {
+    int wordCount = chopByWhite(words, NULL, 0);   
+    ix = wordCount + ix;
+    if (ix < 0)
+        return "";
+    }
 char *s = words;
 int i;
 for (i=0; ; ++i)
     {
     s = skipLeadingSpaces(s);
     if (isEmpty(s))
-        errAbort("There aren't %d words in %s", ix+1, words);
+	return "";
     char *end = skipToSpaces(s);
     if (i == ix)
 	{
 	if (end == NULL)
 	    return lmCloneString(lm, s);
 	else
 	    return lmCloneMem(lm, s, end - s);
 	}
     s = end;
     }
 }
 
 static char *uncsvString(char *csvIn,  int ix,  struct lm *lm)
 /* Return the comma separated value of index ix. Memory for result is lm */
 {
 struct dyString *scratch = dyStringNew(0);
 char *one = csvParseOneOut(csvIn, ix, scratch); 
 char *res = emptyForNull(lmCloneString(lm, one));	// Save in more permanent memory
 dyStringFree(&scratch);
 return res;
 }
 
 static char *separateString(char *string, char *splitter, int ix, struct lm *lm)
 /* Return the ix'th part of string as split apart by splitter */
 {
 int splitterSize = strlen(splitter);
 if (splitterSize != 1)
     errAbort("Separator parameter to split must be a single character, not %s", splitter);
 int count = chopByChar(string, splitter[0], NULL, 0);
+if (ix < 0)	// Negative index.  No problem, count from the end
+    {
+    ix = count + ix;
+    if (ix < 0)
+        return "";	// Still out of range, oh well
+    }
 if (ix >= count)
-    errAbort("There aren't %d fields separated by %s in %s", ix+1, splitter, string);
+    return "";
 char **row;
 lmAllocArray(lm, row, count);
 char *scratch = lmCloneString(lm, string);
 chopByChar(scratch, splitter[0], row, count);
 return row[ix];
 }
 
 static char *untsvString(char *tsvIn, int ix, struct lm *lm)
 /* Return the tab separated value at given index living somewhere in lm. */
 {
 return separateString(tsvIn, "\t", ix, lm);
 }
 
 static char *replaceString(char *in, char *oldVal, char *newVal, struct lm *lm)
 /* Replace every occurrence of oldVal with newVal in string */
@@ -1064,30 +1079,43 @@
     case strexBuiltInUntsv:
         {
         struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
         struct strexEval b = strexLocalEval(p->children->next, record, lookup, lm);
 	res.val.s = untsvString(a.val.s, b.val.i, lm);
 	break;
 	}
     case strexBuiltInReplace:
         {
         struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
         struct strexEval b = strexLocalEval(p->children->next, record, lookup, lm);
         struct strexEval c = strexLocalEval(p->children->next->next, record, lookup, lm);
 	res.val.s = replaceString(a.val.s, b.val.s, c.val.s, lm);
 	break;
 	}
+    case strexBuiltInFix:
+        {
+        struct strexEval string = strexLocalEval(p->children, record, lookup, lm);
+        struct strexEval oldVal = strexLocalEval(p->children->next, record, lookup, lm);
+        struct strexEval newVal = strexLocalEval(p->children->next->next, record, lookup, lm);
+	if (sameString(string.val.s, oldVal.val.s))
+	    {
+	    res.val.s = newVal.val.s;
+	    }
+	else
+	    res.val.s = string.val.s;
+	break;
+	}
     case strexBuiltInStrip:
         {
         struct strexEval a = strexLocalEval(p->children, record, lookup, lm);
         struct strexEval b = strexLocalEval(p->children->next, record, lookup, lm);
 	res.val.s = stripAll(a.val.s, b.val.s, lm);
 	break;
 	}
     }
 return res;
 }
 
 
 static struct strexEval strexLocalEval(struct strexParse *p, void *record, StrexEvalLookup lookup, 
 	struct lm *lm)
 /* Evaluate self on parse tree, allocating memory if needed from lm. */