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:
- output is through text rather than DVI or PDF, using dvi-to-text.
- none of the default fonts support characters with code greater than 127, so we use font called
\octet
with a full code page - hole arguments are pre-filled by a
\argv#1
macro, with arg count given by\argc
Spaces
Space characters can be annoying to manage. Important spacing rules:
- Single newline characters get converted to a space (double newlines are converted to
\par
), so if a bunch of spaces infiltrate your output, try removing newlines or suppressing the newlines with%
. - If you want a literal space, you sometimes have to do an escaped space
\
or a non-breaking space~
(which works only if~
has not been re-defined) - Control sequences remove the space after them, so
\f 123
is the same as\f123
. This rule exists so\f abc
does not have an unwanted space - While parsing a number literal, TeX keeps reading more tokens until it reaches a non-digit or an expandable control sequence. So
\newcount\x\x=5\f
will expand\f
before assigning 5 to\x
because it needs to make sure that\f
does not start with a digit. Add a space if you want to assign 5 to\x
before expanding\f
:\newcount\x\x=5 \f
- This is a source of pain. One way to stay safe is to reverse comparisons so that number literals are quickly terminated, e.g.
\ifnum9>\x
instead of\ifnum\x<9
.
- This is a source of pain. One way to stay safe is to reverse comparisons so that number literals are quickly terminated, e.g.
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 \let
s 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 number | Initial value | Incrementer | More |
---|---|---|---|
0 | 1 | \eject | Page numbering. |
1 to 9 | 0 | Page number. | |
10 | 25 | \newcount | Count allocation. |
11 | 15 | \newdimen | Dimen allocation. |
12 | 17 | \newskip | Skip allocation. |
13 | 9 | \newmuskip | Muskip allocation. |
14 | 15 | \newbox | Box allocation. |
15 | 11 | \newtoks | Toks allocation. |
16 | -1 | \newread | Read file allocation. |
17 | -1 | \newwrite | Write file allocation. |
18 | 7 | \newfam | Math family allocation. |
19 | 0 | \newlanguage | Language allocation. |
20 | 253 | \newinsert (-1) | Insert allocation. (count, dimen, skip are related) |
21 | 26 | The most recently allocated number. | |
22 | -1 | \countdef\m@ne=22 \m@ne=-1 % a handy constant | |
23 | 100 | Is \interdisplaylinepenalty | |
24 | 100 | Is \interfootnotelinepenalty | |
25 to 252 | 0 | Intended useful registers. | |
253 | 1000 | Is \count\topins (insert) | |
254 | 1000 | Is \count\footins (insert) | |
255 | 92 | Idk 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
Unit | pt + sp | sp | pt |
---|---|---|---|
1in | 72pt + 17694sp | 4736286sp | 72.27pt |
1cm | 28pt + 29671sp | 1864679sp | 28.45pt |
1cc | 12pt + 55057sp | 841489sp | 12.84pt |
1pc | 12pt + 0sp | 786432sp | 12pt |
1em | 10pt + 0sp | 655360sp | 10pt |
1ex | 4pt + 20024sp | 282168sp | 4.305pt |
1mm | 2pt + 55395sp | 186467sp | 2.845pt |
1dd | 1pt + 4588sp | 70124sp | 1.070pt |
1bp | 1pt + 245sp | 65781sp | 1.004pt |
1pt | 1pt + 0sp | 65536sp | 1pt |
1sp | 0pt + 1sp | 1sp | 0.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 |
---|---|
\lineskip | 1.0pt |
\baselineskip | 12.0pt |
\parskip | 0.0pt plus 1.0pt |
\abovedisplayskip | 12.0pt plus 3.0pt minus 9.0pt |
\belowdisplayskip | 12.0pt plus 3.0pt minus 9.0pt |
\abovedisplayshortskip | 0.0pt plus 3.0pt |
\belowdisplayshortskip | 7.0pt plus 3.0pt minus 4.0pt |
\leftskip | 0.0pt |
\rightskip | 0.0pt |
\topskip | 10.0pt |
\splittopskip | 10.0pt |
\tabskip | 0.0pt |
\spaceskip | 0.0pt |
\xspaceskip | 0.0pt |
\parfillskip | 0.0pt plus 1.0fil |
\thinmuskip | 3.0mu |
\medmuskip | 4.0mu plus 2.0mu minus 4.0mu |
\thickmuskip | 5.0mu plus 5.0mu |
\output | |
\everypar | |
\everymath | |
\everydisplay | |
\everyhbox | |
\everyvbox | |
\everyjob | |
\everycr | |
\errhelp | |
\pretolerance | 100 |
\tolerance | 200 |
\linepenalty | 10 |
\hyphenpenalty | 50 |
\exhyphenpenalty | 50 |
\clubpenalty | 150 |
\widowpenalty | 150 |
\displaywidowpenalty | 50 |
\brokenpenalty | 100 |
\binoppenalty | 700 |
\relpenalty | 500 |
\predisplaypenalty | 10000 |
\postdisplaypenalty | 0 |
\interlinepenalty | 0 |
\doublehyphendemerits | 10000 |
\finalhyphendemerits | 5000 |
\adjdemerits | 10000 |
\mag | 1000 |
\delimiterfactor | 901 |
\looseness | 0 |
\time | 1349 |
\day | 7 |
\month | 7 |
\year | 2024 |
\showboxbreadth | 5 |
\showboxdepth | 3 |
\hbadness | 1000 |
\vbadness | 1000 |
\pausing | 0 |
\tracingonline | 0 |
\tracingmacros | 0 |
\tracingstats | 0 |
\tracingparagraphs | 0 |
\tracingpages | 0 |
\tracingoutput | 0 |
\tracinglostchars | 1 |
\tracingcommands | 0 |
\tracingrestores | 0 |
\uchyph | 1 |
\outputpenalty | 0 |
\maxdeadcycles | 25 |
\hangafter | 1 |
\floatingpenalty | 0 |
\globaldefs | 0 |
\fam | 0 |
\escapechar | 92 |
\defaulthyphenchar | 45 |
\defaultskewchar | -1 |
\endlinechar | 13 |
\newlinechar | -1 |
\language | 0 |
\lefthyphenmin | 2 |
\righthyphenmin | 3 |
\holdinginserts | 0 |
\errorcontextlines | 5 |
\parindent | 0.0pt |
\mathsurround | 0.0pt |
\lineskiplimit | 0.0pt |
\hsize | 16000.0pt |
\vsize | 16000.0pt |
\maxdepth | 4.0pt |
\splitmaxdepth | 16383.99998pt |
\boxmaxdepth | 16383.99998pt |
\hfuzz | 0.1pt |
\vfuzz | 0.1pt |
\delimitershortfall | 5.0pt |
\nulldelimiterspace | 1.2pt |
\scriptspace | 0.5pt |
\predisplaysize | 0.0pt |
\displaywidth | 0.0pt |
\displayindent | 0.0pt |
\overfullrule | 5.0pt |
\hangindent | 0.0pt |
\hoffset | 0.0pt |
\voffset | 0.0pt |
\emergencystretch | 0.0pt |
\font | |
\parshape | 0 |
\prevgraf | 0 |
\toks0 | |
\count0 | 2 |
\dimen0 | 0.0pt |
\skip0 | 0.0pt |
\muskip0 | 0.0mu |
\spacefactor | 1000 |
\deadcycles | 0 |
\insertpenalties | 0 |
\lastpenalty | 0 |
\lastkern | 0.0pt |
\lastskip | 3.33333pt |
\inputlineno | 173 |
\badness | 0 |
\pagegoal | 16000.0pt |
\pagetotal | 1438.0pt |
\pagestretch | 121.0pt |
\pagefilstretch | 122.0pt |
\pagefillstretch | 0.0pt |
\pagefilllstretch | 0.0pt |
\pageshrink | 0.0pt |
\pagedepth | 0.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.