async function evaluate(expr, input, environment) {
        var result;

        var entryCallback = environment.lookup(Symbol.for('jsonata.__evaluate_entry'));
        if(entryCallback) {
            await entryCallback(expr, input, environment);
        }

        switch (expr.type) {
            case 'path':
                result = await evaluatePath(expr, input, environment);
                break;
            case 'binary':
                result = await evaluateBinary(expr, input, environment);
                break;
            case 'unary':
                result = await evaluateUnary(expr, input, environment);
                break;
            case 'name':
                result = evaluateName(expr, input, environment);
                break;
            case 'string':
            case 'number':
            case 'value':
                result = evaluateLiteral(expr, input, environment);
                break;
            case 'wildcard':
                result = evaluateWildcard(expr, input, environment);
                break;
            case 'descendant':
                result = evaluateDescendants(expr, input, environment);
                break;
            case 'parent':
                result = environment.lookup(expr.slot.label);
                break;
            case 'condition':
                result = await evaluateCondition(expr, input, environment);
                break;
            case 'block':
                result = await evaluateBlock(expr, input, environment);
                break;
            case 'bind':
                result = await evaluateBindExpression(expr, input, environment);
                break;
            case 'regex':
                result = evaluateRegex(expr, input, environment);
                break;
            case 'function':
                result = await evaluateFunction(expr, input, environment);
                break;
            case 'variable':
                result = evaluateVariable(expr, input, environment);
                break;
            case 'lambda':
                result = evaluateLambda(expr, input, environment);
                break;
            case 'partial':
                result = await evaluatePartialApplication(expr, input, environment);
                break;
            case 'apply':
                result = await evaluateApplyExpression(expr, input, environment);
                break;
            case 'transform':
                result = evaluateTransformExpression(expr, input, environment);
                break;
        }

        if (Object.prototype.hasOwnProperty.call(expr, 'predicate')) {
            for(var ii = 0; ii < expr.predicate.length; ii++) {
                result = await evaluateFilter(expr.predicate[ii].expr, result, environment);
            }
        }

        if (expr.type !== 'path' && Object.prototype.hasOwnProperty.call(expr, 'group')) {
            result = await evaluateGroupExpression(expr.group, result, environment);
        }

        var exitCallback = environment.lookup(Symbol.for('jsonata.__evaluate_exit'));
        if(exitCallback) {
            await exitCallback(expr, input, environment, result);
        }

        if(result && isSequence(result) && !result.tupleStream) {
            if(expr.keepArray) {
                result.keepSingleton = true;
            }
            if(result.length === 0) {
                result = undefined;
            } else if(result.length === 1) {
                result =  result.keepSingleton ? result : result[0];
            }

        }

        return result;
    }
Evaluate 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