execute method
Executes the statement within the given ctx and returns the result as a JinjaValue.
Implementation
@override
JinjaValue execute(Context ctx) {
final l = left.execute(ctx);
// Short-circuit logic
if (op.value == 'and') return l.asBool ? right.execute(ctx) : l;
if (op.value == 'or') {
return l.asBool ? l : right.execute(ctx);
}
final r = right.execute(ctx);
switch (op.value) {
case '+':
if (l.isString || r.isString) return _concat(l, r);
if (l.isNumeric && r.isNumeric) {
if (l is JinjaFloat || r is JinjaFloat) {
return JinjaFloat(l.asDouble + r.asDouble);
}
return JinjaInteger(l.asInt + r.asInt);
}
if (l.isList && r.isList) {
return JinjaList([...l.asList, ...r.asList]);
}
throw Exception(
'Invalid operand types for +: ${l.typeName}, ${r.typeName}',
);
case '-':
if (l.isNumeric && r.isNumeric) {
if (l is JinjaFloat || r is JinjaFloat) {
return JinjaFloat(l.asDouble - r.asDouble);
}
return JinjaInteger(l.asInt - r.asInt);
}
throw Exception('Invalid operand types for -');
case '*':
if (l.isNumeric && r.isNumeric) {
if (l is JinjaFloat || r is JinjaFloat) {
return JinjaFloat(l.asDouble * r.asDouble);
}
return JinjaInteger(l.asInt * r.asInt);
}
// String repeat? `~` is concat. `*` is repeat in Python.
if (l is JinjaStringValue && r.isNumeric) {
return deriveRendered(
JinjaStringValue(
JinjaString([
for (var i = 0; i < r.asInt; i++) ...l.value.parts,
], isSafe: l.isSafe),
),
[l],
);
}
if (l is JinjaInteger && r is JinjaStringValue) {
return deriveRendered(
JinjaStringValue(
JinjaString([
for (var i = 0; i < l.value; i++) ...r.value.parts,
], isSafe: r.isSafe),
),
[r],
);
}
throw Exception('Invalid operand types for *');
case '/':
return JinjaFloat(
l.asDouble / r.asDouble,
); // Always float division in Jinja
case '//':
return JinjaInteger((l.asDouble / r.asDouble).floor()); // Floor div
case '%':
if (l.isNumeric && r.isNumeric) {
final res = l.asDouble % r.asDouble;
if (l is JinjaInteger && r is JinjaInteger) {
return JinjaInteger(res.toInt());
}
return JinjaFloat(res);
}
throw Exception('Invalid operand types for %');
case '**':
if (l.isNumeric && r.isNumeric) {
final res = math.pow(l.asDouble, r.asDouble);
if (l is JinjaInteger &&
r is JinjaInteger &&
res == res.toInt().toDouble()) {
return JinjaInteger(res.toInt());
}
return JinjaFloat(res.toDouble());
}
throw Exception('Invalid operand types for **');
case '==':
return JinjaBoolean(l == r);
case '!=':
return JinjaBoolean(l != r);
case '<':
return JinjaBoolean(_compare(l, r) < 0);
case '>':
return JinjaBoolean(_compare(l, r) > 0);
case '<=':
return JinjaBoolean(_compare(l, r) <= 0);
case '>=':
return JinjaBoolean(_compare(l, r) >= 0);
case '~': // concat
return _concat(l, r);
case 'in':
// r must be container
if (r is JinjaList) {
return JinjaBoolean(r.items.any((e) => e == l));
}
if (r is JinjaTuple) {
return JinjaBoolean(r.items.any((e) => e == l));
}
if (r is JinjaMap) {
return JinjaBoolean(r.asJinjaMap.containsKey(l));
}
if (r is JinjaStringValue) {
return JinjaBoolean(r.toString().contains(l.toString()));
}
return const JinjaBoolean(false);
case 'not in':
// negate in
if (r is JinjaList) {
return JinjaBoolean(!r.items.any((e) => e == l));
}
if (r is JinjaTuple) {
return JinjaBoolean(!r.items.any((e) => e == l));
}
if (r is JinjaMap) {
return JinjaBoolean(!r.items.containsKey(l));
}
if (r is JinjaStringValue) {
return JinjaBoolean(!r.toString().contains(l.toString()));
}
return const JinjaBoolean(true);
}
throw Exception('Unknown operator ${op.value}');
}