YODLBUILTINS(7)

๐ŸŽ€ yodlbuiltins ๐ŸŽ€

Yodl builtin functions(7) โ€” Your Own Document Language

okay so. this is the manual name and it's Yodl builtin functions, section 7, from the Your Own Document Language package. buckle up bestie ๐Ÿ’–


๐Ÿ’– NAME ๐Ÿ’–

yodlbuiltins โ€” the Builtins for the Yodl converters. that's the whole name, short and iconic, periodt.


โœจ SYNOPSIS โœจ

This manual page lists the standard builtins of the Yodl package. literally its whole job.


๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

The list below shows the builtins that the Yodl converters define and that you can actually use inside Yodl documents, fr fr. If you want the full description, go read the Yodl user guide โ€” it ships with the Yodl package, no cap. ๐Ÿ“š

And the list below? It's every builtin in the package, in alphabetical order. every single one. we're not skipping anybody. ๐Ÿ‘‘

๐ŸŒธ Yodlโ€™s builtin commands ๐ŸŒธ

As mentioned before, YODL's input is made of text and commands. YODL supports a bunch of built-in commands, and you can either use them straight in a YODL document or use them to build a macro package. both valid, both slay. ๐Ÿ’…

And listen โ€” don't panic if this section reads too technical. That is literally exactly why YODL ships macro packages, to make a documentation writer's life easier. like, see chapter [MACROPACKAGE], which describes a macro package for YODL. ๐Ÿซถ

Most built-in functions and macros expand the info they get the way they get it. i.e., the info itself is only evaluated when it finally gets inserted into an output medium (usually a file). BUT โ€” and this is the plot twist โ€” some builtin functions evaluate their argument(s) once the argument is processed. those ones are:

  • The ERROR built-in function (see section [ERROR]);
  • The EVAL built-in function (see section [EVAL]);
  • The FPUTS built-in function (see section [FPUTS]);
  • The INTERNALINDEX built-in function (see section [INTERNALINDEX]);
  • The PUSHSUBST built-in function (see section [PUSHSUBST]);
  • The TYPEOUT built-in function (see section [TYPEOUT]);
  • The UPPERCASE built-in function (see section [UPPERCASE]);
  • The WARNING built-in function (see section [WARNING]);
  • The XXSUBST internal use only built-in function; all other built-in functions will not evaluate their arguments. see the mentioned functions for the details, and especially EVAL() for a description of this whole evaluation process.

โญ ADDTOCOUNTER โญ

The ADDTOCOUNTER function adds a given value to a counter. it wants two arguments: the counter name, and an additive expression defining the value to add. heads up tho โ€” the counter must already have been created with DEFINECOUNTER first. no counter, no add. ๐Ÿ™ˆ

The additive expression may not contain blank spaces, and it may use + and - operators. its operands can be either integral numeric values or names of (defined) counters. the resulting value can totally be negative โ€” and in that case, a value gets subtracted from the destination counter. yes, adding can subtract. math is wild. ๐Ÿ˜ญ

For example, if one and two are counters, then

ADDTOCOUNTER(one)(-two)\//    subtracts twoโ€™s value from one
ADDTOCOUNTER(one)(two+two)\// adds 2 x twoโ€™s value to one

See further section [COUNTERS].


๐Ÿงš ADDTOSYMBOL ๐Ÿงš

Since Yodl version 2.00, symbols can be manipulated. to add text to an existing symbol you've got the builtin ADDTOSYMBOL. it expects two parameter lists: the symbol's name, and the text to add to the symbol. the symbol must have been created earlier using DEFINECOUNTER (see section [DEFINECOUNTER]).

Here's the cute part: the macro's second argument is not evaluated while ADDTOSYMBOL is processed. which means it's super easy to add the text of another symbol, or the expansion of a macro, to a symbol value. e.g.,

ADDTOSYMBOL(one)(SYMBOLVALUE(two)XXnl())

This adds the text of symbol two, followed by a new line, to the contents of symbol one only when symbol one is evaluated โ€” not when ADDTOSYMBOL is evaluated. timing is everything bestie. โฐ

Example:

ADDTOSYMBOL(LOCATION)(this is appended to LOCATION)

๐ŸŽ€ ATEXIT ๐ŸŽ€

ATEXIT expects one argument. that argument gets appended to the output file. note that this text is subject to character table translations etc.. so like, it's not a free-for-all.

An example using this function: a document in the LaTeX typesetting language requires \end{document} to occur at the end of the document. to automatically append that string to the output file, you can use this spec:

ATEXIT(NOEXPAND(\end{document}))

Several ATEXIT lists can be defined. they get appended to the output file in reverse order of specification โ€” i.e., the first ATEXIT list is appended to the output file last. so in general the ATEXIT text should be specified when a 'matching' starting command is sent to the output file, like so:

COMMENT(Start the LaTeX document.)
NOEXPAND(\begin{document})

COMMENT(Ensure its proper ending.)
ATEXIT(NOEXPAND(\end{document}))

matching energy. open it, close it, iconic. ๐Ÿซถ


๐Ÿ’œ CHAR ๐Ÿ’œ

The command CHAR takes one argument โ€” a number or a character โ€” and outputs its corresponding ASCII character to the final output file. this one is built for 'emergency situations', where you need to typeset a character even though it may be redefined in the current character table (for a discussion of character tables, see [CHARTABLES]). also, CHAR can be used to dodge Yodl's requirement that open- and close-parentheses must match. loophole queen. ๐Ÿ‘‘

