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