Skip to content

Veski0/macros

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TypeScript Macros 🪄

⚠️ Warning: You shouldn't use this. Nobody should use this. I shouldn't use this (I might use it). This is a bad idea. The run-time time and compute savings that this software creates are almost definitely not worth the effort.

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.

Motivation

The motivation for this project is mainly for recreation.

Motivating Example: Paradigm

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.

Map To Loop

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 */

Macro Syntax

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.

Invokation Syntax

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.
  • $panic is called, passing in milkshakes.
  • $panic returns a tagged template function, into which the text < 5 | something catastrophic happened is 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.

Construction Syntax

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 T is 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 be any[] in order to trick the TypeScript compiler when invoking the macro.
  • The TemplateStringsArray is 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 ($).

Installation

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 i

Usage

Average 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 -n

Below 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)

About

A macro expander that facilitates writing macros that can be expanded prior to run-time.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors