Loading src/lib/parser.ts +32 −22 Original line number Original line Diff line number Diff line Loading @@ -653,7 +653,24 @@ export const createParser = ( return Object.keys(traits).length > 0 return Object.keys(traits).length > 0 }; }; const isBinary = (token: Token): TwingOperator | null => { const isBinary = (token: Token): { associativity: TwingOperator["associativity"]; expressionFactory: TwingOperator["expressionFactory"]; name: TwingOperator["name"]; precedence: TwingOperator["precedence"]; } | { expressionFactory: null; name: "is" | "is not"; precedence: TwingOperator["precedence"]; } | null => { if (token.value === "is" || token.value === "is not") { return { expressionFactory: null, name: token.value, precedence: 100 }; } return (token.test("OPERATOR") && binaryOperatorsRegister.get(token.value)) || null; return (token.test("OPERATOR") && binaryOperatorsRegister.get(token.value)) || null; }; }; Loading Loading @@ -1020,31 +1037,30 @@ export const createParser = ( let expression = getPrimary(stream); let expression = getPrimary(stream); let token = stream.current; let token = stream.current; let operator: TwingOperator | null = null; let operator: ReturnType<typeof isBinary> = null; if ((token.value === "is not") || (token.value === "is")) { while (((operator = isBinary(token)) !== null && operator.precedence >= precedence)) { stream.next(); stream.next(); if (token.value === "is not") { if (operator.expressionFactory === null) { expression = parseNotTestExpression(stream, expression); } else { expression = parseTestExpression(stream, expression); expression = parseTestExpression(stream, expression); if (operator.name === "is not") { const {line, column} = stream.current; expression = createNotNode(expression, line, column); } } } } else { else { while (((operator = isBinary(token)) !== null) && operator.precedence >= precedence) { stream.next(); const {expressionFactory} = operator; const {expressionFactory} = operator; const operand = parseExpression(stream, operator.associativity === "LEFT" ? operator.precedence + 1 : operator.precedence, true); const operand = parseExpression(stream, operator.associativity === "LEFT" ? operator.precedence + 1 : operator.precedence, true); expression = expressionFactory([expression, operand], token.line, token.column); expression = expressionFactory([expression, operand], token.line, token.column); } token = stream.current; token = stream.current; } } } if (precedence === 0) { if (precedence === 0) { return parseConditionalExpression(stream, expression); return parseConditionalExpression(stream, expression); Loading Loading @@ -1233,12 +1249,6 @@ export const createParser = ( return createWrapperNode(targets, line, column); return createWrapperNode(targets, line, column); }; }; const parseNotTestExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode): TwingBaseExpressionNode => { const {line, column} = stream.current; return createNotNode(parseTestExpression(stream, node), line, column); }; const parsePostfixExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode, prefixToken: Token): TwingBaseExpressionNode => { const parsePostfixExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode, prefixToken: Token): TwingBaseExpressionNode => { while (true) { while (true) { let token = stream.current; let token = stream.current; Loading test/tests/integration/tags/if/index.ts +2 −1 Original line number Original line Diff line number Diff line Loading @@ -2,3 +2,4 @@ import "./basic"; import "./expression"; import "./expression"; import "./expression_as_boolean"; import "./expression_as_boolean"; import "./on-hash"; import "./on-hash"; import "./with-test-as-binary-expression-operand"; test/tests/integration/tags/if/with-test-as-binary-expression-operand.ts 0 → 100644 +16 −0 Original line number Original line Diff line number Diff line import {runTest} from "../../TestBase"; runTest({ description: '"if" tag with a test as binary expression operand', templates: { "index.twig": ` {% if a is defined and b %}true{% else %}false{% endif %} ` }, expectation: ` true`, context: Promise.resolve({ a: true, b: true }) }); Loading
src/lib/parser.ts +32 −22 Original line number Original line Diff line number Diff line Loading @@ -653,7 +653,24 @@ export const createParser = ( return Object.keys(traits).length > 0 return Object.keys(traits).length > 0 }; }; const isBinary = (token: Token): TwingOperator | null => { const isBinary = (token: Token): { associativity: TwingOperator["associativity"]; expressionFactory: TwingOperator["expressionFactory"]; name: TwingOperator["name"]; precedence: TwingOperator["precedence"]; } | { expressionFactory: null; name: "is" | "is not"; precedence: TwingOperator["precedence"]; } | null => { if (token.value === "is" || token.value === "is not") { return { expressionFactory: null, name: token.value, precedence: 100 }; } return (token.test("OPERATOR") && binaryOperatorsRegister.get(token.value)) || null; return (token.test("OPERATOR") && binaryOperatorsRegister.get(token.value)) || null; }; }; Loading Loading @@ -1020,31 +1037,30 @@ export const createParser = ( let expression = getPrimary(stream); let expression = getPrimary(stream); let token = stream.current; let token = stream.current; let operator: TwingOperator | null = null; let operator: ReturnType<typeof isBinary> = null; if ((token.value === "is not") || (token.value === "is")) { while (((operator = isBinary(token)) !== null && operator.precedence >= precedence)) { stream.next(); stream.next(); if (token.value === "is not") { if (operator.expressionFactory === null) { expression = parseNotTestExpression(stream, expression); } else { expression = parseTestExpression(stream, expression); expression = parseTestExpression(stream, expression); if (operator.name === "is not") { const {line, column} = stream.current; expression = createNotNode(expression, line, column); } } } } else { else { while (((operator = isBinary(token)) !== null) && operator.precedence >= precedence) { stream.next(); const {expressionFactory} = operator; const {expressionFactory} = operator; const operand = parseExpression(stream, operator.associativity === "LEFT" ? operator.precedence + 1 : operator.precedence, true); const operand = parseExpression(stream, operator.associativity === "LEFT" ? operator.precedence + 1 : operator.precedence, true); expression = expressionFactory([expression, operand], token.line, token.column); expression = expressionFactory([expression, operand], token.line, token.column); } token = stream.current; token = stream.current; } } } if (precedence === 0) { if (precedence === 0) { return parseConditionalExpression(stream, expression); return parseConditionalExpression(stream, expression); Loading Loading @@ -1233,12 +1249,6 @@ export const createParser = ( return createWrapperNode(targets, line, column); return createWrapperNode(targets, line, column); }; }; const parseNotTestExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode): TwingBaseExpressionNode => { const {line, column} = stream.current; return createNotNode(parseTestExpression(stream, node), line, column); }; const parsePostfixExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode, prefixToken: Token): TwingBaseExpressionNode => { const parsePostfixExpression = (stream: TwingTokenStream, node: TwingBaseExpressionNode, prefixToken: Token): TwingBaseExpressionNode => { while (true) { while (true) { let token = stream.current; let token = stream.current; Loading
test/tests/integration/tags/if/index.ts +2 −1 Original line number Original line Diff line number Diff line Loading @@ -2,3 +2,4 @@ import "./basic"; import "./expression"; import "./expression"; import "./expression_as_boolean"; import "./expression_as_boolean"; import "./on-hash"; import "./on-hash"; import "./with-test-as-binary-expression-operand";
test/tests/integration/tags/if/with-test-as-binary-expression-operand.ts 0 → 100644 +16 −0 Original line number Original line Diff line number Diff line import {runTest} from "../../TestBase"; runTest({ description: '"if" tag with a test as binary expression operand', templates: { "index.twig": ` {% if a is defined and b %}true{% else %}false{% endif %} ` }, expectation: ` true`, context: Promise.resolve({ a: true, b: true }) });