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; }