To declare a matrix, I'm using a vector of length w*h
, and implementing the Index
trait, which does the necessary maths to find the correct value in the matrix. Now I'd like to define a macro which makes declaring and using matrices easier.
Below is the solution I came up with:
macro_rules! _count {
() => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + _count!($($xs)*));
}
macro_rules! _count_semicolon {
($([$($n:tt),+]);+) => {};
($($($n:tt),+);+) => { _count!(_count_semicolon!($([$($n),+]);+)) - 1 }
}
macro_rules! matrix {
($($($n:expr) +);+) => {{
use std::rc::Rc;
use crate::Matrix;
let len = _count!($($($n)+)+);
let lines = _count_semicolon!($($($n),+);+);
Matrix {
width: len / lines,
height: lines,
data: Rc::new(vec![$($($n as f64),+),+].into_boxed_slice()),
transpose: false
}
}};
}
The intent is simple: Declare a matrix by writing a grid of numbers separated by whitespaces, and rows by semicolons between braces. The height is the number of semicolons + 1, and the width is the total number of elements (ie the length of the vector) / height. The total length is determined by placing all elements as arguments into the count macro. A similar approach is used to count the semicolons, except the elements delimited by semicolons are placed in square brackets, to be treated as a single argument. Since this doesn't work, I use another macro to rewrite this into an empty argument, and pass it to the count macro instead.
So far my implementation seems to work for the following cases:
matrix! [
1 2 3 4 5;
6 7 8 9 10
];
matrix! [
1 2
3 4
];
matrix! [
5 6
7 8
];
However not for the following. The widths and heights are 1 and 2, respectively. It correctly counts the length (3), but yields a height of 2, and since 3/2.floor() == 1
,
matrix![
1 2 3
];
matrix![
4 5 6
];
An explanation and/or fix would be highly appreciated
I received two very good answers while I was asleep. Both of which do exactly as advertised, and answer the question perfectly, however, since @BallpointBen's answer is just slightly more descriptive, will be the one I accept.
Thank you both for your assistance <3
The issue is that the $x:tt
in ( $x:tt $($xs:tt)* )
in _count!
matches any token tree — including _count_semicolon!
! In fact, _count
treats it as two token trees, _count_semicolon
and !
. The expansion of _count_semicolon!
happens after, not before, _count
reads its tokens, and _count!
simply... counts the (two) tokens in _count_semicolon
!
instead of leaving them in place to expand. (You can see this if you compile with Rust nightly and #![feature(trace_macros)]
.)
Instead, the following works:
macro_rules! _count {
() => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + _count!($($xs)*));
}
macro_rules! _count_semicolon {
($($($n:expr),+);+) => { _count_semicolon_bracketed!($([$($n),+]);+) }
}
macro_rules! _count_semicolon_bracketed {
($($n:tt);+) => {_count!( $($n)+ )};
}
We've split _count_semicolon
into two macros (to avoid recursion) and have gotten rid of the -1
.
Now, we have the following expansions:
matrix!();
// ... becomes ...
{
use std::rc::Rc;
use crate::Matrix;
let len = 0usize;
let lines = 0usize;
Matrix {
width: len / lines,
height: lines,
data: Rc::new(($crate::vec::Vec::new()).into_boxed_slice()),
transpose: false,
}
}
matrix!(1 2 3);
// ... becomes ...
{
use std::rc::Rc;
use crate::Matrix;
let len = 1usize + 1usize + 1usize + 0usize;
let lines = 1usize + 0usize;
Matrix {
width: len / lines,
height: lines,
data: Rc::new(
(<[_]>::into_vec(
#[rustc_box]
$crate::boxed::Box::new([(1 as f64), (2 as f64), (3 as f64)]),
))
.into_boxed_slice(),
),
transpose: false,
}
}
matrix!(1 2 3; 4 5 6);
// ... becomes ...
{
use std::rc::Rc;
use crate::Matrix;
let len = 1usize + 1usize + 1usize + 1usize + 1usize + 1usize + 0usize;
let lines = 1usize + 1usize + 0usize;
Matrix {
width: len / lines,
height: lines,
data: Rc::new(
(<[_]>::into_vec(
#[rustc_box]
$crate::boxed::Box::new([
(1 as f64),
(2 as f64),
(3 as f64),
(4 as f64),
(5 as f64),
(6 as f64),
]),
))
.into_boxed_slice(),
),
transpose: false,
}
}