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

Are there logical operators? #50

Closed robinrosenstock closed 6 years ago

robinrosenstock commented 6 years ago

Say I have two different symbols: S1 and S2. If S1 or S2 is defined, then output TEXT.

How do I do that, while writing TEXT only once? I know I can use this:

!ifdef(S1)(TEXT)
!ifdef(S2)(TEXT)

But if TEXT gets very large or there are many symbols, then something like this would be much better: !ifdef(S1|S2)(TEXT)

Similar, this question is valid for other operators as well, notably the AND operator. Same problem with when using !ifeq. Or is there some other way to achieving the above example? At least I don't get it.

tajmone commented 6 years ago

If TEXT gets very large, the simple solution would be to define it in a temporary symbol:

!def( LONG_TEXT )
~~~~~~~~~~~~~~~~~~~
Some very long text
~~~~~~~~~~~~~~~~~~~

!ifdef(S1)( !LONG_TEXT )
!ifdef(S2)( !LONG_TEXT )

As for the many symbols situations, I agree that some logical operators would help.

Unless some new features I've slipped by my attention, currently the only ways to achive what you are suggesting are by means of nesting into the TEXT_IF_NOT_DEFINED parameter:

!ifdef(SYMBOL)(TEXT_IF_DEFINED)(TEXT_IF_NOT_DEFINED)

Example:

!ifdef( S1 )( TEXT )
~~~~~~~~~~~~~~~~~~~
!ifdef( S2 )( TEXT )
~~~~~~~~~~~~~~~~~~~

... which will only carry out the second !ifdef test if the first one failed.

robinrosenstock commented 6 years ago

Thanks @tajmone for reminding me on the !def macro. But this does not tell how to write TEXT only once and your second example is not really correct, or is it (because you do not use TEXT_IF_NOT_DEFINED)?

And even with the usage of TEXT_IF_NOT_DEFINED and nesting several !ifdef, I don't know if these logical connectives (with !ifdef like TEXT_IF_DEFINED, TEXT_IF_NOT_DEFINED ) are functional complete

tajmone commented 6 years ago

your second example is not really correct, or is it (because you do not use TEXT_IF_NOT_DEFINED)?

It's correct, I'm just using fenced delimiters for the last parameter (TEXT_IF_NOT_DEFINED) to make it more readable — so, the whole !ifdef( S2 )( TEXT ) part is inside the TEXT_IF_NOT_DEFINED delimiter.

And even with the usage of TEXT_IF_NOT_DEFINED and nesting several !ifdef, I don't know if these logical connectives (with !ifdef like TEXT_IF_DEFINED, TEXT_IF_NOT_DEFINED ) are functional complete

... chances are that it is possible to implement what you are looking for, but only at the cost of using rather complex nested macros — eg: using !add to emulate non-zero truth states, and using nested conditions to manipulate symbols with numeric values. Possibly not a very elegant solution, but if you build some custom macros to handle more complex operations, you might actually end up with some macros representing logical operations.

Basically, the building blocks at hand are:

If you can manage to build a custom macro that simulates the logical operations required, using the above building blocks, then you'll probably end up with a custom macro that can do the job without nesting too many built-in macros in the source document.

Otherwise, another solution (pending any chances that in the future logical operators might be implement) would be to redirect to some external script these operations.

tajmone commented 6 years ago

Probably the best solution, to cover all the cases of the links you provided, will currently be to build a custom macro that passes on its parameters to an external script:

!mycustomIfdef(S1|S2)(TEXT)

... something like:

!def(mycustomIfdef)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!sh
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
myScript.sh !1 !2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

or:

!def(mycustomIfdef)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!python
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
myPyScript.py !1 !2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(just indicatively, not sure about the actual parameters, if they need to be quoted, in case the actual expandend PP symbols have blank spaces)

... and the script then explodes the firs macro parameter (with "|" delmiter) to a string array, and is able to determine what was defined (non-empty str) and what wasn't (empty), and then decide if echoing or not the TEXT symbol (which it received already expanded by PP, if it was a macro, else it was literal anyway).

An external script (eg Python) should provide you all the logical operator you need.

robinrosenstock commented 6 years ago

@tajmone thanks for explaining the use of fenced delimiters in this regard, that wasn't clear to me the whole time. I don't like the idea of nested command and to build some custom command with other commands. Yeah, well, with scripting languages like bash or python this is of course feasible. I had just though to incorporate this functionality into pp in its core so to say, that you can use the or-operator in every hard coded built-in macro.

CDSoft commented 6 years ago

You can also define a new symbol S1orS2 once and use it everywhere you need it:

!ifdef(S1)(!def(S1orS2))
!ifdef(S2)(!def(S1orS2))

!ifdef(S1orS2)
~~~~~
TEXT
~~~~~
robinrosenstock commented 6 years ago

Clever solution @CDSoft. I can use that. Any chance some locigal operators can get in the core or do you think it is not usefull?

CDSoft commented 6 years ago

My feeling is that it's not useful but we can imagine a more generic !ifmacro that takes an expression and computes a boolean result:

!if( defined(S1) || defined(S2) ) ( TEXT1 ) (TEXT2 )
robinrosenstock commented 6 years ago

I would really like a more generic !if macro.

tajmone commented 6 years ago

I too think that it would be a good idea. Confining logical operators to a single specific macro dedicated to the task will prevent the problems that would araise if these operators were to apply to all macros — ie: the various "|" and "&" operators would end up clashing with string characters.

What type of expressions could the !if macro support, beside the defined() example you provided?

The obvious ones that come to mind are:

If symbols can be interpreted as numbers and used for comparison, this would allow a good degree of freedom in using counters and states in branching blocks.

CDSoft commented 6 years ago

I think about two macros:

expr is an arithmetic or boolean expression with operators like and, or, not, defined, +, *, <, >, ... I think literal operators for boolean expressions is better because the C negation would be ambiguous (same character than macro call).

tajmone commented 6 years ago

C negation would be ambiguous (same character than macro call).

Instead of "!" you could use "~" (on WikiPedia they are listed as synomims, or at least they are in many contexts).

But literals like "not", "and", etc. would be just fine.

bpj commented 6 years ago

The obvious ones that come to mind are:

undefined( SYMBOL )
gt( S1, S2) --- S1 greater than S2?
gte( S1, S2) --- S1 greater than or equal S2?
lt( S1, S2) --- S1 less than S2?

These should rather be gt(S1)(S2) etc. IMO, and you forgot less-than-or-equal! (FWIW I think >= and <= should be ge and le, no doubt because of my being a Perl programmer! :-) Perl distinguishes numerical == != < > <= >= from lexical eq ne lt gt le ge which is a rather useful distinction, although it's probably risk free to assume numeric when neither parameter contains anything else but [-.0-9], although the lexical versions could be called !leq and so on. Another useful distinction is between !not(x) where undefined, empty string and zero are all false and empty() where empty string and undefined are true but zero false.

bpj commented 6 years ago

Instead of "!" you could use "~" (on WikiPedia they are listed as synomims, or at least they are in many contexts).

There is also <> used for != by some languages.

With !macrochars any chars can be made ambiguous, but double-punctuation operators like == != !! && || would be reasonably safe. Users will simply have to have the self-constraint not to make < > !macrochars, although it seems reasonable to make them !macroargs chars in some situations. Perhaps if so they would simply be disabled as !macroargs chars in eval contexts. Certainly << >> as lt/gt feels SE&W! Relatedly the ternary TEST ? IFTRUE : IFFALSE becomes TEST ?? IFTRUE !! IFFALSE in Perl 6 which looks rather good.

This said macro-style !eq(a)(b) !not(a) !and(a) !or(a) !lt(a)(b) etc although more to type would be the safest, since they couldn't possibly clash with macro syntax!

tajmone commented 6 years ago

With !macrochars any chars can be made ambiguous

