Wiki: TeX
GitHub: edit | view

Your best resource for TeX is the TeXBook, particularly chapter 20 ("Definitions (also called Macros)") and Appendix D ("Dirty Tricks"). Appendix B provides a list of almost all macros defined at startup.

The reference for plain TeX at tug.org is also a good resource.

TeX-autogolfer is a tool to help in golfing TeX. It's a combo of a simple minifier (remove whitespace, etc.) and a meta-macro system; for example you can write \usegolf{rebind\advance}, and it creates (effectively) \let\A\advance and replaces all of your uses of \advance with \A.

Basics

Differences from Plain TeX

The TeX language on the site is very similar to Knuth's plain TeX, with a few small modifications:

Spaces

Space characters can be annoying to manage. Important spacing rules:

Math operations

Math can be performed in counters. Define a counter with \newcount, and print the value with \the.

\newcount\x
\x=15  % equivalent to the following line
\x15   % (the equals sign is optional)
\the\x % prints 15 as two tokens, "1" then "5"

Counters are 32-bit signed integers. Operations that overflow cause the program to instantly halt with no output.

\x\y            % x = y
\advance\x5     % x += 5
\advance\x by\y % equivalent to the following line
\advance\x\y    % x += y (the "by" text is optional)
\advance\x-\y   % x += -y
\multiply\x\y   % x *= y
\divide\x\y     % x /= y (floor divide)

There is no modulo operator. Modulo can be calculated (if you must) using the definition x % y = x - x / y * y, where x/y is floor division

% d = x - x % y, x = x % y (requires an extra counter)
\d\x\divide\d\y\multiply\d\y\advance\x-\d

Or modulo can be computed by repeated subtraction, assuming x and y are positive.

% x = x % y (slow)
\loop\ifnum\x>\y\advance\x-\y\repeat

ASCII conversions

Convert a char code to corresponding character with \char: \char97 gives the token a.

Convert a character to char code with a backtick: \number`a gives the two tokens 97, and \newcount\x\x=`a assigns 97 as the value of the counter \x.

Looping over a string

The following code loops over all tokens until ;, replacing all - tokens with (DASH)

\def\f#1{\ifx#1;%
    \let\n\relax%
\else%
    \let\n\f%
    \ifx#1-(DASH)%
    \else #1%
    \fi%
\fi\n}

\f 0-185186-70-;

Improvement: an extra stop token

The following code loops over all tokens until ,;, replacing all - tokens with (DASH)

\def\f#1#2;{\ifx#1,\else
    \ifx#1-(DASH)%
    \else #1%
    \fi%
    \f#2;
\fi}

\f 0-185186-70-,;

Tweak: check for empty argument

The following code loops over all tokens until ;, replacing all - tokens with (DASH). It assumes there are no .s in the input.

\def\f#1#2;{%
    \ifx#1-(DASH)%
    \else #1%
    \fi%
\if.#2.\else%
    \f#2;%
\fi}
\f 0-185186-70-;

Looping over two strings

The following code prints "Equal!" if two input token lists are equal.

\def\streq#1#2;#3#4;{
  \if#1,
    \if#3,
      % strings are equal, done
      Equal!
    \else
      % left string is shorter than right string
    \fi
  \else
    \if#3,
      % right string is shorter than left string
    \else
      %#1 and #3 are both not ','
      \if#1#3
        \streq#2;#4;
      \fi
    \fi
  \fi
}

\streq ABC,;DEF,; % (empty output)

\streq ABC,;ABC,; % outputs "Equal!"

Golfing

One-byte macro ending

If a macro you define takes an argument longer than one token, you might be tempted to use curly braces. However, \def can allow for a single end character, saving one byte per usage.

\def\f#1{something #1 something}
\f{123}\f{456}
% compare
\def\f#1;{something #1 something}
\f123;\f456;

\let

Commonly-used macros can be aliased with \let

\newcount\a\newcount\b\newcount\c
% compare
\let\N\newcount\N\a\N\b\N\c

At the top of a long solution, you might see a block of \lets like \let\N\newcount\let\I\ifnum\let\A\advance

Tilde is active

