macro cps

Assists in the creation of readable and maintainable macro_rules! macros

5 releases

0.2.3 Jun 14, 2023
0.2.2 Mar 5, 2023
0.2.1 Nov 5, 2022
0.2.0 Oct 22, 2022
0.1.0 Jul 17, 2022

#1359 in Procedural macros

Download history 27/week @ 2024-07-22 44/week @ 2024-07-29 15/week @ 2024-08-05 32/week @ 2024-08-12 9/week @ 2024-08-19 107/week @ 2024-08-26 46/week @ 2024-09-02 34/week @ 2024-09-09 66/week @ 2024-09-16 48/week @ 2024-09-23 50/week @ 2024-09-30 30/week @ 2024-10-07 40/week @ 2024-10-14 22/week @ 2024-10-21 18/week @ 2024-10-28 21/week @ 2024-11-04

104 downloads per month
Used in 3 crates (2 directly)

MIT license

45KB
956 lines

Crates.io

CPS (AKA macro variables & let expressions)

This crate allows for more traditional "inside-out" functions to be written in the rust macro syntax, allowing maintainable macros to be written in-line without needing to switch to a proc-macro.

TLDR:

use cps::cps;

#[cps] // Add this macro to unlock additional syntax
macro_rules! foo {
    (1) => { BaseCase1 };
    (2) => { BaseCase2 };

    () =>
    // !!! NEW SYNTAX HERE !!!
    let $x:tt = foo!(1) in
    let $y:tt = foo!(2) in
    {
        concat!($x, " ", $y)
    };
}


fn main() {
    assert_eq!(foo!(), "BaseCase1 BaseCase2");
}

Why?

Reason 1 - Readability and Maintainability

Macro execution order is confusing. Because each macro is passed a token tree, macros execute outside-in. For example:

macro_rules! dog {
    () => {
        woof
    };
}

macro_rules! dog_says {
    () => 
    {
        stringify!(dog!())
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "dog!()", not "woof"
}

Reading the above code as if macros are classical functions, you may expect this program to print woof. However unfortunately it prints dog!(), as if println! expands its macros while stringify! does not. This makes macros hard to maintain.

The Little Book of Macros describes callbacks, where a macro takes as an argument the next macro to execute. This leads to the following improved version of the above example:

macro_rules! dog {
    ($cont:ident) => {
        $cont!(woof)
    };
}

macro_rules! dog_says {
    () => 
    {
        dog!(stringify)
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "woof" but is hard to read
}

While now having the correct behaviour, this is difficult to maintain as the flow of execution is confusing. Using CPS instead we get:

#[cps]
macro_rules! dog {
    () => {
        woof
    };
}

#[cps]
macro_rules! dog_says {
    () => 
    let $x::tt = dog!() in
    {
        stringify!($x)
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "woof"
}

Reason 2 - Extendability

The let expressions in CPS macros must be built from other CPS macros, while the body mustn't. This allows us to add computation to be substituted in to macros developed by other people. For example:

// Non-CPS macro from another crate
macro_rules! load_thing {
    ($path:expr) => {
        ...
    };
}

#[cps]
macro_rules! my_load_thing {
    ($path:expr) => 
    let $new_path::expr = cps::concat!("src/", $path) in
    {
        load_thing!($new_path)
    };
}

This crate comes with a collection of CPS macros that are copies of macros in the standard library, that can be used to perform compile-time computation on token trees in a maintainable way.

Dependencies

~1.5MB
~41K SLoC