execute method

  1. @override
JinjaValue execute(
  1. Context ctx
)
override

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}');
}