Write simple proc-macros inline with other source code.

Overview

script-macro

An experimental way to write simple proc-macros inline with other source code.

Did you ever end up getting frustrated at the boilerplate involved in writing proc macros, and wished you could just write a Python or Bash script to generate the code instead?

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[script_macro::run_script_on(r##"
        let output = item;

        for x in 0..10 {
            for y in 0..10 {
                output += `
                #[test]
                fn it_works_${x}_${y}() {
                    it_works(${x}, ${y}, ${x + y});
                }`;
            }
        }

        return output;
    "##)]
    fn it_works(x: usize, y: usize, out: usize) {
        assert_eq!(add(x, y), out);
    }
}

Macros are not Rust source code, instead they are written in the RHAI scripting language. This comes with advantages and disadvantages:

  • Downside: No access to the Rust crate ecosystem -- RHAI is its own entire separate language, and therefore you can't use Rust crates inside. RHAI can be extended with custom Rust functions, but script-macro does not support that yet. For now, script-macro exposes a few helpers commonly useful in code generation.

  • Upside: Sandboxability -- Proc macros executed with script-macro cannot access the internet or perform arbitrary syscalls. Proc macros are given full access to the filesystem via the functions available through rhai-fs, but in a future version this could be configurable, for example read-only access or restricted to certain directories.

  • Downside: Dependency on RHAI runtime -- RHAI is an entire language runtime that has to be compiled once before any of your proc macros run.

  • Upside: No recompilation when editing proc macros. -- Proc macros are interpreted scripts. When editing them, only the containing crate needs to be recompiled, not script-macro itself. This could end up being faster when dealing with a lot of proc macros.

    See also watt, which appears to have similar tradeoffs about compilation speed (compile runtime for all macros once, run all macros without compilation)

Seriously?

I seriously do wish that proc_macros were easier to write (inline with other code) and didn't contribute as much to compile time. One area where this comes up for me particularly often is programmatic test generation (or, parametrization).

This is my best shot at making this happen today, but that doesn't mean I'm convinced that the end result is viable for production use. I hope that it inspires somebody else to build something better.

API

There are two main macros to choose from:

  • script_macro::run_script_on -- Attribute macro that executes a given script with the annotated function/module's sourcecode available as a global string under item.

    The return value of the script is the source code that the item will be replaced with.

    Here is a simple script macro that adds #[test] to the annotated function.

    #[script_macro::run_script_on(r##"
        return "#[test]" + item;
    "##)]
    fn it_works(x: usize, y: usize, out: usize) {
        assert_eq!(add(x, y), out);
    }
  • script_macro::run_script -- Function macro that executes the given script. There are no inputs.

    script_macro::run_script!(r##"
        return `fn main() { println!("hello world"); }`;
    "##);

Script API

From within the script, the entire stdlib of RHAI + the functions in rhai-fs are available.

Additionally, the following functions are defined:

  • parse_yaml(String) -> Dynamic -- Takes YAML payload as string and returns the parsed payload as unstructured data (such as, RHAI object map or array).

  • parse_json(String) -> Dynamic -- Takes JSON payload as string and returns the parsed payload as unstructured data (such as, RHAI object map or array).

  • stringify_yaml(Dynamic) -> String -- Convert a RHAI object to a YAML string, inverse of parse_yaml.

  • stringify_json(Dynamic) -> String -- Convert a RHAI object to a YAML string, inverse of parse_json.

  • glob(String) -> Vec<PathBuf> -- Takes a glob pattern and returns a list of paths that match it.

  • basename(PathBuf) -> String -- Returns the .file_name() of the given path, or the entire path if there is none.

Examples

Check out the example crates to see all of the above in action.

License

Licensed under the MIT, see ./LICENSE.

See also

You might also like...
Simple console input macros with the goal of being implemented in the standard library.

Simple console input macros with the goal of being implemented in the standard library.

Write a simple CLI script, that when given a 64-byte encoded string

Write a simple CLI script, that when given a 64-byte encoded string, it finds a suitable 4-byte prefix so that, a SHA256 hash of the prefix combined with the original string of bytes, has two last bytes as 0xca, 0xfe. Script should expect the original content of the string to be passed in hexadecimal format and should return two lines, first being the SHA256 string found and second 4-byte prefix used (in hexadecimal format).

H2O Open Source Kubernetes operator and a command-line tool to ease deployment (and undeployment) of H2O open-source machine learning platform H2O-3 to Kubernetes.
H2O Open Source Kubernetes operator and a command-line tool to ease deployment (and undeployment) of H2O open-source machine learning platform H2O-3 to Kubernetes.

H2O Kubernetes Repository with official tools to aid the deployment of H2O Machine Learning platform to Kubernetes. There are two essential tools to b

Valq - macros for querying and extracting value from structured data by JavaScript-like syntax

valq   valq provides a macro for querying and extracting value from structured data in very concise manner, like the JavaScript syntax. Look & Feel: u

Prototype of the `std::io::ensure!` family of macros

io-ensure Prototype of the `std::io::ensure` family of macros API Docs | Releases | Contributing Installation $ cargo add io-ensure Safety This crate

INput maCROs: program your keyboard/mouse

incro INput maCROs: program your keyboard/mouse See macro-template for macro example. Build it and place the resulting *.so file in macros/ directory

wait what? generate your entire infra from rust macros

infra as macro translate rust structs to terraform at compile time // your infra is a macro static DB: Postgres = Postgres16! { host: "env.host",

Create Streamdeck Macros For Helldivers 2 Strategems!
Create Streamdeck Macros For Helldivers 2 Strategems!

Helldivers 2 Stratagem Macro Engine For Streamdeck This program contains macros for various stratagems to use. All macros are contained in the macro_e

Turbine is a toy CLI app for converting Rails schema declarations into equivalent type declarations in other languages.

Turbine Turbine is a toy CLI app for converting Rails schema declarations into equivalent type declarations in other languages. It’s described as a to

Owner
Markus Unterwaditzer
"Do not even think of telephoning me about this program. Send cash first!" --Author of the UNIX file command.
Markus Unterwaditzer
Simple macros to write colored and formatted text to a terminal. Based on `termcolor`, thus also cross-platform.

Bunt: simple macro-based terminal colors and styles bunt offers macros to easily print colored and formatted text to a terminal. It is just a convenie

Lukas Kalbertodt 202 Dec 22, 2022
Js-macros - Quickly prototype Rust procedural macros using JavaScript or TypeScript!

js-macros Quickly prototype Rust procedural macros using JavaScript or TypeScript! Have you ever thought "this would be a great use case for a procedu

null 15 Jun 17, 2022
Downloads and provides debug symbols and source code for nix derivations to gdb and other debuginfod-capable debuggers as needed.

nixseparatedebuginfod Downloads and provides debug symbols and source code for nix derivations to gdb and other debuginfod-capable debuggers as needed

Guillaume Girol 16 Mar 6, 2023
Self-contained template system with Handlebars and inline shell scripts

Handlematters Self-contained template system with Handlebars and inline shell scripts Introduction Handlematters is a template system that combines Ha

Keita Urashima 3 Sep 9, 2022
Owned container for dynamically-sized types backed by inline memory

sized-dst This crate provides Dst, an owned container for dynamically-sized types (DSTs) that's backed by inline memory. The main use-case is owned tr

Yuhan Lin 8 Sep 6, 2024
ChatGPT powered Rust proc macro that generates code at compile-time.

gpt-macro ChatGPT powered Rust proc macro that generates code at compile-time. Implemented Macros auto_impl!{} #[auto_test(...)] Usage Get ChatGPT API

Akira Moroo 429 Apr 15, 2023
Unwrap Macros to help Clean up code and improve production.

unwrap_helpers Unwrap Macros to help Clean up code and improve production. This does include a pub use of https://github.com/Mrp1Dev/loop_unwrap to ga

Ascending Creations 2 Nov 1, 2021
auto-rust is an experimental project that aims to automatically generate Rust code with LLM (Large Language Models) during compilation, utilizing procedural macros.

Auto Rust auto-rust is an experimental project that aims to automatically generate Rust code with LLM (Large Language Models) during compilation, util

Minsky 6 May 14, 2023
Code-shape is a tool for extracting definitions from source code files

Code-shape Code-shape is a tool that uses Tree-sitter to extract a shape of code definitions from a source code file. The tool uses the same language

Andrew Hlynskyi 3 Apr 21, 2023
Library to write x64 Assembly code from Rust, more properly. Designed for the nasm assembler

x64asm Library to write x64 Assembly code from Rust, more properly. Designed for the nasm assembler How to use let mut f = Formatter::new(false); // f

Anтo 7 Dec 30, 2022