execute method
Executes the statement within the given ctx and returns the result as a JinjaValue.
Implementation
@override
@override
JinjaValue execute(Context ctx) {
// Handle slice expressions first, as they are a special case of computed access
if (computed && property is SliceExpression) {
final slice = property as SliceExpression;
final startVal = slice.start?.execute(ctx);
final stopVal = slice.stop?.execute(ctx);
final stepVal = slice.step?.execute(ctx);
final obj = object.execute(ctx);
int? start = (startVal != null && !startVal.isNone)
? startVal.asInt
: null;
int? stop = (stopVal != null && !stopVal.isNone) ? stopVal.asInt : null;
int step = (stepVal != null && !stepVal.isNone) ? stepVal.asInt : 1;
if (obj is JinjaList || obj is JinjaTuple) {
final List<JinjaValue> itemsList = obj is JinjaList
? obj.items
: (obj as JinjaTuple).items;
int len = itemsList.length;
start ??= step > 0 ? 0 : len - 1;
stop ??= step > 0 ? len : -1;
if (start < 0) start += len;
if (stop < 0 && slice.stop != null) {
stop += len; // Only adjust if explicit negative
}
// Clamp
if (step > 0) {
if (start < 0) start = 0;
if (stop > len) stop = len;
} else {
if (start >= len) start = len - 1;
if (stop < -1) stop = -1;
}
final res = <JinjaValue>[];
if (step > 0) {
for (int i = start; i < stop; i += step) {
if (i >= 0 && i < len) res.add(itemsList[i]);
}
} else if (step < 0) {
for (int i = start; i > stop; i += step) {
if (i >= 0 && i < len) res.add(itemsList[i]);
}
}
return obj is JinjaList ? JinjaList(res) : JinjaTuple(res);
}
if (obj is JinjaStringValue) {
final s = obj.value;
int len = s.length;
start ??= step > 0 ? 0 : len - 1;
stop ??= step > 0 ? len : -1;
if (start < 0) start += len;
if (stop < 0 && slice.stop != null) stop += len;
// Clamp
if (step > 0) {
if (start < 0) start = 0;
if (stop > len) stop = len;
} else {
if (start >= len) start = len - 1;
if (stop < -1) stop = -1;
}
if (step == 1) {
if (start >= stop) return const JinjaStringValue(JinjaString([]));
return JinjaStringValue(s.substring(start, stop));
}
final parts = <JinjaStringPart>[];
if (step > 0) {
for (int i = start; i < stop; i += step) {
if (i >= 0 && i < len) parts.addAll(s[i].parts);
}
} else if (step < 0) {
for (int i = start; i > stop; i += step) {
if (i >= 0 && i < len) parts.addAll(s[i].parts);
}
}
return JinjaStringValue(JinjaString(parts));
}
return const JinjaUndefined();
}
if (computed || property is IntegerLiteral) {
// Bracket notation: obj[expr], or an index after a dot: obj.0
final obj = object.execute(ctx);
final prop = property.execute(ctx);
if (!computed && prop.asInt < 0) {
throw Exception('Static member property cannot be negative');
}
if (obj is JinjaMap) {
// Bracket access: check keys FIRST
if (obj.items.containsKey(prop)) {
return obj.items[prop]!;
}
// Special case for 'env': NO methods allowed even via bracket access if it's the env object
if (obj.isEnv) {
return const JinjaUndefined();
}
// Then check if the key is a string that matches a method
if (prop is JinjaStringValue) {
final method = resolveMember(obj, prop.toString());
if (method != null) return method;
}
return const JinjaUndefined();
} else if (obj is JinjaList || obj is JinjaTuple) {
// Corrected: use prop.asInt directly for indexing
if (prop.isNumeric) {
final items = obj is JinjaList
? obj.items
: (obj as JinjaTuple).items;
int idx = prop.asInt;
if (idx < 0) idx += items.length;
if (idx >= 0 && idx < items.length) {
return items[idx];
}
}
return const JinjaUndefined();
}
// String index?
if (obj is JinjaStringValue) {
if (prop is JinjaInteger) {
int idx = prop.value;
String s = obj.value.toString();
if (idx < 0) idx += s.length;
if (idx >= 0 && idx < s.length) {
return JinjaStringValue.fromString(s[idx]);
}
}
}
// For other types, bracket access behaves like dot access for methods if prop is string
if (prop is JinjaStringValue) {
final method = resolveMember(obj, prop.toString());
if (method != null) return method;
}
return const JinjaUndefined();
} else {
// Dot notation: obj.prop
// prop IS Identifier
if (property is! Identifier) {
throw Exception('Member property must be identifier for dot access');
}
final propName = (property as Identifier).name;
final obj = object.execute(ctx);
// Priority: 1. Attributes/Methods, 2. Map keys, 3. Special cases (length)
final method = resolveMember(obj, propName);
if (method != null) return method;
if (obj is JinjaMap) {
final key = JinjaStringValue.fromString(propName);
if (obj.asJinjaMap.containsKey(key)) {
return obj.asJinjaMap[key]!;
}
}
// 3. Fallback: length for sequences (if not a method)
if (propName == 'length') {
if (obj is JinjaList) return JinjaInteger(obj.items.length);
if (obj is JinjaTuple) return JinjaInteger(obj.items.length);
if (obj is JinjaStringValue) return JinjaInteger(obj.value.length);
}
// Special Case: llama.cpp tests expect 'env' to NOT have methods
if (obj is JinjaMap && obj.name == 'env') {
return const JinjaUndefined();
}
return const JinjaUndefined();
}
}