Repository: shepherdwind/velocity.js (ts)

Issue:
text version of logical operators not supported
if I use below then error is seen:

#if ($name eq "aaa" and $age)
#end

Please refer:
http://people.apache.org/~henning/velocity/html/ch06.html

Pull request:
feat: text version of logical operators support
close #88

Patch:
diff --git a/.travis.yml b/.travis.yml
index a2bf59d..a38e530 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,7 @@ notifications:
     - eward.song@gmail.com
 
 node_js:
-  - 'iojs-2'
-  - '4.2.1'
+  - '8.9.0'
 
 install:
   - npm install
diff --git a/src/parse/index.js b/src/parse/index.js
index eca9281..6f4f199 100644
--- a/src/parse/index.js
+++ b/src/parse/index.js
@@ -1,4 +1,4 @@
-/* parser generated by jison 0.4.15 */
+/* parser generated by jison 0.4.18 */
 /*
   Returns a Parser object of the following structure:
 
@@ -71,7 +71,7 @@
     recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
   }
 */
-var parser = (function(){
+var velocity = (function(){
 var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,8],$V1=[1,9],$V2=[1,19],$V3=[1,10],$V4=[1,23],$V5=[1,22],$V6=[4,10,11,20,34,35,80],$V7=[1,27],$V8=[1,31],$V9=[1,30],$Va=[4,10,11,20,23,34,35,38,47,48,49,52,53,54,55,56,57,58,59,60,61,62,63,64,80,82,92],$Vb=[1,47],$Vc=[1,52],$Vd=[1,53],$Ve=[1,68],$Vf=[1,67],$Vg=[1,80],$Vh=[1,75],$Vi=[1,83],$Vj=[1,91],$Vk=[1,81],$Vl=[1,86],$Vm=[1,90],$Vn=[1,87],$Vo=[1,88],$Vp=[4,10,11,20,23,34,35,38,47,48,49,52,53,54,55,56,57,58,59,60,61,62,63,64,73,78,80,81,82,92],$Vq=[1,117],$Vr=[1,113],$Vs=[1,114],$Vt=[1,125],$Vu=[23,48,82],$Vv=[2,93],$Vw=[23,38,47,48,82],$Vx=[23,38,47,48,52,53,54,55,56,57,58,59,60,61,62,63,64,80,82],$Vy=[23,38,47,48,52,53,54,55,56,57,58,59,60,61,62,63,64,80,82,94],$Vz=[2,106],$VA=[23,38,47,48,52,53,54,55,56,57,58,59,60,61,62,63,64,80,82,92],$VB=[2,109],$VC=[1,134],$VD=[1,140],$VE=[23,47,48],$VF=[1,145],$VG=[1,146],$VH=[1,147],$VI=[1,148],$VJ=[1,149],$VK=[1,150],$VL=[1,151],$VM=[1,152],$VN=[1,153],$VO=[1,154],$VP=[1,155],$VQ=[1,156],$VR=[1,157],$VS=[23,52,53,54,55,56,57,58,59,60,61,62,63,64],$VT=[48,82],$VU=[2,110],$VV=[23,34],$VW=[1,206],$VX=[1,205],$VY=[38,48],$VZ=[23,52,53],$V_=[23,52,53,54,55,59,60,61,62,63,64],$V$=[23,52,53,59,60,61,62,63,64];
 var parser = {trace: function trace() { },
 yy: {},
@@ -161,9 +161,33 @@ break;
 case 48:
  this.$ = [$$[$0-2], $$[$0]]; 
 break;
-case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64:
+case 52:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '||' }; 
+break;
+case 53:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '&&' }; 
+break;
+case 54: case 55: case 56: case 57: case 58:
  this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: $$[$0-1] }; 
 break;
+case 59:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '>' }; 
+break;
+case 60:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '<' }; 
+break;
+case 61:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '==' }; 
+break;
+case 62:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '>=' }; 
+break;
+case 63:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '<=' }; 
+break;
+case 64:
+ this.$ = {type: 'math', expression: [$$[$0-2], $$[$0]], operator: '!=' }; 
+break;
 case 66:
  this.$ = {type: 'math', expression: [$$[$0]], operator: 'minus' }; 
 break;