The following arguments may be specified with CHAR (attempted in this order):

  • A decimal number indicating the number of the character in the ascii-table (for example CHAR (41));
  • A plain, single character (for example CHAR (#)).

So, when you're sure you want to send a printable character that is not a closing parenthesis to the output file, you can use the form CHAR (c), c being the character (as in, CHAR (;)). but to send a non-printable character, or a closing parenthesis, to the output file โ€” look up the ASCII number of the character and supply that number as the argument to the CHAR command. don't try to be cute with the parens, bestie, they will betray you. ๐Ÿ’€

Example: the following two statements send an A to the output file.

CHAR(65)
CHAR(A)

The following statement sends a closing parenthesis:

CHAR(41)

Another way to send a string to the output file without expansion by character tables or by macro interpretation is the function NOTRANS (see section [NOTRANS]). and if you want to send a string to the output without macro interpretation but with character table translation, use NOEXPAND (see section [NOEXPAND]). three different vibes, three different tools. ๐Ÿงฐ


๐ŸŒˆ CHDIR ๐ŸŒˆ

The command CHDIR takes one argument: a directory to change to. it's implemented to simplify working with includefile (see includefile in yodlmacros(7)).

As a demonstration, consider this fragment:

includefile(subdir/onefile)
includefile(subdir/anotherfile)
includefile(subdir/yetanotherfile)

This fragment can be changed to:

CHDIR(subdir)
includefile(onefile)
includefile(anotherfile)
includefile(yetanotherfile)
CHDIR(..)

look at that glow-up. โœจ

The current directory, as given to CHDIR, only affects how includefile searches for its files. that's it, that's the whole scope.

Note that this example assumes the current working directory is a member of Yodl's include-path specification (cf., Yodl's --include option). so if it's not, this whole demo is giving nothing. check your --include first. ๐Ÿซ 

๐Ÿ“ COMMENT ๐Ÿ“

The COMMENT function defines one parameter list. the text passed as argument is treated as a comment โ€” i.e., it's ignored, it is not copied to the final output file. silent. invisible. iconic. ๐Ÿคซ

As an alternative to (short) COMMENT, the triplet \// can be used. it starts an 'end of line' comment, ignoring all characters on a line starting at \// up to the first non-blank character encountered on the next line. and if the next line's first non-blank characters are \//, then that begins another end of line comment, which will therefore also be skipped. they chain. ๐Ÿ’…

To actually write \// or, using the current font: // in a yodl-converted document, write, e.g., tt(\)tt(//) or, using the current font: nop(/)// in a yodl-source file, and write \CHAR(/)/ in verb sections.

Example:

Hello world\// producess Hello world, skipping the rest
    \// this line is completely ignored
   s\// at this point Hello worlds has been produced.

๐Ÿ‘‘ COUNTERVALUE ๐Ÿ‘‘

COUNTERVALUE's argument expands to the value of a counter. its single argument must contain the name of a counter. and yes, the counter must have been created earlier using the builtin DEFINECOUNTER. same rules as always, bestie. ๐Ÿ“

Example:

The counter has value COUNTERVALUE(MYCOUNTER).

See also section [COUNTERS].


๐Ÿซถ DECWSLEVEL ๐Ÿซถ

DECWSLEVEL requires one (empty) argument. it reduces the current white-space level. the white-space level is typically used in files that only define Yodl macros. when no output should be generated while processing those files, the white-space level can be used to check for exactly that: if the white-space level exceeds zero, a warning is generated if the file produces non-whitespace output. DECWSLEVEL is the builtin you use to reduce the whitespace level following a previous call of INCWSLEVEL. they're a duo. ๐Ÿซ‚

Once the white space level exceeds zero, no output will be generated. white space, therefore, is effectively ignored. and the white space level cannot be reduced to negative values โ€” a warning is issued if that would have happened if it were allowed. it's not allowed. don't try it. ๐Ÿ˜ญ

Example:

INCWSLEVEL()
DEFINESYMBOL(....)
DEFINEMACRO(...)(...)(...)
DECWSLEVEL()

Without the INCWSLEVEL and DECWSLEVEL calls, the above definition would generate four empty lines to the output stream. four! just floating there! ๐Ÿ™ˆ

The INCWSLEVEL and DECWSLEVEL calls may be nested. the best approach is to put an INCWSLEVEL at the first line of a macro-defining Yodl-file, and a matching DECWSLEVEL call at the very last line. open and close, first and last, symmetrical queen. โœจ

โœจ DEFINECHARTABLE โœจ

okay so DEFINECHARTABLE is literally how you define a character translation table. it expects two parameterlists โ€” one for the name of the character table, and one for the character table translations, each on separate lines. the translations are always in this form:

character = quoted-string

now, character is always a value inside single quotes. it can be:

  • a single character,
  • an octal character value,
  • or a hexadecimal character value.

the single character may be prefixed by a \-character (e.g., โ€™\\โ€™). the octal character value must start with a backslash, followed by three octal digits (e.g., โ€™\045โ€™). the hexadecimal character value starts with 0x, followed by two hexadecimal characters (e.g., โ€™0xbeโ€™). the double quoted string can contain anything โ€” but bestie, it must be on one line โ€” and it may also contain escape-sequences: the standard C escape sequences \a (alert), \b (beep), \f (formfeed), \n (newline), \r (carriage return), \t (tab), and \v (vertical tab) are recognized and automatically converted to their special meanings. starting with Yodl 2.14.0, octal and hexadecimal constants may also be used. e.g., character Y may also be specified using the octal value \131 or the hexadecimal value \x59. any other character following a backslash character (\) defines itself: \\ represents a single backslash character.

example:

DEFINECHARTABLE(demotable)(
    โ€™&โ€™     = "&"
    โ€™\\โ€™    = "\\backslash"
    โ€™\045โ€™  = "oct(45)"
    โ€™0xa4โ€™  = "hex(a4)"
)
โš ๏ธ the builtin function DEFINECHARTABLE does not activate the table. the table is merely defined. to activate the character translation table, use USECHARTABLE. the discussion of character tables is postponed to section [CHARTABLES].

โœจ DEFINECOUNTER โœจ

DEFINECOUNTER creates a new counter. it expects two arguments: the name of the counter and an additive expression whose value is used to initialize the counter.

the additive expression may not contain blank spaces and may use + and - operators. its operands may either be integral numeric values or names of (defined) counters. the resulting value can be negative; in that case, a value is subtracted from the destination counter.

examples:

DEFINECOUNTER(year)(1950)
DEFINECOUNTER(nTimes)(year+12)\// initializes nTimes to 1962

see also section [COUNTERS] and the USECOUNTER and ADDTOCOUNTER builtin functions.


โœจ DEFINEMACRO โœจ

DEFINEMACRO is used to define new macros. this function expects three arguments:

  1. an identifier, being the name of the macro to define. this identifier may only consist of uppercase or lowercase characters. note that it can not contain numbers, nor underscore characters.
  2. a number, stating the number of arguments that the macro will require once itโ€™s used. the number must be in the range 0 to 61.
  3. the text that the macro expands to, once used. this text may contain the strings ARGx, x being 1, 2, etc. at these places the arguments to the macro are pasted in. the numbers that identify the arguments are 1 to 9, then A to Z and finally a to z. this gives a range of 61 expandable arguments, which is enough for all real-life applications.

for example, the following fragment defines a macro bookref, which can be used to typeset a reference to a book. it requires three arguments; say, an author, a title and the name of a publisher:

DEFINEMACRO(bookref)(3)(
    Author(s):           ARG1
    Book title:          ARG2
    Published by:        ARG3
)

such a macro could be used as follows:

bookref(Sobotta/Becher)
       (Atlas der Anatomie des Menschen)
       (Urban und Schwarzenberg, Berlin, 1972)

when called, it would produce the following output:

        Author(s):           Sobotta/Becher
        Book title:          Atlas der Anatomie des Menschen
        Published by:        Urban und Schwarzenberg, Berlin, 1972

while applying a macro, the values of the three arguments are pasted to the places where ARG1, ARG2 etc. occur in the definition.

note the following when defining new macros:

  • the argument containing the name of the new macro, (bookref) in the above example, must occur right after DEFINEMACRO. no spaces are allowed in between. space characters and newlines may however occur following this first argument.

this behavior of the yodl program is similar to the usage of the defined macro: the author information must, enclosed in parentheses, follow right after the bookref identifier. I implemented this feature to improve the distinguishing between macros and real text. e.g., a macro me might be defined, but the text

I like me (but so do you)

still is simple text; the macro me only is activated when a parenthesis immediately follows it.

  • be careful when placing newlines or spaces in the definition of a new macro. e.g., the definition, as given:
DEFINEMACRO(bookref)(3)(
    Author(s):           ARG1
    Book title:          ARG2
    Published by:        ARG3
)

introduces extra newlines at the beginning and ending of the macro, which are copied to the output each time the macro is used. the extra newline occurs, of course, right before the sequence Author(s): and following the evaluation of ARG3. a simple backslash character at the end of the DEFINEMACRO line would prevent the insertion of extra newline characters:

DEFINEMACRO(bookref)(3)(\
    Author(s):           ARG1
    Book title:          ARG2
    Published by:        ARG3
)
  • note that when a macro is used which requires no arguments at all, one empty argument still must be specified. e.g., my macro package (see chapter [MACROPACKAGE]) defines a macro it that starts a bullet item in a list. the macro takes no arguments, but still must be typed as it().

this behavior is consistent: it helps distinguish which identifiers are macros and which are simple text.

  • macro arguments may evaluate to text. when a \ is appended to the macro-argument, or in the default input handling within a non-zero white-space level (see section [INCWSLEVEL]) this may invalidate a subsequent macro call. e.g., the macro
DEFINEMACRO(oops)(1)(
    ARG1
    XXnl()
)

when called as oops(hello world), produces the output:

hello worldXXnl()

to prevent this gluing to arguments to subsequent macros, a single + should be prepended to the macro call:

DEFINEMACRO(oops)(1)(
    ARG1
    +XXnl()
)

see also section [PLUSIDENT] obout the +identifier-sequence.

  • note the preferred layout of macro definitions and macro calls. adhere to this form, to prevent drowning in too many parentheses. in particular:
    • put all elements of the macro definition on one line, except for the macro-expansion itself. each expansion element should be on a line by itself.
    • when calling macros put the macroโ€™s arguments underneath each other. if the macrolists themselves contain macro-calls, put each call again on a line of its own, indenting one tab-position beyond the location of the opening parenthesis of the argument.
    • no continnuation backslashes are required between arguments.
    • with complex calls, indent just the arguments, and put the parentheses in their required of logical locations. example of a complex call:
complex(
    first(
        ARG1
    )(
        ARG2
        +XXnl()
    )
    ARG3
    +nop()
    ARG4
    +XXnl()
)
  • macro expansion proceeds as follows:
    • the arguments are read from the input
    • the contents of the arguments then replace their ARGx references in the macroโ€™s definition (in some exceptional cases, clearly indicated as such when applicable, the arguments themselves are evaluated first, and then these evaluated arguments are used as replacements for their corresponding ARGx references).
    • the now modified macro is read by Yodlโ€™s lexical scanner. this may result in yet another macro expansion, which will then be evaluated recursively.
    • eventually, all expansion is completed (well, should complete, since Yodl doesnโ€™t test for eternal recursion) and scanning of the input continues beyond the original macro call.

for example, assume we have the following two macros:

DEFINEMACRO(First)(1)(
    Hello ARG1
    +XXnl()
)
DEFINEMACRO(Second)(1)(
    First(ARG1)
    First(ARG1)
)

and the following call is issued:

Second(Yodl)

then the following happens:

  • Second(Yodl) is read as encountered.
  • ARG1 in Second is replaced by YODL, and the resulting macro body is sent to the lexical scanner for evaluation: it will see:
First(Yodl)First(Yodl)
  • the first call to First() is now evaluated. this puts (after replacing ARG1 by YODL) the following on the scannerโ€™s input:
Hello Yodl+XXnl()First(Yodl)
  • Hello Yodl contains no macro call, so it is written to the output stream. remains:
+XXnl()First(Yodl)
  • Assume XXnl() is literally just chillin' as a newline (we're writing it as \n here, bestie), so +XXnl() gets swapped out for \n. That hands the lexical scanner this:
\nFirst(Yodl)
  • Now that \n gets written to the output stream, and the scanner is looking at:
First(Yodl)
  • Second call to First() gets evaluated now. Which drops this onto the scanner's input:
