Index: src/th.c ================================================================== --- src/th.c +++ src/th.c @@ -1774,10 +1774,11 @@ ** the expression module means the Th_Expr() and exprXXX() functions. */ typedef struct Operator Operator; struct Operator { const char *zOp; + int nOp; int eOp; int iPrecedence; int eArgType; }; typedef struct Expr Expr; @@ -1831,47 +1832,47 @@ #define ARG_NUMBER 2 #define ARG_STRING 3 static Operator aOperator[] = { - {"(", OP_OPEN_BRACKET, -1, 0}, - {")", OP_CLOSE_BRACKET, -1, 0}, + {"(", 1, OP_OPEN_BRACKET, -1, 0}, + {")", 1, OP_CLOSE_BRACKET, -1, 0}, /* Note: all unary operators have (iPrecedence==1) */ - {"-", OP_UNARY_MINUS, 1, ARG_NUMBER}, - {"+", OP_UNARY_PLUS, 1, ARG_NUMBER}, - {"~", OP_BITWISE_NOT, 1, ARG_INTEGER}, - {"!", OP_LOGICAL_NOT, 1, ARG_INTEGER}, + {"-", 1, OP_UNARY_MINUS, 1, ARG_NUMBER}, + {"+", 1, OP_UNARY_PLUS, 1, ARG_NUMBER}, + {"~", 1, OP_BITWISE_NOT, 1, ARG_INTEGER}, + {"!", 1, OP_LOGICAL_NOT, 1, ARG_INTEGER}, /* Binary operators. It is important to the parsing in Th_Expr() that * the two-character symbols ("==") appear before the one-character * ones ("="). And that the priorities of all binary operators are * integers between 2 and 12. */ - {"<<", OP_LEFTSHIFT, 4, ARG_INTEGER}, - {">>", OP_RIGHTSHIFT, 4, ARG_INTEGER}, - {"<=", OP_LE, 5, ARG_NUMBER}, - {">=", OP_GE, 5, ARG_NUMBER}, - {"==", OP_EQ, 6, ARG_NUMBER}, - {"!=", OP_NE, 6, ARG_NUMBER}, - {"eq", OP_SEQ, 7, ARG_STRING}, - {"ne", OP_SNE, 7, ARG_STRING}, - {"&&", OP_LOGICAL_AND, 11, ARG_INTEGER}, - {"||", OP_LOGICAL_OR, 12, ARG_INTEGER}, - - {"*", OP_MULTIPLY, 2, ARG_NUMBER}, - {"/", OP_DIVIDE, 2, ARG_NUMBER}, - {"%", OP_MODULUS, 2, ARG_INTEGER}, - {"+", OP_ADD, 3, ARG_NUMBER}, - {"-", OP_SUBTRACT, 3, ARG_NUMBER}, - {"<", OP_LT, 5, ARG_NUMBER}, - {">", OP_GT, 5, ARG_NUMBER}, - {"&", OP_BITWISE_AND, 8, ARG_INTEGER}, - {"^", OP_BITWISE_XOR, 9, ARG_INTEGER}, - {"|", OP_BITWISE_OR, 10, ARG_INTEGER}, - - {0,0,0,0} + {"<<", 2, OP_LEFTSHIFT, 4, ARG_INTEGER}, + {">>", 2, OP_RIGHTSHIFT, 4, ARG_INTEGER}, + {"<=", 2, OP_LE, 5, ARG_NUMBER}, + {">=", 2, OP_GE, 5, ARG_NUMBER}, + {"==", 2, OP_EQ, 6, ARG_NUMBER}, + {"!=", 2, OP_NE, 6, ARG_NUMBER}, + {"eq", 2, OP_SEQ, 7, ARG_STRING}, + {"ne", 2, OP_SNE, 7, ARG_STRING}, + {"&&", 2, OP_LOGICAL_AND, 11, ARG_INTEGER}, + {"||", 2, OP_LOGICAL_OR, 12, ARG_INTEGER}, + + {"*", 1, OP_MULTIPLY, 2, ARG_NUMBER}, + {"/", 1, OP_DIVIDE, 2, ARG_NUMBER}, + {"%", 1, OP_MODULUS, 2, ARG_INTEGER}, + {"+", 1, OP_ADD, 3, ARG_NUMBER}, + {"-", 1, OP_SUBTRACT, 3, ARG_NUMBER}, + {"<", 1, OP_LT, 5, ARG_NUMBER}, + {">", 1, OP_GT, 5, ARG_NUMBER}, + {"&", 1, OP_BITWISE_AND, 8, ARG_INTEGER}, + {"^", 1, OP_BITWISE_XOR, 9, ARG_INTEGER}, + {"|", 1, OP_BITWISE_OR, 10, ARG_INTEGER}, + + {0,0,0,0,0} }; /* ** The first part of the string (zInput,nInput) contains an integer. ** Set *pnVarname to the number of bytes in the numeric string. @@ -2123,11 +2124,12 @@ } iLeft = 0; for(jj=nToken-1; jj>=0; jj--){ if( apToken[jj] ){ - if( apToken[jj]->pOp && apToken[jj]->pOp->iPrecedence==1 && iLeft>0 ){ + if( apToken[jj]->pOp && apToken[jj]->pOp->iPrecedence==1 + && iLeft>0 && ISTERM(iLeft) ){ apToken[jj]->pLeft = apToken[iLeft]; apToken[jj]->pLeft->pParent = apToken[jj]; apToken[iLeft] = 0; } iLeft = jj; @@ -2138,13 +2140,11 @@ for(jj=0; jjpOp && !pToken->pLeft && pToken->pOp->iPrecedence==i ){ int iRight = jj+1; - - iRight = jj+1; - for(iRight=jj+1; !apToken[iRight] && iRightpLeft = apToken[iLeft]; apToken[iLeft] = 0; @@ -2179,10 +2179,11 @@ int *pnToken /* OUT: Size of token array */ ){ int i; int rc = TH_OK; + int nNest = 0; int nToken = 0; Expr **apToken = 0; for(i=0; rc==TH_OK && i0 ){ + const char *zOp; + for(j=0; (zOp=aOperator[j].zOp); j++){ + int nOp = aOperator[j].nOp; + int isMatch = 0; + if( (nExpr-i)>=nOp && 0==memcmp(zOp, &zExpr[i], nOp) ){ + isMatch = 1; + } + if( isMatch && aOperator[j].eOp==OP_OPEN_BRACKET ){ + nNest++; + }else if( isMatch && aOperator[j].eOp==OP_CLOSE_BRACKET ){ + nNest--; + } + if( nToken>0 && aOperator[j].iPrecedence==1 ){ Expr *pPrev = apToken[nToken-1]; if( !pPrev->pOp || pPrev->pOp->eOp==OP_CLOSE_BRACKET ){ continue; } } - nOp = th_strlen((const char *)aOperator[j].zOp); - if( (nExpr-i)>=nOp && 0==memcmp(aOperator[j].zOp, &zExpr[i], nOp) ){ + if( isMatch ){ pNew->pOp = &aOperator[j]; i += nOp; break; } } @@ -2265,10 +2275,14 @@ Th_Free(interp, pNew); rc = TH_ERROR; } } } + + if( nNest!=0 ){ + rc = TH_ERROR; + } *papToken = apToken; *pnToken = nToken; return rc; } Index: test/th1.test ================================================================== --- test/th1.test +++ test/th1.test @@ -477,10 +477,40 @@ ############################################################################### fossil test-th-eval "expr 0+0b" test th1-expr-35 {$RESULT eq {TH_ERROR: expected number, got: "0b"}} + +############################################################################### + +fossil test-th-eval "expr (-1)+1" +test th1-expr-36 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "expr (((-1)))" +test th1-expr-37 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr (((1)))" +test th1-expr-38 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "expr (((1))" +test th1-expr-39 {$RESULT eq {TH_ERROR: syntax error in expression: "(((1))"}} + +############################################################################### + +fossil test-th-eval "expr ((1)))" +test th1-expr-40 {$RESULT eq {TH_ERROR: syntax error in expression: "((1)))"}} + +############################################################################### + +fossil test-th-eval "expr (((1)*2)*2)" +test th1-expr-41 {$RESULT eq {4}} ############################################################################### fossil test-th-eval "checkout 1"; # NOTE: Assumes running "in tree". test th1-checkout-1 {[string length $RESULT] > 0} Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -5,10 +5,11 @@ returning an error. * Add the [/help/fusefs|fossil fusefs DIRECTORY] command that mounts a Fuse Filesystem at the given DIRECTORY and populates it with read-only copies of all historical check-ins. This only works on systems that support FuseFS. + * Several fixes to the TH1 expression parser. * Support customization of commands and webpages, including the ability to add new ones, via the "TH1 hooks" feature. Disabled by default. Enabled via a compile-time option. * Add the [checkout], [render], [styleHeader], [styleFooter], [trace], [getParameter], [setParameter], and [artifact] commands