If I understood correctly, the !if macro would be a special case, and the way its parameters are interpreted here is out of the ordinary PP context. So whatever operators will be implemented should not conflict with !macrochars definitions.

bpj commented 6 years ago

But you will want to compare macro values: !if(!x < !y) and soon also !if(not !x and !y < !z) and there you have a precedence problem, so you should need parentheses to disambiguate: !if((not !x) and (!y < !z)). You might as well have !if(!and[!not{!x}][!lt{!y}{!z}]) which is perfectly unambiguous and consistent with the rest of pp syntax. Note that you already have multiple delimiters to improve disambiguation by humans!

I only said that it would be "safest", not that it necessarily be best, in particular for humans used to ordinary math/C notation, but WTH we already have !add(VARNAME)[(INCREMENT)] so why not continue down that road? Note that these operators could be used more easily to compare text pieces without need for quotes, and pp doesn't quote text otherwise, so the macro-like logic operators can be used inside an !if[n]eq argument without need to wrap the expression in !eval(...) and then wrap text in the expression in quotes, adding another layer of syntax.

Or even better use the macro-like operators for logic on text and C-like operators for logic on numbers. In either case you will want to get the values to compare from macro values, otherwise you can do the logic in your head and hardcode the result.

As for !! vs. ~ note that the latter also is used to mean "matches" as in =~ and !~ which are used for regular expression matching in shell, Perl, Vimscript and Ruby and perhaps elsewhere. I'm not saying that pp needs regex matching just yet (!bash(perl -E'say q{!x} =~ /f/') does the trick for me! :-) but on the rare occasions I see bitwise negation in Perl I have to think extra to associate ~ with negation, while !! would fire the negation neurons for most people and blend in nicely with == && ||. Once you realize that ! is taken in pp it will be easy to learn that !! is simple negation.

I admit to being somewhat biassed in favor of the Perl 6 ternary syntax though. I designed a DSL for word generation (as for RPG or conlangs) not long ago where ? meant "zero or one" like in regex, : meant "one or two" and ! meant "not", so I eagerly stole the Perl 6 ternary syntax! :-)

tajmone commented 6 years ago

From @CDSoft proposal:

!if( defined(S1) || defined(S2) ) ( TEXT1 ) (TEXT2 )

... I understood that the syntax would be for SYMBOLS — as in symbols only. Like for !def( SYMBOL ), or !ifdef( SYMBOL ) which takes a symbol name as parameter, not a macro. At least I was assuming so because the thread developed around the initial !ifdef() example.

If this is going to be the case, probably statements like !if(!x < !y) wouldn't be valid.

Usually built-in macros either accept SYMBOLS NAMES or MACROS/LITERAL TEXT as parameters, but not both. I'm not sure if there are some constraints about this which might also apply to the case at hand; but my guessing is that a parameter must be able to distinguish between literal text and a SYMBOL reference.

CDSoft commented 6 years ago

In !if(EXPR)(...), EXPR will be preprocessed before executing the macro, as with most of other macros. EXPR can contain other variables (or macros).

!def(x)(1) !def(y)(2)
!if(!x == 1 or !y == 2)(this text will be printed)(not this one)
  ==> this evaluates (1==1 or 2==2)

using classic precedence rules... Parenthesis can also be used in expression (and must be well-balanced).

Expressions can contain:

Using macros to implement expressions would be easier but I find it less readable. add has got a special semantic. It updates the variable and returns nothing. I used it to implement counters. Evaluating expressions would have no side effects (unless you use macros with side effect).

CDSoft commented 6 years ago

Check version 2.2. It has a simple expression parser and 3 additional macros: !if, !eval and !defined.

e.g.:

!if( !defined(S1) || !defined(S2) ) ( TEXT1 ) (TEXT2 )

See https://github.com/CDSoft/pp#expressions

robinrosenstock commented 6 years ago

Thanks @CDSoft, and it's even already in the readme! I will test it some days later, so I will close this issue. When there are some bugs or something else missing, I will open a new issue.