Skip to content

Instantly share code, notes, and snippets.

@de-sh
Last active May 2, 2020 21:20
Show Gist options
  • Save de-sh/32a5b7cc1cc0d5731875d3ef9e7ac16e to your computer and use it in GitHub Desktop.
Save de-sh/32a5b7cc1cc0d5731875d3ef9e7ac16e to your computer and use it in GitHub Desktop.
Writing macros because we all are such lazy coders :p

So, I had recently contributed to the malluscript esoteric programming language. It was a quick fix to what I thought was broken, or more aptly, shabbily written piece of code. But even with my edits, it seemed to be smelling foul. So let's experiment with something new to see if it helps!

Here's the code we have at the offset

...
#[derive(Clone, Debug)]
pub struct Keywords {
    pub list: HashMap<String, TokenType>,
}
...
impl Keywords {
    pub fn new() -> Self {
        let mut list = HashMap::new();
        ...
        for &addr_tha in &["address_thada", "അഡ്രസ്_താടാ"] {
            list.insert(addr_tha.to_string(), TokenType::InputString);
        }
        ...
        Self { list }
    }
}

There should be some way to have lesser lines of code, right? Well, macros seem to be the best way forward, lets try it out!

A macro is most definetly the least appreciated and most taken for granted coding tool out there. Most languages might not even feature it's powerful abilities, but not rust!

BTW, rust-lang features a macro to build macros! 😂

Ok, before folks start discussing whether rust was made by Christopher Nolan, let's learn about this macro, macro_rules!.

macro_rules! lets us describe a macro, we name it keywordize, and a 'prototype' code expansion, with the necessary code and it's related values, that results from calling it. You can learn more about how this is done from the rustc-dev-guide article on Macro expansion. Now let's look at what we have written.

macro_rules! keywordize {
    // We define what the macro takes as arguments, by denoting the pattern
    ($( $words:expr => $func:expr ), *) => {{
        let mut list = HashMap::new();
        // The below line is where all the magic is happening!
        // The compiler expands the code by replacing $words and $func in the following line of code.
        // The arguments of the macro call take their place, this is pretty simple replacement.
        $( for &word in &($words) { list.insert(word.to_string(), $func); } )*
        list
    }};
}

That was the definition, but what is its function you ask. The rust compiler runs a macro-parser during the pre-processort stage where these macros are expanded. Thus, it's the expanded code blocks that are truly compiled, not the code with macro calls you wrote on your own. Let's see this by making a macro call!

let list = keywordize!(
    ...
    // Here the term to the left of => replace $words when expanded
    // and the one of the right replaces $func.
    ["address_thada", "അഡ്രസ്_താടാ"] => TokenType::InputString,
    ["number_thada", "നമ്പർ_താടാ"] => TokenType::InputNumber
    ...
);

Run in playground

After this experimentation, I made a PR to the malluscript project on GitHub

@de-sh
Copy link
Author

de-sh commented May 2, 2020

This medium article is a pretty interesting read as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment