CDSoft / pp

PP - Generic preprocessor (with pandoc in mind) - macros, literate programming, diagrams, scripts...
http://cdelord.fr/pp
GNU General Public License v3.0
252 stars 21 forks source link

Calling macro B with arguments of macro A as arguments? #29

Closed bpj closed 7 years ago

bpj commented 7 years ago

Is it possible to have one macro call another macro with arguments of the outer macro as arguments of the inner macro, something like !def(A)(!B(!1)) !A(C), where C is passed as argument to !B? It seems this causes an infinite loop. What am I doing wrong? I'm guessing that !1 in !B(!1) doesn't expand into the first argument to !A but tries to expand into the first argument to !B. Compare LaTeX where \newcommand{\StrEmph}[1]{\emph{\textbf{#1}}}\StrEmph{C} gives you a bold italic C.

CDSoft commented 7 years ago

This looks like a bug. The outer call adds the binding ("!1", "C") in the environment and the inner call adds ("!1", "!1"), hiding the first one and looping for ever when it is evaluated. I need to rethink the evaluation model of the preprocessor.

There is another problem. Because of lazy evaluation, "!1" is passed unevaluated to B. When B is evaluated pp can not know if !1 "belongs" to B or not (it assumes it does). Maybe each symbol in the environment should also have an evaluation context (closure).

bpj commented 7 years ago

Would it be possible or easier to treat just the argument variables (those with a digit as name) specially? It would seem to make sense.

tajmone commented 7 years ago

I've done some testing on this example:

!def(A)(!B(!1))
!def(B)(Hello !1???)
!def(C)(world)

!A(!C)

... and tried to see if using !raw, !pp or !rawdef in various places could solve the problem, but it seems there is no way around it currently.

Because of lazy evaluation, "!1" is passed unevaluated to B. When B is evaluated pp can not know if !1 "belongs" to B or not (it assumes it does).

Why are parameters being passed on with lazy evaluation? I seem to remember that this already popped up in a past issue.

I've tried using !pp to force evaluation before carrying on, but the problem remains:

!def(A)(!B(!pp(!1)))

... a macro that forces expansion of the parameter (bypassing lazy eval) might solve the problem. But definitely there are some issue with complex nested macros.

CDSoft commented 7 years ago

The problem is that the value of the parameter contains the name of the parameter itself (it's not the same parameter but they have the same name and live in the same environment) => infinite loop when evaluating the parameter.

The evaluation must be lazy otherwise conditional macros such as ifdef, ifeq... would evaluate all branches and may have unwanted side-effects.

pp is used to preprocess anything. But it is executed when needed.

All the macros preprocess their arguments (if needed) but only if the macro is being evaluated. If a macro is used to evaluate its argument but never gets evaluated, its argument won't be evaluated.

The real problem is that the stack structure is lost and parameters are handled as any other kind of global definitions...

bpj commented 7 years ago

So the solution would be to make the parameter "macros" special -- scoped -- but everything else lazy? Would that be feasible within reasonable limits (complexity, time, effort, ...)? I fully understand if the answer is no; after all there is an easy workaround in most cases: cut and paste.

CDSoft commented 7 years ago

Sorry for being late, I've a family and a job IRL ;-)

I think I have fixed this bug. When a user defined macro is called, values that look like macro arguments are replaced by their current definition (if any). This fix doesn't break the current pp tests as well as this example:

!def(A)(!B(!1))
!def(B)(B got !1)
!A(42)

but I need further tests before releasing it.

It's not lazy any more (only for macro arguments) but still pure since no unnecessary side effect is triggered (if \1 contains side effects (e.g. scripts), there code will replace \1 but they are lazily evaluated).

CDSoft commented 7 years ago

The version 1.9.3 has been pushed to github. Binaries are on cdsoft.fr.

bpj commented 7 years ago

Thanks! That will make the things I do much cleaner!

I'm closing this now.

bpj commented 7 years ago

@CDSoft said:

Binaries are on cdsoft.fr.

It seems it's still the old Linux binary there.

I built it myself though, and it works great. Thanks again!

CDSoft commented 7 years ago

It was a bug in the Makefile that generates binaries for cdsoft.fr. That's fixed. Thanks.

tajmone commented 7 years ago

pp-win-1.9.3.7z (and current latest release) only contains the docs, no actual binary file inside the archive!

ie: inside the archive there is the /doc/ folder but not the /app/ folder.

CDSoft commented 7 years ago

due to a stupid copy/paste error... the windows binary 1.9.3 is now in the archive.

tajmone commented 7 years ago

Thanks! It works marvelous ...

now the nested macros test works:

!def(A)(!B(!1))
!def(B)(Hello !1???)
!def(C)(world)

!A(!C)

... and correctly emits:

Hello world???