[recipes] Break out the number parser as a separate file
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [recipes] Break out the number parser as a separate file
- Date: Fri, 23 Dec 2016 23:59:45 +0000 (UTC)
commit b07efdc486faeba4a9a37d65892fdceacc0a05d5
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Dec 23 18:14:47 2016 -0500
Break out the number parser as a separate file
This will make it easy to write tests for it.
While we are at it, add support for mixed numbers.
src/Makefile.am | 3 +
src/gr-number.c | 395 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/gr-number.h | 47 +++++++
3 files changed, 445 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index c1eb6e6..0c39143 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -5,6 +5,7 @@ commitid := $(shell $(GIT) describe)
recipes_CFLAGS = \
$(WARN_CFLAGS) \
-Wno-sign-compare \
+ -O0 -ggdb \
$(RECIPES_CFLAGS) \
-DPKGDATADIR=\"$(pkgdatadir)\" \
-DLOCALEDIR=\"$(localedir)\" \
@@ -64,6 +65,8 @@ recipes_SOURCES = \
gr-meal.c \
gr-meal-row.h \
gr-meal-row.c \
+ gr-number.h \
+ gr-number.c \
gr-preferences.h \
gr-preferences.c \
gr-query-editor.h \
diff --git a/src/gr-number.c b/src/gr-number.c
new file mode 100644
index 0000000..64d9a1b
--- /dev/null
+++ b/src/gr-number.c
@@ -0,0 +1,395 @@
+/* gr-number.c
+ *
+ * Copyright (C) 2016 Matthias Clasen <mclasen redhat com>
+ *
+ * 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 "config.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gr-number.h>
+
+static int
+gcd (int m, int n)
+{
+ int r;
+
+ if (m == 0 && n == 0)
+ return -1;
+
+ if (m < 0) m = -m;
+ if (n < 0) n = -n;
+
+ while (n) {
+ r = m % n;
+ m = n;
+ n = r;
+ }
+
+ return m;
+}
+
+static void
+gr_number_set_fraction (GrNumber *number,
+ int num,
+ int denom)
+{
+ int g;
+
+ if (denom < 0) {
+ num = -num;
+ denom = -denom;
+ }
+ g = gcd (num, denom);
+ number->fraction = TRUE;
+ number->num = num / g;
+ number->denom = denom / g;
+ number->value = ((double) num) / ((double) denom);
+}
+
+GrNumber *
+gr_number_new_fraction (int num, int denom)
+{
+ GrNumber *number;
+
+ number = g_new (GrNumber, 1);
+ gr_number_set_fraction (number, num, denom);
+
+ return number;
+}
+
+static void
+gr_number_set_float (GrNumber *number,
+ double value)
+{
+ number->fraction = FALSE;
+ number->num = 0;
+ number->denom = 0;
+ number->value = value;
+}
+
+GrNumber *
+gr_number_new_float (double value)
+{
+ GrNumber *number;
+
+ number = g_new (GrNumber, 1);
+ gr_number_set_float (number, value);
+
+ return number;
+}
+
+void
+gr_number_multiply (GrNumber *a1,
+ GrNumber *a2,
+ GrNumber *b)
+{
+ if (a1->fraction && a2->fraction)
+ gr_number_set_fraction (b, a1->num * a2->num, a1->denom * a2->denom);
+ else
+ gr_number_set_float (b, a1->value * a2->value);
+}
+
+void
+gr_number_add (GrNumber *a1,
+ GrNumber *a2,
+ GrNumber *b)
+{
+ if (a1->fraction && a2->fraction)
+ gr_number_set_fraction (b, a1->num * a2->denom + a2->num * a1->denom, a1->denom * a2->denom);
+ else
+ gr_number_set_float (b, a1->value + a2->value);
+}
+
+typedef struct {
+ const char *input;
+ int value;
+} NumberForm;
+
+static NumberForm numberforms[] = {
+ { NC_("number", "a dozen"), 12 },
+ { NC_("number", "a"), 1 },
+ { NC_("number", "an"), 1 },
+ { NC_("number", "one"), 1 },
+ { NC_("number", "two"), 2 },
+ { NC_("number", "three"), 3 },
+ { NC_("number", "four"), 4 },
+ { NC_("number", "five"), 5 },
+ { NC_("number", "six"), 6 },
+ { NC_("number", "seven"), 7 },
+ { NC_("number", "eight"), 8 },
+ { NC_("number", "nine"), 9 },
+ { NC_("number", "ten"), 10 },
+ { NC_("number", "eleven"), 11 },
+ { NC_("number", "twelve"), 12 }
+};
+
+static gboolean
+space_or_nul (char p)
+{
+ return (p == '\0' || g_ascii_isspace (p));
+}
+
+static gboolean
+parse_as_number_form (GrNumber *number,
+ char **input,
+ GError **error)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (numberforms); i++) {
+ const char *nf;
+
+ nf = g_dpgettext2 (NULL, "number", numberforms[i].input);
+ if (g_str_has_prefix (*input, nf) &&
+ space_or_nul ((*input)[strlen (nf)])) {
+ gr_number_set_fraction (number, numberforms[i].value, 1);
+ *input += strlen (nf);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a number form"), *input);
+ return FALSE;
+}
+
+typedef struct {
+ int num;
+ int denom;
+ const char *ch;
+} VulgarFraction;
+
+static VulgarFraction fractions[] = {
+ { 1, 4, "¼" },
+ { 1, 2, "½" },
+ { 3, 4, "¾" },
+ { 1, 7, "⅐" },
+ { 1, 9, "⅑" },
+ { 1, 10, "⅒" },
+ { 1, 3, "⅓" },
+ { 2, 3, "⅔" },
+ { 1, 5, "⅕" },
+ { 2, 5, "⅖" },
+ { 3, 5, "⅗" },
+ { 4, 5, "⅘" },
+ { 1, 6, "⅙" },
+ { 5, 6, "⅚" },
+ { 1, 8, "⅛" },
+ { 3, 8, "⅜" },
+ { 5, 8, "⅝" },
+ { 7, 8, "⅞" }
+};
+
+/* a workaround for poor availability of OpenType frak support in our fonts */
+static gboolean
+parse_as_vulgar_fraction (GrNumber *number,
+ char **input,
+ GError **error)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (fractions); i++) {
+ const char *vf = fractions[i].ch;
+ if (g_str_has_prefix (*input, vf) &&
+ space_or_nul ((*input)[strlen (vf)])) {
+ gr_number_set_fraction (number, fractions[i].num, fractions[i].denom);
+ *input += strlen (vf);
+
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a fraction"), *input);
+
+ return FALSE;
+}
+
+static gboolean
+parse_as_fraction (GrNumber *number,
+ char **input,
+ GError **error)
+{
+ char *orig = *input;
+ guint64 num, denom;
+ char *end = NULL;
+
+ num = g_ascii_strtoull (orig, &end, 10);
+ if (end == NULL || end == orig || end[0] != '/') {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a fraction"), *input);
+ return FALSE;
+ }
+
+ orig = end + 1;
+
+ denom = g_ascii_strtoull (orig, &end, 10);
+ if (end != NULL && !space_or_nul (end[0])) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a fraction"), *input);
+ return FALSE;
+ }
+
+ *input = end;
+
+ gr_number_set_fraction (number, (int)num, (int)denom);
+
+ return TRUE;
+}
+
+static gboolean
+parse_as_integer (GrNumber *number,
+ char **input,
+ gboolean require_whitespace,
+ GError **error)
+{
+ guint64 num;
+ char *end = NULL;
+
+ num = g_ascii_strtoull (*input, &end, 10);
+ if (end == *input ||
+ (require_whitespace && end != NULL && !space_or_nul (end[0]))) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a integer"), *input);
+ return FALSE;
+ }
+ *input = end;
+
+ gr_number_set_fraction (number, (int)num, 1);
+
+ return TRUE;
+}
+
+static gboolean
+skip_whitespace (char **input)
+{
+ char **in = input;
+
+ while (*input && g_ascii_isspace (**input)) {
+ *input += 1;
+ }
+
+ return *in != *input;
+}
+
+static gboolean
+parse_as_float (GrNumber *number,
+ char **input,
+ GError **error)
+{
+ double value;
+ char *end = NULL;
+
+ value = g_strtod (*input, &end);
+
+ if (end != NULL && !space_or_nul (end[0])) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a float"), *input);
+ return FALSE;
+ }
+ *input = end;
+
+ gr_number_set_float (number, value);
+
+ return TRUE;
+}
+
+gboolean
+gr_number_parse (GrNumber *number,
+ char **input,
+ GError **error)
+{
+ char *orig = *input;
+
+ if (parse_as_number_form (number, input, NULL)) {
+ return TRUE;
+ }
+
+ if (parse_as_vulgar_fraction (number, input, NULL)) {
+ return TRUE;
+ }
+
+ if (parse_as_fraction (number, input, NULL)) {
+ return TRUE;
+ }
+
+ if (parse_as_integer (number, &orig, FALSE, NULL)) {
+ gboolean valid;
+ GrNumber n;
+
+ valid = skip_whitespace (&orig);
+
+ if (parse_as_vulgar_fraction (&n, &orig, NULL) ||
+ parse_as_fraction (&n, &orig, NULL)) {
+ gr_number_add (number, &n, number);
+ valid = TRUE;
+ }
+
+ if (valid || *orig == '\0') {
+ *input = orig;
+ return TRUE;
+ }
+ }
+
+ if (parse_as_float (number, input, NULL)) {
+ return TRUE;
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Could not parse %s as a number"), *input);
+
+ return FALSE;
+}
+
+static char *
+format_fraction (int num, int denom)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (fractions); i++) {
+ if (fractions[i].num == num && fractions[i].denom == denom)
+ return g_strdup (fractions[i].ch);
+ }
+
+ return g_strdup_printf ("%d/%d", num, denom);
+}
+
+char *
+gr_number_format (GrNumber *number)
+{
+ if (number->fraction) {
+ if (number->denom == 1)
+ return g_strdup_printf ("%d", number->num);
+ else {
+ int integral;
+ g_autofree char *fraction = NULL;
+
+ if (number->denom == 0)
+ return g_strdup_printf ("%d/0", number->num);
+
+ integral = number->num / number->denom;
+ fraction = format_fraction (number->num % number->denom, number->denom);
+ if (integral == 0)
+ return g_strdup_printf ("%s", fraction);
+ else
+ return g_strdup_printf ("%d %s", integral, fraction);
+ }
+ }
+ else {
+ return g_strdup_printf ("%g", number->value);
+ }
+}
diff --git a/src/gr-number.h b/src/gr-number.h
new file mode 100644
index 0000000..e37a39a
--- /dev/null
+++ b/src/gr-number.h
@@ -0,0 +1,47 @@
+/* gr-number.h
+ *
+ * Copyright (C) 2016 Matthias Clasen <mclasen redhat com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ gboolean fraction;
+ int num, denom;
+ double value;
+} GrNumber;
+
+GrNumber *gr_number_new_fraction (int num, int denom);
+GrNumber *gr_number_new_float (double value);
+void gr_number_add (GrNumber *a1,
+ GrNumber *a2,
+ GrNumber *b);
+void gr_number_multiply (GrNumber *a1,
+ GrNumber *a2,
+ GrNumber *b);
+
+gboolean gr_number_parse (GrNumber *number,
+ char **input,
+ GError **error);
+char *gr_number_format (GrNumber *number);
+
+
+
+G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]