This repository contains a macro expander that facilitates writing macros that can be expanded prior to run-time. Macros are constructed in such a way that they do not require an extension to the TypeScript syntax, and using them will trick your LSP into allowing them to be imported and called. This repository does not provide a set of implemented macros but rather provides a mechanism for implementing them.
The motivation for this project is mainly for recreation.
Functional-style programs are often more natural to write. The problem with
doing this is that, often, functional choices are
slower than
an iterative approach. One solution to this problem would be to write
replacements for map, reduce, etc. However, this solution involves
introducing a new function call overhead, Another solution is compile-time
replacement of those types of functions.
Using a $map macro.
const evens = [2, 4, 6, 8, 10]
const odds = $map<number[]>(evens)`odds <- - 1`After the macro is expanded, the above source code looks like this:
const evens = [2, 4, 6, 8, 10]
/* @macro-expansion: "$map" (evens) `odds <- - 1` */
const odds = new Array(evens.length)
for (let i = 0; i < evens.length; i++) {
odds[i] = evens[i] - 1
}
/* @macro-expansion */The following walks through a possible implementation of a $panic macro as an
example of a minimal macro construction and invokation that generates something
meaningful.
The following is an invokation of a $panic macro.
$panic<void>(milkshakes)`< 5 | something catastrophic happened`- This macro's generic type parameter is
void. This tells the TypeScript compiler that it doesn't return anything. This type annotation is required. $panicis called, passing inmilkshakes.$panicreturns a tagged template function, into which the text< 5 | something catastrophic happenedis templated.
It is important to note that while this looks like TypeScript and is fact valid
TypeScript based on the declaration of the $panic macro (shown below), it is
never actually executed. The characters from the start of $panic's name up to
and including the terminating backtick are replaced by whatever the
implementation of the $panic macro generates.
The following implements the above $panic macro.
/* @macro */
export const $panic = <T>(...[candidate]: any[]) => (_: TemplateStringsArray, ...[$]: string[]): T => {
const [expression, message] = $.split('|').map(parts => parts.trim())
return `if (${candidate} ${expression}) {
console.error('${message}')
process.exit(-1)
}` as T
}
/* @macro */- The first and last lines must be exactly
/* @macro */. This is required and tells the extraction process where the macro begins and ends. - Macro declarations must begin
export const $<name>, where$<name>represents a variable name that begins with a dollar symbol ($). - The type
Tis used to trick the TypeScript compiler and is explained in greater depth in the following section. You cannot elide this annotation. - The first rest operator (
...[candidate]: any[]) will be an array of strings that are the symbols that the macro invokation is passed. The type must beany[]in order to trick the TypeScript compiler when invoking the macro. - The
TemplateStringsArrayis unused and will always yield an array of two empty strings due to the way that the compiler must inject arguments into a tagged template function. - The second rest operator (
...[$]: string[]) will contain the entirety of of the free text that is passed to the macro in its first argument ($).
Clone the repo into a useful directory (like <project>\tools) and install the
dependencies.
cd project/tools
git clone https://github.com/Veski0/macros.git
cd macros
npm iAverage usage might be something like the following, where the compiler is
passed a macro file (-m macros.ts), a source file (-s in.ts), and an output path
(-o out.ts), and comment generation is turned off (-n).
bun src/index.ts -m macros.ts -s in.ts -o out.ts -nBelow is the output from running bun src/index.ts --help.
TypeScript Macro Compiler
Expands TypeScript macros that meet the specification.
Options
--macros path A file containing macro implementations.
--source path A file containing source code that includes macro
invokations.
--output path A path to a file where output should be written.
--cache [path] A path to a file where macros can be cached. (default
= ./macro.cache.ts)
--no-comments [flag] Elide generated expansion comments. (default = true)
--test [flag] Run the test suite and exit. (default = false)
--help [flag] Show this help screen and exit. (default = false)
--version [flag] Print the current version and exit. (default = false)