UNPKG

twig

Version:

JS port of the Twig templating language.

374 lines (304 loc) 10.2 kB
// ## twig.expression.operator.js // // This file handles operator lookups and parsing. module.exports = function (Twig) { 'use strict'; /** * Operator associativity constants. */ Twig.expression.operator = { leftToRight: 'leftToRight', rightToLeft: 'rightToLeft' }; const containment = function (a, b) { if (b === undefined || b === null) { return null; } if (b.indexOf !== undefined) { // String return (a === b || a !== '') && b.includes(a); } let el; for (el in b) { if (Object.hasOwnProperty.call(b, el) && b[el] === a) { return true; } } return false; }; /** * Get the precidence and associativity of an operator. These follow the order that C/C++ use. * See http://en.wikipedia.org/wiki/Operators_in_C_and_C++ for the table of values. */ Twig.expression.operator.lookup = function (operator, token) { switch (operator) { case '..': token.precidence = 20; token.associativity = Twig.expression.operator.leftToRight; break; case ',': token.precidence = 18; token.associativity = Twig.expression.operator.leftToRight; break; // Ternary case '?:': case '?': case ':': token.precidence = 16; token.associativity = Twig.expression.operator.rightToLeft; break; // Null-coalescing operator case '??': token.precidence = 15; token.associativity = Twig.expression.operator.rightToLeft; break; case 'or': token.precidence = 14; token.associativity = Twig.expression.operator.leftToRight; break; case 'and': token.precidence = 13; token.associativity = Twig.expression.operator.leftToRight; break; case 'b-or': token.precidence = 12; token.associativity = Twig.expression.operator.leftToRight; break; case 'b-xor': token.precidence = 11; token.associativity = Twig.expression.operator.leftToRight; break; case 'b-and': token.precidence = 10; token.associativity = Twig.expression.operator.leftToRight; break; case '==': case '!=': token.precidence = 9; token.associativity = Twig.expression.operator.leftToRight; break; case '<=>': token.precidence = 9; token.associativity = Twig.expression.operator.leftToRight; break; case '<': case '<=': case '>': case '>=': case 'not in': case 'in': token.precidence = 8; token.associativity = Twig.expression.operator.leftToRight; break; case '~': // String concatination case '+': case '-': token.precidence = 6; token.associativity = Twig.expression.operator.leftToRight; break; case '//': case '**': case '*': case '/': case '%': token.precidence = 5; token.associativity = Twig.expression.operator.leftToRight; break; case 'not': token.precidence = 3; token.associativity = Twig.expression.operator.rightToLeft; break; case 'matches': token.precidence = 8; token.associativity = Twig.expression.operator.leftToRight; break; case 'starts with': token.precidence = 8; token.associativity = Twig.expression.operator.leftToRight; break; case 'ends with': token.precidence = 8; token.associativity = Twig.expression.operator.leftToRight; break; default: throw new Twig.Error('Failed to lookup operator: ' + operator + ' is an unknown operator.'); } token.operator = operator; return token; }; /** * Handle operations on the RPN stack. * * Returns the updated stack. */ Twig.expression.operator.parse = function (operator, stack) { Twig.log.trace('Twig.expression.operator.parse: ', 'Handling ', operator); let a; let b; let c; if (operator === '?') { c = stack.pop(); } b = stack.pop(); if (operator !== 'not') { a = stack.pop(); } if (operator !== 'in' && operator !== 'not in' && operator !== '??') { if (a && Array.isArray(a)) { a = a.length; } if (operator !== '?' && (b && Array.isArray(b))) { b = b.length; } } if (operator === 'matches') { if (b && typeof b === 'string') { const reParts = b.match(/^\/(.*)\/([gims]?)$/); const reBody = reParts[1]; const reFlags = reParts[2]; b = new RegExp(reBody, reFlags); } } switch (operator) { case ':': // Ignore break; case '??': if (a === undefined) { a = b; b = c; c = undefined; } if (a !== undefined && a !== null) { stack.push(a); } else { stack.push(b); } break; case '?:': if (Twig.lib.boolval(a)) { stack.push(a); } else { stack.push(b); } break; case '?': if (a === undefined) { // An extended ternary. a = b; b = c; c = undefined; } if (Twig.lib.boolval(a)) { stack.push(b); } else { stack.push(c); } break; case '+': b = parseFloat(b); a = parseFloat(a); stack.push(a + b); break; case '-': b = parseFloat(b); a = parseFloat(a); stack.push(a - b); break; case '*': b = parseFloat(b); a = parseFloat(a); stack.push(a * b); break; case '/': b = parseFloat(b); a = parseFloat(a); stack.push(a / b); break; case '//': b = parseFloat(b); a = parseFloat(a); stack.push(Math.floor(a / b)); break; case '%': b = parseFloat(b); a = parseFloat(a); stack.push(a % b); break; case '~': stack.push((typeof a !== 'undefined' && a !== null ? a.toString() : '') + (typeof b !== 'undefined' && b !== null ? b.toString() : '')); break; case 'not': case '!': stack.push(!Twig.lib.boolval(b)); break; case '<=>': stack.push(a === b ? 0 : (a < b ? -1 : 1)); break; case '<': stack.push(a < b); break; case '<=': stack.push(a <= b); break; case '>': stack.push(a > b); break; case '>=': stack.push(a >= b); break; case '===': stack.push(a === b); break; case '==': stack.push(a == b); break; case '!==': stack.push(a !== b); break; case '!=': stack.push(a != b); break; case 'or': stack.push(Twig.lib.boolval(a) || Twig.lib.boolval(b)); break; case 'b-or': stack.push(a | b); break; case 'b-xor': stack.push(a ^ b); break; case 'and': stack.push(Twig.lib.boolval(a) && Twig.lib.boolval(b)); break; case 'b-and': stack.push(a & b); break; case '**': stack.push(a ** b); break; case 'not in': stack.push(!containment(a, b)); break; case 'in': stack.push(containment(a, b)); break; case 'matches': stack.push(b.test(a)); break; case 'starts with': stack.push(typeof a === 'string' && a.indexOf(b) === 0); break; case 'ends with': stack.push(typeof a === 'string' && a.includes(b, a.length - b.length)); break; case '..': stack.push(Twig.functions.range(a, b)); break; default: throw new Twig.Error('Failed to parse operator: ' + operator + ' is an unknown operator.'); } }; return Twig; };