# mexargs

This project provides generic tools to enforce input and output arguments checks in C MEX-files for MATLAB.

## Generic Makefile for MEX-file generation

The project also provides the file `Makefile.mex` which provides checks of the compiling environment and defines variables for use when building MATLAB MEX-files. It includes support for Linux, MacOS and Windows (so far using MSYS2-MINGW64 only).

The file will try to determine the OS type on its own and has default value for the path to the MATLAB installation directory. These can be bypassed by defining the following variables:
  - OS: defines the OS type, accepted values are Linux, MacOS and Windows only
  - MATPATH: defines the location of the MATLAB installation directory.

Unless the goals of the make command are only to clean the working copy, a check of the different MATLAB binaries will be performed.

_NOTE:_ Experimental support for Octave has bin added, use the variable definition `USE_OCTAVE=1` in your make command to build MEX-files for Octave.

## structure of mexFunction
The following structure is recommended with blank lines between each section/item:

1. The `mexargs.h` file is included:

   ```c
   # include "mexargs.h"
   ```

2. The main `mexFunction` function is defined

   ```c
   void mexFunction(int nlhs,       mxArray *plhs[],
                    int nrhs, const mxArray *prhs[])
   {
   ```

3. The name of the MEX-file is defined

   ```c
   # define MEXNAME nansmex
   ```

4. A comment describing an example call is added

   ```c
    /* out = nansmex(in); */
   ```

5. Aliases are defined for output arguments

   ```c
   # define OUT pout[0]
   ```

   The use of the `pout` variable instead of `plhs` will be explained below.

6. Aliases are defined for input arguments, with comments expliciting the assumptions on the argument size and type.

   ```c
   # define IN  prhs[0] /* double, size=3 */
   ```

7. Checks for the number of input arguments are added (there can be more than one)

   ```c
    CHECK_NARGIN_EQ(1);
   ```

8. Optionally variables corresponding to assumed sizes are defined

   ```c
    int n1 = mxGetNumberOfElements(R1);
   ```

9. Check macros for the type of size of arguments are added (see below for a list of available macros)

   ```c
    CHECK_DOUBLE(M);
    CHECK_SCALAR(KIND);
   ```

10. Checks for the number of output arguments

    ```c
     CHECK_NARGOUT_EQ(1);
    ```

11. The `pout` variable is defined as an array with the maximum number of output arguments allowed and initialized to NULL to allow identification of unassigned outputs

    ```c
     mxArray *pout[1] = {NULL};
    ```

12. The function body.
13. The `ASSIGN_PLHS` macro is called just before the end of the `mexFunction` function.

    ```c
     ASSIGN_PLHS;
    }
    ```


## The role of the `pout` variable
In the case where multiple output arguments can be given, an array `pout` of type `mxArray *` is created with a size corresponding to the maximum number of output arguments. We cannot use `plhs` directly as its size can be smaller than that and assigning values outside of its initial size might corrupt the memory.

The function body can then assign values corresponding to the return arguments without necessarily checking the value of `nlhs`.

The last statement `ASSIGN_PLHS` copies the values from `pout` to `plhs` (these are memory addresses only, so no real data is copied), corresponding to the required number of output arguments. Note that `plhs[0]` is always copied to allow the use of the `ans` variable.

Since the return arguments `plhs` are copied from `pout`, initializing the `pout` values to `NULL` allows MATLAB to detect return arguments that were not assigned and throw an error. If the `pout` array remains uninitialized, its contents are random and might (most probably) produce segmentation faults when the MEX-file finally returns if its value hasn't been updated.

## List of available macros for checks
### Input arguments

- `CHECK_NARGIN_EQ(NARG)`, checks that the number of input arguments is exactly `NARG`
- `CHECK_NARGIN_LE(NARG)`, checks that the number of input arguments is less than or equal to `NARG`
- `CHECK_NARGIN_GE(NARG)`, checks that the number of input arguments is greater than or equal to `NARG`

These create exceptions with id `${MEXNAME}:nargin`.

### Argument type

