#[macros] Is there any way I can match members and methods calls?

102 messages · Page 1 of 1 (latest)

left lagoon
#

I'm trying to match self.member.method1().method2(), it's important that I distinguish the members and methods as I will have to treat those differently in the macro body.

For simplicity's sake, let's say I'm not trying to match any args inside parenthesis:

macro_rules! foo {
    ( $($member:ident).* $($method:ident()).* ) => {
        ("ok")
    }
}

doesn't work. I get ambiguous match error.

I guess it's bc method1() could match both $($member:ident).* and $($method:ident())*.

Any way to avoid this?

low dome
#

also try #macros

#

at least direct to this thread

left lagoon
#

I still get the same error

#

error: local ambiguity when calling macro `foo`: multiple parsing options: built-in NTs ident ('member') or tt ('tail')

low dome
#

that message literally doesn't tell me anything

left lagoon
#

well it doesn't know if it should match the first or the second component of the foo macro here

#
macro_rules! foo {
    ( $($member:ident).* $($tail:tt)* ) => {
        ("ok")
    }
}
#

if I remove tail it just tells me that ( token is not expected

low dome
#

can you write a self-contained code snippet into the question that also shows what kind of code you want to generate?

left lagoon
#

I think it's because the first component $($member:ident).* tells rust to match 0 or more... and it doesn't know where that should end and tt should start, but I'm not well versed in macros

left lagoon
plucky hemlock
#

Oh no

#

This sounds like a nightmare with only decl macros

#

(and a nontrivial but possible thing with proc ones)

#

I'd ask #macros too, just in case they have any idea

low dome
#

?play ```rs
// trace_macros!(true);
macro_rules! foo_impl {
([ $($acc:tt)* ] $(.)? $method:ident () $($rest:tt)* ) => {
foo_impl!([ $($acc)* [$method ()] ] $($rest)* )
};

([ $($acc:tt)* ] $(.)? $member:ident $($rest:tt)* ) => {
    foo_impl!([ $($acc)* [$member] ] $($rest)* )
};

([ $($acc:tt)* ]) => {
    stringify!($($acc)*)
};

}

macro_rules! foo {
( $($member:tt)* ) => { foo_impl!([] $($member)*) };
}

fn main() {
dbg!(Outer::new().bar());
dbg!(foo!(self.inner.baz().bar().quux));
}

struct Outer {
pub inner: Inner,
}

impl Outer {
fn new() -> Self {
Outer { inner: Inner {} }
}

fn bar(&self) -> &'static str {
    foo!(self.inner.baz())
}

}

struct Inner {}

impl Inner {
fn baz(&self) {}
}

obtuse fiberBOT
#
     Running `target/debug/playground`
[src/main.rs:21] Outer::new().bar() = "[self] [inner] [baz ()]"
[src/main.rs:22] foo!(self.inner.baz().bar().quux) = "[self] [inner] [baz ()] [bar ()] [quux]"
low dome
#

you can easily distinguish the cases with this setup

left lagoon
#

let me see

low dome
#

and then you can join them at the end from acc

left lagoon
#

what are the brakets on the first arg

#

[ $($acc:tt)* ] what do these do

#

that's different from

#

$($acc:tt)*

low dome
#

it's for grouping elements that you want to use later

#

otherwise it would be ambiguous

#

at the end I'm using all the elements that are in those []

#

as an example

left lagoon
#

if you could point me out to a reading material or write the macro that produces the output:

let instance = self.a.b.clone();
let _ = instance.c().d();

because I'll be real, I am having a hard time understanding what's happening

low dome
#

the tutorial I linked before has the basics

left lagoon
low dome
#

what I wrote is the extrapolation of that

low dome
#

that's the terminal operation

left lagoon
#

ok I'll add that

low dome
#

and why let _ =?

left lagoon
# low dome yeah

but by then I still don't know that part that is the object (to clone) and the part that is the method() (to call) right?

left lagoon
low dome
#

ok can't it be self.a.b.clone().c().d() at the end?

left lagoon
low dome
#

there's quux at the end

left lagoon
#

so the macro needs to be aware

low dome
#

is there always field access followed by method calls?

left lagoon
low dome
#

or can they be mixed?

low dome
#

alright

left lagoon
#

only field access first, method calls later

#

actually if that makes is simplee, I'm pretty sure I'll only ever need one field access at most, methods are variable:

// can be either:
a.b.c().d().e().f()....
// or:
a.c().d().e().f().g().h()...
#

but in practice it'll will be one or two methods ofc

#

field access is for sure 1 at most

low dome
#

?play ```rs
// trace_macros!(true);
macro_rules! foo_impl {
([ $($fields:tt)* ] [ $($methods:tt)* ] $(.)? $method:ident () $($rest:tt)* ) => {
foo_impl!([$($fields)] [ $($methods) [$method()] ] $($rest)* )
};

([ $($fields:tt)* ] [] $(.)? $field:ident $($rest:tt)* ) => {
    foo_impl!([ $($fields)* $field ] [] $($rest)* )
};

([ $($fields:tt)* ] [ $( [$($methods:tt)*] )* ] ) => {{
    let instance = $($fields).* .clone();
    instance. $($($methods)*).*;
}};

}

macro_rules! foo {
( $($access:tt)* ) => { foo_impl!([] [] $($access)*) };
}

fn main() {
Outer::new().bar();
// foo!(self.inner.fooer.baz().bar());
}

#[derive(Debug, Clone)]
struct Outer {
pub inner: Inner,
}

impl Outer {
fn new() -> Self {
Outer { inner: Inner {} }
}

fn bar(&self) {
    foo!(self.inner.baz())
}

}

#[derive(Debug)]
struct Inner {}

impl Clone for Inner {
fn clone(&self) -> Self {
println!("cloning Inner!");
Inner {}
}
}

impl Inner {
fn baz(&self) {
println!("hey");
}
}

obtuse fiberBOT
#
     Running `target/debug/playground`

cloning Inner!
hey
low dome
#

oof

#

I added a debugging thing with the single @

#

I completely forgot that you can just use expand...

#

that is cargo-expand or on the playground

low dome
#

you can use the playgrounds tools>expand macros features to see what it does

#

or uncomment the first line

#

generally it collects all field accesses and then method calls

#

into two [] groups

#

it also only works if calls follow fields

#

although the error message if that's not the case is not very descriptive

#

it could also easily be expanded for methods that take parameters

left lagoon
#

follow up question is

#

is it necessary to have both foo and foo_impl, if you bring the foo case in foo_impl it won't work?

low dome
#

no, because the clause in foo matches everything

#

hmm, but it could work

left lagoon
#

asking because from the user's perspective, it's not like methods where we can hide the implementation, but user of the macro will have to import both foo and foo_impl correct?

left lagoon
low dome
#

there are methods to call the macro without importing

#

but it's kinda weird

#

I'd ask about it in #macros if you need it

left lagoon
#

yeah it would be like requiring both imports of println! and println_impl! even if only println! ever needs to be called

low dome
#

actually it works with the clause in the foo_impl

left lagoon
#

I think it's weird this way, better to hide the weirdness in the implementation

#

it does?

low dome
#

with a previous structure I had while prototyping it didn't work

#

if you put it last it works

left lagoon
#

oh right

#

let me see

#

besides the Little Book of Macros, did you use any other resource to get to your level of proficiency?

#

Something a bit more pragmatical than the reference API?

low dome
#

that's hard...

#

it's mostly practice and hanging out around Yandros in #macros :P

#

rust-by-example might have a chapter on macros

#

but it's probably short and uneventful

#

another change could be ```rs
macro_rules! foo {
(@inner [ $($fields:tt)* ] [ $($methods:tt)* ] $(.)? $method:ident () $($rest:tt)* ) => {
foo!(@inner [$($fields)] [ $($methods) [$method()] ] $($rest)* )
};

(@inner [ $($fields:tt)* ] [] $(.)? $field:ident $($rest:tt)* ) => {
    foo!(@inner [ $($fields)* $field ] [] $($rest)* )
};

(@inner [ $($fields:tt)* ] [ $( [$($methods:tt)*] )* ] ) => {{
    let instance = $($fields).* .clone();
    instance. $($($methods)*).*;
}};

(@inner $($rest:tt)*) => {
    compile_error!(concat!("invalid macro invocation: ", stringify!($($rest)*))); 
};

( $($access:tt)* ) => { foo!(@inner [] [] $($access)*) };

}

left lagoon
#

Are you sure that adding args is trivial? Been fighting with it for a while.

pretty sure we need to add the arg match inside the parenthesis, is needs to be comma separated, and catch multiple fields, so

$($arg:ident),*

OK, now I put it inside the parenthesis, and expand in the body, I get:

macro_rules! foo {
    (@inner [ $($fields:tt)* ] [ $($methods:tt)* ] $(.)? $method:ident ($($arg:ident),*) $($rest:tt)* ) => {
       foo!(@inner [$($fields)*] [ $($methods)* [$method($($arg),*)] ] $($rest)* )
    };

    (@inner [ $($fields:tt)* ] [] $(.)? $field:ident $($rest:tt)* ) => {
        foo!(@inner [ $($fields)* $field ] [] $($rest)* )
    };

    (@inner [ $($fields:tt)* ] [ $( [$($methods:tt)*] )* ] ) => {{
        let instance = $($fields).* .clone();
        instance. $($($methods)*).*;
    }};
    
    (@inner $($rest:tt)*) => {
        compile_error!(concat!("invalid macro invocation: ", stringify!($($rest)*))); 
    };

    ( $($access:tt)* ) => { foo!(@inner [] [] $($access)*) };
}

but this fails.

Do I need to capture and expand then in some other branch? I can't see any other branch that's trying to capture arguments?

#

I get the "invalid macro invocation" error in the middle

#

Wait maybe it's correct, my final expansion was the one causing trouble

low dome
#

I'd change it to $arg:expr though