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;