Hello Yodl+XXnl()
  • Hello Yodl goes to the output stream. What's left over:
+XXnl()
  • +XXnl() gets replaced by \n. The lexical scanner sees:
\n
  • The newline gets printed and boom ๐Ÿ’… we're done.

DEFINESYMBOL ๐ŸŒธ

DEFINESYMBOL wants two arguments, no more no less. First one is an identifier โ€” that's the name of the symbol you're defining. Second one is the textual value of that symbol. And if that second argument is empty? The symbol still gets defined, it just has an empty value. No drama.

So the old-school way of reading a Yodl symbol as a logical flag? Still totally usable. But letting symbols hold actual textual values makes a bunch of Yodl macros so much simpler, and honestly it's giving โœจupgradeโœจ.

Example:

DEFINESYMBOL(Yodl)(Your own document language)
DEFINESYMBOL(Options)()

DELETECHARTABLE ๐ŸŽ€

DELETECHARTABLE yeets a character table definition that was made by DEFINECHARTABLE. It takes exactly one argument: the name of the character table you want gone.

Careful though โ€” it's an error to try to delete a character table that's currently in use, and it's also an error to try to delete one that doesn't exist. So like, don't do that. ๐Ÿ˜ญ

Example:

DELETECHARTABLE(mytable)

DELETECOUNTER ๐Ÿ’œ

DELETECOUNTER removes a counter definition that was made by DEFINECOUNTER. One argument only: the name of the counter to remove.

If the counter doesn't exist, you get a warning. But ngl, it is not considered an error to try to delete a counter that was never defined in the first place. So it's a soft fail, not a whole breakdown.

Example:

DELETECOUNTER(mycounter)

DELETEMACRO ๐Ÿฆ‹

DELETEMACRO removes a macro definition that was made by DEFINEMACRO. Takes one argument: the macro name to remove.

There's no error condition here (except for syntax errors, obviously): if no macro with a matching name was defined before, nothing happens. No action taken, no complaints. ๐Ÿซถ

For example, the safe way to define a macro is to undefine it first. That way any possible previous definitions get cleared out before you do your thing:

Example:

DELETEMACRO(mymacro)

DELETENOUSERMACRO ๐ŸŒˆ

DELETENOUSERMACRO removes a nousermacro definition. The function expects one argument: the name of the nousermacro identifier that's getting removed from the nousermacro-set.

There's no error condition (except for syntax errors, bestie): if the identifier wasn't stored as a nousermacro, no action is taken.

Example:

DELETENOUSERMACRO(mymacro)

DELETESYMBOL ๐Ÿ“

DELETESYMBOL removes the definition of a symbol variable. It expects one argument, which holds the name of the variable to be deleted.

This macro has no error condition (except, once again, syntax errors): the symbol in question may have been previously defined, but honestly it doesn't have to be.

Example:

DELETESYMBOL(Options)

ERROR ๐Ÿ’€

The ERROR function takes one argument: the text you want displayed to the standard error stream. The current input file and line number get displayed too. And then, after showing you that text, the yodl program aborts with an exit status of 1. Like, full stop, we're done here.

The text you pass to the function gets expanded first. Check the example below.

The ERROR function is an example of a function that evaluates its argument itself.

You'd use this command, for instance, in a macro package when an incorrect macro gets expanded. In my macro package (see chapter [MACROPACKAGE]) the ERROR function is used when the sectioning command chapter() shows up in an article document (in the package, chapter's are only available in book's or report's).

There's an analogous builtin function called WARNING, which also prints a message but doesn't exit (see section [WARNING]).

Example: in the following call, COUNTERVALUE(NTRIES) gets replaced by its actual value:

ERROR(Stopping after COUNTERVALUE(NTRIES) attempts)

EVAL ๐Ÿ”ฅ

The EVAL function takes one argument: the text to be evaluated. This one lets you do an indirect evaluation of Yodl commands. Say you've got a symbol varnam holding the name of a counter variable โ€” then this next bit displays the counter's value, after having incremented it:

EVAL(NOTRANS(USECOUNTER)(SYMBOLVALUE(varnam)))

Here's what EVAL does, step by step:

  • First, NOTRANS(USECOUNTER) gets evaluated, producing USECOUNTER.
  • Next, the open parenthesis is processed, producing the open parenthesis itself.
  • Then SYMBOLVALUE(varnam) gets evaluated, producing the name of a counter, e.g. `counter'.
  • The closing parenthesis is processed, producing the closing parenthesis itself.
  • All of that results in the text:
USECOUNTER(counter)
  • This text gets presented to Yodl's lexical scanner, which results in incrementing the counter and displaying its incremented value.
Caveat: macro arguments themselves are usually not evaluated. So a construction like
USECOUNTER(EVAL(SYMBOLVALUE(varnam)))
fails, because `EVAL(SYMBOLVALUE(varnam))' is not a legal name for a counter. Here the EVAL() call is being used as an argument, and is therefore not expanded.

The distinction is subtle, and it's a consequence of the fact that builtin functions receive unprocessed arguments. Builtin functions may impose certain requirements on their arguments (like USECOUNTER requiring the name of a counter) and those requirements get checked on the arguments as received.

Summarizing, EVAL acts like this:

  • Its argument gets presented to Yodl's lexical scanner.
  • The output produced by processing that argument is then inserted into the input stream in lieu of the original EVAL call.

Most built-in functions do not evaluate their arguments. Like, actually โ€” only ERROR, EVAL, FPUTS, INTERNALINDEX, PUSHSUBST, TYPEOUT, UPPERCASE, WARNING and the internally used XXSUBST functions evaluate their arguments.

Postponing evaluations is what lets you write stuff like:

DEFINESYMBOL(later)(SYMBOLVALUE(earlier))

Eventually โ€” and not at the moment later is defined โ€” a statement like

SYMBOLVALUE(later)

produces the value of earlier at the moment SYMBOLVALUE(later) is processed. With all its complex consequences, this is what you'd expect in most cases. It lets us write general macros that produce output only evaluated when the text of symbols and the values of arguments eventually become available, rather than when the macro is defined.

Decisions like these always lead to questions like "okay but what if I have to define variables using values of other variables?" In those cases you have to use EVAL(). The following example shows the definition of three symbols: one receives an initial value, two returns one's actual value when two's value is displayed, and three, using EVAL(), stores one's initial value.

The example also shows yet another way to suppress macro calls, using the macro nop() which is defined in the all standard conversion types:

DEFINESYMBOL(one)(Oneโ€™s first value)
DEFINESYMBOL(two)(SYMBOLVALUE(one))
EVAL(DEFINESYMBOL+nop()(three)(SYMBOLVALUE(one)))
SETSYMBOL(one)(Oneโ€™s second value)
"SYMBOLVALUE(two)"      COMMENT(displays "Oneโ€™s second value")
"SYMBOLVALUE(three)"    COMMENT(displays "Oneโ€™s first value")

FILENAME ๐Ÿงš

The function FILENAME() produces an absolute path to the currently processed Yodl file. It's not necessarily the canonical path name though, since it may contain current- and parent-path directories.


FPUTS โญ

The function FPUTS expects two arguments: the first argument is information to be appended to a file, and the name of that file is given as the second argument. The first argument gets processed by Yodl before it's appended to the requested filename, so it may contain macro calls. Slay.

For example, the following statement appends a countervalue to the mentioned file:

FPUTS(There have been COUNTERVALUE(attempts) attempts)(/tmp/logfile)

The second argument (the name of the file) is not evaluated โ€” it's used as received.


IFBUILTIN ๐Ÿ‘‘

The IFBUILTIN function tests whether its first argument is the name of a builtin function. If it is, the second argument gets evaluated. If it isn't, the third argument gets evaluated. All three arguments (the variable, the true-list and the false-list) must be present โ€” though the true-list and/or the false-list may be empty.

Example:

IFBUILTIN(IFBUILTIN)(\
    `BUILTINโ€™ is a builtin - function
)(\
    `BUILTINโ€™ is NOT a builtin - function
)

Please note the preferred layout: the first argument immediately follows the function name, then the second argument (the true list) is indented, as is the false list. The layout closely follows the preferred layout of if-else statements of many programming languages. Very demure, very mindful. ๐Ÿ’…


IFCHARTABLE ๐ŸŽ€

The IFCHARTABLE function tests whether its first argument is the name of a character table. The character table doesn't have to be active. If the name is the name of a character table, the second argument gets evaluated; if not, the third argument gets evaluated. All three arguments (the name, the true list and the false list) must be present โ€” though the true list and/or the false list may be empty.

Example:

IFCHARTABLE(standard)(\
    `standardโ€™ is a character tablebuiltin - function
)(\
    `standardโ€™ is NOT a character tablebuiltin - function
)

Ok bestie, the preferred layout, and this is a vibe you're gonna see over and over in this whole section: first argument goes right up against the function name, then the second argument (the true list) is indented, and the false list gets indented the same way. ๐Ÿ’… It's literally the same shape as if-else statements in basically every programming language you've ever met, so if that clicks, this clicks. Periodt.

