I have a function that takes a truckload of parameters (29 in total, of whom 13*2 are "pre"/"post"):
fn assert_cpu_execute(
cpu: &mut Cpu,
instruction_bytes: &[u8],
pre_A: u8,
pre_zf: bool,
// ...
post_A: u8,
post_zf: bool,
// ...
)
I've therefore created a macro, which servers two purposes:
make parameters optional,
provide a readable way to specify the "pre" and "post" values as a single expression.
The macro has this structure:
macro_rules! assert_cpu_execute {
(
$cpu:ident,
$instruction_bytes:ident,
$( A: $A_pre_value:literal => $A_post_value:literal , )?
$( zf: $zf_pre_value:literal => $zf_post_value:literal , )?
// ...
) => {
let pre_A = 0x21 $( - 0x21 + $A_pre_value )?;
let post_A = 0x21 $( - 0x21 + $A_post_value )?;
let pre_zf = false $( | $zf_pre_value )?;
let post_zf = false $( | $zf_post_value )?;
// ...
}
However, this is a rather ugly workaround; this is because I couldn't find any way to express, via declarative macros, the concept "if a repetition exists, transcribe as this, if not transcribe as that".
Any suggestions?
And also, bonus question: it'd be great to generalize all the parameters as the single repetition, which I guess would be something around the lines of $( $store:ident: $pre_value:literal => $post_value:literal , )*
; due to the different data type and defaults (u8
, bool
), I can't trivially do this. Is there any simple way to do this?
Thanks!
2e71828
September 12, 2020, 4:52pm
2
64kramsystem:
However, this is a rather ugly workaround; this is because I couldn't find any way to express, via declarative macros, the concept "if a repetition exists, transcribe as this, if not transcribe as that".
This is possible, but not particularly straightforward, with a tt
muncher pattern :
macro_rules! assert_cpu_execute {
(@arg A A { $pre:literal => $post:literal } $($tail:tt)*) => { ($pre, $post) };
(@default A) => { (0x21, 0x21) };
(@arg zf zf { $pre:literal => $post:literal } $($tail:tt)*) => { ($pre, $post) };
(@default zf) => { (false, false) };
// No match, strip the first token and try again
( @ arg $needle:ident $head:tt $($haystack:tt)* ) => {
assert_cpu_execute!(@arg $needle $($haystack)*)
};
// Argument list gone, use default
(@arg $needle:ident) => { assert_cpu_execute!(@default $needle) };
// Main implementation
( $cpu:ident, $instr:ident $($args:tt)*) => {
{
let A:(u8,u8) = assert_cpu_execute!(@arg A $($args)*);
let zf:(bool,bool) = assert_cpu_execute!(@arg zf $($args)*);
assert_cpu_execute($cpu, $instr, A.0, zf.0, /* ... */ A.1, zf.1, /* ... */)
}
};
}
pub fn f() {
assert_cpu_execute!(x86, nop,
zf { true => false },
A { 42 => 42 }
);
assert_cpu_execute!(arm, nop);
}
(Playground— Use “Expand Macros” )
jjpe
September 12, 2020, 5:11pm
3
Perhaps you could get away with creating a type, let's call it World
, containing all those params, then writing a Default
impl for World
.
If you can do that you can cut down on the boilerplate with something like this:
fn foo() {
let world = World {
// params you're interested in
.. Default::default()
};
}
Oh and the best part, no macros necessary.
1 Like
Wow, very interesting. Thanks!
Thanks! I considered this solution, however, this trades readability for simplicity, as invocations would need to specify post_
and pre_
. Example:
assert_cpu_execute(
Values::new {
pre_A: 0xCA, post_A: 0xFE,
.. Values::default()
}
)
assert_cpu_execute!(
A: 0xCA => 0xFE,
)
In the context of a test suite with more than a hundred invocations, I (personally) think readability has more weight.
After considering both suggestions, I think that an interesting solution is to have a mixed approach, with a macro that has the signature and definition as in the question, however, instead the let
s, it instantiates a struct that (additionally) uses a default()
.
Thanks both
2e71828
September 12, 2020, 6:05pm
7
That’s probably the right choice. The trick to using macros effectively usually lies in being clever about what they produce, instead of trying to make them expand to a specific, predefined form.
64kramsystem:
as invocations would need to specify post_
and pre_
. Example:
assert_cpu_execute(
Values::new {
pre_A: 0xCA, post_A: 0xFE,
.. Values::default()
}
)
You could also use 2-tuples to represent pre- and post-values:
assert_cpu_execute(
Values::new {
A: (0xCA, 0xFE),
.. Values::default()
}
)
Another option is something like a builder pattern:
AssertCPUExecute::new(cpu, instr)
.A(0xCA, 0xFE)
.zf(false, false)
.check();
For clarity I would us one of @2e71828 ‘s approaches but add a struct (with a better name)
struct PrePost <T> {
pre: T,
post: T
}
The struct name can hide behind a method call but it’s nicer at the use site to call the fields using .pre
and .post
1 Like
system
Closed
December 11, 2020, 7:03pm
9
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.