


//
// Lambda - dynamically dispatched lambdas
//

record Lambda[In,Out] (
    body : SharedPointer[LambdaBody[In,Out]]
);


// main constructor

[In, Out, T | CallableWithSignature?(T, In, Out)]
alias overload Lambda[In, Out](x:T) {
    return Lambda[In,Out](new(LambdaBody[In,Out](x)));
}

private CallableWithSignature?(T, In, Out) {
    var CodePtr = Type(makeCodePointer(call, T, ..unpack(In)));
    return Out == CodePointerReturnTypes(CodePtr);
}

[In,..Out]
private CodePointerReturnTypes(static CodePointer[In,[..Out]]) = [..Out];


// redefine constructors shadowed by the primary constructor

[In,Out]
alias overload Lambda[In,Out](body : SharedPointer[LambdaBody[In,Out]]) --> returned:Lambda[In,Out] {
    returned.body <-- body;
}

[In,Out]
overload Lambda[In,Out](x : Lambda[In,Out]) --> returned:Lambda[In,Out] {
    returned.body <-- x.body;
}


// makeLambda

[T]
makeLambda(forward x:T, ..In) {
    var CodePtr = Type(makeCodePointer(call, T, ..In));
    var Out = CodePointerReturnTypes(CodePtr);
    return Lambda[[..In],Out](x);
}


// call operator

[..I, ..O]
overload call(x:Lambda[[..I], [..O]], forward ..args:I) : ..O {
    return forward ..x.body^.code(x.body^.obj^, ..args);
}



//
// LambdaBody
//

private record LambdaBody[In, Out] (
    obj : Pointer[Opaque],
    code : CodePointer[[Opaque, ..unpack(In)], Out],
    destructor : CodePointer[[Opaque],[]],
);

[..I, ..O, T]
alias overload LambdaBody[[..I],[..O]](x:T) --> returned:LambdaBody[[..I],[..O]] {
    var codePtr = makeCodePointer(call, T, ..I);
    var destructor = makeCodePointer(destroy, T);
    returned.code = CodePointer[[Opaque, ..I], [..O]](codePtr);
    returned.destructor = CodePointer[[Opaque],[]](destructor);

    var ptr = allocateRawMemoryAligned(T, 1, TypeAlignment(T));
    try {
        ptr^ <-- x;
    }
    catch (e) {
        freeRawMemoryAligned(ptr);
        throw e;
    }
    returned.obj = Pointer[Opaque](ptr);
}

[In, Out]
overload destroy(x:LambdaBody[In, Out]) {
    x.destructor(x.obj^);
    freeRawMemoryAligned(x.obj);
}



//
// helper procs for closure conversion
//

packMultiValuedFreeVar(..x) = [..x];

packMultiValuedFreeVarByRef(..x) =
    Tuple(..mapValues(addressOf, ..x));

unpackMultiValuedFreeVar(x) = forward ..unpackRef(x);

unpackMultiValuedFreeVarAndDereference(x) =
    forward ..mapValues(dereference, ..unpack(x));