@@ -289,7 +313,9 @@ parseError: function parseError(str, hash) {
     if (hash.recoverable) {
         this.trace(str);
     } else {
-        throw new Error(str);
+        var error = new Error(str);
+        error.hash = hash;
+        throw error;
     }
 },
 parse: function parse(input) {
@@ -322,14 +348,14 @@ parse: function parse(input) {
         lstack.length = lstack.length - n;
     }
     _token_stack:
-        function lex() {
+        var lex = function () {
             var token;
             token = lexer.lex() || EOF;
             if (typeof token !== 'number') {
                 token = self.symbols_[token] || token;
             }
             return token;
-        }
+        };
     var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
     while (true) {
         state = stack[stack.length - 1];
@@ -826,48 +852,66 @@ case 20: return yy_.yytext;
 break;
 case 21: return yy_.yytext; 
 break;
-case 22: return yy_.yytext; 
+case 22: return 63; 
 break;
 case 23: return yy_.yytext; 
 break;
-case 24: return yy_.yytext; 
+case 24: return 62; 
 break;
 case 25: return yy_.yytext; 
 break;
-case 26: return yy_.yytext; 
+case 26: return 59; 
 break;
-case 27: return yy_.yytext; 
+case 27: return 60; 
 break;
-case 28: return 34; 
+case 28: return yy_.yytext; 
 break;
-case 29: return 34; 
+case 29: return 61; 
 break;
 case 30: return yy_.yytext; 
 break;
-case 31: return 49; 
+case 31: return 52; 
 break;
-case 32:
+case 32: return yy_.yytext; 
+break;
+case 33: return 53; 
+break;
+case 34: return yy_.yytext; 
+break;
+case 35: return 64; 
+break;
+case 36: return 66; 
+break;
+case 37: return 34; 
+break;
+case 38: return 34; 
+break;
+case 39: return yy_.yytext; 
+break;
+case 40: return 49; 
+break;
+case 41:
                                     var len = this.stateStackSize();
                                     if (len >= 2 && this.topState() === 'c' && this.topState(1) === 'run') {
                                       return 47;
                                     }
                                   
 break;
-case 33: /*ignore whitespace*/ 
+case 42: /*ignore whitespace*/ 
 break;
-case 34: return 37; 
+case 43: return 37; 
 break;
-case 35: return 38; 
+case 44: return 38; 
 break;
-case 36: return 94; 
+case 45: return 94; 
 break;
-case 37: yy.begin = true; return 72; 
+case 46: yy.begin = true; return 72; 
 break;
-case 38: this.popState(); if (yy.begin === true) { yy.begin = false; return 73;} else { return 80; } 
+case 47: this.popState(); if (yy.begin === true) { yy.begin = false; return 73;} else { return 80; } 
 break;
-case 39: this.begin("c"); return 22; 
+case 48: this.begin("c"); return 22; 
 break;
-case 40:
+case 49:
                                     if (this.popState() === "c") {
                                       var len = this.stateStackSize();
 
@@ -892,9 +936,9 @@ case 40:
                                     }
                                   
 break;
-case 41: this.begin("i"); return 81; 
+case 50: this.begin("i"); return 81; 
 break;
-case 42: 
+case 51: 
                                     if (this.popState() === "i") {
                                       return 82; 
                                     } else {
@@ -902,46 +946,46 @@ case 42:
                                     }
                                   
 break;
-case 43: return 92; 
+case 52: return 92; 
 break;
-case 44: return 78; 
+case 53: return 78; 
 break;
-case 45: return 88; 
+case 54: return 88; 
 break;
-case 46: return 48; 
+case 55: return 48; 
 break;
-case 47: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2).replace(/\\"/g,'"'); return 90; 
+case 56: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2).replace(/\\"/g,'"'); return 90; 
 break;
-case 48: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2).replace(/\\'/g,"'"); return 89; 
+case 57: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2).replace(/\\'/g,"'"); return 89; 
 break;
-case 49: return 85; 
+case 58: return 85; 
 break;
-case 50: return 85; 
+case 59: return 85; 
 break;
-case 51: return 85; 
+case 60: return 85; 
 break;
-case 52: return 87; 
+case 61: return 87; 
 break;
-case 53: return 35; 
+case 62: return 35; 
 break;
-case 54: this.begin("run"); return 35; 
+case 63: this.begin("run"); return 35; 
 break;
-case 55: this.begin('h'); return 20; 
+case 64: this.begin('h'); return 20; 
 break;
-case 56: this.popState(); return 80; 
+case 65: this.popState(); return 80; 
 break;
-case 57: this.popState(); return 80; 
+case 66: this.popState(); return 80; 
 break;
-case 58: this.popState(); return 80; 
+case 67: this.popState(); return 80; 
 break;
-case 59: this.popState(); return 4; 
+case 68: this.popState(); return 4; 
 break;
-case 60: return 4; 
+case 69: return 4; 
 break;
 }
 },