IFDEF

So IFDEF is your lil detective ๐Ÿ•ต๏ธ โ€” it checks the definition status of whatever you hand it in the first argument. Is it a defined entity? Great, the second argument gets evaluated. Not defined? Then the third argument gets evaluated instead.

All three arguments have to be there โ€” the entity, the true list, and the false list. No skipping! Though heads up: the true list and/or the false list are allowed to be empty. Just gotta be present. ๐Ÿซถ

The true list gets evaluated if that first argument is the name of:

  • a built-in function, or
  • a character table, or
  • a counter, or
  • a no-user-macro symbol, or
  • a symbol, or
  • a user-defined macro, or

Example:

IFDEF(someName)(\
    `someNameโ€™ is a defined entity
)(\
    `someName is not defined.
)

And again, the preferred layout: first argument hugs the function name, second argument (true list) indented, false list indented right along with it. Same energy as the if-else layout everyone uses in every programming language ever. It's giving consistency. โœจ

IFEMPTY

IFEMPTY takes three arguments: a symbol, a true-list, and a false-list. If the symbol is an empty string, IFEMPTY evaluates to the true-list. If it's not? It evaluates to the false-list. Simple as that. ๐Ÿ’–

Important though: this function does not further evaluate its argument. Nope. Its whole reason for existing is mostly to test whether a macro actually received an argument or not. So if what you actually want is to check whether a symbol's value is empty or not, you should be using IFSTREQUAL [IFSTREQUAL] instead โ€” where the first argument is the name of a symbol and the second argument is empty. iykyk. ๐Ÿ™ˆ

Example:

IFEMPTY(something)(\
    `somethingโ€™ is empty...
)(\
    `somethingโ€™ is not an empty string
)

Same deal, different day: you can use IFEMPTY to test whether an argument expands to a non-empty string. Let's do a more elaborate example, because we love a glow-up. ๐ŸŒˆ Say you wanna define a bookref macro that typesets info about an author, a book title, and a publisher. But plot twist โ€” the publisher info might be absent, in which case the macro just typesets unknown:

DEFINEMACRO(bookref)(3)(\
    Author(s):      ARG1
    Title:          ARG2
    Published by:   \
    IFEMPTY(ARG3)
    (\
        Unknown\
    )(\
        ARG3\
    )
)

Using the macro, as in:

bookref(Helmut Leonhardt)
       (Histologie, Zytologie und Microanatomie des Menschen)
       ()

...would now result in the text Unknown sitting right there behind the Published by: line. Chef's kiss. ๐Ÿ‘‘

And yes, once more with feeling โ€” the preferred layout: first argument immediately follows the function name, second argument (the true list) indented, false list indented too. It mirrors the if-else layout of many programming languages, and honestly? It's iconic.

IFEQUAL

IFEQUAL wants four argument lists, bestie. It tests whether its first argument is equal to its second argument. Equal? Then the third argument is evaluated. Not equal? Then the fourth argument is evaluated. All four argument lists must be present โ€” though all of them can be empty, so go wild. ๐Ÿ’…

The first two arguments of IFEQUAL should be integral numeric arguments. To figure out whether those first two arguments are equal, their values get determined:

  • If the argument starts with an integral numerical value, that value is the value of the argument.
  • If the argument is the name of a counter, the counter's value is the value of the argument.
  • If the values of the first two arguments can be determined accordingly, their equality determines whether the true list (when the values are equal) or the false list (when the values are unequal) gets evaluated.
  • Otherwise, IFEQUAL evaluates the false list.

Example:

IFEQUAL(0)()(\
    0 and an empty string are equal
)(\
    0 and an empty string are not equal
)

Ok and, say it with me now, the preferred layout: first argument immediately follows the function name, second argument (the true list) gets indented, false list gets indented. Same shape as if-else in so many programming languages. It's the house style. ๐ŸŽ€

IFGREATER

IFGREATER also expects four argument lists. It tests whether its first argument is greater than its second argument. If so, the third parameter list is evaluated; otherwise, the fourth argument is evaluated. All four must be present โ€” and yeah, all can be empty. โœจ

The first two arguments of IFGREATER should be integral numeric arguments. To work out whether the first two arguments compare, their values get determined:

  • If the argument starts with an integral numerical value, that value is the value of the argument.
  • If the argument is the name of a counter, the counter's value is the value of the argument.
  • If the values of the first two arguments can be determined accordingly, their order relation determines whether the true list (when the first value is greater than the second value) or the false list (when the first value is smaller or equal to the second value) is evaluated.
  • Otherwise, IFGREATER evaluates the false list.

Example:

IFGREATER(counter)(5)(\
    counter exceeds the value 5
)(\
    counter does not exceeds the value 5, or counter is no Yodl-counter.
)

You already know what's coming: the preferred layout. First argument immediately follows the function name, then the second argument (the true list) indented, and the false list indented the same. It closely follows the preferred layout of if-else statements in many programming languages. Lowkey iconic. ๐ŸŒธ

IFMACRO

IFMACRO tests whether its first argument is the name of a macro. If that name is the name of a macro, the second argument gets evaluated. If not, the third argument gets evaluated. All three arguments โ€” the name, the true list, and the false list โ€” must be present. Though the true list and/or the false list may be empty. ๐Ÿฆ‹

Example:

IFMACRO(nested)(\
    `nestedโ€™ is the name of a macro
)(\
    There is no macro named `nestedโ€™
)

And the preferred layout, one more time for the people in the back: first argument immediately follows the function name, second argument (the true list) indented, false list indented. It closely follows the if-else layout of many programming languages. Consistency is the whole vibe. ๐Ÿ’œ

IFSMALLER

IFSMALLER expects four argument lists. It tests whether its first argument is smaller than its second argument. If yes, the third parameter list is evaluated; otherwise, the fourth argument is evaluated. All four argument lists must be present โ€” though all can be empty. ๐Ÿ˜ญ

The first two arguments of IFSMALLER should be integral numeric arguments. To determine whether the first two arguments compare, their values are determined:

  • If the argument starts with an integral numerical value, that value is the value of the argument.
  • If the argument is the name of a counter, the counter's value is the value of the argument.
  • If the values of the first two arguments can be determined accordingly, their order relation determines whether the true list (when the first value is smaller than the second value) or the false list (when the first value is greater than or equal to the second value) is evaluated.
  • Otherwise, IFSMALLER evaluates the false list.

Example:

IFSMALLER(counter)(5)(\
    counter is smaller than the value 5, or counter is no Yodl-counter
)(\
    counter exceeds the value 5
)

And the preferred layout: first argument immediately follows the function name, second argument (the true list) is indented, false list is indented too. Closely follows the preferred if-else layout of many programming languages. No cap. ๐Ÿซถ

IFSTREQUAL

IFSTREQUAL tests for the equality of two strings, bestie. It expects four arguments: two strings to match, a true list, and a false list. The true list only gets evaluated when the contents of the two string arguments exactly match. Exact. Not close. Not "eh, same vibe". Exact. ๐Ÿ’…

The first two arguments of IFSTREQUAL are partially evaluated:

  • If the argument is the name of a symbol, the symbol's value is the value of the argument.
  • Otherwise, the argument itself is used.

Now the degenerate case, and listen up because this is a tricky one ๐Ÿ™ˆ: if the string you want to compare is actually the name of a SYMBOL, then use a temporary SYMBOL variable holding the name of that symbol, and compare that to whatever you want to compare it with. Alternatively, write a blank space behind the arguments, since the arguments are then interpreted as is. In practice, the need for these constructions seems to arise seldom, though. So like, don't stress unless you hit it. โœจ

Example:

IFSTREQUAL(MYSYMBOL)(Hello world)(
    The symbol `MYSYMBOLโ€™ holds the value `Hello worldโ€™
)(
    The symbol `MYSYMBOLโ€™ doesnโ€™t hold the value `Hello worldโ€™
)

IFSTRSUB ๐Ÿ’…

okay bestie, IFSTRSUB is here to check if one string is a sub-string of another string. It's giving similar energy to IFSTREQUAL, but instead of checking if they're equal, it tests whether the second string is part of the first one. ๐Ÿ”

The first two arguments of IFSTREQULA are partially evaluated:

  • If the argument is the name of a symbol, the symbol's value is the value of the argument.
  • Otherwise, the argument itself is used.

In that awkward degenerate case where the string to be compared is literally the name of a SYMBOL, you gotta use a temporary SYMBOL variable containing the name of that symbol, and compare it to whatever you want to compare it with. Or, alternative slay: write a blank space behind the arguments, since the arguments are then interpreted as is. In practice, the need for these constructions seems to arise seldom, however. ๐Ÿ˜Œ

Example:

IFSTRSUB(haystack)(needle)(
    `needleโ€™ was found in `haystackโ€™
)(
    `needleโ€™ was not found in `haystackโ€™
)

Note that both haystackโ€™ and needleโ€™ may be the names of symbols. If they are, their contents are is compared, rather than the literal names haystackโ€™ and needleโ€™. ๐Ÿ‘€


