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
.
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!"
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.