> Tadziu's opinion matters to me; I read every bit of troff > he posts here because I normally learn something. :-)
Wow. I feel flattered. But lest I come across as a dinosaur born yesterday and stubbornly resisting progress, let me explain my position a bit better. [Warning: aimless rambling ahead.] My opinion is that we're trying to fix a problem that doesn't actually need fixing. The syntax of expressions and conditionals isn't what's keeping new users from using groff. As Peter has already pointed out, the entire working model of roff presents a much greater mental hurdle than the one given by an idiosyncratic expression syntax. Will someone already tripping over the syntax of conditionals have the perseverance to fathom the rest of roff? (Not to mention the whole structural-vs.-presentational-markup propaganda that casts roff as unmodern and backward, instead of simply the low-level engine for higher-level functions implemented on top of it in terms of macros.) > The input language is the main criticism of roff today > (by users who use LaTeX or GUI tools). Srsly? No, the input language of roff is not being criticized by these people for the simple fact that most have never even heard of roff, let alone seen its input. Go ask around. (And, by the way, perl has no shortage of people using it despite being a write-only language.) > Where would LaTeX be without the many users that do step > beyond it into TeX to write extra packages that do all kinds > of different things? Exactly. And TeX is on par with roff for idiosyncrasy of language features. [Is "\advance \x by \y" more "modern" than ".nr x +\ny"?] That does not deter those users. And my guess is neither would roff's expression syntax. And to see things from a little self-centered perspective: I use groff because it's a programmable text formatter that despite its peculiarities is based on a relatively simple conceptual model that I can understand and that I can usually coax into doing what I want. But otherwise I have no stake in it. My job doesn't depend on it and I'm not making money from it. The number of other users has no bearing on the usefulness of groff for me, and the enhancements we're discussing are nothing that I would profit from. (I do have a few ideas regarding some typesetting aspects that I would like to experiment with. But I probably wouldn't choose groff as my experimental platform because I feel it's already too cumbersome (and it's written in C++). Instead, I'd put my money where my mouth is and start over from scratch. Sure, I'd have to reinvent 90% of what's necessary to accomplish the basic functions. But I'd also be getting rid of 90% of the cruft that has accumulated over the years.) > I'd like to read `.nr i 3+4*6' and know if that's 27 or 42 > without having to know the value of a `new expression syntax' > register; [...] That statement is what I had in mind when I said I believe we shouldn't change the interpretation of numeric expressions. Of course you could argue that .nr i x 3+4*6 (or some other syntax) is visually distinct from .nr i 3+4*6 and so the first can rightfully be expected to give 27 whereas the second should yield 42, but instead I think it's a dreadful opportunity for causing great confusion. Let me say again that I'm not per se against a "modern" syntax. What I am against is this terrible clash of two incompatible traditions in the same package. I think the python people did the right thing when they realized some decisions of the past were suboptimal in the light of recent developments -- make a clean cut and start over without the ballast. Python 2 and python 3 can coexist as separate programs. Why shouldn't groff and groff2? > However, I wonder if a preprocessor could give this new syntax > à la Ratfor. Ah. One of the "cuisinarts of programming" that are "great for making quiche". :-D > Could something work for troff in a similar way, introducing > its own variables to track state during expression evaluation? > I don't see why not. I'm always amazed at the lengths to which people are willing to go... Although I might consider this an interesting programming exercise, I have to ask: isn't implementing yet another whole new language on top of groff much more complicated than implementing groff's typesetting functions in an existing language like python? That would immediately give access to all of python's data types, functions, and control structures. -=(*)=- Nevertheless, having said all that, and after asking again the provocative question whether this is really necessary and whether it offers something new which couldn't be done with the existing facilities (laziness doesn't count, unless the only existing mechanism is inordinately cumbersome), if it's still decided to implement a new syntax, I'd like to make some suggestions. For the treatment of expressions, the main points have already been made: 1. Map booleans to numbers. In roff, positive = true, zero or negative = false. (This is already how it's handled.) 2. Make string comparison operators and the built-in condition tests return appropriate numbers. (Logical operators already accept and return appropriate numbers.) That will allow having only one class of "expression" which can be used by .if and .nr alike (allowing conditional tests to be assigned to number registers without an additional .if). My vote for the syntax of these "extended expressions" is to simply enclose them in brackets: .nr i [3+4*6] .if ['\\$1'abc':\\n[.$]=0] stuff I believe this feels much more natural than the other suggestions offered so far. Regarding the if/then/else syntax: although I admit that Ralph's example is easier to read than the original mgm fragment, that's partly because the original wasn't very good. (Eight-space indents?! C'mon! "Everyone knows" that the optimum is two.) Here's how I might have written it: (but see below) .\" Copy to .de NS .sp .ie !''\\$2' \{.\" 2nd arg flag set: use 1st arg as-is . ds let*str \\$1\} .el \{.\" empty/no second arg . ie \\n[.$]>0 \{.\" argument present . ie !\w'\\$1' \{.\" has zero/negative width: use default . ds let*str \\*[Letns!\\*[Letnsdef]]\} . el \{.\" non-empty arg . ie d Letns!\\$1 \{.\" is reference to predefined item . ds let*str \\*[Letns!\\$1]\} . el \{.\" not in list: use as special attribute . ds let*str \\*[Letns!copy](\\$1)\\*[Letns!to]\}\}\} . el \{.\" no args: use default . ds let*str \\*[Letns!\\*[Letnsdef]]\}\} .ne 2 .nf \\*[let*str] .. Still, Ralph's ".} else {" construct gets my unrestricted opposition. Could there be anything more at odds with the general roff syntax? Yes, braces are familiar to C programmers. And yes, the original roff multiline block syntax used braces. But that's because it lived outside the normal request/macro mechanism, and the easiest way to achieve that was using the existing escape mechanism with one-character delimiters. (And I can imagine it already felt like an afterthought at the time.) But the new syntax is supposed to mesh with roff's request syntax[*], and the Bourne-shell/Fortran-inspired model is much more agreeable with that: (I also find it much easier to read.) .\" Copy to .de NS .sp .if ['\\$2' != ''] .then . ds let*str \\$1 .elseif [\\n[.$]] .then . if [width('\\$1') == 0] .then . ds let*str \\*[Letns!\\*[Letnsdef]] . elseif [defined(Letns!\\$1)] .then . ds let*str \\*[Letns!\\$1] . else . ds let*str \\*[Letns!copy](\\$1)\\*[Letns!to] . endif .else . ds let*str \\*[Letns!\\*[Letnsdef]] .endif .ne 2 .nf \\*[let*str] .. [*] Note, however, that I'm not convinced that a request-level block mechanism would work in all circumstances, due to roff's line-by-line processing. I'm convinced that the original roff inventors did deliberate on how best to implement multiline conditionals, and chose their syntax out of necessity. I got bitten once by block-closing braces sitting on a line by themselves instead of at the end of the last line of the block, and something similar can happen here, too. As an aside at the end, I'm surprised that no-one has yet pointed out the obvious: we're working with a macro processor, where the natural block structuring element is not a tacked-on brace structure, but the *macro*! Multiline conditionals are completely unnecessary: .\" Copy to .de NS .sp .ie \\n[.$]=0 .ds let*str \\*[Letns!\\*[Letnsdef]] .el .NS1 \\$@ .ne 2 .nf \\*[let*str] .. .de NS1 .ie !''\\$2' .ds let*str \\$1 .el .NS2 \\$@ .. .de NS2 .ie !\w'\\$1' .ds let*str \\*[Letns!\\*[Letnsdef]] .el .NS3 \\$@ .. .de NS3 .ie d Letns!\\$1 .ds let*str \\*[Letns!\\$1] .el .ds let*str \\*[Letns!copy](\\$1)\\*[Letns!to] .. which is much more pleasing in terms of roff aesthetics. And another example, since something similar had come up: .de inlist? .ie \\n(.$<2 .nr result 0 .el .inlist1 \\$@ .. .de inlist1 .ie '\\$1'\\$2' .nr result 1 .el .inlist2 \\$@ .. .de inlist2 .ds _1 \\$1 .shift 2 .inlist? \\*(_1 \\$@ .. .ds fruit apple banana lemon grape .ds myfruit lemon .inlist? \*[myfruit] \*[fruit] .ie \n[result] Yes, ``\*[myfruit]'' is in ``\*[fruit]''. .el No, ``\*[myfruit]'' is not in ``\*[fruit]''. Programming roff is a bit like programming in assembler: complex expressions are built up piece by piece using simple instructions, and temporary registers are a useful aid, not something that must be avoided at all costs.