IFSYMBOL ๐Ÿง

The IFSYMBOL function tests whether its first argument is the name of a symbol. If it is the name of a symbol, the second argument is evaluated; otherwise, the third argument is evaluated. All three arguments (the name, the true list, and the false list) must be present โ€” though the true list and/or the false list may be empty. No skipping!

Example:

IFSYMBOL(nested)(\
    `nestedโ€™ is the name of a symbol
)(\
    There is no symbol named `nestedโ€™
)

Please note the preferred layout: the first argument immediately follows the function name, then the second argument (the true list) is indented, as is the false list. The layout closely follows the preferred layout of `if-elseโ€™ statements of many programming languages. โœจ

C( FBB consider additive expressions )

IFZERO 0๏ธโƒฃ

IFZERO expects three arguments. If the first argument is zero (0), the function expands to the true list (the second argument). Otherwise, it expands to the false list (the third argument).

The first argument of IFZERO should be an integral numeric value. Its value is determined as follows:

  • If the argument starts with an integral numerical value, that value is the value of the argument.
  • If the argument is the name of a counter, the counter's value is the value of the argument.
  • Otherwise, the first arguments evaluates as 0, and the false list is used.

Note that, starting with Yodl version 2.00, the first argument is not evaluated. So COUNTERVALUE(somecounter)โ€™ always evaluates as 0. If the value of a counter is required, simply provide its name as the first argument of the IFZEROโ€™ function. ๐Ÿง 

Example:

DEFINEMACRO(environment)(2)(\
    IFZERO(ARG2)(\
        NOEXPAND(\end{ARG1})\
    )(\
        NOEXPAND(\begin{ARG1})\
    )\
)

Such a macro may be used as follows:

environment(center)(1)
    Now comes centered text.
environment(center)(0)

which would of course lead to \beginโ€™ and \end{center}โ€™. The numeric second argument is used here as a on/off switch. ๐ŸŽš๏ธ


INCLUDEFILE ๐Ÿ“‚

INCLUDEFILE takes one argument, a filename. The file is processed by Yodl. If a file should be inserted without processing, the builtin function NOEXPANDINCLUDE [NOEXPANDINCLUDE] or NOEXPANDPATHINCLUDE [NOEXPANDPATHINCLUDE] should be used.

The yodl program supplies, when necessary, an extension to the filename. The supplied extension is .yo, unless defined otherwise during the compilation of the program.

Furthermore, Yodl tries to locate the file in Yodl's include path (which may be set using the --include option). The actual value of the include path is shown in the usage information, displayed when Yodl is started without arguments.

Example:

INCLUDEFILE(latex)

Here, Yodl attempts to include the file latex or latex.yo from the current include path. When the file is not found, Yodl aborts. ๐Ÿ˜ฑ


INCWSLEVEL โฌ†๏ธ

INCWSLEVEL requires one (empty) argument.

It increments the current white-space level. The white-space level typically is used in files that only define Yodl macros. When no output should be generated while processing these files, the white-space level can be used to check for this. If the white-space level exceeds zero, a warning is generated if the file produces non-whitespace output. The builtin function DECWSLEVEL is used to decrement the whitespace level following a previous INCWSLEVEL call.

Once the white space level exceeds zero, no output is generated. White space, therefore, is effectively ignored. The white space level cannot be reduced to negative values. A warning is issued if that would have happened if it were allowed. ๐Ÿšจ

Example:

INCWSLEVEL()
DEFINESYMBOL(....)
DEFINEMACRO(...)(...)(...)
DECWSLEVEL()

Without the INCWSLEVEL and DECWSLEVEL calls, the above definition would generate four empty lines to the output stream.

The INCWSLEVEL and DECWSLEVEL calls may be nested. The best approach is to put an INCWSLEVEL at the first line of a macro-defining Yodl-file, and a matching DECWSLEVEL call at the very last line. ๐Ÿ’ฏ


INTERNALINDEX ๐Ÿ—‚๏ธ

INTERNALINDEX expects one argument list. The argument list is evaluated and written to the index file.

The index file is defined since Yodl version 2.00, and contains the fixup information which was previously written to Yodl's output as the .YODLTAGSTART. ... .YODLTAGEND. sequence.

The index file allows for greater processing speed, at the expense of an additional file. The associated yodlpost postprocessing program reads and processes the index file, and modifies the corresponding yodl-output accordingly.

The index file is not created when output is written to the standard output name, since Yodl is unable to request the system for the current file offset.

The entries of the index file always fit on one line. INTERNALINDEX changes newline characters in its argument into single blank spaces. Each line starts with the current offset of Yodl's output file, thus indicating the exact location where a fixup is requested. An example of a produced fixup line could be

3004 ref MACROPACKAGE

indicating that at offset 3004 in the produced output file a reference to the label MACROPACKAGE is requested. Assuming a html conversion, the postprocessor thereupon writes something like

<a href="outfile04.html#MACROPACKAGE">4.3.2.</a>

into the actual output file while processing Yodl's output up to offset location 3004.

Consequently, producing Yodl-output normally consists of two steps:

  1. First, Yodl itself is started, producing, e.g., out.idx (the index file) and out.yodl (Yodl's raw output).
  2. Then, Yodl's post-processor processes out.idx and out.yodl, producing one or more final output files, in which the elements of the index file have been properly handled. This may result in multiple output file, like report.html, report01.html, report02.html etc.

NOEXPAND ๐Ÿšซ

NOEXPAND is used to send text to the final output file without being expanded by Yodl (the other methods are the CHAR macro, see section [CHAR], and the NOTRANS macro, see section [NOTRANS]). NOEXPAND takes one argument, the text in question. Whatever occurs in the argument is not subject to parsing or expansion by Yodl, but is simply copied to the output file (except for CHAR and (iinternally used) XXSUBST functions in the argument, which are expanded. If CHAR-expansion is not required either, NOTRANS [NOTRANS] can be used).

Furthermore, the contents of the argument are also subject to character table translations, using the currently active table. This should come as no surprise. Ignoring character tables would make both the processing of CHAR calls and the NOTRANS function superfluous.

So, the following situations are recognized:

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                      support chartables
                           and CHAR
                    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Macro expansion   yes          no
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Yes               (standard)   Push chartable
                               (standard)
                               Pop chartable
No                NOEXPAND     NOTRANS
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

E.g., let's assume that you need to write in your document the following text:

    INCLUDEFILE(something or the other)
    IFDEF(onething)(
        ...
    )(
        ....
    )
    NOEXPAND(whatever)

okay so bestie, the way to pull this off is by prefixing the text with `NOEXPANDโ€™ and then an open parenthesis, and postfixing it with a closing parenthesis. If you donโ€™t, the text would be expanded by Yodl while itโ€™s processing it โ€” and thatโ€™s a whole syntax-error situation, because the text isnโ€™t correct in the sense of the Yodl language. ๐Ÿ’…

For this function, keep these caveats in mind, fr fr:

  • There is only one thing that a NOEXPANDโ€™</hotpink> cannot protect from expansion: an <purple>ARGโ€™x in a macro definition. The argument specifier is always processed. E.g., after
DEFINEMACRO(thatsit)(1)(
    That is --> NOEXPAND(ARG1) <-- it!
)
thatsit(after all)

the ARG1โ€™ inside the NOEXPANDโ€™ statement is replaced with `after allโ€™. ๐Ÿ˜ญ

  • The NOEXPANDโ€™</hotpink> function must, as all functions, be followed by an argument. The parentheses of the list must therefore be balanced. For unbalanced lists, use <cyan>CHAR(40)โ€™ to set an open parenthesis, or `CHAR(41)โ€™ to typeset a closing parenthesis. ๐Ÿซถ

NOEXPANDINCLUDE ๐Ÿ’–

NOEXPANDINCLUDE takes one argument, a filename. The file is included.

The filename is used as specified. The include path is not used when locating this file.

The argument to NOEXPANDINCLUDE is partially evaluated:

  • If the argument is the name of a symbol, the symbolโ€™s value is the value of the argument.
  • Otherwise, the argument itself is used. The thus obtained file name is not further evaluated: in particular, it is not affected by available character translations.

The contents of the file are included literally, not subject to macro expansion. Character translations are performed, though. If character translations are not appropriate, PUSHCHARTABLE can be used to suppress character table translations temporarily.

The purpose of NOEXPANDINCLUDE is to include source code literally in the document, as in:

NOEXPANDINCLUDE(literal.c)

The function NOEXPANDPATHINCLUDE can be used to insert a file which is located in one of the directories specified in Yodlโ€™s include path.

NOEXPANDPATHINCLUDE ๐Ÿฆ‹

NOEXPANDPATHINCLUDE takes one argument, a filename. The file is included. The file is searched for in the directories specified in Yodlโ€™s includepath.

The argument to NOEXPANDPATHINCLUDE is partially evaluated:

  • If the argument is the name of a symbol, the symbolโ€™s value is the value of the argument.
  • Otherwise, the argument itself is used. The thus obtained file name is not further evaluated: in particular, it is not affected by available character translations.

