Some macro magic

Days ago, my friend sent me a question from LeetCode. Her solution is pretty tricky; it generates the string during compile time and directly retrieves the answer from it.

I was impressed by how tricky it is. This is because LeetCode only counts the runtime of the puzzle solution, not the compile time. So, for some languages (she used C++), it can brute-force the result, making the runtime extremely fast.

Because I don’t know C++, but I know some other languages can do this too. For example, Rust (proc macro) and Common Lisp (macro).

Rust version

I have worked with macros before in cl-format-rs, so this wasn’t too difficult for me.

extern crate proc_macro;

use proc_macro::TokenStream;
use syn::{Expr, ExprLit, ExprTuple, Lit, parse_macro_input};

#[proc_macro]
pub fn make_answer(item: TokenStream) -> TokenStream {
    let expr: Expr = parse_macro_input!(item as Expr);

    let mut string_value: Option<String> = None;
    let mut int_value: Option<i64> = None;

    let Expr::Tuple(ExprTuple { elems, .. }) = expr else {
        panic!()
    };

    for elem in elems {
        match elem {
            Expr::Lit(ExprLit { lit, .. }) => {
                match lit {
                    Lit::Str(lit_str) => {
                        if string_value.is_none() {
                            string_value = Some(lit_str.value());
                        } else {
                        }
                    }
                    Lit::Int(lit_int) => {
                        if int_value.is_none() {
                            match lit_int.base10_parse::<i64>() {
                                Ok(val) => int_value = Some(val),
                                Err(e) => {
                                    return syn::Error::new_spanned(
                                        lit_int,
                                        format!("failed to parse integer literal: {}", e),
                                    )
                                    .to_compile_error()
                                    .into();
                                }
                            }
                        } else {
                        }
                    }
                    _ => {}
                }
            }
            _ => {}
        }
    }

    //println!("{:?}, {:?}", string_value, int_value);
    let mut ss = string_value.unwrap();
    let intt = int_value.unwrap() as usize;
    for _ in 0..intt {
        ss = one_round(ss);
        if ss.len() >= intt {
            return format!("const all: &str = \"{}\";", ss).parse().unwrap();
        }
    }

    r#"const all: &str = "";"#.parse().unwrap()
}

fn one_round(s: String) -> String {
    let mut res = vec![];
    for c in s.chars() {
        res.push(c);
        res.push(match c {
            'a'..='y' => (c as u8 + 1) as char,
            'z' => 'a',
            _ => unreachable!(),
        })
    }
    String::from_iter(res)
}

It basically just brute-forces the string and assigns it to a constant variable.

Then, in main.rs:

make_answer!(("a", 50000000));

fn main() {
    //make_answer!(("a", 0));
    //println!("{}", answer());
    println!("{}", all.get(49999999..50000000).unwrap());
}

Lisp version

The Lisp version is even simpler:

(defmacro alphab (c)
  (let* ((x (concatenate 'list "abcdefghijklmnopqrstuvwxyz"))
         (y (append (subseq x 1) (list (first x)))))
    `(case ,c
       ,@(loop for xx in x
               for yy in y
               collect (list xx yy)))))

(eval-when (:execute :load-toplevel :compile-toplevel)
  (defun one-round (s)
    (loop for c in (concatenate 'list s)
          collect c into res
          collect (alphab c) into res
          finally (return (concatenate 'string res)))))

(defmacro all (k)
  (loop with res = "a"
        for i from 1 to k
        do (setf res (one-round res))
        finally (return res)))

(defun main ()
  (assert (char= (nth 5 (all 5)) #\b))
  (assert (char= (nth 10 (all 10)) #\c))
  (assert (char= (nth 10 (all 30)) #\c))
  ;;(assert (char= (nth 500 (all 500)) #\h))
  ;;(assert (char= (nth 5000 (all 1000)) #\h))
  )

The only thing to worry about is the eval-when block. It needs to compile at the top-level so that the all macro can call the function defined within it.

Performance

This is the fun part I wanted to test. Because almost all the work is done during compile time, the compile time should increase with the number of arguments.

rust:

compile time arguments number
0.21s 500000
0.55s 5000000
3.18s 50000000

lisp:

(all 10)

; compilation finished in 0:00:00.020
Evaluation took:
  0.020 seconds of real time
  0.018004 seconds of total run time (0.015299 user, 0.002705 system)
  90.00% CPU
  34 forms interpreted
  91 lambdas converted
  5 page faults
  4,886,592 bytes consed

(all 500)

memory failed

(all 100)

memory failed

(all 20)

; compilation finished in 0:00:00.101
Evaluation took:
  0.101 seconds of real time
  0.101081 seconds of total run time (0.091436 user, 0.009645 system)
  100.00% CPU
  34 forms interpreted
  91 lambdas converted
  5 page faults
  181,083,424 bytes consed
Written on September 26, 2025