Tilde (~) is an active character (\catcode`\~=\active), so you can use it in lieu of one control sequence.

\let\N\newcount\N\a\N\b\N\c
% compare
\let~\newcount~\a~\b~\c

Repeated macro vs loop

Repeating a macro several times is often shorter than a loop.

\newcount~\loop\ifnum10>~\the~,\advance~1\repeat
% compare (uses an extra variable)
\newcount\i\def~{\the\i\advance\i1,}~~~~~~~~~~

Newlines

The conventional way to make a new paragraph is the \par command. Two or more consecutive newlines are converted to a single \par and are the most common way to get a newline.

Form feed (ASCII character 12 = 0x0C) is equivalent to \par, but it has one restriction: It is \outer, so it is disallowed in many contexts, such as the replacement text of \def definitions (unless the definition is declared as \long\def) and inside \loop...\repeat constructs. Form feed can be typed in the code.golf editor by typing "0c" while holding down the Alt key.

A few other commands are loosely equivalent to \par and useful over a double-newline in niche situations. Try playing around with these to get a feel for when they're useful: \vfil, \eject, \char10, \endgraf. In particular, \eject forces a page break, so it flushes the output, so you can terminate by an error such as stack overflow.

Relevant lines from plain.tex
\catcode`\^^L=\active \outer\def^^L{\par} % ascii form-feed is "\outer\par"
\let\endgraf=\par
\def\break{\penalty-\@M}
\def\eject{\par\break}

\vfil and \char are primitive commands.

The following prints 0 through 9 on their own lines by two approaches which are the same length. The two approaches are a recursive approach using double-newline and a \loop...\repeat approach using \vfil.

\newcount\i\loop\ifnum10>\i\the\i\vfil\advance\i1\repeat
% compare
\newcount\i\def\f{\ifnum10>\i\the\i\advance\i1

\f\fi}\f

Counters

TeX has a fixed set of 256 counters, accessible in a few different ways. Usually you would use newcount to be able to reference a previously unused by a name:

\newcount\i   \the\i  % 0, newcounts are initially 0
\advance\i 3  \the\i  % 3

If you just need one or two counters, you can instead make use of predefined counters:

\day3 \the\day \the\fam % 3 0

If you use lots of counters, alias \count to an active character and address counters by their index (0-255):

\let~\count
\the~0               % 1, if you use a counter that is already in use by TeX, it might have a non-zero value
\advance~0 3 \the~0  % 4

% this way also allows using counters as small arrays:
\the~~0  % index in \count0

Reference tables

Count registers

Table
Register numberInitial valueIncrementerMore
01\ejectPage numbering.
1 to 90Page number.
1025\newcountCount allocation.
1115\newdimenDimen allocation.
1217\newskipSkip allocation.
139\newmuskipMuskip allocation.
1415\newboxBox allocation.
1511\newtoksToks allocation.
16-1\newreadRead file allocation.
17-1\newwriteWrite file allocation.
187\newfamMath family allocation.
190\newlanguageLanguage allocation.
20253\newinsert (-1)Insert allocation. (count, dimen, skip are related)
2126The most recently allocated number.
22-1\countdef\m@ne=22 \m@ne=-1 % a handy constant
23100Is \interdisplaylinepenalty
24100Is \interfootnotelinepenalty
25 to 2520Intended useful registers.
2531000Is \count\topins (insert)
2541000Is \count\footins (insert)
25592Idk why 92. Used as scratch? \count@
Methodology
\count0 0
\loop\ifnum\count0<256
 |\ \the\count0\ |\ \the\count\count0\ |%
 \vfil
 \advance\count0 1
\repeat

Lengths

Table
Unitpt + spsppt
1in72pt + 17694sp4736286sp72.27pt
1cm28pt + 29671sp1864679sp28.45pt
1cc12pt + 55057sp841489sp12.84pt
1pc12pt + 0sp786432sp12pt
1em10pt + 0sp655360sp10pt
1ex4pt + 20024sp282168sp4.305pt
1mm2pt + 55395sp186467sp2.845pt
1dd1pt + 4588sp70124sp1.070pt
1bp1pt + 245sp65781sp1.004pt
1pt1pt + 0sp65536sp1pt
1sp0pt + 1sp1sp0.000015pt

Note the ex and em dimensions are font-specific, but code.golf only uses the one font \octet.

Methodology
\newdimen\x%
\x1in %
\newcount\pt%
\newcount\sp%
\def\div#1,#2;{%
  \def\iterate{
    \ifdim#1>\x\else%
      \advance\x-#1%
      \advance#2 1%
      \expandafter%
      \iterate%
    \fi%
  }%
  \iterate%
}%
\div 1pt,\pt;
\div 1sp,\sp;
(\the\pt pt + \the\sp sp)