-rules: [/^(?:[^#]*?(?=\$))/,/^(?:[^\$]*?(?=#))/,/^(?:[^\x00]+)/,/^(?:#\*[\s\S]+?\*#)/,/^(?:#\[\[[\s\S]+?\]\]#)/,/^(?:##[^\n]*)/,/^(?:#(?=[a-zA-Z{]))/,/^(?:set[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:if[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:elseif[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:else\b)/,/^(?:\{else\})/,/^(?:end\b)/,/^(?:\{end\})/,/^(?:break\b)/,/^(?:foreach[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:noescape(?=[^a-zA-Z0-9_]+))/,/^(?:define[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:macro[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:in\b)/,/^(?:[%\+\-\*\/])/,/^(?:<=)/,/^(?:>=)/,/^(?:[><])/,/^(?:==)/,/^(?:\|\|)/,/^(?:&&)/,/^(?:!=)/,/^(?:\$!(?=[{a-zA-Z_]))/,/^(?:\$(?=[{a-zA-Z_]))/,/^(?:!)/,/^(?:=)/,/^(?:[ ]+(?=[^,]))/,/^(?:\s+)/,/^(?:\{)/,/^(?:\})/,/^(?::[\s]*)/,/^(?:\{)/,/^(?:\})/,/^(?:\([\s]*(?=[$'"\[\{\-0-9\w()!]))/,/^(?:\))/,/^(?:\[[\s]*(?=[\-$"'0-9{\[\]]+))/,/^(?:\])/,/^(?:\.\.)/,/^(?:\.(?=[a-zA-Z_]))/,/^(?:\.(?=[\d]))/,/^(?:,[ ]*)/,/^(?:"(\\"|[^\"])*")/,/^(?:'(\\'|[^\'])*')/,/^(?:null\b)/,/^(?:false\b)/,/^(?:true\b)/,/^(?:[0-9]+)/,/^(?:[_a-zA-Z][a-zA-Z0-9_\-]*)/,/^(?:[_a-zA-Z][a-zA-Z0-9_\-]*[ ]*(?=\())/,/^(?:#)/,/^(?:.)/,/^(?:\s+)/,/^(?:[\$#])/,/^(?:$)/,/^(?:$)/],
-conditions: {"mu":{"rules":[5,28,29,37,38,39,40,41,42,44,53,55,56,57,59],"inclusive":false},"c":{"rules":[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,39,40,41,42,44,45,46,47,48,49,50,51,52,53],"inclusive":false},"i":{"rules":[19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,34,35,35,36,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53],"inclusive":false},"h":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,28,29,30,31,36,39,40,41,42,44,52,54,56,57,59],"inclusive":false},"esc":{"rules":[58],"inclusive":false},"run":{"rules":[28,29,30,32,33,34,35,36,39,40,41,42,44,45,46,47,48,49,50,51,52,53,56,57,59],"inclusive":false},"INITIAL":{"rules":[0,1,2,60],"inclusive":true}}
+rules: [/^(?:[^#]*?(?=\$))/,/^(?:[^\$]*?(?=#))/,/^(?:[^\x00]+)/,/^(?:#\*[\s\S]+?\*#)/,/^(?:#\[\[[\s\S]+?\]\]#)/,/^(?:##[^\n]*)/,/^(?:#(?=[a-zA-Z{]))/,/^(?:set[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:if[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:elseif[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:else\b)/,/^(?:\{else\})/,/^(?:end\b)/,/^(?:\{end\})/,/^(?:break\b)/,/^(?:foreach[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:noescape(?=[^a-zA-Z0-9_]+))/,/^(?:define[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:macro[ ]*(?=[^a-zA-Z0-9_]+))/,/^(?:in\b)/,/^(?:[%\+\-\*\/])/,/^(?:<=)/,/^(?:le\b)/,/^(?:>=)/,/^(?:ge\b)/,/^(?:[><])/,/^(?:gt\b)/,/^(?:lt\b)/,/^(?:==)/,/^(?:eq\b)/,/^(?:\|\|)/,/^(?:or\b)/,/^(?:&&)/,/^(?:and\b)/,/^(?:!=)/,/^(?:ne\b)/,/^(?:not\b)/,/^(?:\$!(?=[{a-zA-Z_]))/,/^(?:\$(?=[{a-zA-Z_]))/,/^(?:!)/,/^(?:=)/,/^(?:[ ]+(?=[^,]))/,/^(?:\s+)/,/^(?:\{)/,/^(?:\})/,/^(?::[\s]*)/,/^(?:\{)/,/^(?:\})/,/^(?:\([\s]*(?=[$'"\[\{\-0-9\w()!]))/,/^(?:\))/,/^(?:\[[\s]*(?=[\-$"'0-9{\[\]]+))/,/^(?:\])/,/^(?:\.\.)/,/^(?:\.(?=[a-zA-Z_]))/,/^(?:\.(?=[\d]))/,/^(?:,[ ]*)/,/^(?:"(\\"|[^\"])*")/,/^(?:'(\\'|[^\'])*')/,/^(?:null\b)/,/^(?:false\b)/,/^(?:true\b)/,/^(?:[0-9]+)/,/^(?:[_a-zA-Z][a-zA-Z0-9_\-]*)/,/^(?:[_a-zA-Z][a-zA-Z0-9_\-]*[ ]*(?=\())/,/^(?:#)/,/^(?:.)/,/^(?:\s+)/,/^(?:[\$#])/,/^(?:$)/,/^(?:$)/],
+conditions: {"mu":{"rules":[5,37,38,46,47,48,49,50,51,53,62,64,65,66,68],"inclusive":false},"c":{"rules":[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,48,49,50,51,53,54,55,56,57,58,59,60,61,62],"inclusive":false},"i":{"rules":[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,43,44,44,45,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62],"inclusive":false},"h":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,37,38,39,40,45,48,49,50,51,53,61,63,65,66,68],"inclusive":false},"esc":{"rules":[67],"inclusive":false},"run":{"rules":[37,38,39,41,42,43,44,45,48,49,50,51,53,54,55,56,57,58,59,60,61,62,65,66,68],"inclusive":false},"INITIAL":{"rules":[0,1,2,69],"inclusive":true}}
 });
 return lexer;
 })();