Like the NOEXPANDINCLUDEโ€™</hotpink> function, the contents of the file are included literally, not subject to macro expansion. Character translations are performed, though. If character translations are not appropriate, <mint>PUSHCHARTABLE</mint> [PUSHCHARTABLE]` can be used to suppress character table translations temporarily.

The purpose of NOEXPANDPATHINCLUDE is to include source code as defined in a macro package literally into the document, as in:

NOEXPANDPATHINCLUDE(rug-menubegin.xml)

NOTRANS ๐Ÿ’…

NOTRANS copies its one argument literally to the output file, without expanding macros in it and without translating the characters with the current translation table. The NOTRANS function is typically used to send commands for the output format to the output file.

For example, consider the following code fragment:

COMMENT(--- Define character translations for \, { and } in LaTeX. ---)
DEFINECHARTABLE(standard)(
    โ€™\\โ€™    =    "$\\backslash$"
    โ€™{โ€™     =    "\\verb+{+"
    โ€™}โ€™     =    "\\verb+}+"
)

COMMENT(--- Activate the translation table. ---)
USECHARTABLE(standard)

COMMENT(--- Now two tests: ---)

NOEXPAND(\input{epsf.tex})
NOTRANS(\input{epsf.tex})

NOEXPAND sends

$\backslash$input\verb+{+epsf.tex\verb+}+

since the characters in its argument are translated with the standard translation table. In contrast, NOTRANS sends \input{epsf.tex}.

The argument of NOTRANS must be balanced with respect to its parentheses. When using an unbalanced set of parentheses, use CHAR(40) to send a literal (, or CHAR(41) to send a ).

While converting Yodl-documents to target document types Yodl frequently uses the (not further documented) builtin function XXSUBST. In the unlikely event that the text XXSUBST(...) must be written in a document, the sequence

XXSUBST+CHAR(40)...CHAR(41)

can be used.

The NOEXPAND description summarizes all combinations of character translations and/or macro expansion, and how they are handled and realized by Yodl.

NOUSERMACRO ๐Ÿ˜ญ

NOUSERMACRO controls yodlโ€™โ€™s warnings in the following way: When Yodl is started with the -w flag on the command line, warnings are generated when Yodl encounters a possible macro name, i.e., a name that is followed by a parenthesized argument, while no macro by that name has been defined. Yodl then prints something like cannot expand possible user macro.

Examples of such sequences are, The necessary file(s) are in /usr/local/lib/yodl, or see the manual page for sed(1). The candidate macros are file and sed; these names could just as well be valid user macros followed by their argument.

When a corresponding NOUSERMACRO statement appears before yodl encounters the candidate macros, no warning is generated. A fragment might therefore be:

NOUSERMACRO(file sed)
The necessary file(s) are in ...
See the manual page for sed(1).

The NOUSERMACRO accepts one or more names in its argument, separated by white space, commas, colons, or semi-colons.

OUTBASE โœจ

OUTBASE inserts the current basename of the output file into the output file. The basename is the name of the file of which the directory components and extension were stripped.

If the output file is the standard output file, - is inserted.

OUTDIR ๐ŸŒˆ

OUTDIR inserts the current path name of the output file into the output file. The path name is a, not necessarily absolute, designator of the directory in which the output file is located. If the output file is indicated as, e.g., -o out, then OUTDIR simply inserts a dot.

If the output file is the standard output file, a dot is inserted too.

OUTFILENAME ๐ŸŽ€

OUTFILENAME inserts the current filename of the output file into the output file. The filename is the name of the file of which the directory components were stripped.

If the output file is the standard output file, - is inserted.

PARAGRAPH ๐Ÿ‘‘

PARAGRAPH isnโ€™t really a builtin function, but as Yodl handles paragraphs in a special way it is probably useful to describe paragraph handling here nonetheless. Starting with Yodl 2.00 PARAGRAPH operates as follows:

If the macro is not defined, new paragraphs, defined as series of consecutive empty lines written to the output stream, are not handled different from any other series of characters sent to the output stream. I.e., they are inserted into that stream.

However, if the macro has been defined, Yodl calls it whenever a new paragraph (defined as a series of at least two blank lines) has been recognized.

The empty lines that were actually recognized may be obtained inside the PARAGRAPH macro from the XXparagraph symbol, if this symbol has been be defined by that time. If defined, it contains the white space that caused Yodl to call the PARAGRAPH macro.

Note that, in order to inspect XXparagraph it must have been defined first. Yodl itself does not define this symbol itself.

The PARAGRAPH macro should be defined as a macro not expecting arguments. The macro is thus given a chance to process the paragraph in a way thatโ€™s fitting for the particular conversion type. If the PARAGRAPH macro produces series of empty lines itself, then those empty lines do not cause Yodl to activate PARAGRAPH. So, Yodl itself will not recursively call PARAGRAPH, although the macro could call itself recursively. Of course, such recursive activcation of PARAGRAPH is then the sole responsibility of the macroโ€™s author, and not Yodlโ€™s.

Some document languages do not need paragraph starts; e.g., LaTeX handles its own paragraphs. Other document languages do need it: typically, PARAGRAPH is then defined in a macro file to trigger some special action. E.g., a HTML converter might define a paragraph as:

DEFINEMACRO(PARAGRAPH)(0)(
    XXnl()
    NOTRANS(<p>)
)

A system like xml has more strict requirements. Paragraphs here must be opened and closed using pairs of <p> and </p> tags. In those cases an auxiliary counter can be used to indicate whether there is an open paragraph or not. The PARAGRAPH macro could check for this as follows, assuming the availability of a counter XXp:

DEFINEMACRO(PARAGRAPH)(0)(
    XXnl()
    IFZERO(XXp)(
    )(
        NOTRANS(</p>)
    )
    NOTRANS(<p>)
    SETCOUNTER(XXp)(1)
)

Note that the above fragment exemplifies an approach, not necessarily the implementation of the PARAGRAPH macro for an xml-converter. ๐Ÿซถ

๐Ÿš€ PIPETHROUGH

Okay so PIPETHROUGH is the second function (the other one being SYSTEM) that lets a Yodl doc actually mess with its own environment, and bestie, it is so useful. The whole vibe: you start up an external command, pipe a block of text from inside your Yodl document into it, and whatever that child program spits out on standard output gets piped right back into the Yodl document. That's the "piped through" part. ๐ŸŽ€ And then whatever comes back gets processed further, obviously.

It takes two arguments:

  • the command to run, and
  • the text to send to that command.

Functionally, the whole PIPETHROUGH call + its two arguments just gets replaced by whatever the child program writes to its standard output. That's it, that's the magic. โœจ

Classic example โ€” dropping the current date in, like:

The current date is:
PIPETHROUGH(date)()

Here the command is date and the text we're sending it is empty (the () at the end is justโ€ฆ nothing). ๐Ÿ’…

The main purpose of this function is giving you a way to use external programs to build stuff like tables or figures for a given output format. And heads up: further releases of Yodl may ship dedicated programs for output formats like this. ๐Ÿ‘‘


๐Ÿซง POPCHARTABLE

Character tables that got pushed onto the table stack with PUSHCHARTABLE() are restored (aka popped) with POPCHARTABLE(). If you want the whole mechanism explained, go read section [PUSHINGTABLES]. ๐Ÿ“š


๐Ÿ”ข POPCOUNTER

POPCOUNTER is how you yeet the topmost counter off the counter stack. Counter values get pushed on a stack via PUSHCOUNTER [PUSHCOUNTER], and POPCOUNTER is the one that removes the topmost element of that counter's stack.

It expects one argument: the name of the counter to pop. The previously pushed value then becomes the counter's new value. ๐ŸŽฏ

So a counter's value can be popped after you've defined it, and after that the stack is empty โ€” but the counter is still defined. And in that case, using the counter's value is considered an error. Careful, bestie. ๐Ÿ˜ญ

Examples:

DEFINECOUNTER(YEAR)(1950)
POPCOUNTER(YEAR)
COMMENT(YEAR now has an undefined value)

See also section [COUNTERS]. ๐Ÿ’œ


๐Ÿงต POPMACRO

POPMACRO removes the actual macro definition and restores a previously pushed one. Macro values get pushed on a stack using PUSHMACRO. ๐ŸŒธ

To remove the topmost element of a macro's stack, POPMACRO is your girl. It expects one argument: the name of the macro to pop. The previously pushed value then becomes the macro's new value.

A macro's value may be popped after defining it, and then its stack is empty. In that case, using the macro (even though the macro's name is still defined) is considered an error. ๐Ÿ’€

Example:

DEFINEMACRO(Hello)(1)(Hello, ARG1, this is a macro definition)
Hello(Karel)
PUSHMACRO(Hello)(1)(Hello, ARG1, this is the new definition)
Hello(Karel)
POPMACRO(Hello)
Hello(Karel)
COMMENT(The third activation of Hello() produces the same output
        as the first activation)

โœจ POPSUBST

POPSUBST reverts to a previous level of interpretation of SUBST definitions. For the full lore, check the descriptions of the PUSHSUBST and SUBST builtin commands below. ๐Ÿ‘€

There's no limit to how many times you can call POPSUBST. And once the "PUSHSUBST stack" is empty, SUBST definitions are automatically interpreted โ€” so you will never hit a stack-underflow error here. Slay. ๐ŸŽ€


๐Ÿฆ‹ POPSYMBOL

POPSYMBOL removes the topmost symbol from the symbol stack. Symbol values get pushed on a stack using PUSHSYMBOL [PUSHSYMBOL], and POPSYMBOL removes the topmost element of a symbol's stack.

It expects one argument: the name of the symbol to pop. The previously pushed value then becomes the symbol's new value. ๐Ÿ’ซ

A symbol's value may be popped after defining it, and then its stack is empty. In that case, using the symbol (even though the symbol's name is still defined) is considered an error. ๐Ÿ™ˆ

Example:

DEFINESYMBOL(YEAR)(This happened in 1950)
POPSYMBOL(YEAR)
COMMENT(YEAR now has an undefined value)

๐Ÿงš POPWSLEVEL

POPWSLEVEL removes the topmost wslevel from the wslevel stack. Wslevel values get pushed on a stack using PUSHWSLEVEL [PUSHWSLEVEL]. Also see section DECWSLEVEL [DECWSLEVEL]. ๐ŸŒˆ

To remove the topmost element of a wslevel's stack, POPWSLEVEL is the move. It expects one argument: the name of the wslevel to pop. The previously pushed value then becomes the wslevel's new value. A wslevel's value may be popped after defining it, emptying the stack, but the wslevel will still be defined. And in that case, using the wslevel's value is considered an error. ๐Ÿ˜ฌ

Example:

COMMENT(Assume WS level is zero)

PUSHWSLEVEL(1)
COMMENT(WS level now equals 1)

POPWSLEVEL()
COMMENT(WS level now equals 0 again)

๐Ÿ“š PUSHCHARTABLE

Once a character table has been defined, you can push it onto a stack using PUSHCHARTABLE. The pushed chartable can be popped later. There's a whole deep-dive on this in section [PUSHINGTABLES]. ๐Ÿซถ


๐Ÿ”ข PUSHCOUNTER

PUSHCOUNTER starts another lifetime for a counter by pushing its current value onto a stack. Every individual counter gets its own stack. ๐Ÿ‘‘

It expects two arguments: the name of the counter to push, and an additive expression whose value becomes the counter's new value (after the current value gets pushed). โœจ

That additive expression may not contain blank spaces, and it may use + and - operators. Its operands can either be integral numeric values or the names of (defined) counters. The resulting value can be negative โ€” and in that case, a value is subtracted from the destination counter. ๐Ÿ˜ฎ

When the second argument is empty, the new value will be zero. And if you just want to push the value without touching the current one, specify the name of the counter twice. ๐Ÿ’…

Examples:

DEFINECOUNTER(YEAR)(1950)
PUSHCOUNTER(YEAR)(1962)
COMMENT(YEAR now has the value 1962, and a pushed value of 1950)

See also section [COUNTERS]. ๐Ÿ’œ


๐Ÿงต PUSHMACRO

PUSHMACRO starts another lifetime for a macro by pushing its current definition onto a stack. Every individual macro gets its own stack. ๐ŸŒธ

It expects three arguments: the name of the macro to push, the number of its arguments after pushing (which may be different from the number of arguments interpreted by the pushed macro), and its new definition. ๐ŸŽฏ

So PUSHMACRO is used exactly like DEFINEMACRO, except it redefines a current macro (or defines a new macro if no macro was defined under the name you passed as its first argument). ๐Ÿซง

Example:

DEFINEMACRO(Hello)(1)(Hello, ARG1, this is a macro definition)
Hello(Karel)
PUSHMACRO(Hello)(1)(Hello, ARG1, this is the new definition)
Hello(Karel)
POPMACRO(Hello)
Hello(Karel)
COMMENT(The third activation of Hello() produces the same output
        as the first activation)

โœจ PUSHSUBST

PUSHSUBST lets you (temporarily) suppress the interpretation of SUBST definitions. The SUBST builtin command is covered below, so peep its description for an example. ๐Ÿ‘€

PUSHSUBST expects one argument: an integral number which is either 0 or non-zero (commonly 1). After calling PUSHSUBST(0), SUBST definitions are not interpreted anymore; use POPSUBST() to revert to the previous type of interpretation. Alternatively, PUSHSUBST(0) can be used to stack another level of SUBST interpretations on top of the last-used one. ๐ŸŒ€

On a 64-bit computer, the PUSHSUBST stack can hold slightly more than 60 SUBST interpretation levels. When more levels get pushed, the oldest levels are silently forgotten. ๐Ÿ˜ญ And calling POPSUBST once the PUSHSUBST stack is empty results in activating the SUBST interpretations โ€” so a stack-underflow error will not be encountered. ๐Ÿ’–


๐Ÿฆ‹ PUSHSYMBOL

PUSHSYMBOL starts another lifetime for a symbol by pushing its current value onto a stack. Every individual symbol gets its own stack. ๐ŸŒˆ

It expects two arguments: the name of the symbol to push, and its new text after pushing. When the second argument is an empty argument, the new text will be empty. The new text may be specified as literal text, or as the name of an existing symbol. And if you just want to push the value without changing the current one, specify the name of the symbol twice. ๐Ÿ’ซ

Examples:

DEFINESYMBOL(YEAR)(This happened in 1950)
PUSHSYMBOL(YEAR)(This happened in 1962)
COMMENT(YEAR now has the value 'This happened in 1962' and a
        pushed value of 'This happened in 1950')

๐Ÿงš PUSHWSLEVEL

PUSHWSLEVEL starts another lifetime of the white-space level by pushing the level's current value onto a stack. Also see section INCWSLEVEL [INCWSLEVEL]. ๐ŸŒธ

PUSHWSLEVEL expects one argument: the new value of the white-space level. This value may be specified as a numerical value or as the name of a counter. The argument may be empty, in which case the new value will be zero. ๐ŸŽ€

Example:

COMMENT(Assume WS level is zero)
PUSHWSLEVEL(1)
COMMENT(WS level now equals 1)

POPWSLEVEL()
COMMENT(WS level now equals 0 again)

๐ŸŽ€ RENAMEMACRO

okay so RENAMEMACRO takes two arguments: the name of a built-in macro (think INCLUDEFILE or whatever) and the new name you want it to have. glow-up for a macro, basically.

E.g., after

RENAMEMACRO(INCLUDEFILE)(include)

a file has to be included by include(file). and INCLUDEFILE?? girl is gone. Following the RENAMEMACRO action, the old name can no longer be used โ€” it becomes an undefined symbol. no coming back, she's blocked ๐Ÿ’€

If you actually want an alias for a built-in command, do it with DEFINEMACRO instead. E.g., after:

DEFINEMACRO(include)(1)(INCLUDEFILE(ARG1))

both INCLUDEFILE and include can be used to include a file. two names, one job, iconic. ๐Ÿ‘‘


โœจ SETCOUNTER

SETCOUNTER expects two arguments: the name of a counter, and an additive expression defining the value to assign. The counter must have been previously created with DEFINECOUNTER โ€” no counter, no setting, that's the vibe.

The additive expression may not contain blank spaces, and it may use + and - operators. Its operands may either be integral numeric values or names of (defined) counters. The resulting value can be negative; in that case, a negative value is assigned to the destination counter. negative slay, honestly.

For example, if one and two are counters, then

SETTOCOUNTER(one)(-two)//    assigns -two's value to one
SETTOCOUNTER(one)(two+two)// assigns 2 x two's value to one

See also section [COUNTERS]. ๐Ÿ“Ž


๐Ÿ’– SETSYMBOL

SETSYMBOL expects two arguments: the name of a symbol, and the text to assign to the named symbol. The symbol must previously have been defined by DEFINESYMBOL. same energy as SETCOUNTER but for symbols, periodt.


๐ŸŒธ SUBST

SUBST is a general-purpose substitution mechanism for strings appearing in the input. SUBST takes two arguments: a search string and a substitution string. E.g., after

SUBST(VERSION)(1.00)

YODL transforms all occurrences of VERSION in its input into 1.00. every single one. she's thorough like that.

SUBST is also useful in situations where multi-character sequences should be converted to accented characters. E.g., a LaTeX converter might define:

SUBST('e)(+NOTRANS(\'{e}))

Each 'e in the input will subsequently be converted to e.

SUBST may be used in combination with the command line flag -P, as in a invocation

yodl2html -P'SUBST(VERSION)(1.00)' myfile.yo

Another useful substitution might be:

SUBST(_OP_)(CHAR(40))
SUBST(_CP_)(CHAR(41))

which defines an opening parenthesis (_OP_) and a closing parenthesis (_CP_) as mapped to the CHAR function. The strings _OP_ and _CP_ might then be used to produce unbalanced arguments. unhinged? maybe. useful? absolutely. ๐Ÿฆ‹

Note that:

  • The first argument of the SUBST command, the search string, is taken literally. Yodl does not expand it; the string must be literally matched in the input.
  • The second argument, the replacement, is further processed by Yodl. Protect this text by NOTRANS or NOEXPAND where appropriate.

Substitutions occur extremely early while YODL processes its input files. In order to process its input files, YODL takes the following steps:

  1. It requests input from its lexical scanner (so-called tokens)
  2. Its parser processes the tokens produced by the lexical scanner
  3. Its parser may send text to an output object, which eventually appears in the output file generated by YODL. YODL performs all macro substitutions in step 2, and all character table conversions in step 3. However, the lexical scanner has access to the SUBST definitions: as soon as its lexical analyzer detects a series of characters matching the defining sequence of a SUBST definition, it replaces that defining sequence by its definition. That definition is then again read by the lexical scanner. Of course, this definition may, in turn, contain defining sequences of other SUBST definitions: these are then replaced by their definitions as well. This implies:
  • Circular definitions may cause the lexical scanner to get stuck in a replacement loop. It is the responsibility of the author defining SUBST definitions to make sure that this doesn't happen. ๐Ÿ˜ญ that's on you, bestie.
  • Neither the parser, nor the output object ever sees the SUBST defining character sequences: they only see their definitions.

In some cases substitutions must be suppressed. Consider double quoted text strings that are frequently used in programming languages. E.g., "hello world". The text inside the string should not be converted by Yodl, but unless substitutions can be suppressed the string

"\"evil code"

appears as

"evil code"

To suppress the interpretation of SUBST definitions PUSHSUBST, introduced earlier, can be used. The predefined macro verb suppresses the interpretation of SUBST definitions by starting with PUSHSUBST(0) and ending with POPSUBST().


๐Ÿงš SYMBOLVALUE

SYMBOLVALUE expands to the value of a symbol. Its single argument must be the name of a symbol. The symbol must have been created earlier using DEFINESYMBOL. Example:

The symbol has value SYMBOLVALUE(MYSYMBOL).

๐Ÿ’… SYSTEM

SYSTEM takes one argument: a command to execute. The command is run via the standard C function system.

SYSTEM can be useful in many ways. E.g., you might want to log when someone processes your document, as in:

SYSTEM(echo Document processed! | mail myself@my.host)

Note that SYSTEM merely performs an system-related task. It's a process that is separated from the YODL process itself. One of the consequences of this is that any output generated by SYSTEM not normally appears into YODL's output file. If the output of a subprocess should be inserted into YODL's output file, either use PIPETHROUGH [PIPETHROUGH], or insert a temporary file as shown in the following example:

SYSTEM(date > datefile)
The current date is:
INCLUDEFILE(datefile)
SYSTEM(rm datefile)
โš ๏ธ fr fr, SYSTEM runs off and does its own thing in its own little process โ€” it's not whispering into YODL's output file unless you make it.

๐Ÿ“ TYPEOUT

TYPEOUT requires one argument. The text of the list is sent to the standard error stream, followed by a newline. This feature can be handy to show, e.g., messages such as version numbers in macro package files. lowkey a little printf to stderr moment. ๐Ÿ’œ

Example: The following macro includes a file and writes to the screen that this file is currently processed.

DEFINEMACRO(includefile)(1)(
    TYPEOUT(About to process document: ARG1)
    INCLUDEFILE(ARG1)
)

๐ŸŒˆ UPPERCASE

UPPERCASE converts a string or a part of it to upper case. It has two arguments:

  • The string to convert;
  • A length, indicating how many characters (starting from the beginning of the string) should be converted. The length indicator can be smaller than one or larger than the length of the string; in that case, the whole string is converted.

Example:

UPPERCASE(hello world)(1)
UPPERCASE(hello world)(5)
UPPERCASE(hello world)(0)

This code sample expands to:

Hello world
HELLO world
HELLO WORLD

not UPPERCASE giving main character energy in three acts ๐Ÿ˜ญ


โญ USECHARTABLE

USECHARTABLE takes one argument: the name of a translation table to activate. The table must previously have been defined using DEFINECHARTABLE. See section [CHARTABLES] for a description of character translation tables.

Alternatively, the name may be empty, in which case the default character mapping is restored. factory reset but make it fashion. โœจ


๐Ÿ’– USECOUNTER

USECOUNTER is a combination of ADDTOCOUNTER and COUNTERVALUE. It expects one argument: the name of an defined counter (see DEFINECOUNTER [DEFINECOUNTER]).

The counter is first incremented by 1. Then the function expands to the counter's value. bump then flex, that's the whole bit.

See also section [COUNTERS].


๐Ÿ‘‘ VERBOSITY

VERBOSITY expects two arguments, and may be used to change the verbosity level inside YODL files. The function may be used profitably for debugging purposes, to debug the expansion of a macro or the processing of a YODL input file.

The first argument indicates the processing mode of the second argument, and it may be:

  • Empty, in which case the message-level is set to the value specified in the second argument;
  • +, in which case the value specified in the second argument augments the current message level;
  • -, in which case the value specified in the second argument augments is removed from the current message level

The second argument specifies one or more, separated by blanks, message level names or it may be set to a hexadecimal value (starting with 0x), using hexadecimal values to represent message levels. Also, NONE may be used, to specify no message level, or ALL can be used to specify all message levels.

The following message levels are defined:

  • ALERT (0x40). When an alert-error occurs, Yodl terminates. Here Yodl requests something of the system (like a get_cwd()), but the system fails.
  • CRITICAL (0x20). When a critical error occurs, Yodl terminates. The message itself can be suppressed, but exiting can't. A critical condition is, e.g., the omission of an open parenthesis at a location where a parenthesized argument should appear, or a non-existing file in an INCLUDEFILE specification (as this file should be parsed). A non-existing file with a NOEXPANDINCLUDE specification is a plain (non-critical) error.
  • DEBUG (0x01). Probably too much info, like getting information about each character that was read by Yodl.
  • ERROR (0x10). An error (like doubly defined symbols). Error messages will not stop the parsing of the input (up to a maximum number of errors), but no output is generated.
  • INFO (0x02) โ€” not as extra as debug, but still very much info, like the tea about media switches. ๐Ÿซ–
  • NOTICE (0x04) โ€” the gossip about stuff like calls to the builtin function calls.
  • WARNING (0x08) โ€” something you should know about, but that probably isn't affecting Yodl's proper functioning. bestie it's giving "heads up, not a crisis" ๐Ÿ’…
there's also a level EMERG (0x80) which cannot be suppressed. periodt. it's showing up whether you like it or not ๐Ÿ˜ญ

The value 0x00 represents NONE, and the value 0xff represents ALL. iconic range honestly ๐ŸŒˆ

When you're specifying multiple message levels using the hexadecimal form, their hexadecimal values should be binary-or-ed: adding them is ok, as long as you don't specify ALL:

VERBOSITY()(0x06)
COMMENT(this specifies `INFO' and `NOTICE')

