async function evaluateGroupExpression(expr, input, environment) {
        var result = {};
        var groups = {};
        var reduce = input && input.tupleStream ? true : false;
        // group the input sequence by 'key' expression
        if (!Array.isArray(input)) {
            input = createSequence(input);
        }
        // if the array is empty, add an undefined entry to enable literal JSON object to be generated
        if (input.length === 0) {
            input.push(undefined);
        }

        for(var itemIndex = 0; itemIndex < input.length; itemIndex++) {
            var item = input[itemIndex];
            var env = reduce ? createFrameFromTuple(environment, item) : environment;
            for(var pairIndex = 0; pairIndex < expr.lhs.length; pairIndex++) {
                var pair = expr.lhs[pairIndex];
                var key = await evaluate(pair[0], reduce ? item['@'] : item, env);
                // key has to be a string
                if (typeof  key !== 'string' && key !== undefined) {
                    throw {
                        code: "T1003",
                        stack: (new Error()).stack,
                        position: expr.position,
                        value: key
                    };
                }

                if (key !== undefined) {
                    var entry = {data: item, exprIndex: pairIndex};
                    if (groups.hasOwnProperty(key)) {
                        // a value already exists in this slot
                        if(groups[key].exprIndex !== pairIndex) {
                            // this key has been generated by another expression in this group
                            // when multiple key expressions evaluate to the same key, then error D1009 must be thrown
                            throw {
                                code: "D1009",
                                stack: (new Error()).stack,
                                position: expr.position,
                                value: key
                            };
                        }

                        // append it as an array
                        groups[key].data = fn.append(groups[key].data, item);
                    } else {
                        groups[key] = entry;
                    }
                }
            }
        }

        // iterate over the groups to evaluate the 'value' expression
        let generators = await Promise.all(Object.keys(groups).map(async (key, idx) => {
            let entry = groups[key];
            var context = entry.data;
            var env = environment;
            if (reduce) {
                var tuple = reduceTupleStream(entry.data);
                context = tuple['@'];
                delete tuple['@'];
                env = createFrameFromTuple(environment, tuple);
            }
            environment.isParallelCall = idx > 0
            return [key, await evaluate(expr.lhs[entry.exprIndex][1], context, env)];
        }));

        for (let generator of generators) {
            var [key, value] = await generator;
            if(typeof value !== 'undefined') {
                result[key] = value;
            }
        }

        return result;
    }
Evaluate group expression against input data
@param {Object} expr - JSONata expression
@param {Object} input - Input data to evaluate against
@param {Object} environment - Environment
@returns {{}} Evaluated input data