Primitive commands with \the usable

Table
\command\the\command
\lineskip1.0pt
\baselineskip12.0pt
\parskip0.0pt plus 1.0pt
\abovedisplayskip12.0pt plus 3.0pt minus 9.0pt
\belowdisplayskip12.0pt plus 3.0pt minus 9.0pt
\abovedisplayshortskip0.0pt plus 3.0pt
\belowdisplayshortskip7.0pt plus 3.0pt minus 4.0pt
\leftskip0.0pt
\rightskip0.0pt
\topskip10.0pt
\splittopskip10.0pt
\tabskip0.0pt
\spaceskip0.0pt
\xspaceskip0.0pt
\parfillskip0.0pt plus 1.0fil
\thinmuskip3.0mu
\medmuskip4.0mu plus 2.0mu minus 4.0mu
\thickmuskip5.0mu plus 5.0mu
\output
\everypar
\everymath
\everydisplay
\everyhbox
\everyvbox
\everyjob
\everycr
\errhelp
\pretolerance100
\tolerance200
\linepenalty10
\hyphenpenalty50
\exhyphenpenalty50
\clubpenalty150
\widowpenalty150
\displaywidowpenalty50
\brokenpenalty100
\binoppenalty700
\relpenalty500
\predisplaypenalty10000
\postdisplaypenalty0
\interlinepenalty0
\doublehyphendemerits10000
\finalhyphendemerits5000
\adjdemerits10000
\mag1000
\delimiterfactor901
\looseness0
\time1349
\day7
\month7
\year2024
\showboxbreadth5
\showboxdepth3
\hbadness1000
\vbadness1000
\pausing0
\tracingonline0
\tracingmacros0
\tracingstats0
\tracingparagraphs0
\tracingpages0
\tracingoutput0
\tracinglostchars1
\tracingcommands0
\tracingrestores0
\uchyph1
\outputpenalty0
\maxdeadcycles25
\hangafter1
\floatingpenalty0
\globaldefs0
\fam0
\escapechar92
\defaulthyphenchar45
\defaultskewchar-1
\endlinechar13
\newlinechar-1
\language0
\lefthyphenmin2
\righthyphenmin3
\holdinginserts0
\errorcontextlines5
\parindent0.0pt
\mathsurround0.0pt
\lineskiplimit0.0pt
\hsize16000.0pt
\vsize16000.0pt
\maxdepth4.0pt
\splitmaxdepth16383.99998pt
\boxmaxdepth16383.99998pt
\hfuzz0.1pt
\vfuzz0.1pt
\delimitershortfall5.0pt
\nulldelimiterspace1.2pt
\scriptspace0.5pt
\predisplaysize0.0pt
\displaywidth0.0pt
\displayindent0.0pt
\overfullrule5.0pt
\hangindent0.0pt
\hoffset0.0pt
\voffset0.0pt
\emergencystretch0.0pt
\font
\parshape0
\prevgraf0
\toks0
\count02
\dimen00.0pt
\skip00.0pt
\muskip00.0mu
\spacefactor1000
\deadcycles0
\insertpenalties0
\lastpenalty0
\lastkern0.0pt
\lastskip3.33333pt
\inputlineno173
\badness0
\pagegoal16000.0pt
\pagetotal1438.0pt
\pagestretch121.0pt
\pagefilstretch122.0pt
\pagefillstretch0.0pt
\pagefilllstretch0.0pt
\pageshrink0.0pt
\pagedepth0.0pt
Methodology

Grep through tex.web for calls like primitive("lineskip"). Then run \f\lineskip where \def\f#1{| `\string#1` | \the#1 |\vfil}. If it doesn't give an error, then add the output to the table. If it says missing number, then add a 0 and try again.