When you're specifying message levels by their names, the names may be truncated at a unique point. BUT. the message level names are interpreted case sensitively, so INF for INFO is recognized as such, but info for INFO isn't. lowercase girlies stay losing on this one ๐Ÿ’€ The following examples all specify verbosity levels INFO and NOTICE:

VERBOSITY()(I N)
VERBOSITY()(N I)
VERBOSITY()(NOT IN)
VERBOSITY()(INFO NOTICE)

WARNING

WARNING takes one argument: text to display as a warning. The yodl program makes sure that before showing the text, the current file and line number are printed. Other than this, WARNING works just as TYPEOUT (see section [TYPEOUT]). ๐ŸŽ€

Note that an analogous function ERROR exists, which prints a message and then terminates the program (see section [ERROR]). yeah that one's the nuclear option fr fr ๐Ÿ’ฅ


๐Ÿ“ FILES

The files in tmp/wip/macros define the converter's macro packages. The scripts yodl2tex, yodl2html, yodl2man etc. perform the conversions. slay ๐Ÿฆ‹


๐Ÿ‘€ SEE ALSO

yodl(1), yodlconverters(1), yodlletter(7), yodlmacros(7), yodlmanpage(7), yodlpost(1), yodlstriproff(1), yodltables(7), yodlverbinsert(1).

the whole squad is here, no cap ๐Ÿ‘‘


๐Ÿ› BUGS

--

literally empty bestie, we're flawless โœจ (jk but also the manpage says so ๐Ÿซถ)


โœ๏ธ AUTHOR

Frank B. Brokken (f.b.brokken@rug.nl),


and that's a wrap on the whole page!! ๐ŸŽ‰๐Ÿ’– this was yodl_4.05.02, dated 1996-2025, from the Yodl builtin functions(7) manual. thank you for coming to my ted talk, go forth and yodl responsibly ๐ŸŒธ๐Ÿฆ‹โœจ

๐Ÿ“ฆ where this page comes from