0

I am making a macro to expand geometric algebra expressions, and in order for the macro to work, it needs to know to know the type of geometric algebra (3D VGA, 2D PGA, etc.) at compile time.

I could do this easily by passing the type (3D PGA) in implicitly at each call of eq!:

let a = eq!((3, 0, 1): e1 + e2);
let b = eq!((3, 0, 1): 3 + e12);
println!("{:?}", eq!((3, 0, 1): a + b))

or

let a = eq!("PGA3d": e1 + e2);
let b = eq!("PGA3d": 3 + e12);
println!("{:?}", eq!("PGA3d": a + b))

or by creating a specific macro for each type:

let a = pga_3d!(e1 + e2);
let b = pga_3d!(3 + e12);
println!("{:?}", pga_3d!(a + b))

but I'd rather not have to write out the type at every single eq! call or create a specific macro for every single type someone could want (there are an infinite amount of types and I want to be able to, at least theoretically, handle them all). Is there a way to define the type as a const or with another procedural macro in order to have all eq! calls to know the specified type? (eq! needs to know what the type actually is in order to expand.) Ideally, I'd like the code to look like this:

const TYPE: (usize, usize, usize) = (3, 0, 1);
// or 
set_type!(3, 0, 1);

fn main() {
    let a = eq!(e1 + e2); // eq somehow knows to use 3D PGA because of TYPE or set_type!
    let b = eq!(3 + e12);
    println!("{:?}", eq!(a + b));
}
  • Did you try just using `TYPE` inside the macro, and then requiring per your macro documentation that a suitable `TYPE` must be in scope? – rodrigo Jun 29 '22 at 17:49
  • @rodrigo No, because I need to know the actual value of type in order to expand the expression the way i want to (the macro expands to an array and the length of that array is dependent on the type). I edited the post to clarify this. – Tom Wolcott Jun 29 '22 at 18:14
  • 1
    Why not simply creating a new macro, like `eq_pga3d!` that, in turn, calls `eq!` with a default argument before the colon? You can make the name of that new macro shorter if it's a length issue. – jthulhu Jun 29 '22 at 18:52
  • @BlackBeans I could, but my end-goal is to be able to make switching types as easy as editing one constant (many expression that I would write in 3D VGA would work in 4D VGA and 5D VGA, so I want to allow switching types to be extremely easy). I'll edit the post to acknowledge these solutions. – Tom Wolcott Jun 29 '22 at 20:18
  • Well, that would be as easy as just modifying the "default" parameter in a single macro definition. Or you could even put that default value into a constant. However, I think that defining your own macro is the most convenient way you'll find to achieve what you want. You can see it in action [on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=39544047f3c3cfe9de86f9b0a8622d1a) to get an idea of what I mean. – jthulhu Jun 29 '22 at 20:29
  • Incidentally, I just noticed but once you have defined your constant `TYPE`, you could just write `eq!(TYPE: e1+e2)`, and then you would just need to change `TYPE` once to change the type everywhere... – jthulhu Jun 29 '22 at 20:32
  • @BlackBeans Can you give an example? When the procedural macro function is called, I don't have access to the value of any of the variables, even if they're constants. Or if you mean writing `TYPE` as a placeholder, and then ctrl+f replacing it with the actual desired type, then that is a valid solution, and I might end up having to resort to it. – Tom Wolcott Jun 29 '22 at 20:54
  • 1
    Does this answer your question? [Is there a way to get the type of a variable in a macro?](https://stackoverflow.com/q/70405884/7884305) or [How do I match the type of an expression in a Rust macro?](https://stackoverflow.com/q/34214136/7884305) – Chayim Friedman Jun 29 '22 at 22:48
  • @TomWolcott You can't access a variable that you didn't pass as an argument in a macro, that's the macro hygiene. However, it is still possible to reference constants in macro. If you want an example of that, just look at the playground link I sent. You'll see a macro `eq!` that takes two arguments, a constant `a` that defines the "default" first argument, and finally a new macro `my_eq!` that only takes one argument, and which calls `eq!` with `a` as its first argument. Pretty much what you want, isn't it? – jthulhu Jun 30 '22 at 05:54
  • @BlackBeans: I think that what the OP wants is to use different macro rules depending on the context of that macro: either `((3, 0, 1): $e1:expr + $e2:expr) => { /*A*/ }` or `((2, 0, 1): $e1:expr + $e2:expr) => { /*B*/ }`, but macro rules cannot depend on a constant or a type, only on the current syntax tree. – rodrigo Jun 30 '22 at 16:27
  • An interesting possible approach if all options type-check is to expand to both and choose at runtime. But without more context, this is a duplicate to the questions I linked. – Chayim Friedman Jun 30 '22 at 23:21

1 Answers1

2

I believe I've found a workable solution to the problem I was having. The solution I found was to have the lib.rs file containing the procedural macros, read from a json file created alongside the cargo.toml file where the library was imported:

ga_macros_test
│   cargo.toml       <-- ga_macros is imported as a dependency here
│   type.json        <-- type is specified here
└───src
│   │   main.rs      <-- eq! can be used here with the specified type
│
└───ga_macros
    │   cargo.toml   <-- proc_macro imported here as dependency
    └───src
        │   lib.rs   <-- eq defined here along with a read for "type.json"

Inside the lib.rs file:

extern crate proc_macro;
use proc_macro::*;

use lazy_static::lazy_static;
use std::{fs, collections::HashMap};

extern crate serde_json;

lazy_static! {
    static ref TYPE: (usize, usize, usize) = {
        if let Ok(str) = fs::read_to_string("type.json") {
            let json: HashMap<&str, Vec<usize>> = serde_json::from_str(str.as_str()).expect("The json file couldn't be parsed");

            (json["algebra"][0], json["algebra"][1], json["algebra"][2])
        } else {    
            (3, 0, 0) // 3D Vectorspace Geometric Algebra is the default
        }
    };
}

#[proc_macro]
pub fn eq(tokens: TokenStream) -> TokenStream {
    /*Expand expression using TYPE*/
}

This allows ga_macros to support arbitrary types, read the type while expanding the macro, and provide a single place where type is defined and can be changed. Another benefit is that more information could be added to type.json which could then better specialize eq! to the user's needs. Link to example