<commit_before>package org.jliquid.liqp.tags;

import java.util.List;
import org.jliquid.liqp.nodes.BlockNode;
import org.jliquid.liqp.nodes.LNode;

import java.util.HashMap;
import java.util.Map;

class For extends Tag {

    private static final String OFFSET = "offset";
    private static final String LIMIT = "limit";
    private static final String CONTINUE = "continue";

    /*
     * forloop.length      # => length of the entire for loop
     * forloop.index       # => index of the current iteration
     * forloop.index0      # => index of the current iteration (zero based)
     * forloop.rindex      # => how many items are still left?
     * forloop.rindex0     # => how many items are still left? (zero based)
     * forloop.first       # => is this the first iteration?
     * forloop.last        # => is this the last iteration?
     */
    private static final String FORLOOP = "forloop";
    private static final String LENGTH = "length";
    private static final String INDEX = "index";
    private static final String INDEX0 = "index0";
    private static final String RINDEX = "rindex";
    private static final String RINDEX0 = "rindex0";
    private static final String FIRST = "first";
    private static final String LAST = "last";
    private static final String NAME = "name";

    /*
     * For loop
     */
    @Override
    public Object render(Map<String, Object> context, LNode... nodes) {

        // The first node in the array denotes whether this is a for-tag
        // over an array, `for item in array ...`, or a for-tag over a
        // range, `for i in (4..item.length)`.
        boolean array = super.asBoolean(nodes[0].render(context));

        String id = super.asString(nodes[1].render(context));

        context.put(FORLOOP, new HashMap<String, Object>());

        Object rendered = array ? renderArray(id, context, nodes) : renderRange(id, context, nodes);

        context.remove(FORLOOP);

        return rendered;
    }

    private Object renderArray(String id, Map<String, Object> context, LNode... tokens) {

        StringBuilder builder = new StringBuilder();

        // attributes start from index 5
        Map<String, Integer> attributes = getAttributes(5, context, tokens);

        int offset = attributes.get(OFFSET);
        int limit = attributes.get(LIMIT);

        Object renderedObj = tokens[2].render(context);
        Object[] array;
        if(renderedObj instanceof Map){
            array = ((Map)(renderedObj)).entrySet().toArray();
        }else{
            array = super.asArray(renderedObj);
        }

        LNode block = tokens[3];
        LNode blockIfEmptyOrNull = tokens[4];
<commit_msg>Fix missing 'forloop' context object for nested for loops<commit_after>package org.jliquid.liqp.tags;

import java.util.List;
import org.jliquid.liqp.nodes.BlockNode;
import org.jliquid.liqp.nodes.LNode;

import java.util.HashMap;
import java.util.Map;

class For extends Tag {

    private static final String OFFSET = "offset";
    private static final String LIMIT = "limit";
    private static final String CONTINUE = "continue";

    /*
     * forloop.length      # => length of the entire for loop
     * forloop.index       # => index of the current iteration
     * forloop.index0      # => index of the current iteration (zero based)
     * forloop.rindex      # => how many items are still left?
     * forloop.rindex0     # => how many items are still left? (zero based)
     * forloop.first       # => is this the first iteration?
     * forloop.last        # => is this the last iteration?
     */
    private static final String FORLOOP = "forloop";
    private static final String LENGTH = "length";
    private static final String INDEX = "index";
    private static final String INDEX0 = "index0";
    private static final String RINDEX = "rindex";
    private static final String RINDEX0 = "rindex0";
    private static final String FIRST = "first";
    private static final String LAST = "last";
    private static final String NAME = "name";

    /*
     * For loop
     */
    @Override
    public Object render(Map<String, Object> context, LNode... nodes) {

        // The first node in the array denotes whether this is a for-tag
        // over an array, `for item in array ...`, or a for-tag over a
        // range, `for i in (4..item.length)`.
        boolean array = super.asBoolean(nodes[0].render(context));

        String id = super.asString(nodes[1].render(context));

        //store existing for loop context if it exists, will be null otherwise. We need this for nested for loops.
        Object originalForLoopContext = context.get(FORLOOP);

        //put new forloop context for this loop
        context.put(FORLOOP, new HashMap<String, Object>());

        Object rendered = array ? renderArray(id, context, nodes) : renderRange(id, context, nodes);

        //remove current forloop context, put back outer/parent forloop context if it exists
        context.remove(FORLOOP);
        if(originalForLoopContext != null) context.put(FORLOOP, originalForLoopContext);

        return rendered;
    }

    private Object renderArray(String id, Map<String, Object> context, LNode... tokens) {

        StringBuilder builder = new StringBuilder();

        // attributes start from index 5
        Map<String, Integer> attributes = getAttributes(5, context, tokens);

        int offset = attributes.get(OFFSET);
        int limit = attributes.get(LIMIT);

        Object renderedObj = tokens[2].render(context);
        Object[] array;
        if(renderedObj instanceof Map){
            array = ((Map)(renderedObj)).entrySet().toArray();
        }else{
            array = super.asArray(renderedObj);
        }

        LNode block = tokens[3];
        LNode blockIfEmptyOrNull = tokens[4];

{'fraction_non_alphanumeric': 0.10416666666666667, 'fraction_numerical': 0.0048209366391184574, 'mean_word_length': 2.6488693467336684, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7650035', 'n_tokens_mistral': 1611, 'n_tokens_neox': 1559, 'n_words': 547}