- `CHECK_NUMERIC(ARG)`, checks that argument `ARG` is of numeric type
- `CHECK_DOUBLE(ARG)`, checks that argument `ARG` is of `double` type
- `CHECK_INT8(ARG)`, checks that argument `ARG` is of `int8` type
- `CHECK_INT32(ARG)`, checks that argument `ARG` is of `int32` type
- `CHECK_UINT32(ARG)`, checks that argument `ARG` is of `uint32` type
- `CHECK_LOGICAL(ARG)`, checks that argument `ARG` is of `logical` type
- `CHECK_REAL(ARG)`, checks that argument `ARG` is of `double` or `single` type
- `CHECK_TYPE_MATCH(ARG1,ARG2)`, checks that type of argument `ARG1` matches that of `ARG2`

These create exceptions with id `${MEXNAME}:inputType`.

### Argument size

- `CHECK_SCALAR(ARG)`, checks that argument `ARG` is a scalar
- `CHECK_SCALAR_OR_EMPTY(ARG)`, checks that argument `ARG` is a scalar or is empty
- `CHECK_NUMEL(ARG,SIZE)`, checks that argument `ARG` has `SIZE` elements
- `CHECK_NUMEL_MSG(ARG,SIZE,MSG)`, same as `CHECK_NUMEL` but adds `MSG` to the error message
- `CHECK_NROWS(ARG,SIZE)`, checks that argument `ARG` has `SIZE` rows
- `CHECK_NCOLS(ARG,SIZE)`, checks that argument `ARG` has `SIZE` columns (for N-dimensional arrays, this is the product of dimensions 2 to N)

These create exceptions with id `${MEXNAME}:inputSize`.

### Output arguments

- `CHECK_NARGOUT_LE(NARG)`, checks that the number of output arguments is less than or equal to `NARG`

These create exceptions with id `${MEXNAME}:nargout`.

### Notes

- MATLAB does not guarantee that the `mxLogical` type is the same as the standard `bool` C-type, altough at the moment for Linux 64-bit installs, these are striclty equivalent.
- `CHECK_SCALAR` is widely used as a check that the array is a numerical scalar, but so far the macro does not actually check that the type is numeric.


## MATLAB tests
### Procedure
This project also provides the `mexargs_test` helper class to design tests for the argument checks in C MEX-files.

To use it one must:
1. create a class-based test file
2. Include the boilerplate code as present in `tests/tbx_mexargs_test.m`
3. define the `mexargs_path` property with the path to the `mexargs` project
4. define the `mexfile` property with a list of strings corresponding to each MEX-file name to test.
5. For each of those create a `${MEXNAME}_args` static method generating a `mexargs` object using the following methods:
  - the constructor has the signature `S = mexargs(mexname,nargin_checks,nargout_checks)`
  - the `addarg(S,default,checks)` method then defines each argument default value and required checks. It must first be called with the parameters for the 1st input argument, then with those for the 2nd inputargument and so on.
  - each set of checks must be provided as a list of name-value pairs (see below for the list of accepted names)

*Note:* The list of checks must be synchronized between 2 separate locations, in the source files (where the checks are defined and implemented) and in the test class (where they are tested).

### Example

The file [tests/tbx_mexargs_test.m](tests/tbx_mexargs_test.m) provides an example of how to implement such a test file.

### List of available checks
#### nargin checks

 - `{'eq',val}` checks that an error is thrown if `nargin` is different than `val`
 - `{'ge',val}` checks that an error is thrown if `nargin` is less than `val`
 - `{'le',val}` checks that an error is thrown if `nargin` is greater than `val`
 - `{'one_of',val}` checks that an error is thrown if `nargin` is not within the array `val`

#### nargout checks

 - `{'le',val}` checks that an error is thrown if `nargout` is greater than `val`

#### argument checks

 - `{'type',val}` checks that an error is thrown if the type of the argument is not `val` (assumes this is a numeric type)
 - `{'type_match',val}` checks that an error is thrown if the type of the argument does not match that of the `val`-th argument
 - `{'numel',val}` checks that an error is thrown if the number of elements does not match that of the default value (here `val` is ignored)
 - `{'ncols',val}` checks that an error is thrown if the number of rows does not match that of the default value (here `val` is ignored)
 - `{'nrows',val}` checks that an error is thrown if the number of columns does not match that of the default value (here `val` is ignored)
 - `{'scalar',val}` checks that an error is thrown if the argument is not a scalar (here `val` is ignored)
 - `{'scalar_or_empty',val}` checks that an error is thrown if the argument is not a scalar or empty (here `val` is ignored)
