diff --git a/.github/workflows/nalgebra-ci-build.yml b/.github/workflows/nalgebra-ci-build.yml index d302c908a..fd3ec2734 100644 --- a/.github/workflows/nalgebra-ci-build.yml +++ b/.github/workflows/nalgebra-ci-build.yml @@ -69,6 +69,12 @@ jobs: - name: test nalgebra-sparse (slow tests) # Unfortunately, the "slow-tests" take so much time that we need to run them with --release run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,slow-tests slow + test-nalgebra-macros: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: test nalgebra-macros + run: cargo test -p nalgebra-macros build-wasm: runs-on: ubuntu-latest # env: diff --git a/Cargo.toml b/Cargo.toml index 45fdb17a2..71f723845 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ name = "nalgebra" path = "src/lib.rs" [features] -default = [ "std" ] +default = [ "std", "macros" ] std = [ "matrixmultiply", "simba/std" ] sparse = [ ] debug = [ "approx/num-complex", "rand" ] @@ -32,6 +32,7 @@ compare = [ "matrixcompare-core" ] libm = [ "simba/libm" ] libm-force = [ "simba/libm_force" ] no_unsound_assume_init = [ ] +macros = [ "nalgebra-macros" ] # Conversion convert-mint = [ "mint" ] @@ -60,6 +61,7 @@ proptest-support = [ "proptest" ] slow-tests = [] [dependencies] +nalgebra-macros = { version = "0.1", path = "nalgebra-macros", optional = true } typenum = "1.12" rand-package = { package = "rand", version = "0.8", optional = true, default-features = false } num-traits = { version = "0.2", default-features = false } @@ -92,7 +94,7 @@ matrixcompare = "0.2.0" itertools = "0.10" [workspace] -members = [ "nalgebra-lapack", "nalgebra-glm", "nalgebra-sparse" ] +members = [ "nalgebra-lapack", "nalgebra-glm", "nalgebra-sparse", "nalgebra-macros" ] resolver = "2" [[example]] @@ -109,4 +111,4 @@ lto = true [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs -features = [ "proptest-support", "compare" ] +features = [ "proptest-support", "compare", "macros" ] diff --git a/nalgebra-macros/Cargo.toml b/nalgebra-macros/Cargo.toml new file mode 100644 index 000000000..d95784e06 --- /dev/null +++ b/nalgebra-macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "nalgebra-macros" +version = "0.1.0" +authors = [ "Andreas Longva", "Sébastien Crozet " ] +edition = "2018" +description = "Procedural macros for nalgebra" +documentation = "https://www.nalgebra.org/docs" +homepage = "https://nalgebra.org" +repository = "https://github.com/dimforge/nalgebra" +readme = "../README.md" +categories = [ "science", "mathematics" ] +keywords = [ "linear", "algebra", "matrix", "vector", "math" ] +license = "Apache-2.0" + +[lib] +proc-macro = true + +[dependencies] +syn = { version="1.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" + +[dev-dependencies] +nalgebra = { version = "0.26.1", path = ".." } +trybuild = "1.0.42" diff --git a/nalgebra-macros/src/lib.rs b/nalgebra-macros/src/lib.rs new file mode 100644 index 000000000..5b1dd6778 --- /dev/null +++ b/nalgebra-macros/src/lib.rs @@ -0,0 +1,282 @@ +//! Macros for `nalgebra`. +//! +//! This crate is not intended for direct consumption. Instead, the macros are re-exported by +//! `nalgebra` if the `macros` feature is enabled (enabled by default). + +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::Expr; +use syn::{parse_macro_input, Token}; + +use proc_macro2::{Delimiter, Spacing, TokenStream as TokenStream2, TokenTree}; +use proc_macro2::{Group, Punct}; + +struct Matrix { + // Represent the matrix as a row-major vector of vectors of expressions + rows: Vec>, + ncols: usize, +} + +impl Matrix { + fn nrows(&self) -> usize { + self.rows.len() + } + + fn ncols(&self) -> usize { + self.ncols + } + + /// Produces a stream of tokens representing this matrix as a column-major nested array. + fn to_col_major_nested_array_tokens(&self) -> TokenStream2 { + let mut result = TokenStream2::new(); + for j in 0..self.ncols() { + let mut col = TokenStream2::new(); + let col_iter = (0..self.nrows()).map(move |i| &self.rows[i][j]); + col.append_separated(col_iter, Punct::new(',', Spacing::Alone)); + result.append(Group::new(Delimiter::Bracket, col)); + result.append(Punct::new(',', Spacing::Alone)); + } + TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, result))) + } + + /// Produces a stream of tokens representing this matrix as a column-major flat array + /// (suitable for representing e.g. a `DMatrix`). + fn to_col_major_flat_array_tokens(&self) -> TokenStream2 { + let mut data = TokenStream2::new(); + for j in 0..self.ncols() { + for i in 0..self.nrows() { + self.rows[i][j].to_tokens(&mut data); + data.append(Punct::new(',', Spacing::Alone)); + } + } + TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, data))) + } +} + +type MatrixRowSyntax = Punctuated; + +impl Parse for Matrix { + fn parse(input: ParseStream) -> Result { + let mut rows = Vec::new(); + let mut ncols = None; + + while !input.is_empty() { + let row_span = input.span(); + let row = MatrixRowSyntax::parse_separated_nonempty(input)?; + + if let Some(ncols) = ncols { + if row.len() != ncols { + let row_idx = rows.len(); + let error_msg = format!( + "Unexpected number of entries in row {}. Expected {}, found {} entries.", + row_idx, + ncols, + row.len() + ); + return Err(Error::new(row_span, error_msg)); + } + } else { + ncols = Some(row.len()); + } + rows.push(row.into_iter().collect()); + + // We've just read a row, so if there are more tokens, there must be a semi-colon, + // otherwise the input is malformed + if !input.is_empty() { + input.parse::()?; + } + } + + Ok(Self { + rows, + ncols: ncols.unwrap_or(0), + }) + } +} + +/// Construct a fixed-size matrix directly from data. +/// +/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// +/// This macro facilitates easy construction of matrices when the entries of the matrix are known +/// (either as constants or expressions). This macro produces an instance of `SMatrix`. This means +/// that the data of the matrix is stored on the stack, and its dimensions are fixed at +/// compile-time. If you want to construct a dynamic matrix, use [`dmatrix!`] instead. +/// +/// `matrix!` is intended to be both the simplest and most efficient way to construct (small) +/// matrices, and can also be used in *const fn* contexts. +/// +/// The syntax is MATLAB-like. Column elements are separated by a comma (`,`), and a semi-colon +/// (`;`) designates that a new row begins. +/// +/// # Examples +/// +/// ``` +/// use nalgebra::matrix; +/// +/// // Produces a Matrix3<_> == SMatrix<_, 3, 3> +/// let a = matrix![1, 2, 3; +/// 4, 5, 6; +/// 7, 8, 9]; +/// ``` +/// +/// You can construct matrices with arbitrary expressions for its elements: +/// +/// ``` +/// use nalgebra::{matrix, Matrix2}; +/// let theta = 0.45f64; +/// +/// let r = matrix![theta.cos(), - theta.sin(); +/// theta.sin(), theta.cos()]; +/// ``` +#[proc_macro] +pub fn matrix(stream: TokenStream) -> TokenStream { + let matrix = parse_macro_input!(stream as Matrix); + + let row_dim = matrix.nrows(); + let col_dim = matrix.ncols(); + + let array_tokens = matrix.to_col_major_nested_array_tokens(); + + // TODO: Use quote_spanned instead?? + let output = quote! { + nalgebra::SMatrix::<_, #row_dim, #col_dim> + ::from_array_storage(nalgebra::ArrayStorage(#array_tokens)) + }; + + proc_macro::TokenStream::from(output) +} + +/// Construct a dynamic matrix directly from data. +/// +/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// +/// The syntax is exactly the same as for [`matrix!`], but instead of producing instances of +/// `SMatrix`, it produces instances of `DMatrix`. At the moment it is not usable +/// in `const fn` contexts. +/// +/// ``` +/// use nalgebra::dmatrix; +/// +/// // Produces a DMatrix<_> +/// let a = dmatrix![1, 2, 3; +/// 4, 5, 6; +/// 7, 8, 9]; +/// ``` +#[proc_macro] +pub fn dmatrix(stream: TokenStream) -> TokenStream { + let matrix = parse_macro_input!(stream as Matrix); + + let row_dim = matrix.nrows(); + let col_dim = matrix.ncols(); + + let array_tokens = matrix.to_col_major_flat_array_tokens(); + + // TODO: Use quote_spanned instead?? + let output = quote! { + nalgebra::DMatrix::<_> + ::from_vec_storage(nalgebra::VecStorage::new( + nalgebra::Dynamic::new(#row_dim), + nalgebra::Dynamic::new(#col_dim), + vec!#array_tokens)) + }; + + proc_macro::TokenStream::from(output) +} + +struct Vector { + elements: Vec, +} + +impl Vector { + fn to_array_tokens(&self) -> TokenStream2 { + let mut data = TokenStream2::new(); + data.append_separated(&self.elements, Punct::new(',', Spacing::Alone)); + TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, data))) + } + + fn len(&self) -> usize { + self.elements.len() + } +} + +impl Parse for Vector { + fn parse(input: ParseStream) -> Result { + // The syntax of a vector is just the syntax of a single matrix row + if input.is_empty() { + Ok(Self { + elements: Vec::new(), + }) + } else { + let elements = MatrixRowSyntax::parse_separated_nonempty(input)? + .into_iter() + .collect(); + Ok(Self { elements }) + } + } +} + +/// Construct a fixed-size column vector directly from data. +/// +/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// +/// Similarly to [`matrix!`], this macro facilitates easy construction of fixed-size vectors. +/// However, whereas the [`matrix!`] macro expects each row to be separated by a semi-colon, +/// the syntax of this macro is instead similar to `vec!`, in that the elements of the vector +/// are simply listed consecutively. +/// +/// `vector!` is intended to be the most readable and performant way of constructing small, +/// fixed-size vectors, and it is usable in `const fn` contexts. +/// +/// ## Examples +/// +/// ``` +/// use nalgebra::vector; +/// +/// // Produces a Vector3<_> == SVector<_, 3> +/// let v = vector![1, 2, 3]; +/// ``` +#[proc_macro] +pub fn vector(stream: TokenStream) -> TokenStream { + let vector = parse_macro_input!(stream as Vector); + let len = vector.len(); + let array_tokens = vector.to_array_tokens(); + let output = quote! { + nalgebra::SVector::<_, #len> + ::from_array_storage(nalgebra::ArrayStorage([#array_tokens])) + }; + proc_macro::TokenStream::from(output) +} + +/// Construct a dynamic column vector directly from data. +/// +/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// +/// The syntax is exactly the same as for [`vector!`], but instead of producing instances of +/// `SVector`, it produces instances of `DVector`. At the moment it is not usable +/// in `const fn` contexts. +/// +/// ``` +/// use nalgebra::dvector; +/// +/// // Produces a DVector<_> +/// let v = dvector![1, 2, 3]; +/// ``` +#[proc_macro] +pub fn dvector(stream: TokenStream) -> TokenStream { + let vector = parse_macro_input!(stream as Vector); + let len = vector.len(); + let array_tokens = vector.to_array_tokens(); + let output = quote! { + nalgebra::DVector::<_> + ::from_vec_storage(nalgebra::VecStorage::new( + nalgebra::Dynamic::new(#len), + nalgebra::Const::<1>, + vec!#array_tokens)) + }; + proc_macro::TokenStream::from(output) +} diff --git a/nalgebra-macros/tests/tests.rs b/nalgebra-macros/tests/tests.rs new file mode 100644 index 000000000..339dd048b --- /dev/null +++ b/nalgebra-macros/tests/tests.rs @@ -0,0 +1,258 @@ +use nalgebra::{ + DMatrix, DVector, Matrix1x2, Matrix1x3, Matrix1x4, Matrix2, Matrix2x1, Matrix2x3, Matrix2x4, + Matrix3, Matrix3x1, Matrix3x2, Matrix3x4, Matrix4, Matrix4x1, Matrix4x2, Matrix4x3, SMatrix, + SVector, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, +}; +use nalgebra_macros::{dmatrix, dvector, matrix, vector}; + +fn check_statically_same_type(_: &T, _: &T) {} + +/// Wrapper for `assert_eq` that also asserts that the types are the same +macro_rules! assert_eq_and_type { + ($left:expr, $right:expr $(,)?) => { + check_statically_same_type(&$left, &$right); + assert_eq!($left, $right); + }; +} + +// Skip rustfmt because it just makes the test bloated without making it more readable +#[rustfmt::skip] +#[test] +fn matrix_small_dims_exhaustive() { + // 0x0 + assert_eq_and_type!(matrix![], SMatrix::::zeros()); + + // 1xN + assert_eq_and_type!(matrix![1], SMatrix::::new(1)); + assert_eq_and_type!(matrix![1, 2], Matrix1x2::new(1, 2)); + assert_eq_and_type!(matrix![1, 2, 3], Matrix1x3::new(1, 2, 3)); + assert_eq_and_type!(matrix![1, 2, 3, 4], Matrix1x4::new(1, 2, 3, 4)); + + // 2xN + assert_eq_and_type!(matrix![1; 2], Matrix2x1::new(1, 2)); + assert_eq_and_type!(matrix![1, 2; 3, 4], Matrix2::new(1, 2, 3, 4)); + assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6], Matrix2x3::new(1, 2, 3, 4, 5, 6)); + assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8], Matrix2x4::new(1, 2, 3, 4, 5, 6, 7, 8)); + + // 3xN + assert_eq_and_type!(matrix![1; 2; 3], Matrix3x1::new(1, 2, 3)); + assert_eq_and_type!(matrix![1, 2; 3, 4; 5, 6], Matrix3x2::new(1, 2, 3, 4, 5, 6)); + assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6; 7, 8, 9], Matrix3::new(1, 2, 3, 4, 5, 6, 7, 8, 9)); + assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12], + Matrix3x4::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); + + // 4xN + assert_eq_and_type!(matrix![1; 2; 3; 4], Matrix4x1::new(1, 2, 3, 4)); + assert_eq_and_type!(matrix![1, 2; 3, 4; 5, 6; 7, 8], Matrix4x2::new(1, 2, 3, 4, 5, 6, 7, 8)); + assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12], + Matrix4x3::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); + assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12; 13, 14, 15, 16], + Matrix4::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); +} + +#[test] +fn matrix_const_fn() { + // Ensure that matrix! can be used in const contexts + const _: SMatrix = matrix![]; + const _: SMatrix = matrix![1, 2]; + const _: SMatrix = matrix![1, 2, 3; 4, 5, 6]; +} + +// Skip rustfmt because it just makes the test bloated without making it more readable +#[rustfmt::skip] +#[test] +fn dmatrix_small_dims_exhaustive() { + // 0x0 + assert_eq_and_type!(dmatrix![], DMatrix::::zeros(0, 0)); + + // 1xN + assert_eq_and_type!(dmatrix![1], DMatrix::from_row_slice(1, 1, &[1])); + assert_eq_and_type!(dmatrix![1, 2], DMatrix::from_row_slice(1, 2, &[1, 2])); + assert_eq_and_type!(dmatrix![1, 2, 3], DMatrix::from_row_slice(1, 3, &[1, 2, 3])); + assert_eq_and_type!(dmatrix![1, 2, 3, 4], DMatrix::from_row_slice(1, 4, &[1, 2, 3, 4])); + + // 2xN + assert_eq_and_type!(dmatrix![1; 2], DMatrix::from_row_slice(2, 1, &[1, 2])); + assert_eq_and_type!(dmatrix![1, 2; 3, 4], DMatrix::from_row_slice(2, 2, &[1, 2, 3, 4])); + assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6], DMatrix::from_row_slice(2, 3, &[1, 2, 3, 4, 5, 6])); + assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8], DMatrix::from_row_slice(2, 4, &[1, 2, 3, 4, 5, 6, 7, 8])); + + // 3xN + assert_eq_and_type!(dmatrix![1; 2; 3], DMatrix::from_row_slice(3, 1, &[1, 2, 3])); + assert_eq_and_type!(dmatrix![1, 2; 3, 4; 5, 6], DMatrix::from_row_slice(3, 2, &[1, 2, 3, 4, 5, 6])); + assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6; 7, 8, 9], DMatrix::from_row_slice(3, 3, &[1, 2, 3, 4, 5, 6, 7, 8, 9])); + assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12], + DMatrix::from_row_slice(3, 4, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])); + + // 4xN + assert_eq_and_type!(dmatrix![1; 2; 3; 4], DMatrix::from_row_slice(4, 1, &[1, 2, 3, 4])); + assert_eq_and_type!(dmatrix![1, 2; 3, 4; 5, 6; 7, 8], DMatrix::from_row_slice(4, 2, &[1, 2, 3, 4, 5, 6, 7, 8])); + assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12], + DMatrix::from_row_slice(4, 3, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])); + assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12; 13, 14, 15, 16], + DMatrix::from_row_slice(4, 4, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])); +} + +// Skip rustfmt because it just makes the test bloated without making it more readable +#[rustfmt::skip] +#[test] +fn vector_small_dims_exhaustive() { + assert_eq_and_type!(vector![], SVector::::zeros()); + assert_eq_and_type!(vector![1], Vector1::::new(1)); + assert_eq_and_type!(vector![1, 2], Vector2::new(1, 2)); + assert_eq_and_type!(vector![1, 2, 3], Vector3::new(1, 2, 3)); + assert_eq_and_type!(vector![1, 2, 3, 4], Vector4::new(1, 2, 3, 4)); + assert_eq_and_type!(vector![1, 2, 3, 4, 5], Vector5::new(1, 2, 3, 4, 5)); + assert_eq_and_type!(vector![1, 2, 3, 4, 5, 6], Vector6::new(1, 2, 3, 4, 5, 6)); +} + +#[test] +fn vector_const_fn() { + // Ensure that vector! can be used in const contexts + const _: SVector = vector![]; + const _: Vector1 = vector![1]; + const _: Vector2 = vector![1, 2]; + const _: Vector6 = vector![1, 2, 3, 4, 5, 6]; +} + +// Skip rustfmt because it just makes the test bloated without making it more readable +#[rustfmt::skip] +#[test] +fn dvector_small_dims_exhaustive() { + assert_eq_and_type!(dvector![], DVector::::zeros(0)); + assert_eq_and_type!(dvector![1], DVector::from_column_slice(&[1])); + assert_eq_and_type!(dvector![1, 2], DVector::from_column_slice(&[1, 2])); + assert_eq_and_type!(dvector![1, 2, 3], DVector::from_column_slice(&[1, 2, 3])); + assert_eq_and_type!(dvector![1, 2, 3, 4], DVector::from_column_slice(&[1, 2, 3, 4])); + assert_eq_and_type!(dvector![1, 2, 3, 4, 5], DVector::from_column_slice(&[1, 2, 3, 4, 5])); + assert_eq_and_type!(dvector![1, 2, 3, 4, 5, 6], DVector::from_column_slice(&[1, 2, 3, 4, 5, 6])); +} + +#[test] +fn matrix_trybuild_tests() { + let t = trybuild::TestCases::new(); + + // Verify error message when we give a matrix with mismatched dimensions + t.compile_fail("tests/trybuild/matrix_mismatched_dimensions.rs"); +} + +#[test] +fn dmatrix_trybuild_tests() { + let t = trybuild::TestCases::new(); + + // Verify error message when we give a matrix with mismatched dimensions + t.compile_fail("tests/trybuild/dmatrix_mismatched_dimensions.rs"); +} + +#[test] +fn matrix_builtin_types() { + // Check that matrix! compiles for all built-in types + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0, 1; 2, 3]; + const _: SMatrix = matrix![0.0, 1.0; 2.0, 3.0]; + const _: SMatrix = matrix![0.0, 1.0; 2.0, 3.0]; +} + +#[test] +fn vector_builtin_types() { + // Check that vector! compiles for all built-in types + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0, 1, 2, 3]; + const _: SVector = vector![0.0, 1.0, 2.0, 3.0]; + const _: SVector = vector![0.0, 1.0, 2.0, 3.0]; +} + +#[test] +fn dmatrix_builtin_types() { + // Check that dmatrix! compiles for all built-in types + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0, 1; 2, 3]; + let _: DMatrix = dmatrix![0.0, 1.0; 2.0, 3.0]; + let _: DMatrix = dmatrix![0.0, 1.0; 2.0, 3.0]; +} + +#[test] +fn dvector_builtin_types() { + // Check that dvector! compiles for all built-in types + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0, 1, 2, 3]; + let _: DVector = dvector![0.0, 1.0, 2.0, 3.0]; + let _: DVector = dvector![0.0, 1.0, 2.0, 3.0]; +} + +/// Black box function that's just used for testing macros with function call expressions. +fn f(x: T) -> T { + x +} + +#[rustfmt::skip] +#[test] +fn matrix_arbitrary_expressions() { + // Test that matrix! supports arbitrary expressions for its elements + let a = matrix![1 + 2 , 2 * 3; + 4 * f(5 + 6), 7 - 8 * 9]; + let a_expected = Matrix2::new(1 + 2 , 2 * 3, + 4 * f(5 + 6), 7 - 8 * 9); + assert_eq_and_type!(a, a_expected); +} + +#[rustfmt::skip] +#[test] +fn dmatrix_arbitrary_expressions() { + // Test that dmatrix! supports arbitrary expressions for its elements + let a = dmatrix![1 + 2 , 2 * 3; + 4 * f(5 + 6), 7 - 8 * 9]; + let a_expected = DMatrix::from_row_slice(2, 2, &[1 + 2 , 2 * 3, + 4 * f(5 + 6), 7 - 8 * 9]); + assert_eq_and_type!(a, a_expected); +} + +#[rustfmt::skip] +#[test] +fn vector_arbitrary_expressions() { + // Test that vector! supports arbitrary expressions for its elements + let a = vector![1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9]; + let a_expected = Vector4::new(1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9); + assert_eq_and_type!(a, a_expected); +} + +#[rustfmt::skip] +#[test] +fn dvector_arbitrary_expressions() { + // Test that dvector! supports arbitrary expressions for its elements + let a = dvector![1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9]; + let a_expected = DVector::from_column_slice(&[1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9]); + assert_eq_and_type!(a, a_expected); +} diff --git a/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.rs b/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.rs new file mode 100644 index 000000000..786b58498 --- /dev/null +++ b/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.rs @@ -0,0 +1,6 @@ +use nalgebra_macros::dmatrix; + +fn main() { + dmatrix![1, 2, 3; + 4, 5]; +} \ No newline at end of file diff --git a/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.stderr b/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.stderr new file mode 100644 index 000000000..eaedc650e --- /dev/null +++ b/nalgebra-macros/tests/trybuild/dmatrix_mismatched_dimensions.stderr @@ -0,0 +1,5 @@ +error: Unexpected number of entries in row 1. Expected 3, found 2 entries. + --> $DIR/dmatrix_mismatched_dimensions.rs:5:13 + | +5 | 4, 5]; + | ^ diff --git a/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.rs b/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.rs new file mode 100644 index 000000000..c5eb87b7a --- /dev/null +++ b/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.rs @@ -0,0 +1,6 @@ +use nalgebra_macros::matrix; + +fn main() { + matrix![1, 2, 3; + 4, 5]; +} \ No newline at end of file diff --git a/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.stderr b/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.stderr new file mode 100644 index 000000000..c83e8d0cb --- /dev/null +++ b/nalgebra-macros/tests/trybuild/matrix_mismatched_dimensions.stderr @@ -0,0 +1,5 @@ +error: Unexpected number of entries in row 1. Expected 3, found 2 entries. + --> $DIR/matrix_mismatched_dimensions.rs:5:13 + | +5 | 4, 5]; + | ^ diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 3b4bc50c7..3cde8a4f6 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -29,7 +29,10 @@ use crate::base::storage::{ ContiguousStorage, ContiguousStorageMut, Owned, SameShapeStorage, Storage, StorageMut, }; use crate::base::{Const, DefaultAllocator, OMatrix, OVector, Scalar, Unit}; -use crate::SimdComplexField; +use crate::{ArrayStorage, SMatrix, SimdComplexField}; + +#[cfg(any(feature = "std", feature = "alloc"))] +use crate::{DMatrix, DVector, Dynamic, VecStorage}; /// A square matrix. pub type SquareMatrix = Matrix; @@ -317,6 +320,49 @@ impl Matrix { } } +impl SMatrix { + /// Creates a new statically-allocated matrix from the given [ArrayStorage]. + /// + /// This method exists primarily as a workaround for the fact that `from_data` can not + /// work in `const fn` contexts. + #[inline(always)] + pub const fn from_array_storage(storage: ArrayStorage) -> Self { + // This is sound because the row and column types are exactly the same as that of the + // storage, so there can be no mismatch + unsafe { Self::from_data_statically_unchecked(storage) } + } +} + +// TODO: Consider removing/deprecating `from_vec_storage` once we are able to make +// `from_data` const fn compatible +#[cfg(any(feature = "std", feature = "alloc"))] +impl DMatrix { + /// Creates a new heap-allocated matrix from the given [VecStorage]. + /// + /// This method exists primarily as a workaround for the fact that `from_data` can not + /// work in `const fn` contexts. + pub const fn from_vec_storage(storage: VecStorage) -> Self { + // This is sound because the dimensions of the matrix and the storage are guaranteed + // to be the same + unsafe { Self::from_data_statically_unchecked(storage) } + } +} + +// TODO: Consider removing/deprecating `from_vec_storage` once we are able to make +// `from_data` const fn compatible +#[cfg(any(feature = "std", feature = "alloc"))] +impl DVector { + /// Creates a new heap-allocated matrix from the given [VecStorage]. + /// + /// This method exists primarily as a workaround for the fact that `from_data` can not + /// work in `const fn` contexts. + pub const fn from_vec_storage(storage: VecStorage) -> Self { + // This is sound because the dimensions of the matrix and the storage are guaranteed + // to be the same + unsafe { Self::from_data_statically_unchecked(storage) } + } +} + impl> Matrix { /// Creates a new matrix with the given data. #[inline(always)] diff --git a/src/lib.rs b/src/lib.rs index bc6d17187..ea05e17d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,9 @@ pub use crate::sparse::*; )] pub use base as core; +#[cfg(feature = "macros")] +pub use nalgebra_macros::{dmatrix, dvector, matrix, vector}; + use simba::scalar::SupersetOf; use std::cmp::{self, Ordering, PartialOrd}; diff --git a/tests/core/macros.rs b/tests/core/macros.rs new file mode 100644 index 000000000..eaa134ffe --- /dev/null +++ b/tests/core/macros.rs @@ -0,0 +1,11 @@ +use nalgebra::{dmatrix, dvector, matrix, vector}; + +#[test] +fn sanity_test() { + // The macros are already tested in `nalgebra-macros`. Here we just test that they compile fine. + + let _ = matrix![1, 2, 3; 4, 5, 6]; + let _ = dmatrix![1, 2, 3; 4, 5, 6]; + let _ = vector![1, 2, 3, 4, 5, 6]; + let _ = dvector![1, 2, 3, 4, 5, 6]; +} diff --git a/tests/core/mod.rs b/tests/core/mod.rs index 4f9bc745e..c03461dc4 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -16,3 +16,6 @@ mod matrixcompare; #[cfg(feature = "arbitrary")] pub mod helper; + +#[cfg(feature = "macros")] +mod macros; diff --git a/tests/lib.rs b/tests/lib.rs index 8ee85f075..20d38d7a5 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,7 +1,12 @@ -#[cfg(not(all(feature = "debug", feature = "compare", feature = "rand")))] +#[cfg(not(all( + feature = "debug", + feature = "compare", + feature = "rand", + feature = "macros" +)))] compile_error!( - "Please enable the `debug`, `compare`, and `rand` features in order to compile and run the tests. - Example: `cargo test --features debug,compare,rand`" + "Please enable the `debug`, `compare`, `rand` and `macros` features in order to compile and run the tests. + Example: `cargo test --features debug,compare,rand,macros`" ); #[cfg(feature = "abomonation-serialize")]