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 and TeX for the Impatient are also good resources.
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
.
Note that all whitespaces and special characters in hole arguments are tweaked to have the category code of 12 ("other").
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
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-;
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!"
Since all whitespaces from \argv
have the category code of 12, the usual macro can't be used to parse inputs:
\def\parse#1 #2\relax{...}
\parse123 456\relax % Works
{\catcode32=12\gdef\expandedarg{123 456}} % Simulate an argument with a space
\parse\expandedarg\relax % Fails
You may want \catcode32=12
for that particular character, but that can break many things. If you can't avoid using a space in your definition, a classic trick is to use another character with the same category code by default, and then replace it with \lowercase
or \uppercase
:
\lccode`?=32
\lowercase{\def\parse#1?#2\relax{...}}
\parse\expandedarg\relax
Note that \lowercase
and \uppercase
will apply to every token in the definition that is not a control sequence, so you have to try both and/or tweak \lccode
or \uccode
to avoid an unwanted substitution.
Debugging
You may find \tracingmacros=1
, \message
, \meaning
, \show
and \showthe
indispensable for debugging complex macros.
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
As noted above, the <number> argument common to \count
and other commands only gets parsed after the first token that can't form a number and extra spaces may be required. You can often eliminate them by using hexadecimal numbers ("41
) or character code (`A
), or swapping arguments whenever possible:
\count42 100 \foo % Both spaces are required.
\count42"64 \foo
\count42`d\foo % No more space is required in general.
% However note that if `\foo` were `\the\count42`,
% This still requires one space due to the lookahead.
\ifnum\x<9 \the\x\fi % A space is required.
\ifnum9>\x\the\x\fi % No space is required.
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 |
|
Is \count@ (scratch), set via \newif among others |
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 |
varying (0..1439) |
\day |
varying (1..31) |
\month |
varying (1..12) |
\year |
varying (e.g. 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.