Commit e1a9315c authored by Eric MORAND's avatar Eric MORAND
Browse files

Merge branch 'issue-602' into 'main'

Resolve issue #602 - Unexpected token "operator" of value "and" ("end of statement block" expected)

Closes #602

See merge request !597
parents 52b1b657 b79b79ff
Loading
Loading
Loading
Loading
Loading
+32 −22
Original line number Original line Diff line number Diff line
@@ -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;
    };
    };


@@ -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);
@@ -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;
+2 −1
Original line number Original line Diff line number Diff line
@@ -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";
+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
    })
});