-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for arrow functions #871
Changes from all commits
5a4a271
d108a81
a23266a
f7c4666
5915fe9
93a1b6b
bdcd4ce
5a7d299
1f81225
ee22d77
88e21a6
ae3e172
1a6b3c1
d91d7c8
ff44558
5c905a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ module.exports = function (Twig) { | |
unary: 'Twig.expression.type.operator.unary', | ||
binary: 'Twig.expression.type.operator.binary' | ||
}, | ||
arrowFunction: 'Twig.expression.type.arrowFunction', | ||
string: 'Twig.expression.type.string', | ||
bool: 'Twig.expression.type.bool', | ||
slice: 'Twig.expression.type.slice', | ||
|
@@ -71,6 +72,7 @@ module.exports = function (Twig) { | |
// What can follow an expression (in general) | ||
operations: [ | ||
Twig.expression.type.filter, | ||
Twig.expression.type.arrowFunction, | ||
Twig.expression.type.operator.unary, | ||
Twig.expression.type.operator.binary, | ||
Twig.expression.type.array.end, | ||
|
@@ -212,9 +214,9 @@ module.exports = function (Twig) { | |
}, | ||
{ | ||
type: Twig.expression.type.operator.binary, | ||
// Match any of ??, ?:, +, *, /, -, %, ~, <, <=, >, >=, !=, ==, **, ?, :, and, b-and, or, b-or, b-xor, in, not in | ||
// Match any of ??, ?:, +, *, /, -, %, ~, <=>, <, <=, >, >=, !=, ==, **, ?, :, and, b-and, or, b-or, b-xor, in, not in | ||
// and, or, in, not in, matches, starts with, ends with can be followed by a space or parenthesis | ||
regex: /(^\?\?|^\?:|^(b-and)|^(b-or)|^(b-xor)|^[+\-~%?]|^[:](?!\d\])|^[!=]==?|^[!<>]=?|^\*\*?|^\/\/?|^(and)[(|\s+]|^(or)[(|\s+]|^(in)[(|\s+]|^(not in)[(|\s+]|^(matches)|^(starts with)|^(ends with)|^\.\.)/, | ||
regex: /(^\?\?|^\?:|^(b-and)|^(b-or)|^(b-xor)|^[+\-~%?]|^(<=>)|^[:](?!\d\])|^[!=]==?|^[!<>]=?|^\*\*?|^\/\/?|^(and)[(|\s+]|^(or)[(|\s+]|^(in)[(|\s+]|^(not in)[(|\s+]|^(matches)|^(starts with)|^(ends with)|^\.\.)/, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this change and submit as separate PR. |
||
next: Twig.expression.set.expressions, | ||
transform(match, tokens) { | ||
switch (match[0]) { | ||
|
@@ -479,6 +481,31 @@ module.exports = function (Twig) { | |
throw new Twig.Error('Unexpected subexpression end when token is not marked as an expression'); | ||
} | ||
}, | ||
{ | ||
/** | ||
* Match an arrow function after a filter. | ||
* ((params) => body) | ||
*/ | ||
type: Twig.expression.type.arrowFunction, | ||
regex: /^\(\(*\s*([a-zA-Z_]\w*\s*(?:\s?,\s?[a-zA-Z_]\w*\s*)*)\)*\s*=>\s*([^,]*),*\s*(\w*(?:,\s?\w*)*)\s*\)/, | ||
next: Twig.expression.set.expressions.concat([Twig.expression.type.subexpression.end]), | ||
compile(token, stack, output) { | ||
const filter = output.pop(); | ||
if (filter.type !== Twig.expression.type.filter) { | ||
throw new Twig.Error('Expected filter before arrow function.'); | ||
} | ||
|
||
token.params = token.match[1].trim(); | ||
token.body = '{{ ' + token.match[2] + ' }}'; | ||
token.args = token.match[3].trim(); | ||
filter.params = token; | ||
|
||
output.push(filter); | ||
}, | ||
parse(token, stack) { | ||
stack.push(token); | ||
} | ||
}, | ||
{ | ||
/** | ||
* Match a parameter set start. | ||
|
@@ -756,6 +783,7 @@ module.exports = function (Twig) { | |
// Match a | then a letter or _, then any number of letters, numbers, _ or - | ||
regex: /^\|\s?([a-zA-Z_][a-zA-Z0-9_-]*)/, | ||
next: Twig.expression.set.operationsExtended.concat([ | ||
Twig.expression.type.arrowFunction, | ||
Twig.expression.type.parameter.start | ||
]), | ||
compile(token, stack, output) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,11 @@ module.exports = function (Twig) { | |
token.associativity = Twig.expression.operator.leftToRight; | ||
break; | ||
|
||
case '<=>': | ||
token.precidence = 9; | ||
token.associativity = Twig.expression.operator.leftToRight; | ||
break; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this change and submit as separate PR. |
||
case '<': | ||
case '<=': | ||
case '>': | ||
|
@@ -275,6 +280,10 @@ module.exports = function (Twig) { | |
stack.push(!Twig.lib.boolval(b)); | ||
break; | ||
|
||
case '<=>': | ||
stack.push(a === b ? 0 : (a < b ? -1 : 1)); | ||
break; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this change and submit as separate PR. |
||
case '<': | ||
stack.push(a < b); | ||
break; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,9 +72,24 @@ module.exports = function (Twig) { | |
return value; | ||
} | ||
}, | ||
sort(value) { | ||
sort(value, params) { | ||
if (is('Array', value)) { | ||
return value.sort(); | ||
let ret; | ||
if (params) { | ||
const callBackParams = params.params.split(','); | ||
ret = value.sort((_a, _b) => { | ||
const data = {}; | ||
data[callBackParams[0]] = _a; | ||
data[callBackParams[1]] = _b; | ||
|
||
const template = Twig.exports.twig({data: params.body}); | ||
return template.render(data); | ||
}); | ||
} else { | ||
ret = value.sort(); | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
if (is('Object', value)) { | ||
|
@@ -820,6 +835,50 @@ module.exports = function (Twig) { | |
}, | ||
spaceless(value) { | ||
return value.replace(/>\s+</g, '><').trim(); | ||
}, | ||
filter(value, params) { | ||
if (is('Array', value)) { | ||
return value.filter(_a => { | ||
const data = {}; | ||
data[params.params] = _a; | ||
|
||
const template = Twig.exports.twig({data: params.body}); | ||
return template.render(data) === 'true'; | ||
}); | ||
} | ||
}, | ||
map(value, params) { | ||
if (is('Array', value)) { | ||
const callBackParams = params.params.split(','); | ||
// Since Javascript does not support a callBack function to map() with both keys and values; we use forEach here | ||
// Note: Twig and PHP use ((value[, key])) for map(); whereas Javascript uses (([key, ]value)) for forEach() | ||
const newValue = []; | ||
value.forEach((_b, _a) => { | ||
const data = {}; | ||
data[callBackParams[0].trim()] = _b; | ||
if (callBackParams[1]) { | ||
data[callBackParams[1].trim()] = _a; | ||
} | ||
|
||
const template = Twig.exports.twig({data: params.body}); | ||
newValue[_a] = template.render(data); | ||
}); | ||
return newValue; | ||
} | ||
}, | ||
reduce(value, params) { | ||
if (is('Array', value)) { | ||
const callBackParams = params.params.split(','); | ||
return value.reduce((_carry, _v, _k) => { | ||
const data = {}; | ||
data[callBackParams[0]] = _carry; | ||
data[callBackParams[1].trim()] = _v; | ||
data[callBackParams[2].trim()] = _k; | ||
|
||
const template = Twig.exports.twig({data: params.body}); | ||
return template.render(data); | ||
}, params.args || 0); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove these changes and submit as a separate PR after this is one merged. |
||
} | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,6 +173,13 @@ describe('Twig.js Expressions ->', function () { | |
{a: false, b: true}, | ||
{a: false, b: false} | ||
]; | ||
it('should support spaceship operator', function () { | ||
const testTemplate = twig({data: '{{ a <=> b }}'}); | ||
numericTestData.forEach(pair => { | ||
const output = testTemplate.render(pair); | ||
output.should.equal((pair.a === pair.b ? 0 : (pair.a < pair.b ? -1 : 1)).toString()); | ||
}); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this change and submit as separate PR. |
||
it('should support less then', function () { | ||
const testTemplate = twig({data: '{{ a < b }}'}); | ||
numericTestData.forEach(pair => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,6 +151,10 @@ describe('Twig.js Filters ->', function () { | |
testTemplate = twig({data: '{% set obj = {\'z\':\'abc\',\'a\':2,\'y\':7,\'m\':\'test\'} %}{% for key,value in obj|sort %}{{key}}:{{value}} {%endfor %}'}); | ||
testTemplate.render().should.equal('a:2 y:7 z:abc m:test '); | ||
}); | ||
it('should sort an array of objects with a callback function', function () { | ||
let testTemplate = twig({data: '{% for item in items|sort((left,right) => left.num - right.num) %}{{ item|json_encode }}{% endfor %}'}); | ||
testTemplate.render({items:[{id: 1,num: 6},{id: 2, num: 3},{id: 3, num: 4}]}).should.equal('{"id":2,"num":3}{"id":3,"num":4}{"id":1,"num":6}'); | ||
}); | ||
|
||
it('should handle undefined', function () { | ||
const testTemplate = twig({data: '{% set obj = undef|sort %}{% for key, value in obj|sort %}{{key}}:{{value}}{%endfor%}'}); | ||
|
@@ -855,6 +859,42 @@ describe('Twig.js Filters ->', function () { | |
}); | ||
}); | ||
|
||
describe('filter ->', function () { | ||
it('should filter an array (with perenthesis)', function () { | ||
let testTemplate = twig({data: '{{ [1,5,2,7,8]|filter((f) => f % 2 == 0) }}'}); | ||
testTemplate.render().should.equal('2,8'); | ||
}); | ||
|
||
it('should filter an array (without perenthesis)', function () { | ||
let testTemplate = twig({data: '{{ [1,5,2,7,8]|filter(f => f % 2 == 0) }}'}); | ||
testTemplate.render().should.equal('2,8'); | ||
}); | ||
}); | ||
|
||
describe('map ->', function () { | ||
it('should map an array (with keys)', function () { | ||
let testTemplate = twig({data: '{{ [1,5,2,7,8]|map((v, k) => v*v+k) }}'}); | ||
testTemplate.render().should.equal('1,26,6,52,68'); | ||
}); | ||
|
||
it('should map an array', function () { | ||
let testTemplate = twig({data: '{{ [1,5,2,7,8]|map((v) => v*v) }}'}); | ||
testTemplate.render().should.equal('1,25,4,49,64'); | ||
}); | ||
}); | ||
|
||
describe('reduce ->', function () { | ||
it('should reduce an array (with inital value)', function () { | ||
let testTemplate = twig({data: '{{ [1,2,3]|reduce((carry, v, k) => carry + v * k) }}'}); | ||
testTemplate.render().should.equal('8'); | ||
}); | ||
|
||
it('should reduce an array)', function () { | ||
let testTemplate = twig({data: '{{ [1,2,3]|reduce((carry, v, k) => carry + v * k, 10) }}'}); | ||
testTemplate.render().should.equal('18'); | ||
}); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove these changes and submit as a separate PR after this is one merged. |
||
it('should chain', function () { | ||
const testTemplate = twig({data: '{{ ["a", "b", "c"]|keys|reverse }}'}); | ||
testTemplate.render().should.equal('2,1,0'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this change and submit as separate PR.