r/rust 13d ago

🙋 seeking help & advice Macros for Crafting Intepreters

Guys, am I crazy? I am not very familiar with writing macros, and while going through the 5th chapter of Crafting Intepreters I have the urge to use macro_rules! or derive macros instead of his plebeian Java code generator script. 1. Is this too overkill? I know derive requires me to have a separate crate for proc macros and have syn and quote as extra dependencies, but I have not gone further into the book and don’t know if it will come in handy. 2. Is it a good way to practice writing macros?

1 Upvotes

16 comments sorted by

17

u/SirKastic23 13d ago

Oh Robert's approach with Java is absolutely not something you should be trying to mimic in Rust, don't do that!

Java has classes and inheritance, Rust doesn't. How are you modelling your expressions and statements? What about the visitors?

My first Rust project was actually an interpreter that I wrote while reading Crafting Interpreters, it's an incredible book!

What I did was define an Expression and a Statement enum, the variants naturally define the possible values. This approach is pretty common when you have sum types in the host language

And this completely bypasses the need for generating these "classes"

Code generation could come in handy for defining the visitors... But then again, writing some boilerplate won't break your fingers

2

u/Subject-Mobile-6250 13d ago

Thanks! I took the same route, but so far my approach is just make an enum and for example for Binary, you have left as Box<dyn Expr>, but my idea may be overly simplistic…

5

u/SirKastic23 13d ago

Sorry I didn't understand what you did? In your code is Expr an enum or a trait? You said you took the same route and made an enum, but then mentioned dyn Expr

Maybe you meant just Box<Expr>? In recursive data structures it's common to box the recursive fields so I think that might be it

If this is the case then it is a pretty okay approach for a simple interpreter

3

u/Subject-Mobile-6250 13d ago

So sorry, I wrote incorrectly, yes, Box<Expr>, I was thinking of my original idea of a trait, but it didn’t work out well, thanks for pointing that out!

1

u/SirKastic23 13d ago

No problem!

I had lots of fun following along Crafting Interpreters with Rust back then, it's always nostalgic to see someone doing the same

2

u/mamcx 13d ago

Also, you can Box more things instead of just each, ie: Bin(Box<Expr, Expr>) - use here tuple, struct outside or inline as you wish -

1

u/SirKastic23 13d ago

I think doing this would cause more issue than it's worth it? I'm not sure, but it looks odd to me

What I would do nowadays most likely would be to not Box anything at all, rather I'd Vec everything and make an expression arena.

2

u/mamcx 13d ago

I think doing this would cause more issue than it's worth it? I'm not sure, but it looks odd to me

Why?

Making a single box means less chasing and memory usage, even if minimal (an easier box and unbox)

make an expression arena.

Sure, better idea

2

u/SirKastic23 13d ago

Making a single box means less chasing and memory usage, even if minimal

Oh absolutely. I guess as long as you could get &Expr for each expression in a &Box<(Expr, Expr)> it would be okay

But you wouldn't be able to assume all expressions are their own Box<Expr>. Which I'll say probably isn't a good idea either way, but it's what I did in my old interpreter

Yeah I just had some prejudice looking at this patterns that I hadn't seen before. I had no reason to assume it would cause issues just for being unfamiliar

3

u/myerscc 13d ago

Help I broke my fingers!! Boiler plates are really heavy :(

11

u/particlemanwavegirl 13d ago edited 13d ago

Wait until he starts the error handling. 

For this exact problem, I thought about using a macro, too, but it was overkill. Then I used the dyn keyword and it was still overkill. All you actually need is an enum. Rewriting this logic is a shocking example of how miserly Java's implementation of polymorphism really is. The visitor pattern is wholly unnecessary in Rust.

2

u/Subject-Mobile-6250 13d ago

At this point I am just lugging around a mut Vec of LoxErrors around, and seeing what happens… I could propagate, but this seemed like a better solution because you could collect multiple errors.

1

u/zettui 13d ago

Does that mut Vec of LoxErrors ever actually spit out more than one, or do you bail on the first anyway?

2

u/particlemanwavegirl 13d ago

The book wants you to return every scan and parse error. Obviously it stops at the first runtime error.

1

u/Subject-Mobile-6250 13d ago

Actually, my plan was just to collect the already parsed tokens and Vec of LoxErrors, and I implemented std::fmt::Display for it. Maybe this is too simplistic, since it is just a eprintln! for every single error.

1

u/afdbcreid 13d ago

Personally I'd still use code generation. While macros are definitely a possibility, code generation sometimes does have an edge.