Search code examples
c#lempec#

Generating Strings with LeMP


I'm trying to use LeMP to generate some C# bindings for a C++ library, and as part of this I need to generate a string that combines together some arguments from a LeMP macro to use in the DllImport EntryPoint value. Looking at the docs, it seems like a combination of concatId and stringify should do the job, but I can't get it to work. Here's a slightly simplified version of the code in question:

define TypedIndexer2D($CONTAINER_TYPE, $T1, $T2)
{
    replace(MethodName => concatId(Buffer, $CONTAINER_TYPE, GetExpr_, $T1, $T2));
    replace(CFunction => concatId(buffer_, $CONTAINER_TYPE, _getexpr__, $T1, $T2));

    [DllImport(Constants.LibName, EntryPoint = CFunction)]
public static extern IntPtr MethodName(IntPtr obj, IntPtr x, IntPtr y);
}

TypedIndexer2D(Int, Var, Var);

This emits the following:

[DllImport(Constants.LibName, EntryPoint = buffer_Int_getexpr__VarVar)] 
public static extern IntPtr BufferIntGetExpr_VarVar(IntPtr obj, IntPtr x, IntPtr y);

However, I need this:

[DllImport(Constants.LibName, EntryPoint = "buffer_Int_getexpr__VarVar")] 
public static extern IntPtr BufferIntGetExpr_VarVar(IntPtr obj, IntPtr x, IntPtr y);

(note the quoted EntryPoint).

I had thought that it would be something like the following:

replace(CFunction => stringify(concatId(buffer_, $CONTAINER_TYPE, _getexpr__, $T1, $T2)));

However that just emits the following:

[DllImport(Constants.LibName, EntryPoint = "concatId(buffer_, Int, _getexpr__, Var, Var)")]

How can I persuade LeMP to generate the string that I need here? Thanks!


Solution

  • The answer is indeed to run stringify on the output of concatId, but there's a trick to it.

    The difficulty is caused by execution order. Macros ordinarily run "outside-in", outermost macro first, in contrast to normal functions that run "inside-out". Therefore

    stringify(concatId(Tea, ring, Cot, ton));
    

    produces "concatId(Tea, ring, Cot, ton)". There isn't a super elegant way to reverse the order yet - in custom define macros you can use a [ProcessChildrenBefore] attribute, but this doesn't let you to modify the existing behavior of stringify. Here's a technique that works:

    replacePP(xx => concatId(Tea, ring, Cot, ton)) { stringify(xx); }
    // Output: "TearingCotton";
    

    in contrast to normal replace, replacePP preprocesses the match and replacement expressions, thus concatId happens before stringify. Applying this solution to your TypedIndexer2D, we get

    define TypedIndexer2D($CONTAINER_TYPE, $T1, $T2)
    {
        replace(MethodName => concatId(Buffer, $CONTAINER_TYPE, GetExpr_, $T1, $T2));
        replacePP(CFunction => concatId(buffer_, $CONTAINER_TYPE, _getexpr__, $T1, $T2));
    
        [DllImport(Constants.LibName, EntryPoint = stringify(CFunction))]
        public static extern IntPtr MethodName(IntPtr obj, IntPtr x, IntPtr y);
    }