# HG changeset patch
# User Andrew Janke <andrew@apjanke.net>
# Date 1579364911 18000
#      Sat Jan 18 11:28:31 2020 -0500
# Branch validator-implementation
# Node ID e3999a0bee6bfb271830386f33213f6dab38614b
# Parent  27a5cc0614f1724f854133cc333954d624e9f08a
Validator functions implementation

diff --git a/scripts/help/__unimplemented__.m b/scripts/help/__unimplemented__.m
--- a/scripts/help/__unimplemented__.m
+++ b/scripts/help/__unimplemented__.m
@@ -1008,34 +1008,16 @@ function rlist = missing_functions ()
   "movAbsHDU",
   "move",
   "movie2avi",
   "movNamHDU",
   "movRelHDU",
   "mput",
   "multibandread",
   "multibandwrite",
-  "mustBeFinite",
-  "mustBeGreaterThan",
-  "mustBeGreaterThanOrEqual",
-  "mustBeInteger",
-  "mustBeLessThan",
-  "mustBeLessThanOrEqual",
-  "mustBeMember",
-  "mustBeNegative",
-  "mustBeNonempty",
-  "mustBeNonNan",
-  "mustBeNonnegative",
-  "mustBeNonpositive",
-  "mustBeNonsparse",
-  "mustBeNonzero",
-  "mustBeNumeric",
-  "mustBeNumericOrLogical",
-  "mustBePositive",
-  "mustBeReal",
   "NaT",
   "nccreate",
   "ncdisp",
   "ncinfo",
   "ncread",
   "ncreadatt",
   "ncwrite",
   "ncwriteatt",
