aradi / fypp

Python powered Fortran preprocessor
http://fypp.readthedocs.io
BSD 2-Clause "Simplified" License
180 stars 30 forks source link

block/call macros: Is there a way to define a fypp custom macro in a procedural manner? #11

Closed 14NGiestas closed 3 years ago

14NGiestas commented 3 years ago

Is more of a question: I don't know if i'm doing this right, but I want to define a fypp macro pass as a argument inside the block/call macro as a argument and then call it there. context: I want to create a modern interface to BLAS and LAPACK (here it is the repo) in the same fashion as blas95 and lapack95 from MKL and netlib's lapack95, using fypp macros.

Is there a way to do that or a better way to achieve the same results? I made a sample code here

PS: I've tried using Python's replace but ain't a good solution

14NGiestas commented 3 years ago

Found a solution/workaround: I can define a callable macro with NAME, TYPE and KIND arguments and them pass it as a argument to the other macro, however I would like to somehow use it in block/call macros as well but i think this will do for now. Thank you for creating this really handful tool.

#:def gemm(NAME,TYPE,KIND)
subroutine mfi_${NAME}$(a, b, c, transa, transb, alpha, beta)
@:parameter(${TYPE}$, wp=${KIND}$)
@:args(${TYPE}$, in, a(:,:), b(:,:))
@:args(${TYPE}$, inout, c(:,:))
@:optional(character, transa, transb)
@:optional(${TYPE}$,  alpha, beta)
@:localvars(integer, m, n, k, lda, ldb, ldc)
@:defaults(transa='N', transb='N', alpha=1, beta=0)
    lda = max(1,size(a,1))
    ldb = max(1,size(b,1))
    ldc = max(1,size(c,1))
    m = size(c,1)
    n = size(c,2)
    if (local_transa == 'N' .or. local_transa == 'n') then
        k = size(a,2)
    else
        k = size(a,1)
    end if
    call ${NAME}$(local_transa,local_transb,m,n,k,local_alpha,a,lda,b,ldb,local_beta,c,ldc)
end subroutine
#:enddef
$:mfi_implement('?gemm', DEFAULT_KINDS, gemm)
aradi commented 3 years ago

Sorry, for the very late answer, I was quite distracted recently. In case, it is still relevant:

Your original idea does not work, because the argument of the macro (the body of the block directive) is evaluated first, and only the result of this evaluation will be passed to the actual macro. The evaluation (text representation) of a function defintion is the empty string. The only thing you could do is to call your 'internal' function within the block argument, and pass the result of that call, as in

#:def dummyfunc(txt)
DUMMY { ${txt}$ }
#:enddef

#:block dummyfunc
#:def blockfunc(arg)
BLOCKFUNC { ${arg}$ }
#:enddef
#:block blockfunc
Hello
#:endblock blockfunc
#:endblock dummyfunc

By the way, it is possible pass a name of a macro as argument to an other macro and execute it then, but the macro has to be defined outside of the block argument:

#:def macro1(arg)
MACRO1: ${arg}$
#:enddef

#:def macro2(arg)
MACRO2: ${arg}$
#:enddef

#:def choose(arg, macro)
$:macro(arg)
#:enddef

#:call choose(macro=macro1)
Hello!
#:endcall