>_PRIMITIVE REFERENCE
Every primitive texrs carries, what it does, and where it happens — at compile time while lowering, or at run time on the VM. A primitive not on this page is not implemented; BUGS.md says so explicitly for the ones that are commonly reached for.
Category codes
| Primitive | What it does | Syntax and example |
|---|---|---|
\catcode | Set or read a character's category code. A character's category is a mutable table entry, not a property of the language: texrs starts from INITEX's sparse defaults, where { is an ordinary character until something makes it a group opener. Takes effect at COMPILE time, because it changes how the rest of the file reads. | \catcode`\X=N
\catcode`\{=1 \catcode`\}=2 \catcode`\#=6
\message{now braces group} |
^^X | Control-character notation (tex.web §352), in two forms decided by what follows: ^^ and two LOWERCASE hex digits is that hex code, while ^^ and anything else is one character shifted by 64. So ^^41 is A and ^^4a is J, but ^^4A is tA — A is not a lowercase hex digit, so the shift applies to the 4 alone. The substitution belongs to the input processor (§353), which runs before anything is classified, so it applies inside a control sequence name too: plain.tex writes \catcode\^^K=7. This is also how a line end is written inside a macro body. | ^^M % carriage return ^^I % tab ^^41 % the hex form: A |
` | A character code as a number, for a register assignment or a comparison: a backtick followed by a character is that character's code, and the backslash before the character is optional unless it is a control sequence. | \count1=`\A
\message{\the\count1} % => 65 |
Macro definition
| Primitive | What it does | Syntax and example |
|---|---|---|
\def | Define a macro. Parameters may be undelimited (#1) or delimited (\def\pair#1,#2.{...}, where the argument is whatever precedes the delimiter), and ## is a literal parameter character. The parameter text is validated as tex.web §476 validates it: every # must be followed by a digit, and the digits must run consecutively. Takes effect at COMPILE time. The name may be an ACTIVE CHARACTER as well as a control sequence (tex.web §1215): \catcode\~=13 \def~{...} defines ~ itself, and an active ~ and the control sequence \~ stay different things. | \def\name<parameter text>{<body>}
\def\greet#1{HELLO-#1}
\def\pair#1,#2.{[#1|#2]}
\message{\greet{WORLD}} % => HELLO-WORLD
\message{\pair 1,2.} % => [1|2] |
\gdef | Define globally: the definition survives the enclosing group, where a \def inside braces is undone at the closing brace. | \gdef\name{<body>}
{\gdef\v{IN}}
\message{\v} % => IN |
\edef | Define with the body expanded NOW, so a register read is frozen at definition time and a later assignment cannot move it. tex.web §366 expands the body while READING it, which decides a conditional in it there and turns \the into §478's characters. texrs does that whenever it can prove the registers the body reads — a register nothing has assigned yet is at INITEX's zero, which is what the compiler writes into every slot — and otherwise falls back to snapshotting the \the\count read into a scratch register (taken from the top of the count range) and carrying the conditional. The fallback is the case BUGS.md records: a conditional over a register the program HAS written is still decided at use time. | \edef\name{<body>}
\count1=1
\edef\frozen{\the\count1}
\count1=2
\message{\frozen} % => 1
\edef\case{\ifcase\count2 ZERO\else OTHER\fi}
\count2=5
\message{\case} % => ZERO |
\xdef | A global \edef: the body is expanded now and the definition survives the enclosing group. | \xdef\name{<body>}
{\xdef\frozen{\the\count1}} |
\let | Give a control sequence another's CURRENT meaning, not a reference to it: redefining the source afterwards does not change the alias. | \let\alias=\source
\def\v{ONE}
\let\w=\v
\def\v{TWO}
\message{\w} % => ONE |
\futurelet | Look one token past the next without eating either: \futurelet\a\b\c gives \a the meaning of \c and then puts \b and \c back, so the stream is exactly as it was (tex.web §1221). That non-destructive peek is what LaTeX's \@ifnextchar is built from, and so is every optional argument in the language. A control sequence let to a character MEANS that character, so \ifx\next[ compares true — which is the comparison the whole idiom rests on. | \futurelet\next<token><token>
\def\peek{\futurelet\next\decide}
\def\decide{\ifx\next[ OPTIONAL\else PLAIN\fi} |
\global | Prefix making the following assignment global, so it survives the enclosing group. | \global\count1=5
\global\def\v{OUT} |
\long | A definition prefix: the macro's arguments may contain \par. Without it a paragraph break inside an argument is a runaway, which is TeX's guard against a missing closing brace swallowing the rest of a document. texrs records the prefix and does not yet enforce the restriction it lifts. | \long\def\note#1{[#1]} |
\outer | A definition prefix: the macro may not then appear in an argument, in a group being scanned as text, or in skipped conditional text. It is an error-detection feature -- plain.tex marks its sectioning macros \outer so a missing brace is caught at the next section rather than at the end of the file. texrs records the prefix and does not police the restriction, so a use tex forbids is accepted here; tests/cases/outer_forbidden_use.tex pins the difference. Prefixes are part of the meaning, so \ifx tells a prefixed definition from a bare one. | \outer\def\chapter{...} |
\protected | A definition prefix: the macro does not expand inside an \edef, it survives as itself and runs when the result does. Redefining it afterwards therefore changes what the \edef'd macro produces, which is the observable difference. An eTeX primitive, and the one LaTeX leans on to keep a fragile command safe in a moving argument. | \protected\def\note#1{[#1]} |
Expansion
| Primitive | What it does | Syntax and example |
|---|---|---|
\csname | Build a control sequence out of the characters up to \endcsname, so a macro can name another macro. A name with no meaning becomes \relax, as in tex. | \csname <characters>\endcsname
\def\greeting{HI}
\def\name{greeting}
\message{\csname \name\endcsname} % => HI |
\endcsname | Terminate a \csname. It is an error to reach one without an open \csname. | \csname foo\endcsname |
\string | Print a control sequence as text, escape character included — the inverse of \csname. | \string\cs
\message{\string\undefined} % => \undefined |
\meaning | What a token MEANS, as characters (tex.web §296's print_meaning). A macro reads back as macro: then its parameter text, -> and its body, with \long, \outer and \protected run together in front; a \chardef constant reads back as \char"41; a register name reads back as the register it stands for; a character carries the NAME of its category, so \meaning A is the letter A. A name nothing has defined is undefined. Expandable, like \string, so it works inside a \message. | \meaning<token>
\def\a#1{[#1]}
\message{\meaning\a} % => macro:#1->[#1] |
\uppercase | Read the following group WITHOUT expanding it, replace every character by its \uccode and put the result back to be read again (tex.web §1288). A zero code means the character has no case and stands, so digits and punctuation come through untouched, and the category code travels with the character unchanged — which is what LaTeX's \MakeUppercase is built on. | \uppercase{<text>}
\uppercase{\message{abc 123}} % => ABC 123 |
\lowercase | The same as \uppercase over the \lccode table: the group is read unexpanded, every character is replaced by its lowercase code, and the result is read again with its category codes intact. | \lowercase{<text>}
\lowercase{\message{ABC dEf}} % => abc def |
\the | The value of a register, as characters. The read happens at RUN time on the VM, which is why a \message containing \the\count0 follows a later assignment. | \the\count<N>
\count1=12
\message{count=\the\count1} % => count=12 |
\number | A scanned number, as characters, with no leading zeros or plus sign. The scan keeps reading (and expanding) until something that cannot be part of a number stops it, so a following letter needs a terminating space. | \number<number>
\message{\number0042} % => 42 |
\expandafter | One token of lookahead: hold the next token back, expand what follows it once, then put the held token in front of the result. | \expandafter<token><token>
\def\a{\b}
\def\b{DEEP}
\message{\expandafter\string\a} % => \b |
\noexpand | Suppress expansion of the next token for one step — the token is used for its own sake rather than its meaning. | \noexpand<token>
\edef\keep{\noexpand\later} |
\message | Write to the terminal. This is texrs's parity contract for the milestone: two host builtins, one appending a rendered piece and one flushing the assembled string, so a message reads its registers at run time. | \message{<text>}
\message{HELLO-WORLD} % => (./file.tex HELLO-WORLD ) |
\relax | Do nothing. Accepted so a document can stop a number scan, or fill a slot that needs a token but no action — which is also what \csname makes of a name with no meaning. | \relax |
\par | End a paragraph. A blank line produces one, which is why the line scanner tracks its state, and in a typesetting run it reaches the page: the breaker starts a fresh paragraph, so the line before it ends where the paragraph does however short that leaves it. | \par |
\ignorespaces | Skip the spaces that follow (tex.web §1060). It is a mouth-level effect: the spaces are read and dropped, so what comes next is the first non-blank token. That is the whole of it here — the horizontal-mode half needs a stomach. | \ignorespaces
\def\a{\ignorespaces}
\a X % the spaces before X are gone |
\end | Stop the run, and ship what it built. A document that set text comes out as Output written on NAME.pdf (N pages, N bytes).; one that only sent messages reports No pages of output., which is what real tex says for every case in the parity corpus and why the contract for those is still the \message stream. | \end |
\detokenize | The tokens of {...} written as text, by the token-list rule: a control word carries a trailing space, a one-character control sequence does not. Nothing in the group expands. An eTeX primitive. | \message{\detokenize{\a b}} % => \a b |
\csstring | \string without the escape character: \csstring\foo is foo where \string\foo is \foo. A LuaTeX primitive. | \message{\csstring\foo} % => foo |
\Uchar | The character with the given code: \Uchar65 is A. A LuaTeX primitive, and the one that reaches past 255 -- texrs reads characters rather than bytes, so it carries the whole range. | \message{\Uchar65\Uchar97} % => Aa |
\expanded | Expand the group's contents completely, here and now, and put the result back. Inside an \edef it is the wrapper coming off, since everything there expands anyway; in running text it forces the expansion a macro would otherwise have deferred. An eTeX primitive. | \message{\expanded{\body}} |
\unexpanded | The opposite: the group's tokens are used as they stand. Inside an \edef they survive as TOKENS, so a macro among them is called when the body runs rather than when it is defined -- which is where this and \expanded part company. In a message the two render alike, by the token-list rule. An eTeX primitive. | \edef\keep{\unexpanded{\later}} |
\begincsname | \csname that does not define what it does not find: an unknown name expands to nothing, where \csname would make it \relax and leave it defined ever after. A LuaTeX primitive, and the one that makes "is this defined?" answerable without changing the answer. | \begincsname maybe\endcsname |
Conditionals
| Primitive | What it does | Syntax and example |
|---|---|---|
\ifnum | Compare two numbers with <, = or >. It tests run-time state, so it lowers to a real branch — a comparison plus a jump — rather than a decision taken while walking a tree. | \ifnum<number><rel><number> <true>\else <false>\fi
\count1=5
\message{\ifnum\count1>3 BIG\else SMALL\fi} % => BIG |
\ifodd | True when a number is odd. Reads run-time state, so it lowers to a real branch rather than folding. Negative odd numbers are odd: the test is on the remainder being non-zero, not on its sign. | \ifodd<number> <true>\else <false>\fi
\message{\ifodd\count1 ODD\else EVEN\fi} |
\ifcase | Switch on a number: the first case is 0, each \or starts the next, and \else catches everything past the last. Lowers to a real branch. A selector past the last \or with no \else selects nothing and the run continues. DIVERGENCE: a NEGATIVE selector takes case 0 here where tex takes \else — \ifcase -1 ZERO\else DEFAULT\fi prints ZERO rather than DEFAULT. Pinned by tests/cases/cond_ifcase_negative.tex. | \ifcase<number> <0>\or <1>\or <2>\else <other>\fi
\count1=2
\message{\ifcase\count1 ZERO\or ONE\or TWO\else MANY\fi} % => TWO |
\iftrue | Constant truth. Decidable without the VM, so it is FOLDED while lowering and the untaken arm is never emitted — it does not merely jump over that code, it never emits it. This is why a \def inside the untaken arm of an \iftrue is safe where the same \def inside a run-time conditional is not; see def_in_conditional_arm.tex., so it is FOLDED while lowering and the untaken arm is never emitted. | \iftrue <true>\else <false>\fi |
\iffalse | Constant falsity. Folded while lowering, like \iftrue, so the taken arm is the only code that reaches the chunk. | \iffalse <true>\else <false>\fi |
\ifx | Compare two meanings: two macros are equal when their parameter texts and bodies match, and two primitives when they are the same primitive. Decidable from the macro table alone, so it is folded while lowering. | \ifx<token><token> <true>\else <false>\fi
\def\a{X}\def\b{X}
\message{\ifx\a\b SAME\else DIFF\fi} % => SAME |
\if | Compare two character codes after expansion. A control sequence compares equal to any OTHER control sequence, because neither has a character code to compare — that is tex.web §506's rule, not a shortcut. Both sides are expanded first, so \if\a\b tests what the macros produce, not their names. | \if<token><token> <true>\else <false>\fi |
\ifdefined | True when a control sequence has a meaning. Decidable from the macro table alone, so it is FOLDED while lowering and only the taken arm is emitted. An undefined name is false rather than an error, which is the one place an undefined control sequence is not a divergence here. | \ifdefined<token> <true>\else <false>\fi |
\else | Start the false arm of a conditional. Both arms are collected as token runs and lowered, so the untaken one is real code the VM jumps over rather than a subtree nobody walked. | \else |
\or | Start the next case of an \ifcase: the case before the first \or is 0, and each \or moves to the next. | \or |
\fi | End a conditional. A control word swallows the space after it, so \fi X prints X with no leading space. | \fi |
\ifcat | RECOGNISED BUT NOT EVALUATED. Compares category codes; texrs skips the construct correctly so an unbalanced branch cannot confuse the scanner, but cannot decide it. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifcat<token><token> <true>\else <false>\fi |
\ifdim | Compare two dimensions. tex.web §503 shares the comparison with \ifnum and differs only in the scanner, and a dimension is an integer in a slot here, so this lowers to the same run-time branch \ifnum does rather than being recognised and skipped. | \ifdim<dimen><rel><dimen> <true>\else <false>\fi
\dimen1=2pt
\ifdim\dimen1>1pt \message{[WIDER]}\else \message{[NARROWER]}\fi % => [WIDER] |
\ifvoid | Is box register N empty? The register number is read as tex.web §433 reads one, and the answer is that the register is VOID — which is what it is: texrs has no \setbox, §462's box(n) is null for a register nothing has filled, and all 256 of them are in that state. So this is decided while lowering, in running text and inside a \message alike, and agrees with tex for every document that fills no box. The reference tex loads plain.tex, which fills \box0 and puts an \hbox in \box11, so those two disagree the way \count0 does. | \ifvoid<N> <true>\else <false>\fi
\ifvoid200 \message{[EMPTY]}\else \message{[FULL]}\fi % => [EMPTY] |
\ifhbox | Does box register N hold an \hbox? The register number is read as tex.web §433 reads one, and the answer is that the register is VOID — which is what it is: texrs has no \setbox, §462's box(n) is null for a register nothing has filled, and all 256 of them are in that state. So this is decided while lowering, in running text and inside a \message alike, and agrees with tex for every document that fills no box. The reference tex loads plain.tex, which fills \box0 and puts an \hbox in \box11, so those two disagree the way \count0 does. | \ifhbox<N> <true>\else <false>\fi
\ifhbox200 \message{[H]}\else \message{[NO]}\fi % => [NO] |
\ifvbox | Does box register N hold a \vbox? The register number is read as tex.web §433 reads one, and the answer is that the register is VOID — which is what it is: texrs has no \setbox, §462's box(n) is null for a register nothing has filled, and all 256 of them are in that state. So this is decided while lowering, in running text and inside a \message alike, and agrees with tex for every document that fills no box. The reference tex loads plain.tex, which fills \box0 and puts an \hbox in \box11, so those two disagree the way \count0 does. | \ifvbox<N> <true>\else <false>\fi
\ifvbox200 \message{[V]}\else \message{[NO]}\fi % => [NO] |
\ifvmode | RECOGNISED BUT NOT EVALUATED: modes belong to the stomach. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifvmode <true>\else <false>\fi |
\ifhmode | RECOGNISED BUT NOT EVALUATED: modes belong to the stomach. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifhmode <true>\else <false>\fi |
\ifmmode | RECOGNISED BUT NOT EVALUATED: modes belong to the stomach. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifmmode <true>\else <false>\fi |
\ifinner | RECOGNISED BUT NOT EVALUATED: modes belong to the stomach. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifinner <true>\else <false>\fi |
\ifeof | RECOGNISED BUT NOT EVALUATED: there is no file I/O yet. Reaching one stops the run with ! Unsupported conditional \NAME. and exit status 1; SKIPPING one inside an untaken branch is correct, because the skipper counts it for nesting. | \ifeof<N> <true>\else <false>\fi |
\ifcsname | Is the name built from these characters defined? etex.ch's if_cs_code reads them exactly as \csname does and then looks the name up with no_new_control_sequence still true, so a name it does not find is NOT entered — which is the whole difference from \csname, whose lookup DEFINES what it misses as \relax (tex.web §372) and so changes the answer for every later ask. Decided while lowering, in running text and inside a \message body alike, because the macro table is a frontend fact. LaTeX's \@ifundefined is built on this. | \ifcsname <characters>\endcsname <true>\else <false>\fi
\def\foo{F}
\ifcsname foo\endcsname \message{[YES]}\else \message{[NO]}\fi % => [YES]
\ifcsname nope\endcsname \message{[YES]}\else \message{[NO]}\fi % => [NO], twice over |
\unless | Negate the conditional that follows: \unless\ifnum 1>2 A\else B\fi runs A. An eTeX primitive. A negated conditional is the same conditional with its arms exchanged, which is how it is lowered. | \unless\ifnum \count0>2 few\else many\fi |
Registers
| Primitive | What it does | Syntax and example |
|---|---|---|
\count | A count register. There are exactly 256 of them (tex.web §236) and they map onto VM slots 0..255, so a read is an array index rather than a hash lookup. texrs loads no format, so every register starts at zero as INITEX leaves them — where the reference tex has plain's values, including \count0 = the page number. | \count<N>=<number>
\count1=7
\message{\the\count1} % => 7 |
\advance | Add to a register. Lowers to GetSlot / LoadInt / Add / SetSlot — native ops the JIT can compile. | \advance\count<N> by <number>
\count1=7 \advance\count1 by 5
\message{\the\count1} % => 12 |
\multiply | Multiply a register. Like the other arithmetic, it lowers to native fusevm ops on the register's slot rather than a call. | \multiply\count<N> by <number> \count1=7 \multiply\count1 by 3 % => 21 |
\divide | Divide a register, truncating toward zero as TeX's does. | \divide\count<N> by <number> \count1=7 \divide\count1 by 2 % => 3 |
\chardef | Define a control sequence that IS a number: \chardef\active=13 makes \active usable wherever a number is scanned, which is how plain.tex writes \catcode\~=\active. The code is 0..255 and a wider one is ! Bad character code (N).. The value is fixed when it is defined, so it folds while lowering rather than being read at run time. | \chardef\active=13 \catcode`\~=\active |
\countdef | Give a count register a name, usable in every position the register itself is: assignment, \advance and \the all reach the same register through either spelling. plain.tex's \pageno is \countdef\pageno=0. The register number is 0..255 and a wider one is ! Bad register code (N).. | \countdef\pageno=0 \pageno=7 \advance\pageno by 1 |
\mathcode | How a character is set in math mode, as a 15-bit code: class, family and position. INITEX gives a letter §7100+c, a digit §7000+c and everything else its own code, so \mathcode\A is "7141 and \mathcode\+ is 43. Written and read like \catcode, and restored at the end of a group the same way. | \mathcode`\x="2201 |
\lccode | A character's lowercase form, which is what \lowercase consults. INITEX sets it for the letters and leaves it 0 for everything else -- a character with no case is not lowercased to a null, it is left alone. | \lccode`\A=`\a |
\uccode | The same for uppercase, consulted by \uppercase. Also 0 for a character with no case. | \uccode`\a=`\A |
\sfcode | The space factor a character leaves behind, which stretches the space after it. INITEX gives every character 1000 except an uppercase letter, which gets 999 -- that is what stops a sentence appearing to end at the full stops in "N.A.S.A.". | \sfcode`\A=999 |
\delcode | A character's meaning as a delimiter, as a 24-bit code naming a small and a large variant. INITEX gives -1 everywhere, meaning "not a delimiter", except the period, whose code is 0. | \delcode`\(="161361 |
\mathchardef | Define a control sequence standing for a math code, as \chardef does for a character code. The range runs to "7FFF and a wider value is ! Bad mathchar (N).. | \mathchardef\half="2201 \mathcode`\y=\half |
" | A hexadecimal constant, and ' an octal one (tex.web \u{a7}445). The hex digits are UPPERCASE: "FF is 255 and "ff is an error -- the opposite of ^^ notation, which takes lowercase. plain.tex writes every \mathcode as a hexadecimal constant. | \count1="FF % 255 \count2='777 % 511 |
\dimen | A dimension register. A dimension is an integer count of scaled points, 65536 to the printer's point, and a unit is an exact integer ratio to a point (tex.web §458) rather than a float -- which is why 1in is 72.26999pt. The units are pt, in, pc, cm, mm, bp, dd, cc and sp. \the writes one back by Knuth's print_scaled (§103), the fewest digits that read back as the same integer, and \number gives the scaled points instead. | \dimen0=1in
\message{\the\dimen0} % => 72.26999pt |
\dimendef | Give a dimension register a name, as \countdef does for a count. The name behaves as the register does on both sides: an assignment through it reads a dimension, and \the through it writes one. | \dimendef\dimen@=0 \dimen@=2pt |
\skip | A glue register: a natural dimension that can stretch and shrink. The stretch and shrink may be infinite -- fil, fill, filll -- and an infinite component beats any finite one however large, which is what \hfil is made of. \the writes the components back with plus and minus, omitting a zero one, and \number gives the natural component alone. | \skip0=1pt plus 2pt minus 3pt \skip1=0pt plus 1fil |
\skipdef | Give a glue register a name, as \countdef and \dimendef do for theirs. An assignment through the name reads a whole glue, not the dimension at the front of one. | \skipdef\skip@=0 \skip@=1pt plus 2pt |
\muskip | A MATH glue register: the same natural-plus-stretch-minus-shrink a \skip holds, measured in mu. tex.web §455 makes mu the only finite unit a math glue may be written in, and the only place mu is a unit at all — \muskip0=1pt and \dimen0=1mu are both ! Illegal unit of measure. The arithmetic is a point's: a mu is 65536ths, so \advance, \multiply and a group's save and restore are the ones \skip already had. An infinite component is still fil, fill or filll, in either kind of glue. | \muskip0=3mu plus 1mu minus 2mu
\message{[\the\muskip0]} % => [3.0mu plus 1.0mu minus 2.0mu] |
\muskipdef | Give a math glue register a name, as \skipdef does for an ordinary one. The name stands for the register everywhere the spelt-out form works, and only where a math glue is wanted: a \muskipdef name is not accepted as the source of a \skip assignment, nor the other way about. | \muskipdef\thinmuskip=0 \thinmuskip=3mu |
\toks | A token register: a token list stored VERBATIM, since nothing inside the braces expands -- which is the difference between it and a macro, and why \toks0={\x} reads back as \x whatever \x means. \toks1=\toks0 copies one register to another. \the writes the list back by the token-list rule rather than \string's: a control word carries a trailing space however short, a one-character control sequence does not. | \toks0={a\b c}
\message{\the\toks0} % => a\b c |
\toksdef | Give a token register a name, as \countdef does for a count. The list is frontend state like a macro body rather than a number in a slot, so the name stands for the register itself. | \toksdef\toks@=0
\toks@={...} |
\numexpr | An integer expression, closed by an optional \relax: +, -, *, / with ordinary precedence and parentheses. Division ROUNDS, half away from zero, so \numexpr 7/2 is 4 where \divide gives 3 -- the two are different operations and texrs keeps them apart. An eTeX primitive, so the oracle for it is LuaTeX rather than tex; tests/etex.rs holds the comparison. | \count0=\numexpr (2+3)*4\relax % => 20 |
\dimexpr | The same for dimensions: the operands are lengths and the multiplier and divisor are integers, so \dimexpr 1pt*3 is three points. The arithmetic happens in scaled points, which is the only form a dimension has. | \dimen0=\dimexpr 1pt+2pt\relax % => 3.0pt |
\glueexpr | A glue expression, the same grammar as \numexpr over glue. Addition is componentwise with TeX's order rule -- an infinite component beats a finite one however large, a higher infinity beats a lower, and only equal orders add -- while * and / scale every component. An eTeX primitive. The operands are glue WRITTEN OUT; a register standing where one is wanted is not read yet. | \skip0=\glueexpr 1pt plus 2pt+3pt plus 4fil\relax |
\muexpr | The same expression grammar over MATH glue, and the only kind of expression a \muskip assignment accepts — eTeX gives each unit its own primitive so the two cannot be mixed. Its operands are written in mu, and like \glueexpr's they are glue written out rather than registers. | \muskip0=\muexpr 3mu plus 1mu + 2mu plus 2mu\relax % => 5.0mu plus 3.0mu |
Grouping
| Primitive | What it does | Syntax and example |
|---|---|---|
{ | Open a group (category code 1), which scopes the macro table AND the count registers written inside it: the lowered body is wrapped in save/restore for exactly the registers it assigns. It also delimits a macro argument. | {<body>}
\def\v{OUT}
{\def\v{IN}\message{\v}} % => IN
\message{\v} % => OUT |
} | Close a group (category code 2), undoing every non-global assignment made inside it. | {<body>} |
\begingroup | Open a group without braces, scoping the macro table and the registers written inside it. It must be closed by \endgroup, not by }. | \begingroup <body>\endgroup |
\endgroup | Close a \begingroup, undoing every non-global assignment made since it. A } will not close one, and neither will the end of the file. | \begingroup <body>\endgroup |
\aftergroup | Hold the next token and insert it after the enclosing group closes (tex.web §326). It goes on the SAVE STACK rather than in a list of its own, which is what makes several in one group come back in the order they were given and a nested group's tokens come out at ITS } — so a whole call can be assembled a token at a time. Outside every group there is nothing to wait for and the token is inserted at once. | \aftergroup<token>
{\aftergroup\message\aftergroup{\aftergroup X\aftergroup}} % => X |
\afterassignment | Hold the next token until the following ASSIGNMENT has finished (tex.web §1269). One only: a second \afterassignment before the assignment replaces the first. A \message is not an assignment and neither is a group, so the token waits for the next \def, \let, register write or arithmetic and is inserted after it. | \afterassignment<token>
\def\m{\message{DONE}}
\afterassignment\m \count1=5 % => DONE, with \count1 already 5 |
Intercepts
| Primitive | What it does | Syntax and example |
|---|---|---|
\intercept | Register advice on macro expansion: before puts the handler's body in front of the expansion, after puts it behind, and around replaces it with \proceed standing for what the macro would have expanded to. The pattern is a GLOB over macro names, so advice registered now catches macros a package defines later. The handler is a macro that takes no parameters. Like \def, this takes effect at compile time and is undone by the group it was registered in. | \intercept{before|after|around}{<glob>}{\handler}
\def\greet#1{HELLO-#1}
\def\trace{[in]}
\intercept{before}{greet}{\trace}
\message{\greet{WORLD}} % => [in]HELLO-WORLD |
\proceed | Inside an around handler, what the intercepted macro would have expanded to. A handler with no \proceed replaces the call outright, which is how advice suppresses one. Outside an around handler it means nothing. | \def\loud{<<\proceed>>}
\intercept{around}{greet}{\loud}
\message{\greet{WORLD}} % => <<HELLO-WORLD>> |
Inline Rust
| Primitive | What it does | Syntax and example |
|---|---|---|
\rust | Open a block of Rust compiled and loaded at run time. The body is Rust, not TeX — it is lifted out of the file BEFORE the mouth reads it, because #, {, } and & are category codes the mouth would act on. Every #[no_mangle] pub extern "C" function the block exports becomes callable with \rustcall. Needs rustc on PATH; the compiled library is cached by body hash, so a second run does not compile it again. | \rust{ <rust source> }
\rust{
#[no_mangle]
pub extern "C" fn twice(n: i64) -> i64 { n * 2 }
} |
\rustcall | Call a function a \rust block exported. The name runs to the first space, the arguments are numbers, and \endrust ends the list. It is a NUMBER wherever TeX reads one — a register assignment, an arithmetic operand, a conditional, or a \message body — and in running text it is called for its effect with the value dropped. | \rustcall <name> <numbers…>\endrust
\count1=21
\message{\rustcall twice \count1 \endrust} % => 42
\count2=\rustcall add \count1 22 \endrust |
\rustcompile | What a \rust{ … } block becomes: compile and register the block whose base64 body follows, up to \endrust. Written by the desugarer rather than by hand, and carried in a brace-free form so it reads correctly whatever the category codes are where the block appeared. | \rustcompile <base64>\endrust |
\endrust | Terminate a \rustcompile body or a \rustcall argument list. A control sequence rather than a brace, so neither form depends on a category code the document may not have set yet. | \rustcall twice 21\endrust |
Files
| Primitive | What it does | Syntax and example |
|---|---|---|
\input | Read another file here, sharing every piece of state with it: a macro it defines is defined afterwards, and a \catcode it sets stays set. The name runs to the first space or end of line (tex.web §537) and .tex is supplied when it carries no extension. texrs searches the working directory and then TEXINPUTS, and does not shell out to kpsewhich, so running a document never depends on a TeX Live installation being present. Fifteen text input levels are allowed, counting the document's own, which is tex's limit and tex's wording when it is passed. | \input macros \input chapters/one.tex |
LaTeX
| Primitive | What it does | Syntax and example |
|---|---|---|
\newcommand | Define a macro with n positional parameters: \newcommand{\x}[2]{#1 and #2}. LaTeX writes this as a chain of \ifnum...\def, which cannot run here because lowering emits both arms of a conditional, so texrs dispatches on the argument count natively instead. Two divergences from latex.ltx: redefining an existing name is allowed rather than an error, and the [default] form's default is recorded but not yet substituted at a call that omits the bracket. | \newcommand{\NAME}[ARGC][DEFAULT]{BODY}
\newcommand{\greet}[1]{hello #1}
\message{\greet{world}} % => hello world |
\renewcommand | Redefine a macro. Identical to \newcommand here, because texrs does not check whether the name already exists in either direction. | \renewcommand{\NAME}[ARGC]{BODY}
\newcommand{\x}{one}
\renewcommand{\x}{two}
\message{\x} % => two |
\providecommand | Define a macro only if the name is free. An existing definition is kept and the new body is consumed rather than left in the document as text. | \providecommand{\NAME}[ARGC]{BODY}
\newcommand{\x}{first}
\providecommand{\x}{second}
\message{\x} % => first |
\DeclareRobustCommand | Define a macro. Robustness is a property of LaTeX's expansion-in-moving-arguments machinery, which has no counterpart here, so this behaves exactly as \newcommand. | \DeclareRobustCommand{\NAME}[ARGC]{BODY}
\DeclareRobustCommand{\x}{text}
\message{\x} % => text |
\documentclass | Consumed, with its arguments, and produces nothing except the page. A class is TeX that builds boxes, and nothing here runs it — the boxes on the page come from this engine's own layout rather than from the class, so the directive is read and dropped — which is what lets the REST of the document be read instead of the run failing at line one. The TYPE SIZE among its options is kept, because it needs no class to honour: [11pt] sets the text at 11pt on the 13.6pt leading size11.clo pairs with it, and --pdf sets the page from that. | \documentclass[OPTIONS]{CLASS}
\documentclass[12pt]{article}
\message{the body still runs} |
\usepackage | Consumed with its arguments, producing nothing, for the same reason as \documentclass: the package cannot be loaded, and dropping it reads the document minus whatever the package would have drawn. One exception: [margin=...]{geometry} is a page rather than a drawing, and --pdf sets the margins, the measure and the text height from it. | \usepackage[OPTIONS]{PACKAGE}
\usepackage[margin=0.95in]{geometry} |
\RequirePackage | Consumed with its optional arguments, producing nothing. Same treatment as \usepackage; the difference between them is where LaTeX allows each, which does not matter to an engine that loads neither. | \RequirePackage[OPTIONS]{PACKAGE}
\RequirePackage{amsmath} |
\PassOptionsToPackage | Consumed with both of its arguments, producing nothing, because the package it would have carried options to is never loaded. | \PassOptionsToPackage{OPTIONS}{PACKAGE}
\PassOptionsToPackage{dvipsnames}{xcolor} |
\PassOptionsToClass | Consumed with both of its arguments, producing nothing. The class counterpart of \PassOptionsToPackage. | \PassOptionsToClass{OPTIONS}{CLASS}
\PassOptionsToClass{a4paper}{article} |
\begin | Open an environment. Most environments are the prelude's business, but a VERBATIM one is the lowerer's: it is caught here, before \begin expands, because expanding is exactly what must not happen to the body. A code listing is full of backslashes that are not control sequences, and reading them as control sequences is why a book of code samples could not be read at all. The body is taken as raw characters up to the matching \end. | \begin{ENVIRONMENT}
\begin{verbatim}
\this is text, not a command
\end{verbatim} |
\newpage | Start a new page. The prelude defined this to expand to nothing, so a book's title page, copyright page and first chapter ran together as one stream of prose and the page count came out at roughly half what the document asks for. Carried through the text as a form feed, which is what the character means, and split out before words are because Rust counts it as whitespace. | \newpage first page \newpage second page |
\clearpage | Start a new page, after placing any pending floats. There are no floats here, so it is \newpage. Two breaks in a row are one break: \clearpage straight after \newpage does not leave a blank sheet between them. | \clearpage |
\cleardoublepage | Start a new page, and in a two-sided document a new RIGHT-hand one. The blank verso a two-sided run would insert is not written, so this is \clearpage here. | \cleardoublepage |
\pagebreak | Break the page. \pagebreak[0-4] takes an optional strength, which is advice about how badly the break is wanted rather than a break itself; the break is taken either way, because the document asked for one. | \pagebreak \pagebreak[4] |
\chapter | Begin a chapter, on a new page. The prelude defined this as its own argument -- the heading text and nothing else -- so no chapter began a page. \chapter*{...} is the unnumbered form and \chapter[short]{long} carries a running-head title; both start a page and both set the long title. The heading is not yet set in a larger face, and the number is not printed. | \chapter{TITLE}
\chapter*{Preface}
\chapter[Short]{A Longer Title} |
\section | Begin a section: the heading text, with a line of vertical space above it and a line below. The prelude defined this as its own argument, so a heading was simply the first words of the paragraph under it and the page held lines that a real run spends on white space. \section*{...} and \section[short]{long} are read the same way as \chapter's. The number is not printed and the heading is not yet set in a larger face. | \section{TITLE}
\section*{Unnumbered}
\section[Short]{A Longer Title} |
\subsection | Begin a subsection. Set exactly as \section is -- its own lines, with vertical space around them -- because nothing here sets a heading in a different size yet, and the depth of a heading is otherwise invisible on the page. | \subsection{TITLE}
\subsection*{Unnumbered} |
\subsubsection | Begin a subsubsection, set as \section and \subsection are: its own lines, with a line of vertical space above and below. | \subsubsection{TITLE}
\subsubsection*{Unnumbered} |
\tableofcontents | Set a table of contents: one line per heading, with leader dots and the page it starts on. The prelude expanded this to nothing, so every book in the corpus opened straight on its first chapter and was short by the pages a contents takes. The page numbers cannot be known when this is read -- the contents is set before the chapters it lists, and it moves them itself -- so what goes into the text here is a REQUEST, and the typesetter breaks and paginates the document repeatedly until the numbers stop moving, which is what running latex twice does with the .aux file. | \setcounter{tocdepth}{0}
\tableofcontents
\chapter{First} |
\setcounter | Set a counter. Both arguments are read and nothing is set, as the prelude's stub did, with one exception: tocdepth says how deep \tableofcontents lists, 0 for chapters alone and one more for each step down, and it is read here because a contents two levels deeper lists some hundreds of headings a document never asked for. The class default is 2. | \setcounter{tocdepth}{0}
\setcounter{secnumdepth}{-1} |
\endtitlepage | End a title page, which \end{titlepage} runs. It is \newpage, and then \setcounter{page}{1} unless the class is two-sided (extreport.cls, which every book in the corpus loads): the cover sheet is not one of the document's numbered pages, so every number the contents prints is one less than the sheet it stands on. The command is handed on rather than consumed, because an \end... is also what closes a \centering region and a title page is built out of centred pieces. | \begin{titlepage}
cover
\end{titlepage} |
\label | Name the place this stands, so that \ref and \pageref can point at it. The prelude read the key and produced nothing, which is right -- a label is a name for a place rather than text -- but nothing recorded the place either, so neither reference could be answered. What goes into the text here is the key, marked, and the typesetter reads back which sectioning unit it stands in and which page it fell on. | \chapter{First}\label{ch:one} |
\ref | Set the number of the sectioning unit the label stands in: 1 for the first chapter, 2.1 for the first section of the second. The prelude expanded this to nothing, so see chapter \ref{ch:one} set as see chapter. The number cannot be known where the reference is written -- the unit may not have been read yet -- so the typesetter resolves it, counting chapters and sections the way the class does. A label the document never declared sets ??, as LaTeX's own \@setref does, because a missing reference an author can see is worth more than a silent gap. | See chapter \ref{ch:one}. |
\pageref | Set the page the label fell on, in the document's own numbering. Resolved after the contents is built, because the contents is pages of its own and moves every page after it, and resolved repeatedly until the numbers stop moving -- the digits are text on a line, so one reference can move the page the next one names. A label the document never declared sets ??, and text output, which has no pages, sets ?? for every one of these. | See page \pageref{ch:one}. |
\center | Start centring the lines that follow. This is what \begin{center} runs, the way latex.ltx has it: \begin{x} is \csname x\endcsname here, so the environment IS this control sequence. Each line inside is positioned by the width it measures rather than at the left margin. Without it a centred line and the line after it were filled into one flowing line, which is most of why a title page collapsed into the prose under it. | \begin{center}
centred
\end{center} |
\endcenter | Stop centring: what \end{center} runs, and the close of the region \center opened. Lines after it go back to the left margin. | \begin{center}
centred
\end{center}
at the margin |
\centering | Centre every line up to the end of the group or the environment that holds it. The switch form of \begin{center}: a title page is regularly one \centering inside a titlepage or a minipage rather than a centred environment. Environments here are a macro pair rather than a group, so the region ends at the \end{...} of whichever one is open -- otherwise a single \centering would centre every remaining page of the book. | {\centering centred}
at the margin |
\centerline | Centre exactly its argument, on a line of its own. The box form rather than the switch: what follows it is not centred. The prelude answered it with its argument alone, which set an imprint or a colophon flush left in the middle of the page. | \centerline{TEXT}
\centerline{a colophon} |
\tabular | Open a table. \begin{tabular} runs this, the way latex.ltx has it: \begin{x} is \csname x\endcsname here, so the environment IS this control sequence. The optional position and the column specification are read and dropped -- columns are as wide as their content here, not as wide as the specification asks. Inside a table & is a cell boundary and \ ends a row; outside one both mean what they meant before. Without it a table was filled into the prose around it as one paragraph: Name Value alpha 1 beta 2. | \begin{tabular}{ll}
Name & Value \\
alpha & 1 \\
\end{tabular} |
\endtabular | Close a table: what \end{tabular} runs. It also ends the row in hand, so a last row written without a closing \ is still a row. | \begin{tabular}{ll}
a & b \\
\end{tabular} |
\longtable | Open a longtable, which is set exactly as tabular is. This is what pandoc emits for every markdown table, so it is the shape of nearly every table in a generated book. A longtable states its head, then its foot, then its body, and it is set head, body, foot -- see \endhead and \endlastfoot. | \begin{longtable}[]{@{}lr@{}}
\toprule
Name & Value \\
\midrule
\endhead
\bottomrule
\endlastfoot
alpha & 1 \\
\end{longtable} |
\endlongtable | Close a longtable: what \end{longtable} runs, and the same close \endtabular is. | \begin{longtable}[]{@{}ll@{}}
a & b \\
\end{longtable} |
\toprule | booktabs' rule above a table, drawn as a filled rectangle the width of the table at booktabs' own \heavyrulewidth of 0.08em. Written 3,455 times across the corpus and drawn none of them before this, because the prelude answered it with nothing. | \begin{tabular}{ll}
\toprule
Name & Value \\
\bottomrule
\end{tabular} |
\midrule | booktabs' rule between a table's head and its body, drawn at \lightrulewidth -- 0.05em, thinner than the two outer rules, which is the whole visual difference between them. An optional width may follow and is read and dropped. | \begin{tabular}{ll}
Name & Value \\
\midrule
alpha & 1 \\
\end{tabular} |
\bottomrule | booktabs' rule under a table, drawn at \heavyrulewidth as \toprule is. In a longtable it is written in the foot, before the body, and is set where a reader gets it: under the last row. | \begin{tabular}{ll}
alpha & 1 \\
\bottomrule
\end{tabular} |
\hline | The kernel's own horizontal rule across a table, drawn the way \midrule is. Outside a table it does nothing, which is what it did everywhere before tables were set. | \begin{tabular}{ll}
\hline
a & b \\
\hline
\end{tabular} |
\tabularnewline | End a table row. The unambiguous spelling of \\ inside a table, for the places where \\ would be read as something else; both end a row here, and both take the * and the optional extra space LaTeX allows after them. | \begin{tabular}{ll}
a & b \tabularnewline
c & d \tabularnewline
\end{tabular} |
\endhead | End longtable's head: everything above it is set at the top of EVERY page the table runs onto, not only the first. Written above the body, which is where it is set on the first page. | \begin{longtable}[]{@{}ll@{}}
\toprule
Name & Value \\
\midrule
\endhead
alpha & 1 \\
\end{longtable} |
\endfirsthead | End longtable's FIRST head, when it differs from the one that repeats. What stands above this is set at the top of the first page, and what stands between it and \endhead at the top of every page after that. | \begin{longtable}[]{@{}ll@{}}
Name & Value \\
\endfirsthead
Name & Value \\
\endhead
alpha & 1 \\
\end{longtable} |
\endfoot | End longtable's foot: what stands at the bottom of every page the table runs PAST -- so on no page at all when it fits on one, and never on the last, where \endlastfoot stands instead. Written before the body and set under it. | \begin{longtable}[]{@{}ll@{}}
\endhead
\bottomrule
\endfoot
alpha & 1 \\
\end{longtable} |
\endlastfoot | End longtable's LAST foot: what is set once, under the end of the table, where \endfoot stands under every page before it. This is the boundary pandoc writes, with \bottomrule above it, in every markdown table it emits. | \begin{longtable}[]{@{}ll@{}}
\endhead
\bottomrule
\endlastfoot
alpha & 1 \\
\end{longtable} |
\itemize | Open a bulleted list. \begin{itemize} runs this, the way latex.ltx has it: \begin{x} is \csname x\endcsname here, so the environment IS this control sequence. Each \item inside it starts its own line, carries a bullet, and sets 2.5em in from the margin in a measure narrowed by the same amount, so a long item wraps inside the list. A list inside a list indents by that again. Without it a list was filled into the prose around it: an itemize of two items read back as first item second item. | \begin{itemize}
\item first
\item second
\end{itemize} |
\enditemize | Close a bulleted list: what \end{itemize} runs. The lines after it go back to the margin, or to the indent of the list this one was nested inside. | \begin{itemize}
\item only
\end{itemize}
back at the margin |
\enumerate | Open a numbered list, set exactly as itemize is except for the mark: each item carries its own number in the list, counted from one. Pandoc emits an \enumerate with a \def\labelenumi above it for every ordered markdown list; the label definition is read as an ordinary \def and the numbering here is arabic at every level. | \begin{enumerate}
\item first
\item second
\end{enumerate} |
\endenumerate | Close a numbered list: what \end{enumerate} runs, and the same close \enditemize is. | \begin{enumerate}
\item only
\end{enumerate}
back at the margin |
\description | Open a list of terms. The mark of each item is the term in its own \item[...], set in the bold face and followed by the item's body on the same line. An item with no term has no mark, which is the honest reading of a description item that named nothing. | \begin{description}
\item[term] what it means
\end{description} |
\enddescription | Close a list of terms: what \end{description} runs, and the same close the other two lists have. | \begin{description}
\item[term] meaning
\end{description}
back at the margin |
\item | Start a list item: its own line, at the depth of the list holding it, with the mark that list sets -- a bullet, a number, or a bold term. \item[label] gives the item an explicit label, which IS the term in a description list and REPLACES the mark elsewhere; the label is TeX and is expanded, so \item[\texttt{--flag}] sets in the monospace face. Outside any list this is the prelude's own \item, which yields its optional argument and nothing else. 8,683 occurrences across the corpus. | \item[LABEL] <text>
\begin{itemize}
\item plain
\item[!] labelled
\end{itemize} |
\definecolor | Name a colour: \definecolor{neonCyan}{HTML}{05D9E8}. The models read are HTML (six hex digits), rgb (three components in 0..=1), RGB (the same in 0..=255), gray and cmyk. A model that is not one of those defines nothing rather than guessing, because a colour read in the wrong model is wrong on every page it reaches. Documents define their palette once in the preamble and refer to it by name afterwards, so without this every later \color names something unknown and the page comes out black. | \definecolor{NAME}{MODEL}{SPEC}
\definecolor{neonCyan}{HTML}{05D9E8} |
\providecolor | Define a colour only if that name is not already defined. Otherwise \definecolor. | \providecolor{NAME}{MODEL}{SPEC}
\providecolor{link}{HTML}{05D9E8} |
\colorlet | Give an existing colour another name. A name nothing has defined yet defines nothing. | \colorlet{NEW}{OLD}
\definecolor{brand}{HTML}{FF2A6D}
\colorlet{heading}{brand} |
\color | Switch the colour of everything that follows, until the group holding it closes -- a switch rather than a wrapper, which is why {\color{red}...} colours only what is inside the braces. Takes a defined name, or a model and a spec: \color[rgb]{1,0,0}. A second \color in one group replaces the first rather than nesting, because TeX has one current colour and not a stack. A name nothing defined leaves the text in the colour it already had. | \color{NAME}
\color[MODEL]{SPEC}
{\color{neonCyan}cyan words} |
\textcolor | Colour exactly one argument: \textcolor{neonCyan}{words}. Takes the same two forms as \color. Unlike \color it puts the previous colour back afterwards, so it never leaks into the text beside it. | \textcolor{NAME}{TEXT}
\textcolor[MODEL]{SPEC}{TEXT}
\textcolor{red}{warning} |
\pagecolor | Paint the page. Drawn under everything else, which is the only order that leaves the words on top of it -- and it has to be honoured, because a document that sets a dark page also sets light text to go on it, and doing one without the other leaves white on white. | \pagecolor{NAME}
\definecolor{bgPrimary}{HTML}{05050A}
\pagecolor{bgPrimary} |
\titleformat | titlesec's heading format, applied rather than discarded. Both spellings are read — the starred \titleformat*{\section}{FORMAT} and the plain \titleformat{\chapter}[SHAPE]{FORMAT}{LABEL}{SEP}{BEFORE}, whose optional argument follows a mandatory one. The FORMAT is lowered with the title, so the sizes, colours and family in it reach the heading and end with it; it REPLACES the class default for that level, which is what titlesec does, so a format naming no size leaves the heading at the body size. The shape, label, separator and before-code are titlesec's own layout and are still dropped. A format combining switches composes: {\sffamily\bfseries\Huge} reaches the page as the bold cut of the SANS family at \Huge, because family, series and shape are independent axes and each declaration sets only its own. | \titleformat{COMMAND}[SHAPE]{FORMAT}{LABEL}{SEP}{BEFORE}
\titleformat{\chapter}[hang]{\sffamily\bfseries\Huge}{}{0pt}{}
\titleformat*{\section}{\sffamily\Large} |
\pandocbounded | Set the argument, scaled down if it would overflow the page. Pandoc wraps every figure it emits in this, and a pandoc document also DEFINES it — as a box, a \Gscale@div against the box's height, and a \scalebox — and the document's definition wins over the preamble's. None of that is on this path, so the figure went into a box that was never set and vanished silently, the caption being outside the wrapper and surviving to look right. It is read here instead of expanded, so the argument is set. The bounding is real: an image with no stated size is brought down to the measure and the text height, keeping its proportions, which is what the wrapper's own comment says it is for. Scaling up never happens, which is also what it does. | \pandocbounded{CONTENT}
\pandocbounded{\includegraphics[keepaspectratio]{diagrams/plot.pdf}} |
\includegraphics | Place an image, and reserve the room it takes. PNG and JPEG are read; the file is embedded once however many pages draw it, with its alpha carried as an /SMask. width= and height= are honoured, in any of TeX's units and as a multiple of \textwidth, \linewidth or \columnwidth; give one and the other follows the file's own proportions, give neither and a pixel is a big point, which is what graphicx does with a file that states no resolution. The figure owns its own lines rather than running into the prose either side, and is centred by its own width inside a center region. A file that cannot be found or read costs the document its picture and not its remaining pages. Other keys — scale, angle, keepaspectratio, trim, clip — are consumed and not acted on, and no format beyond PNG and JPEG is read. | \includegraphics[OPTIONS]{FILE}
\includegraphics[width=0.8\textwidth]{figures/plot.png}
\includegraphics[width=5cm,height=3cm]{cover.jpg} |
\setmainfont | Record the document's body typeface, and keep the name rather than dropping it. The PDF backend looks the family up on the system and EMBEDS it when it finds a TrueType-flavoured one, so the page is set in that face and measured with its own widths; failing that it falls back to one of the fourteen faces every reader has, chosen by metrics — Arimo, Liberation Sans and Arial all set at Helvetica's widths, and a name nothing is known about falls to Helvetica too. The DVI backend names .tfm fonts and cannot carry an OpenType one, so it still sets in Computer Modern. An optional bracket on either side of the family is consumed — and read: Path= and UprightFont= name a file the document ships, and BoldFont= and ItalicFont= name the files \bfseries and \itshape are set from. A mandatory argument ending in .ttf, .otf, .ttc or .otc, in any case, names that FILE rather than a family — fontspec's other spelling, resolved against Path= the same way — and an explicit UprightFont=/Extension= still wins where a document writes both. | \setmainfont[OPTIONS]{FAMILY}[OPTIONS]
\setmainfont{Arimo} % embedded if Arimo is installed, else Helvetica's metrics
\setmainfont{Arimo-VF.ttf}[Path=./.fonts/] % the file itself, embedded from Path= |
\setromanfont | The older fontspec spelling of \setmainfont, and the same thing here: it fills the same slot, so whichever of the two the preamble writes last is the family the PDF backend embeds or maps. | \setromanfont[OPTIONS]{FAMILY}
\setromanfont{Arimo} % identical in effect to \setmainfont{Arimo} |
\setsansfont | Record the document's sans-serif family. The name is read and kept and its bracketed options are consumed, but no backend selects it yet: the PDF page is set in the main family throughout, so this records an intention rather than changing the output. Documented because a preamble writes it and the engine resolves it. | \setsansfont[OPTIONS]{FAMILY}[OPTIONS]
\setsansfont{Arimo} |
\setmonofont | Record the document's monospace family, the counterpart of \setsansfont, and the face \ttfamily selects. Its options are read the way \setmainfont's are, so a Path=/UprightFont= naming a file the document ships is what the PDF backend embeds — which is the usual case, since a book carries its monospace face beside itself rather than installing it. The DVI backend names .tfm fonts and still sets in Computer Modern throughout. | \setmonofont[OPTIONS]{FAMILY}[OPTIONS]
\setmonofont{Cousine}
\setmonofont{ShareTechMono}[Path=./.fonts/,Extension=.ttf,UprightFont=ShareTechMono-Regular] |
\directlua | Hand a chunk to an embedded Lua interpreter, and RUN it: PUC-Lua 5.3, the version LuaTeX itself embeds. Whatever the chunk prints comes back as input — \count10=20 a\directlua{tex.print(tex.count[10]+5)}b typesets a25b, the manual's own example. The tex table reaches the engine's real registers (tex.count, tex.dimen, tex.toks, tex.glue, and the get*/set*/is* functions), token reaches the input the chunk stands in front of (token.scan_int, scan_dimen, scan_keyword, scan_string, scan_word, scan_csname, get_macro, set_macro), and texio.write reaches the terminal. A chunk that fails STOPS the run with the Lua error as a TeX error, never silently. What is absent rather than stubbed is everything built on node lists: there is no node library, and tex.skip/tex.getbox refuse instead of inventing a value — tex.getglue is the same glue register as five plain numbers. luaotfload.add_fallback is answered for real: it names the faces a glyph the document's own face lacks is fetched from, and the PDF backend loads those families in order, asks each one's cmap for the character, and draws the first face that has it, carrying only the glyphs the document borrowed. \luadirect is the same primitive under ConTeXt's name; \latelua runs its chunk but contributes no input; \luaescapestring makes a TeX value safe inside a Lua string literal; \luafunction, \luafunctioncall and \luadef call a function stored in lua.get_functions_table(). | \directlua{CHUNK}
\count10=20 \directlua{tex.print(tex.count[10]+5)}
\directlua{luaotfload.add_fallback("symfb", {"Arial Unicode MS:mode=base;", "Arial:mode=base;"})} |
\ttfamily | Set in the monospace face, until the group holding the declaration closes. This is what \texttt{...} is made of — {\ttfamily ...} — and it is honoured here rather than in the text command, because a document that redefines \texttt to colour its inline code writes the declaration in the replacement. The face is the file or family \setmonofont named, embedded when it can be found, Courier when it cannot, and the main face when the document named no monospace family at all. | {\ttfamily fixed pitch}
\texttt{fixed pitch} |
\slshape | Set slanted, until the group holding the declaration closes. There is no separate slanted cut here, so it is the italic one — which is what \textsl has always expanded to. Like every other font declaration it sets ONE axis: {\sffamily\slshape} is the slanted cut of the sans family, not a replacement of it. | {\slshape leaning}
\textsl{leaning} |
\bfseries | Set in the bold series, until the group holding the declaration closes; \textbf{...} is {\bfseries ...}. It sets the SERIES only and leaves the family and the shape as they stand, so {\sffamily\bfseries} is the bold cut of the sans family rather than a replacement of it. The face is the file BoldFont= named in THAT family's options — the sans family's for a sans context, the mono family's for a mono one — or the bold member of the fourteen carrying that family (Helvetica-Bold, Courier-Bold, Times-Bold) where the document named no file. A variable font whose bold is the upright file at another weight is the SAME file, so it comes out as the upright face: instantiating a weight axis is not something this does. | {\bfseries heavy}
\textbf{heavy} |
\itshape | Set in the italic face, until the group holding the declaration closes; \textit{...} and \emph{...} are both {\itshape ...}. The face is the file ItalicFont= named in the main family's options, or the italic member of the fourteen — Helvetica-Oblique, Times-Italic — and the main face where the document supplies neither. | {\itshape stressed}
\emph{stressed} |
\rmfamily | Back to the body face, until the group holding the declaration closes. One face is in force at a time here rather than a family, a series and a shape combining, so this replaces whatever the group had selected rather than changing one axis of it. | {\ttfamily code \rmfamily prose} |
\sffamily | Select the sans-serif family. \setsansfont is read and kept, but no backend selects that family yet, so this sets in the main face — which is what a document asking for sans in a sans-set book wanted anyway. It is a declaration and runs to the end of its group. | {\sffamily sans} |
\normalfont | Undo every face declaration in force and set in the body face, to the end of the group. A heading writes it to start from the document's own face rather than from whatever was selected around it. | {\bfseries heavy \normalfont plain} |
\makeatletter | Make @ a letter (category code 11), so LaTeX's internal names like \@ifnextchar become spellable as single control sequences. This is a catcode change, so it takes effect at COMPILE time, exactly as \catcode\@=11 does. | \makeatletter <internal names> \makeatother |
\makeatother | Make @ an ordinary character again (category code 12), closing a \makeatletter region. After it, \@x is the control sequence \ followed by the characters @x rather than one name. | \makeatletter <internal names> \makeatother |
INITEX category codes
The table INITEX starts from, which is what texrs starts from: no format is loaded. {, }, $, &, #, ^, _ and ~ get their familiar meanings from plain.tex, not from the engine.
| Code | Category | Characters INITEX puts here |
|---|---|---|
0 | escape | \ |
1 | begin group | — |
2 | end group | — |
3 | math shift | — |
4 | alignment tab | — |
5 | end of line | line feed, carriage return |
6 | parameter | — |
7 | superscript | — |
8 | subscript | — |
9 | ignored | NUL |
10 | space | space |
11 | letter | A–Z, a–z |
12 | other | every other code (197 of them) |
13 | active | — |
14 | comment | % |
15 | invalid | DEL |
Builtin calls
What the VM cannot do natively is a builtin call, emitted by the lowerer and dispatched by id. The numbers are a wire format: the bytecode cache keeps compiled chunks on disk and --aot serializes one into the executable it writes, and both call these BY NUMBER — so renumbering an op does not fail to build, it makes every cached chunk and every already-built binary call the wrong function.
| Constant | Id | What it does |
|---|---|---|
MSG_APPEND | 4000 | Append one rendered piece to the message being built. |
MSG_FLUSH | 4001 | Finish the message being built and record it. |
DBG_LINE | 4002 | A statement boundary, emitted only under `--dap`. The debug adapter stops here; an ordinary run carries none of these ops. |
FFI_COMPILE | 4003 | Compile and register a `\rust{ … }` block: one argument, the base64 body. |
FFI_CALL | 4004 | Call a function a block exported: the name, then its arguments. |
TEXT | 4005 | Append a run of the document's own text. |
ARITH_CHECKED | 4006 | `\multiply` / `\divide` under TeX's overflow rule: the old value, the operand, and 0 for multiply or 1 for divide. |
MSG_CLOSE | 4007 | Close an `\input` file: append `)` to the message already recorded, rather than record one, because tex writes the paren hard against what came before it while the stream is joined with spaces. No arguments. |
MSG_DIMEN | 4008 | Append a dimension, written as TeX writes one: one argument, the value in scaled points. |
MSG_GLUE | 4009 | Append a glue, written as TeX writes one: four arguments -- natural, stretch, shrink, and the packed orders. |
COLOR_PUSH | 4010 | Open a colour: three components follow. 4010 and 4011, not 4006 and 4007: those are ARITH_CHECKED and MSG_CLOSE. Registering a builtin twice silently keeps the LAST one, so the colour ops were compiled, called, and quietly handled by somebody else's function -- the chunk was right and the page had no colour in it. |
COLOR_POP | 4011 | Close the innermost colour. |
ERR_SITE | 4012 | Record where the command about to run would be reported from: one argument, `tex.web` §311's context display for that point in the source. |
TRANSCRIPT_NOTICE | 4013 | §1335's `(see the transcript file for additional information)`, written only if something was reported during the run. No arguments. |
MSG_MUGLUE | 4014 | Append a MATH glue, written as TeX writes one: the same four arguments `MSG_GLUE` takes, with `mu` for the unit. |
SCALE_DIMEN | 4015 | `tex.web` §453's `<factor><internal unit>`: the register's value, the factor's integer part, and its fraction in 65536ths. A builtin rather than a run of arithmetic ops because §107's truncation and §460's clamp are the port, and one Rust function is where they stay readable. |
The --tiers report
texrs --tiers FILE runs a document and then queries fusevm's own eligibility and cache predicates, so the answer comes from the compiler that would have done the work rather than from an assumption about it.
| Line | What it answers |
|---|---|
ops | How many bytecode ops the document lowered to, prologue included. |
block-JIT eligible | Whether a region of this chunk is a shape the block tier will take at all. Eligibility is not compilation. |
block-JIT compiled | Whether the block tier actually compiled one, asked of fusevm's cache rather than assumed from eligibility. |
largest eligible region | The widest run of ops the block tier would take, as start..end with its length, or none. |
loops | Every loop header the lowerer emitted, and whether the tracing tier compiled a trace for it. none means the document has no backward branch, which is the usual reason nothing reaches native code. |
block-ineligible ops | The ops that disqualified a region, counted by kind. A pair of CallBuiltins is enough, which is why the smallest document reaches no tier. |
reaches native code | The one line that answers the question the flag was run to ask. |
Divergences from tex
Where this engine and real tex disagree, taken from tests/known_gaps.txt — the baseline the differential gate enforces in both directions, so this list can neither grow silently nor go stale.
| Case | What differs |
|---|---|
undefined_cs.tex | An undefined control sequence is not an error. tex reports `! Undefined control sequence.' and expands it to nothing; texrs prints its NAME into the message stream and exits 0. Found by the parity fuzzer (`cargo run --bin parity-fuzz`). Fixing it means an error path through the expander that the milestone does not have. |
plain_count0.tex | The oracle is `tex' with the PLAIN format preloaded, where \count0 is the page number and already holds 1. texrs starts every register at zero, as INITEX does, and loads no format. Not an engine bug -- a startup-state difference, recorded so it cannot be mistaken for one later. |
let_char_token.tex | `\let\egroup=}' copies a CHARACTER token, and printing one through \message must print the character. texrs prints the name with the trailing space a multi-letter control word gets (tex.web 294's print_cs), so the stream ends `INSIDE\egroup ' where tex ends `INSIDE\egroup'. The meaning-copy half of \let is in parity; this is the char-token half. |
def_in_conditional_arm.tex | A `\def' inside an arm of a RUN-TIME conditional is executed while lowering, and lowering emits both arms because neither is decided yet -- so the else arm's definition overwrites the then arm's and the losing branch wins. tex interprets and runs only the arm it took: it prints `<a>' where texrs prints `[a]'. This is a miscompile rather than a refusal, which is what makes it worth a case: nothing errors, the answer is just wrong. It blocks LaTeX's `\newcommand{\x}[n]{...}', whose argument-count dispatch is exactly a chain of `\ifnum...\def...\else'. Fixing it needs the macro table to be run-time state, or the conditional to be decidable while lowering; neither is a small change. |
outer_forbidden_use.tex | texrs records \outer but does not police it. tex forbids an outer macro inside a group being scanned as text and reports `Forbidden control sequence found while scanning text of \message'; texrs expands it and prints the body. \outer is ONLY an error-detection feature, and every position it governs -- an argument, a text group, skipped conditional text -- is one tex reports and recovers from while texrs stops, so enforcing it cannot reach parity either. Recorded rather than half-enforced: a false positive here would refuse a document that works. |
param_brace_illegal_number.tex | `\def\greet#{1HELLO-#1}' -- `#{' takes NO numbered parameter (tex.web 476 puts the brace in the parameter text as a delimiter, and the macro has none), so `#1' in the body is illegal. tex reports `! Illegal parameter number in definition of \greet.', recovers by doubling the `#', and prints `1HELLO-##1{WORLD}'; texrs accepts the body and reads `#1' as the delimited argument, printing `1HELLO-{WORLD}'. The recovery is the same error model recorded above, but the DEFINITION is wrong here rather than only the recovery. Found by `#{' being implemented: the input had been kept as fuzz/corpus/lower/crash_param_brace.tex because it PANICKED, and once it stopped panicking tests/fuzz_smoke.rs reported it as a seed that now compiles cleanly. Renamed to fuzz/corpus/lower/param_brace_zero_params.tex. |
Invocation
USAGE: texrs [OPTIONS] [FILE[.tex]]... [COMMANDS]
texrs [OPTIONS] \FIRST-LINE // the arguments are the input
texrs [OPTIONS] &FMT ARGS // with a named format
texrs // no arguments: the prompt
TEX OPTIONS
| Option | What it does |
|---|---|
-interaction=MODE | batchmode, nonstopmode, scrollmode or errorstopmode |
-jobname=NAME | Set the job name |
-output-directory=DIR | Write the output there instead of beside the input |
-progname=NAME | Set the program name |
-fmt=NAME | Use a named format |
-ini | Be initex |
-halt-on-error | Stop at the first error |
-file-line-error, -no-file-line-error | file:line:error style messages |
-recorder | Record the files read |
-8bit | Write 8-bit characters as themselves |
RUNNING
| Option | What it does |
|---|---|
--repl | Start the interactive prompt |
--jobs=N | Compile N documents at once (default: one per core) |
--build | Compile into the bytecode cache and stop, without running |
--text | Print the document text, not only the message stream |
--dvi | Typeset to FILE.dvi -- first-fit lines, no hyphenation |
--pdf | Typeset to FILE.pdf, in the font the document asked for |
--no-cache | Compile this run rather than reading the bytecode cache |
--aot | Compile the document to a standalone native executable |
LOOKING INSIDE
| Option | What it does |
|---|---|
--dump-tokens | Print the mouth's token stream and exit |
--dump-ast | Print the command stream the frontend lowered to, and exit |
--disasm | Print the lowered fusevm bytecode and exit |
--tiers | Run it, then report which fusevm tier took its bytecode |
EDITORS
| Option | What it does |
|---|---|
--lsp | Speak the Language Server Protocol over stdio |
--dap | Speak the Debug Adapter Protocol over stdio |
CACHE
| Option | What it does |
|---|---|
--cache-stats | Say what the bytecode cache holds and where |
--cache-clear | Delete it |
DOCUMENTS
| Option | What it does |
|---|---|
-X new [DIR] | Make one (Texrs.toml + index.tex) |
-X init | Make one here, named after this directory |
-X build [--profile P] | Build the document this directory is in |
-X watch [--profile P] | Rebuild it whenever an input changes |
-X show | Say what the document is and can produce |
-X dump [--profile P] | Build to stdout, writing nothing |
-X bundle fetch URL | Download a bundle into the cache |
-X bundle list | Say which bundles have been fetched |
-X dvi FILE.dvi | Read what real tex shipped for a document |
-X dvi A.dvi B.dvi | Say whether two files are the same document |
-X bib FILE.bib | Read a bibliography database |
-X bib FILE.aux | Say what a document cites, and what is missing |
-X bst FILE.bst | Read a bibliography style, and check its names |
-X bibtex FILE.aux | Run the style: write the .bbl a document reads |
-X tfm FILE.tfm [C] | Read a font's metrics, or one character's |
-X vf FILE.vf [C] | Read a virtual font: what it really sets |
-X pk FILE.pk [C] | Read a packed bitmap font, and draw a character |
-X otf FILE.otf [C] | Read an OpenType font: its tables and its cmap |
-X pfb FILE.pfb [C] | Read a Type 1 font: its glyphs and their widths |
-X map FILE.map [NAME] | Read a font map: what a TeX font name means |
-X enc FILE.enc | Read an encoding: what each code is called |
-X itar FILE.tar [NAME] | Index a tar bundle, or read one file out of it |
-X special TEXT | Say what a \special means to a driver |
--profile NAME | Which output to build |
--interval MS | How often -X watch looks (default 250) |
SYSTEM
| Option | What it does |
|---|---|
-h, --help | Print this |
--version | Print the version banner |
Environment
| Variable | What it does |
|---|---|
TEXRS_CACHE | Set to 0, false or no to turn the bytecode cache off for a run; set to a path to put it somewhere else. A disabled cache is how you work around a cache you suspect. |
TEXRS_PARALLEL | Set to 1 to lex a document on several threads. Off by default: the mouth is 22% of the time on the documents measured, so the win did not pay for the coordination. |
TEXRS_PRELEX_STATS | Set to anything to print the pre-lexer's hit and miss counts when the run ends. |
TEXRS_STATICLIB | The libtexrs.a that --aot links against, when the installed copy is not the one you want. |
Links
- Documentation — index.html
- Engineering report — report.html
- Source — github.com/MenkeTechnologies/texrs