aboutsummaryrefslogtreecommitdiffstats
path: root/src/slibc-human
diff options
context:
space:
mode:
Diffstat (limited to 'src/slibc-human')
-rw-r--r--src/slibc-human/machinesize.c158
1 files changed, 158 insertions, 0 deletions
diff --git a/src/slibc-human/machinesize.c b/src/slibc-human/machinesize.c
new file mode 100644
index 0000000..75aa989
--- /dev/null
+++ b/src/slibc-human/machinesize.c
@@ -0,0 +1,158 @@
+/**
+ * slibc — Yet another C library
+ * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <slibc-human.h>
+#include <slibc/internals.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <ctype.h>
+
+
+
+/**
+ * Get prefix-value.
+ *
+ * @param str Pointer to the string, will be updated to point to the end of the unit.
+ * @param mode How to parse ambiguous units.
+ * @return The multiple for the value, 0 on error.
+ *
+ * @throws EINVAL The prefix is unrecognised.
+ * @throws ERANGE The prefix is too large.
+ */
+__attribute__((nonnull))
+static size_t prefix(char** restrict str, enum machinesize_mode mode)
+{
+#define P(A, B) case A: case B: n++
+ char* p = *str;
+ size_t power = 0;
+ size_t base = 0;
+ size_t rc = 1;
+
+ switch (*p++)
+ {
+ P('Y', 'y');
+ P('Z', 'z');
+ P('E', 'e');
+ P('P', 'p');
+ P('T', 't');
+ P('G', 'g');
+ P('M', 'm');
+ P('k', 'K');
+ case 'B': case 'b':
+ break;
+ default:
+ return errno = EINVAL, (size_t)0;
+ }
+ if (power == 0)
+ goto done;
+
+ if ((*p == 'i') || (*p == 'I'))
+ base = 1024, p++;
+ if ((*p == 'B') || (*p == 'b'))
+ {
+ p++;
+ if ((!base) && (mode == (MACHINESIZE_SI | MACHINESIZE_IEC)))
+ base = 1000;
+ }
+ if (!base)
+ base = (mode == MACHINESIZE_SI) ? 1000 : 1024;
+
+ while (power--)
+ OVERFLOW(umull, rc, base, &rc, ERANGE, (size_t)0);
+
+ done:
+ return *str = p, rc;
+#undef P
+}
+
+
+/**
+ * Parses a human representation of storage size or file offset.
+ *
+ * If no unit is used, bytes are assumed. If you rather it be
+ * (for example) kilobytes, you can multiply it if
+ * `strpbrk(str, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM") == NULL`.
+ *
+ * @etymology Convert to (machine)-representation: `(size)_t`.
+ *
+ * @param size Output parameter for the value, must not be `NULL`.
+ * @param str The value to parse, must not `NULL`.
+ * @param mode How to parse ambiguous strings, 0 for default.
+ * @param space Characters to ignore (thousand-group delimiters).
+ * Supports UTF-8. `NULL` for default. Default value
+ * is " '".
+ * @param point Decimal pointer chracters. Supports UTF-8. `NULL`
+ * for default. Default value is ",.".
+ * @return Zero on success, -1 on error.
+ *
+ * @throws EINVAL If `mode` is invalid.
+ * @throws EINVAL If `str` is not parseable.
+ * @throws ERANGE If the value is too range to fit in a `size_t`.
+ *
+ * @since Always.
+ */
+int machinesize(size_t* restrict size, const char* restrict str, enum machinesize_mode mode,
+ const char* restrict space, const char* restrict point)
+{
+ size_t r = 0;
+ size_t word;
+ long double dword;
+ size_t u;
+ char* p;
+ char* q;
+ int started = 0;
+ int pluses = 0;
+ int have_unitless = 0;
+ size_t words = 0;
+
+ if (space == NULL) space = " '";
+ if (point == NULL) point = ".,";
+
+ if (mode == 0) mode = MACHINESIZE_SI | MACHINESIZE_IEC;
+ if (mode > 3) goto invalid;
+
+ for (p = str; *p;)
+ if (strchr(" \t+", *p))
+ {
+ if ((pluses += (*p++ == '+')) > 1)
+ goto invalid;
+ }
+ else if ((q = machinefloat(&dword, p, space, point)))
+ {
+ p = q, words++, pluses = 0, started = 1;
+ while (strchr(" \t-", *p))
+ p++;
+ if (isalpha(*p) == 0) u = 1, have_unitless = 1;
+ else u = prefix(&p, mode);
+ if (u == 0) return -1;
+ dword *= (long double)u;
+ if (dword > (long double)SIZE_MAX)
+ return errno = ERANGE, -1;
+ down = (size_t)dword;
+ OVERFLOW(uaddl, word, r, &r, ERANGE, -1);
+ }
+ else
+ return -1;
+
+ if ((!started) || (have_unitless && (words > 1)))
+ goto invalid;
+ return *save = r, 0;
+ invalid:
+ return errno = EINVAL, -1;
+}
+