@@ -955,9 +999,9 @@ return new Parser;
 
 
 if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
-exports.parser = parser;
-exports.Parser = parser.Parser;
-exports.parse = function () { return parser.parse.apply(parser, arguments); };
+exports.parser = velocity;
+exports.Parser = velocity.Parser;
+exports.parse = function () { return velocity.parse.apply(velocity, arguments); };
 exports.main = function commonjsMain(args) {
     if (!args[1]) {
         console.log('Usage: '+args[0]+' FILE');
diff --git a/src/parse/velocity.l b/src/parse/velocity.l
index 47cef2d..0388956 100644
--- a/src/parse/velocity.l
+++ b/src/parse/velocity.l
@@ -52,12 +52,22 @@
 <c,i>"in"                         { return 'IN'; }
 <c,i>[%\+\-\*/]                   { return yytext; }
 <c,i>"<="                         { return yytext; }
+<c,i>"le"                         { return '<='; }
 <c,i>">="                         { return yytext; }
+<c,i>"ge"                         { return '>='; }
 <c,i>[><]                         { return yytext; }
+<c,i>"gt"                         { return '>'; }
+<c,i>"lt"                         { return '<'; }
 <c,i>"=="                         { return yytext; }
+<c,i>"eq"                         { return '=='; }
 <c,i>"||"                         { return yytext; }
+<c,i>"or"                         { return '||'; }
 <c,i>"&&"                         { return yytext; }
+<c,i>"and"                        { return '&&'; }
 <c,i>"!="                         { return yytext; }
+<c,i>"ne"                         { return '!='; }
+<c,i>"not"                        { return '!'; }
+
 <mu,c,run,h,i>"$!"/[{a-zA-Z_]     { return 'DOLLAR'; }
 <mu,run,h,c,i>"$"/[{a-zA-Z_]      { return 'DOLLAR'; }
 <h,run,c,i>"!"                    { return yytext; }
diff --git a/src/parse/velocity.yy b/src/parse/velocity.yy
index 8513288..41eccf8 100644
--- a/src/parse/velocity.yy
+++ b/src/parse/velocity.yy
@@ -169,9 +169,9 @@ expression
 
 math
   : math '||' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '||' }; }
   | math '&&' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '&&' }; }
   | math '+' math
       { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
   | math '-' math
@@ -183,17 +183,17 @@ math
   | math '%' math
       { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
   | math '>' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '>' }; }
   | math '<' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '<' }; }
   | math '==' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '==' }; }
   | math '>=' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '>=' }; }
   | math '<=' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '<=' }; }
   | math '!=' math