diff --git a/scripts/miscellaneous/mustBeFinite.m b/scripts/miscellaneous/mustBeFinite.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeFinite.m
@@ -0,0 +1,22 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeFinite (@code{x})
+##
+## Requires that input is finite.
+##
+## Raises an error if any element of the input @code{x} is not finite, as determined by
+## @code{isfinite (x)}.
+##
+## @end deftypefn
+
+function x = mustBeFinite (x)
+  tf = isfinite (x);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    error ("%s must be finite; got Infs in %d elements: indexes %s", ...
+      label, numel (ix_bad), mat2str (ix_bad));
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeGreaterThan.m b/scripts/miscellaneous/mustBeGreaterThan.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeGreaterThan.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeGreaterThan (@code{x}, @code{c})
+##
+## Requires that input is greater than a given value.
+##
+## Raises an error if any element of the input @code{x} is not greater than @code{c}, as determined by
+## @code{x > c}.
+##
+## @end deftypefn
+
+function x = mustBeGreaterThan (x, c)
+  tf = x > c;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be greater than %f; got %d elements that were not: values %s", ...
+        label, c, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be greater than %f; got %d elements that were not: indexes %s", ...
+        label, c, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeGreaterThanOrEqual.m b/scripts/miscellaneous/mustBeGreaterThanOrEqual.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeGreaterThanOrEqual.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeGreaterThanOrEqual (@code{x}, @code{c})
+##
+## Requires that input is greater than or equal to a given value.
+##
+## Raises an error if any element of the input @code{x} is not greater than or equal to @code{c}, as determined by
+## @code{x >= c}.
+##
+## @end deftypefn
+
+function x = mustBeGreaterThanOrEqual (x, c)
+  tf = x >= c;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be greater than or equal to %f; got %d elements that were not: values %s", ...
+        label, c, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be greater than or equal to %f; got %d elements that were not: indexes %s", ...
+        label, c, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeInteger.m b/scripts/miscellaneous/mustBeInteger.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeInteger.m
@@ -0,0 +1,33 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeInteger (@code{x})
+##
+## Requires that input is integer-valued (but not necessarily integer-typed).
+##
+## Raises an error if any element of the input @code{x} is not a finite,
+## real, integer-valued numeric value, as determined by various checks.
+##
+## @end deftypefn
+
+function x = mustBeInteger (x)
+  if isinteger (x) || islogical (x)
+    return
+  endif
+  but = [];
+  if ! isnumeric (x)
+    but = sprintf ("it was non-numeric (got a %s)", class (x));
+  elseif any (! isfinite (x))
+    but = "there were Inf values";
+  elseif ! isreal (x)
+    but = "it was complex";
+  elseif ! all (floor (x) == x)
+    but = "it had fractional values in some elements";
+  end
+  if ! isempty (but)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be integer-valued; but %s", ...
+      label, but);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeLessThan.m b/scripts/miscellaneous/mustBeLessThan.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeLessThan.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeLessThan (@code{x}, @code{c})
+##
+## Requires that input is less than a given value.
+##
+## Raises an error if any element of the input @code{x} is not less than @code{c}, as determined by
+## @code{x < c}.
+##
+## @end deftypefn
+
+function x = mustBeLessThan (x, c)
+  tf = x < c;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be less than %f; got %d elements that were not: values %s", ...
+        label, c, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be less than %f; got %d elements that were not: indexes %s", ...
+        label, c, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeLessThanOrEqual.m b/scripts/miscellaneous/mustBeLessThanOrEqual.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeLessThanOrEqual.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeLessThanOrEqual (@code{x}, @code{c})
+##
+## Requires that input is less than or equal to a given value.
+##
+## Raises an error if any element of the input @code{x} is not less than or equal to @code{c}, as determined by
+## @code{x <= c}.
+##
+## @end deftypefn
+
+function x = mustBeLessThanOrEqual (x, c)
+  tf = x <= c;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be less than or equal to %f; got %d elements that were not: values %s", ...
+        label, c, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be less than or equal to %f; got %d elements that were not: indexes %s", ...
+        label, c, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeMember.m b/scripts/miscellaneous/mustBeMember.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeMember.m
@@ -0,0 +1,28 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeMember (@code{x}, @code{valid})
+##
+## Requires that input is a member of a set of given valid values.
+##
+## Raises an error if any element of the input @code{x} is not a member 
+## of @code{valid}, as determined by @code{ismember (x)}.
+##
+## Note that char inputs may behave weirdly, because of the interaction between
+## chars and cellstrs when calling ismember() on them. But it will probably
+## "do what you mean" if you just use it naturally.
+##
+## @end deftypefn
+
+function x = mustBeMember (x, valid)
+  tf = ismember (x, valid);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    n_bad = numel (find (! tf));
+    # TODO: Fancy inclusion of bad & valid values in the error message.
+    # Probably use mat2str() in a try/catch for that.
+    error ("%s must be one of the specified valid values; got %d elements that weren't", ...
+      label, n_bad);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNegative.m b/scripts/miscellaneous/mustBeNegative.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNegative.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNegative (@code{x})
+##
+## Requires that input is negative.
+##
+## Raises an error if any element of the input @code{x} is not negative, as determined by
+## @code{x < 0}.
+##
+## @end deftypefn
+
+function x = mustBeNegative (x)
+  tf = x < 0;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be negative; got %d elements that were not: values %s", ...
+        label, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be negative; got %d elements that were not: indexes %s", ...
+        label, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonNan.m b/scripts/miscellaneous/mustBeNonNan.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonNan.m
@@ -0,0 +1,24 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonNan (@code{x})
+##
+## Requires that input is non-NaN.
+##
+## Raises an error if any element of the input @code{x} is NaN, as determined by
+## @code{isnan(x)}.
+##
+## @end deftypefn
+
+function x = mustBeNonNan (x)
+  tf = ! isnan (x);
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    errmsg = sprintf ("%s must be non-NaN; got %d elements that were not: indexes %s", ...
+      label, numel (ix_bad), mat2str (ix_bad));
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonempty.m b/scripts/miscellaneous/mustBeNonempty.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonempty.m
@@ -0,0 +1,20 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonempty (@code{x})
+##
+## Requires that input is nonempty.
+##
+## Raises an error if the input @code{x} is not empty, as determined by
+## @code{! isempty (x)}.
+##
+## @end deftypefn
+
+function x = mustBeNonempty (x)
+  if isempty (x)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be nonempty; got an empty", ...
+      label);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonnegative.m b/scripts/miscellaneous/mustBeNonnegative.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonnegative.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonnegative (@code{x})
+##
+## Requires that input is not negative.
+##
+## Raises an error if any element of the input @code{x} is negative, as determined by
+## @code{x >= 0}.
+##
+## @end deftypefn
+
+function x = mustBeNonnegative (x)
+  tf = x >= 0;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be non-negative; got %d elements that were not: values %s", ...
+        label, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be non-negative; got %d elements that were not: indexes %s", ...
+        label, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonpositive.m b/scripts/miscellaneous/mustBeNonpositive.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonpositive.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonpositive (@code{x})
+##
+## Requires that input is not positive.
+##
+## Raises an error if any element of the input @code{x} is positive, as determined by
+## @code{x <= 0}.
+##
+## @end deftypefn
+
+function x = mustBeNonpositive (x)
+  tf = x <= 0;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be non-positive; got %d elements that were not: values %s", ...
+        label, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be non-positive; got %d elements that were not: indexes %s", ...
+        label, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonsparse.m b/scripts/miscellaneous/mustBeNonsparse.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonsparse.m
@@ -0,0 +1,20 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonsparse (@code{x})
+##
+## Requires that input is finite.
+##
+## Raises an error if the input @code{x} is sparse, as determined by
+## @code{issparse (x)}.
+##
+## @end deftypefn
+
+function x = mustBeNonsparse (x)
+  if issparse (x)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be nonsparse; got a sparse array", ...
+      label);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNonzero.m b/scripts/miscellaneous/mustBeNonzero.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNonzero.m
@@ -0,0 +1,24 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNonzero (@code{x})
+##
+## Requires that input is not zero.
+##
+## Raises an error if any element of the input @code{x} is zero, as determined by
+## @code{x ~= 0}.
+##
+## @end deftypefn
+
+function x = mustBeNonzero (x)
+  tf = x ~= 0;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    errmsg = sprintf ("%s must be non-zero; got %d elements that were zero: indexes %s", ...
+      label, numel (ix_bad), mat2str (ix_bad));
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNumeric.m b/scripts/miscellaneous/mustBeNumeric.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNumeric.m
@@ -0,0 +1,20 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNumeric (@code{x})
+##
+## Requires that input is numeric.
+##
+## Raises an error if the input @code{x} is not numeric, as determined by
+## @code{isnumeric (x)}.
+##
+## @end deftypefn
+
+function x = mustBeNumeric (x)
+  if ! isnumeric (x)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be numeric; got a %s", ...
+      label, class (x));
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeNumericOrLogical.m b/scripts/miscellaneous/mustBeNumericOrLogical.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeNumericOrLogical.m
@@ -0,0 +1,20 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeNumericOrLogical (@code{x})
+##
+## Requires that input is numeric or logical.
+##
+## Raises an error if the input @code{x} is not numeric or logical, as determined by
+## @code{isnumeric (x) || islogical (x)}.
+##
+## @end deftypefn
+
+function x = mustBeNumericOrLogical (x)
+  if ! (isnumeric (x) || islogical (x))
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be numeric or logical; got a %s", ...
+      label, class (x));
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBePositive.m b/scripts/miscellaneous/mustBePositive.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBePositive.m
@@ -0,0 +1,30 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBePositive (@code{x})
+##
+## Requires that input is positive.
+##
+## Raises an error if any element of the input @code{x} is not positive, as determined by
+## @code{x > 0}.
+##
+## @end deftypefn
+
+function x = mustBePositive (x)
+  tf = x > 0;
+  tf = tf(:);
+  if ! all (tf)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    ix_bad = find (!tf);
+    try
+      bad = x(ix_bad);
+      errmsg = sprintf ("%s must be positive; got %d elements that were not: values %s", ...
+        label, numel (ix_bad), mat2str (bad));
+    catch err
+      errmsg = sprintf ("%s must be positive; got %d elements that were not: indexes %s", ...
+        label, numel (ix_bad), mat2str (ix_bad));
+    end
+    error (errmsg);
+  endif
+endfunction
diff --git a/scripts/miscellaneous/mustBeReal.m b/scripts/miscellaneous/mustBeReal.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/mustBeReal.m
@@ -0,0 +1,20 @@
+## -*- texinfo -*-
+## @deftypefn {Function File} {@code{x} =} mustBeReal (@code{x})
+##
+## Requires that input is finite.
+##
+## Raises an error if the input @code{x} is not real, as determined by
+## @code{isreal (x)}.
+##
+## @end deftypefn
+
+function x = mustBeReal (x)
+  if ! isreal (x)
+    label = inputname (1);
+    if isempty (label)
+      label = "input";
+    endif
+    error ("%s must be real; got a complex value", ...
+      label);
+  endif
+endfunction
