in packages/jinja/src/runtime.ts [548:656]
private evaluateBinaryExpression(node: BinaryExpression, environment: Environment): AnyRuntimeValue {
const left = this.evaluate(node.left, environment);
// Logical operators
// NOTE: Short-circuiting is handled by the `evaluate` function
switch (node.operator.value) {
case "and":
return left.__bool__().value ? this.evaluate(node.right, environment) : left;
case "or":
return left.__bool__().value ? left : this.evaluate(node.right, environment);
}
// Equality operators
const right = this.evaluate(node.right, environment);
switch (node.operator.value) {
case "==":
return new BooleanValue(left.value == right.value);
case "!=":
return new BooleanValue(left.value != right.value);
}
if (left instanceof UndefinedValue || right instanceof UndefinedValue) {
if (right instanceof UndefinedValue && ["in", "not in"].includes(node.operator.value)) {
// Special case: `anything in undefined` is `false` and `anything not in undefined` is `true`
return new BooleanValue(node.operator.value === "not in");
}
throw new Error(`Cannot perform operation ${node.operator.value} on undefined values`);
} else if (left instanceof NullValue || right instanceof NullValue) {
throw new Error("Cannot perform operation on null values");
} else if (node.operator.value === "~") {
// toString and concatenation
return new StringValue(left.value.toString() + right.value.toString());
} else if (
(left instanceof IntegerValue || left instanceof FloatValue) &&
(right instanceof IntegerValue || right instanceof FloatValue)
) {
// Evaulate pure numeric operations with binary operators.
const a = left.value,
b = right.value;
switch (node.operator.value) {
// Arithmetic operators
case "+":
case "-":
case "*": {
const res = node.operator.value === "+" ? a + b : node.operator.value === "-" ? a - b : a * b;
const isFloat = left instanceof FloatValue || right instanceof FloatValue;
return isFloat ? new FloatValue(res) : new IntegerValue(res);
}
case "/":
return new FloatValue(a / b);
case "%": {
const rem = a % b;
const isFloat = left instanceof FloatValue || right instanceof FloatValue;
return isFloat ? new FloatValue(rem) : new IntegerValue(rem);
}
// Comparison operators
case "<":
return new BooleanValue(a < b);
case ">":
return new BooleanValue(a > b);
case ">=":
return new BooleanValue(a >= b);
case "<=":
return new BooleanValue(a <= b);
}
} else if (left instanceof ArrayValue && right instanceof ArrayValue) {
// Evaluate array operands with binary operator.
switch (node.operator.value) {
case "+":
return new ArrayValue(left.value.concat(right.value));
}
} else if (right instanceof ArrayValue) {
const member = right.value.find((x) => x.value === left.value) !== undefined;
switch (node.operator.value) {
case "in":
return new BooleanValue(member);
case "not in":
return new BooleanValue(!member);
}
}
if (left instanceof StringValue || right instanceof StringValue) {
// Support string concatenation as long as at least one operand is a string
switch (node.operator.value) {
case "+":
return new StringValue(left.value.toString() + right.value.toString());
}
}
if (left instanceof StringValue && right instanceof StringValue) {
switch (node.operator.value) {
case "in":
return new BooleanValue(right.value.includes(left.value));
case "not in":
return new BooleanValue(!right.value.includes(left.value));
}
}
if (left instanceof StringValue && right instanceof ObjectValue) {
switch (node.operator.value) {
case "in":
return new BooleanValue(right.value.has(left.value));
case "not in":
return new BooleanValue(!right.value.has(left.value));
}
}
throw new SyntaxError(`Unknown operator "${node.operator.value}" between ${left.type} and ${right.type}`);
}