-      { $$ = {type: 'math', expression: [$1, $3], operator: $2 }; }
+      { $$ = {type: 'math', expression: [$1, $3], operator: '!=' }; }
   | parenthesis
       { $$ = $1; }
   | '-' parenthesis


Test patch:
diff --git a/tests/operators.test.js b/tests/operators.test.js
new file mode 100644
index 0000000..e69de29
diff --git a/tests/set.test.js b/tests/set.test.js
index 979ca7f..fbb38d0 100644
--- a/tests/set.test.js
+++ b/tests/set.test.js
@@ -109,6 +109,17 @@ describe('Set && Expression', function() {
     assert.equal(false, getContext('#set($foo = 10 == 11)').foo)
   })
 
+  it('expression compare text version', function() {
+    assert.equal(false, getContext('#set($foo = 10 gt 11)').foo)
+    assert.equal(true, getContext('#set($foo = 10 lt 11)').foo)
+    assert.equal(true, getContext('#set($foo = 10 ne 11)').foo)
+    assert.equal(true, getContext('#set($foo = 10 le 11)').foo)
+    assert.equal(true, getContext('#set($foo = 11 le 11)').foo)
+    assert.equal(false, getContext('#set($foo = 12 le 11)').foo)
+    assert.equal(true, getContext('#set($foo = 12 ge 11)').foo)
+    assert.equal(false, getContext('#set($foo = 10 eq 11)').foo)
+  })
+
   it('expression logic', function() {
     assert.equal(false, getContext('#set($foo = 10 == 11 && 3 > 1)').foo)
     assert.equal(true, getContext('#set($foo = 10 < 11 && 3 > 1)').foo)
@@ -119,6 +130,16 @@ describe('Set && Expression', function() {
     assert.equal(true, getContext('#set($foo = $a || $b)', {a: 1, b: 0}).foo)
   })
 
+  it('expression logic text version', function() {
+    assert.equal(false, getContext('#set($foo = 10 eq 11 and 3 gt 1)').foo)
+    assert.equal(true, getContext('#set($foo = 10 lt 11 and 3 gt 1)').foo)
+    assert.equal(true, getContext('#set($foo = 10 gt 11 or 3 gt 1)').foo)
+    assert.equal(true, getContext('#set($foo = not(10 gt 11) and 3 gt 1)').foo)
+    assert.equal(false, getContext('#set($foo = $a gt $b)', {a: 1, b: 2}).foo)
+    assert.equal(false, getContext('#set($foo = $a and $b)', {a: 1, b: 0}).foo)
+    assert.equal(true, getContext('#set($foo = $a or $b)', {a: 1, b: 0}).foo)
+  })
+
   it('#set context should be global, #25', function() {
     var vm = '#macro(local) #set($val =1) $val #end #local() $val'
     var ret = render(vm).replace(/\s+/g, '')