Typtex!
Messing typst with macros.
Typst vs Latex
Both Typst and Latex are powerful typesetting tools. Typst, the newborn, uses a rust-like[1] syntax and rules on variable declaration, while latex, the powerful, uses a macro based system that looks like this:
% dogs go woof
\documentclass{doggies}
\usepackage{dogs_are_good}
\title{it is a dog!}
\a \b \c \something \thatthing
This, of course, is not real Latex code. Anyways, backslashes are usually used as the escape character in modern systems. Typst also use the backslash as an escape character, but only for entering special characters. For code and function calls, Typst uses “#”, the pound.
#function-call(arg1: 114, arg2: 514)
// my favourite space character - the ideographic
// space character U+3000
#other-function-call(str: "\u{3000}")
// enters code mode
#{
1 + 1
}
I’ve never used latex before, neither do I know the stuff happening under the hood. Nevertheless, I do know something about general text macros. Text macros only replace text. While they remain a good choice to reduce boilerplate, it is impossible to ensure the contents generated by text macros can be parsed into valid ASTs. On the other hand, this also means they are extremely general, and can serve for almost any purposes, it’s just that it becomes the user’s obligation to validate the output, not the macro processor’s.
Macro Processors
A lot of programming language have macros. C has macros, C++ has macros, Rust has macros, Nim has macros,
Latex has macros, and the entire language is built using \define
s. Sadly, Typst doesn’t have
builtin macros, and the authors determined that functions can already cover all scenarios where we do need
macros.
…And they are quite right. A lot of times macros act as barriers for analyzers. Expanding them means compiling some code ahead of time, especially those recursive ones.
Though typst doesn’t have builtin macros, we can still use external macros in typst. The first thing to note is Typst has a very strict lifetime rule, much like that of rust’s. Once a variable is out of its lifetime, or functioning zone [2]. Even when using macros we still have to follow Typst’s rules. Next, we would need to use a macro processor. Something like GNU M4 would work very well, the standard C/C++ preprocessor is also good. Some might remember jinja2 from python, but that’s an overkill for our example.
GPP
Introducing GPP, the generic preprocessor. It’s modern, its syntax is even
simpler than M4’s, while having the same flexibility. It’s standalone, so there’s no compiler overhead
that comes with the preprocessor like when you decide to install gcc
.
One major advantage of GPP is its customizable syntax. By default, it has 6 modes, simulating
the syntax seen in five different languages (two are for C/C++). The one we’re using is, of course,
the TeX
mode.
Defining and calling something under this mode is easy;
% define a macro
\define{foo}{bar}
% this line evaluates to `bar`
\foo
% execute a command and reading from stdout
\exec{python3 -c 'print("Do you wanna build a snowman?")'}
% output the current line
\line
So it’s possible to modify typst as follows:[3]
\define{cmd}{##1}
\define{page}{page(#1)}
\define{text}{text(#1)}
\define{set}{set #1}
\cmd{\set{\page{width: auto, height: auto}}}
\cmd{\set{\text{fill: red}}}
lorem ipsum
Typst doesn’t support calling external commands during compilation, nor getting the current time down to the
second. Those could be bypassed using the exec
macro - this just calls an external command and inserts the
results back to the output stream
// now we have nanoseconds - even better!
\exec{date --iso-8601=ns}
\exec{python3 -c 'import itertools; print(list(itertools.combinations("马冬梅", 2)))'}
Doing Everything in One Run
gpp
outputs everything to stdout
, the standard output. Since typst supporting compiling from
the standard input stdin
, We can redirect the text stream to the typst compiler’s standard input using |
,
the pipe operator, in the command line.
gpp -T -x input.typ | typst c - output.pdf
…and that should be it.
I have to be honest with you: I haven’t written that much rust code yet. Don’t take my words seriously as if they are from a rust professionals. ↩︎
I read too many rust tutorials in Chinese and I completely forgot the correct English name. ↩︎
Outputs:
#set page(width: auto, height: auto) #set text(fill: red) lorem ipsum 2025-08-29T23:40:11,634448127-07:00 [('马', '冬'), ('马', '梅'), ('冬', '梅')]
The execution result of the
date
command depends on the current date. ↩︎