b3a34c696d317da9ab87bd11f71e979181bd638c kent Mon Oct 8 15:52:31 2018 -0700 Adding support for + - * / as mathematical binary expressions. Also making fields with undefined values act as zeroes when an integer is expected. Also making from, where, and, or etc. not case sensitive. diff --git src/lib/rqlParse.c src/lib/rqlParse.c index a8c67b2..fe4ad9a 100644 --- src/lib/rqlParse.c +++ src/lib/rqlParse.c @@ -52,30 +52,39 @@ return "rqlOpLt"; case rqlOpGe: return "rqlOpGe"; case rqlOpLe: return "rqlOpLe"; case rqlOpLike: return "rqlOpLike"; case rqlOpAnd: return "rqlOpAnd"; case rqlOpOr: return "rqlOpOr"; case rqlOpNot: return "rqlOpNot"; + case rqlOpAdd: + return "rqlOpAdd"; + case rqlOpSubtract: + return "rqlOpSubtract"; + case rqlOpMultiply: + return "rqlOpMultiply"; + case rqlOpDivide: + return "rqlOpDivide"; + case rqlOpUnaryMinusInt: return "rqlOpUnaryMinusInt"; case rqlOpUnaryMinusDouble: return "rqlOpUnaryMinusDouble"; case rqlOpArrayIx: return "rqlOpArrayIx"; default: return "rqlOpUnknown"; } } void rqlValDump(union rqlVal val, enum rqlType type, FILE *f) /* Dump out value to file. */ @@ -108,31 +117,31 @@ for (child = p->children; child != NULL; child= child->next) rqlParseDump(child, depth+1, f); } static void expectingGot(struct tokenizer *tkz, char *expecting, char *got) /* Print out error message about unexpected input. */ { errAbort("Expecting %s, got %s, line %d of %s", expecting, got, tkz->lf->lineIx, tkz->lf->fileName); } static void skipOverRequired(struct tokenizer *tkz, char *expecting) /* Make sure that next token is tok, and skip over it. */ { tokenizerMustHaveNext(tkz); -if (!sameString(tkz->string, expecting)) +if (!sameWord(tkz->string, expecting)) expectingGot(tkz, expecting, tkz->string); } struct rqlParse *rqlParseExpression(struct tokenizer *tkz); /* Parse out a clause, usually a where clause. */ static struct rqlParse *rqlParseAtom(struct tokenizer *tkz) /* Return low level (symbol or literal) */ { char *tok = tokenizerMustHaveNext(tkz); struct rqlParse *p; AllocVar(p); char c = tok[0]; if (c == '\'' || c == '"') @@ -347,91 +356,175 @@ } p->children = c; return p; } else { tokenizerReuse(tkz); return rqlParseIndex(tkz); } } static boolean eatMatchingTok(struct tokenizer *tkz, char *s) /* If next token matches s then eat it and return TRUE */ { char *tok = tokenizerNext(tkz); -if (tok != NULL && sameString(tok, s)) +if (tok != NULL && sameWord(tok, s)) return TRUE; else { tokenizerReuse(tkz); return FALSE; } } +static struct rqlParse *rqlParseProduct(struct tokenizer *tkz) +/* Parse out plus or minus. */ +{ +struct rqlParse *p = rqlParseUnaryMinus(tkz); +for (;;) + { + enum rqlOp op = rqlOpUnknown; + char *tok = tokenizerNext(tkz); + if (tok != NULL) + { + if (sameString(tok, "*")) + op = rqlOpMultiply; + else if (sameString(tok, "/")) + op = rqlOpDivide; + } + if (op == rqlOpUnknown) // No binary operation token, just return what we have so far + { + tokenizerReuse(tkz); + return p; + } + + /* What we've parsed so far becomes left side of binary op, next term ends up on right. */ + struct rqlParse *l = p; + struct rqlParse *r = rqlParseUnaryMinus(tkz); + + /* Make left and right side into a common type */ + enum rqlType childType = commonTypeForBop(l->type, r->type); + l = rqlParseCoerce(l, childType); + r = rqlParseCoerce(r, childType); + + /* Create the binary operation */ + AllocVar(p); + p->op = op; + p->type = childType; + + /* Now hang children onto node. */ + p->children = l; + l->next = r; + } +} + + +static struct rqlParse *rqlParseSum(struct tokenizer *tkz) +/* Parse out plus or minus. */ +{ +struct rqlParse *p = rqlParseProduct(tkz); +for (;;) + { + enum rqlOp op = rqlOpUnknown; + char *tok = tokenizerNext(tkz); + if (tok != NULL) + { + if (sameString(tok, "+")) + op = rqlOpAdd; + else if (sameString(tok, "-")) + op = rqlOpSubtract; + } + if (op == rqlOpUnknown) // No binary operation token, just return what we have so far + { + tokenizerReuse(tkz); + return p; + } + + /* What we've parsed so far becomes left side of binary op, next term ends up on right. */ + struct rqlParse *l = p; + struct rqlParse *r = rqlParseProduct(tkz); + + /* Make left and right side into a common type */ + enum rqlType childType = commonTypeForBop(l->type, r->type); + l = rqlParseCoerce(l, childType); + r = rqlParseCoerce(r, childType); + + /* Create the binary operation */ + AllocVar(p); + p->op = op; + p->type = childType; + + /* Now hang children onto node. */ + p->children = l; + l->next = r; + } +} + + static struct rqlParse *rqlParseCmp(struct tokenizer *tkz) /* Parse out comparison. */ { -struct rqlParse *l = rqlParseUnaryMinus(tkz); +struct rqlParse *l = rqlParseSum(tkz); struct rqlParse *p = l; char *tok = tokenizerNext(tkz); boolean forceString = FALSE; boolean needNot = FALSE; if (tok != NULL) { enum rqlOp op = rqlOpUnknown; if (sameString(tok, "=")) { op = rqlOpEq; } else if (sameString(tok, "!")) { op = rqlOpNe; skipOverRequired(tkz, "="); } else if (sameString(tok, ">")) { if (eatMatchingTok(tkz, "=")) op = rqlOpGe; else op = rqlOpGt; } else if (sameString(tok, "<")) { if (eatMatchingTok(tkz, "=")) op = rqlOpLe; else op = rqlOpLt; } - else if (sameString(tok, "not")) + else if (sameWord(tok, "not")) { forceString = TRUE; op = rqlOpLike; needNot = TRUE; skipOverRequired(tkz, "like"); } - else if (sameString(tok, "like")) + else if (sameWord(tok, "like")) { forceString = TRUE; op = rqlOpLike; } else { tokenizerReuse(tkz); return p; } - struct rqlParse *r = rqlParseUnaryMinus(tkz); + struct rqlParse *r = rqlParseSum(tkz); AllocVar(p); p->op = op; p->type = rqlTypeBoolean; /* Now force children to be the same type, inserting casts if need be. */ if (forceString) { if (l->type != rqlTypeString || r->type != rqlTypeString) { errAbort("Expecting string type around comparison line %d of %s", tkz->lf->lineIx, tkz->lf->fileName); } } else { @@ -450,86 +543,86 @@ struct rqlParse *n; AllocVar(n); n->op = rqlOpNot; n->type = rqlTypeBoolean; n->children = p; p = n; } } return p; } static struct rqlParse *rqlParseNot(struct tokenizer *tkz) /* parse out a logical not. */ { char *tok = tokenizerNext(tkz); -if (sameString(tok, "not")) +if (sameWord(tok, "not")) { struct rqlParse *p = rqlParseCoerce(rqlParseCmp(tkz), rqlTypeBoolean); struct rqlParse *n; AllocVar(n); n->op = rqlOpNot; n->type = rqlTypeBoolean; n->children = p; return n; } else { tokenizerReuse(tkz); return rqlParseCmp(tkz); } } static struct rqlParse *rqlParseAnd(struct tokenizer *tkz) /* Parse out and or or. */ { struct rqlParse *p = rqlParseNot(tkz); struct rqlParse *parent = NULL; for (;;) { char *tok = tokenizerNext(tkz); - if (tok == NULL || !sameString(tok, "and")) + if (tok == NULL || !sameWord(tok, "and")) { tokenizerReuse(tkz); return p; } else { if (parent == NULL) { p = rqlParseCoerce(p, rqlTypeBoolean); AllocVar(parent); parent->op = rqlOpAnd; parent->type = rqlTypeBoolean; parent->children = p; p = parent; } struct rqlParse *r = rqlParseCoerce(rqlParseNot(tkz), rqlTypeBoolean); slAddTail(&parent->children, r); } } } static struct rqlParse *rqlParseOr(struct tokenizer *tkz) /* Parse out and or or. */ { struct rqlParse *p = rqlParseAnd(tkz); struct rqlParse *parent = NULL; for (;;) { char *tok = tokenizerNext(tkz); - if (tok == NULL || !sameString(tok, "or")) + if (tok == NULL || !sameWord(tok, "or")) { tokenizerReuse(tkz); return p; } else { if (parent == NULL) { p = rqlParseCoerce(p, rqlTypeBoolean); AllocVar(parent); parent->op = rqlOpOr; parent->type = rqlTypeBoolean; parent->children = p; p = parent; } @@ -593,38 +686,38 @@ struct rqlParse *child; for (child = p->children; child != NULL; child = child->next) rqlParseVarsUsed(child, pVarList); } struct rqlStatement *rqlStatementParse(struct lineFile *lf) /* Parse an RQL statement out of text */ { struct tokenizer *tkz = tokenizerOnLineFile(lf); tkz->uncommentShell = TRUE; tkz->uncommentC = TRUE; tkz->leaveQuotes = TRUE; struct rqlStatement *rql; AllocVar(rql); rql->command = cloneString(tokenizerMustHaveNext(tkz)); -if (sameString(rql->command, "select")) +if (sameWord(rql->command, "select")) { struct dyString *buf = dyStringNew(0); struct slName *list = NULL; char *tok = rqlParseFieldSpec(tkz, buf); /* Look for count(*) as special case. */ boolean countOnly = FALSE; - if (sameString(tok, "count")) + if (sameWord(tok, "count")) { char *paren = tokenizerNext(tkz); if (paren[0] == '(') { while ((paren = tokenizerMustHaveNext(tkz)) != NULL) { if (paren[0] == ')') break; } countOnly = TRUE; freez(&rql->command); rql->command = cloneString("count"); } else { @@ -638,85 +731,85 @@ { /* Parse out comma-separated field list. */ char *comma = tokenizerNext(tkz); if (comma == NULL || comma[0] != ',') { tokenizerReuse(tkz); break; } slNameAddHead(&list, rqlParseFieldSpec(tkz, buf)); } slReverse(&list); rql->fieldList = list; } dyStringFree(&buf); } -else if (sameString(rql->command, "count")) +else if (sameWord(rql->command, "count")) { /* No parameters to count. */ } else errAbort("Unknown RQL command '%s line %d of %s\n", rql->command, lf->lineIx, lf->fileName); char *from = tokenizerNext(tkz); if (from != NULL) { - if (sameString(from, "from")) + if (sameWord(from, "from")) { for (;;) { struct dyString *buf = dyStringNew(0); char *table = rqlParseFieldSpec(tkz, buf); slNameAddTail(&rql->tableList, table); char *comma = tokenizerNext(tkz); if (comma == NULL) break; if (comma[0] != ',') { tokenizerReuse(tkz); break; } dyStringFree(&buf); } } else { errAbort("missing 'from' clause in %s\n", rql->command); } } /* Parse where clause. */ char *where = tokenizerNext(tkz); if (where != NULL) { - if (!sameString(where, "where")) + if (!sameWord(where, "where")) { tokenizerReuse(tkz); } else { rql->whereClause = rqlParseExpression(tkz); rqlParseVarsUsed(rql->whereClause, &rql->whereVarList); } } /* Parse limit clause. */ char *limit = tokenizerNext(tkz); rql->limit = -1; if (limit != NULL) { - if (!sameString(limit, "limit")) + if (!sameWord(limit, "limit")) errAbort("Unknown clause '%s' line %d of %s", limit, lf->lineIx, lf->fileName); char *count = tokenizerMustHaveNext(tkz); if (!isdigit(count[0])) errAbort("Expecting number after limit, got %s line %d of %s", count, lf->lineIx, lf->fileName); rql->limit = atoi(count); } /* Check that are at end of statement. */ char *extra = tokenizerNext(tkz); if (extra != NULL) errAbort("Extra stuff starting with '%s' past end of statement line %d of %s", extra, lf->lineIx, lf->fileName); return rql; }