Bladeren bron

add support for optional fields (minmea_scan ";" modifier)

Kosma Moczek 11 jaren geleden
bovenliggende
commit
dd2f2f20ff
2 gewijzigde bestanden met toevoegingen van 165 en 67 verwijderingen
  1. 97 65
      minmea.c
  2. 68 2
      tests.c

+ 97 - 65
minmea.c

@@ -72,27 +72,45 @@ static inline bool minmea_isfield(char c) {
 bool minmea_scan(const char *sentence, const char *format, ...)
 {
     bool result = false;
+    bool optional = false;
     va_list ap;
     va_start(ap, format);
 
     const char *field = sentence;
 #define next_field() \
     do { \
-        while (minmea_isfield(*sentence++)) {} \
-        field = sentence; \
+        /* Progress to the next field. */ \
+        while (minmea_isfield(*sentence)) \
+            sentence++; \
+        /* Make sure there is a field there. */ \
+        if (*sentence == ',') { \
+            sentence++; \
+            field = sentence; \
+        } else { \
+            field = NULL; \
+        } \
     } while (0)
 
     while (*format) {
         char type = *format++;
 
+        if (type == ';') {
+            // All further fields are optional.
+            optional = true;
+            continue;
+        }
+
+        if (!field && !optional) {
+            // Field requested but we ran out if input. Bail out.
+            goto parse_error;
+        }
+
         switch (type) {
             case 'c': { // Single character field (char).
                 char value = '\0';
 
-                if (minmea_isfield(*field))
+                if (field && minmea_isfield(*field))
                     value = *field;
-                else
-                    value = '\0';
 
                 *va_arg(ap, char *) = value;
             } break;
@@ -100,7 +118,7 @@ bool minmea_scan(const char *sentence, const char *format, ...)
             case 'd': { // Single character direction field (int).
                 int value = 0;
 
-                if (minmea_isfield(*field)) {
+                if (field && minmea_isfield(*field)) {
                     switch (*field) {
                         case 'N':
                         case 'E':
@@ -111,7 +129,7 @@ bool minmea_scan(const char *sentence, const char *format, ...)
                             value = -1;
                             break;
                         default:
-                            goto end;
+                            goto parse_error;
                     }
                 }
 
@@ -123,27 +141,29 @@ bool minmea_scan(const char *sentence, const char *format, ...)
                 int value = -1;
                 int scale = 0;
 
-                while (minmea_isfield(*field)) {
-                    if (*field == '+' && !sign && value == -1) {
-                        sign = 1;
-                    } else if (*field == '-' && !sign && value == -1) {
-                        sign = -1;
-                    } else if (isdigit((unsigned char) *field)) {
-                        if (value == -1)
-                            value = 0;
-                        value = (10 * value) + (*field - '0');
-                        if (scale)
-                            scale *= 10;
-                    } else if (*field == '.' && scale == 0) {
-                        scale = 1;
-                    } else {
-                        goto end;
+                if (field) {
+                    while (minmea_isfield(*field)) {
+                        if (*field == '+' && !sign && value == -1) {
+                            sign = 1;
+                        } else if (*field == '-' && !sign && value == -1) {
+                            sign = -1;
+                        } else if (isdigit((unsigned char) *field)) {
+                            if (value == -1)
+                                value = 0;
+                            value = (10 * value) + (*field - '0');
+                            if (scale)
+                                scale *= 10;
+                        } else if (*field == '.' && scale == 0) {
+                            scale = 1;
+                        } else {
+                            goto parse_error;
+                        }
+                        field++;
                     }
-                    field++;
                 }
 
                 if ((sign || scale) && value == -1)
-                    goto end;
+                    goto parse_error;
 
                 if (value == -1) {
                     value = 0;
@@ -157,12 +177,14 @@ bool minmea_scan(const char *sentence, const char *format, ...)
             } break;
 
             case 'i': { // Integer value, default 0 (int).
-                int value;
+                int value = 0;
 
-                char *endptr;
-                value = strtol(field, &endptr, 10);
-                if (minmea_isfield(*endptr))
-                    goto end;
+                if (field) {
+                    char *endptr;
+                    value = strtol(field, &endptr, 10);
+                    if (minmea_isfield(*endptr))
+                        goto parse_error;
+                }
 
                 *va_arg(ap, int *) = value;
             } break;
@@ -170,17 +192,24 @@ bool minmea_scan(const char *sentence, const char *format, ...)
             case 's': { // String value (char *).
                 char *buf = va_arg(ap, char *);
 
-                while (minmea_isfield(*field))
-                    *buf++ = *field++;
+                if (field) {
+                    while (minmea_isfield(*field))
+                        *buf++ = *field++;
+                }
+
                 *buf = '\0';
             } break;
 
             case 't': { // NMEA talker+sentence identifier (char *).
+                // This field is always mandatory.
+                if (!field)
+                    goto parse_error;
+
                 if (field[0] != '$')
-                    goto end;
+                    goto parse_error;
                 for (int i=0; i<5; i++)
                     if (!minmea_isfield(field[1+i]))
-                        goto end;
+                        goto parse_error;
 
                 char *buf = va_arg(ap, char *);
                 memcpy(buf, field+1, 5);
@@ -191,16 +220,18 @@ bool minmea_scan(const char *sentence, const char *format, ...)
                 struct minmea_date *date = va_arg(ap, struct minmea_date *);
 
                 int d = -1, m = -1, y = -1;
-                // Always six digits.
-                for (int i=0; i<6; i++)
-                    if (!isdigit((unsigned char) field[i]))
-                        goto end_D;
 
-                d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
-                m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
-                y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
+                if (field && minmea_isfield(*field)) {
+                    // Always six digits.
+                    for (int i=0; i<6; i++)
+                        if (!isdigit((unsigned char) field[i]))
+                            goto parse_error;
+
+                    d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
+                    m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
+                    y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
+                }
 
-            end_D:
                 date->day = d;
                 date->month = m;
                 date->year = y;
@@ -210,30 +241,32 @@ bool minmea_scan(const char *sentence, const char *format, ...)
                 struct minmea_time *time = va_arg(ap, struct minmea_time *);
 
                 int h = -1, i = -1, s = -1, u = -1;
-                // Minimum required: integer time.
-                for (int i=0; i<6; i++)
-                    if (!isdigit((unsigned char) field[i]))
-                        goto end_T;
-
-                h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
-                i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
-                s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
-                field += 6;
-
-                // Extra: fractional time. Saved as microseconds.
-                if (*field++ == '.') {
-                    int value = 0;
-                    int scale = 1000000;
-                    while (isdigit((unsigned char) *field) && scale > 1) {
-                        value = (value * 10) + (*field++ - '0');
-                        scale /= 10;
+
+                if (field && minmea_isfield(*field)) {
+                    // Minimum required: integer time.
+                    for (int i=0; i<6; i++)
+                        if (!isdigit((unsigned char) field[i]))
+                            goto parse_error;
+
+                    h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
+                    i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
+                    s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
+                    field += 6;
+
+                    // Extra: fractional time. Saved as microseconds.
+                    if (*field++ == '.') {
+                        int value = 0;
+                        int scale = 1000000;
+                        while (isdigit((unsigned char) *field) && scale > 1) {
+                            value = (value * 10) + (*field++ - '0');
+                            scale /= 10;
+                        }
+                        u = value * scale;
+                    } else {
+                        u = 0;
                     }
-                    u = value * scale;
-                } else {
-                    u = 0;
                 }
 
-            end_T:
                 time->hours = h;
                 time->minutes = i;
                 time->seconds = s;
@@ -244,17 +277,16 @@ bool minmea_scan(const char *sentence, const char *format, ...)
             } break;
 
             default: { // Unknown.
-                goto end;
+                goto parse_error;
             } break;
         }
 
-        // Advance to next field.
         next_field();
     }
 
     result = true;
 
-end:
+parse_error:
     va_end(ap);
     return result;
 }

+ 68 - 2
tests.c

@@ -61,7 +61,7 @@ END_TEST
 
 START_TEST(test_minmea_scan_c)
 {
-    char ch;
+    char ch, extra;
 
     ck_assert(minmea_scan("A,123.45", "c", &ch) == true);
     ck_assert_int_eq(ch, 'A');
@@ -71,6 +71,19 @@ START_TEST(test_minmea_scan_c)
 
     ck_assert(minmea_scan(",123.45", "c", &ch) == true);
     ck_assert_int_eq(ch, '\0');
+
+    ck_assert(minmea_scan("A,B", "cc", &ch, &extra) == true);
+    ck_assert_int_eq(ch, 'A');
+    ck_assert_int_eq(extra, 'B');
+
+    ck_assert(minmea_scan("C", "cc", &ch, &extra) == false);
+
+    ck_assert(minmea_scan("D", "c;c", &ch, &extra) == true);
+    ck_assert_int_eq(ch, 'D');
+    ck_assert_int_eq(extra, '\0');
+    ck_assert(minmea_scan("E,F", "c;c", &ch, &extra) == true);
+    ck_assert_int_eq(ch, 'E');
+    ck_assert_int_eq(extra, 'F');
 }
 END_TEST
 
@@ -131,6 +144,45 @@ START_TEST(test_minmea_scan_f)
     ck_assert(minmea_scan("-18000.00000", "f", &value, &scale) == true);
     ck_assert_int_eq(value, -1800000000);
     ck_assert_int_eq(scale, 100000);
+
+    /* optional values */
+    ck_assert(minmea_scan("foo", "_;f", &value, &scale) == true);
+    ck_assert_int_eq(scale, 0);
+    ck_assert(minmea_scan("foo,", "_;f", &value, &scale) == true);
+    ck_assert_int_eq(scale, 0);
+    ck_assert(minmea_scan("foo,12.3", "_;f", &value, &scale) == true);
+    ck_assert_int_eq(value, 123);
+    ck_assert_int_eq(scale, 10);
+}
+END_TEST
+
+START_TEST(test_minmea_scan_i)
+{
+    int value, extra;
+
+    // valid parses
+    ck_assert(minmea_scan("14", "i", &value) == true);
+    ck_assert_int_eq(value, 14);
+    ck_assert(minmea_scan("-1234", "i", &value) == true);
+    ck_assert_int_eq(value, -1234);
+
+    // empty field
+    ck_assert(minmea_scan("", "i", &value) == true);
+    ck_assert_int_eq(value, 0);
+
+    // invalid value
+    ck_assert(minmea_scan("foo", "i", &value) == false);
+
+    // missing field
+    ck_assert(minmea_scan("41", "ii", &value, &extra) == false);
+
+    /* optional values */
+    ck_assert(minmea_scan("10", "i;i", &value, &extra) == true);
+    ck_assert_int_eq(value, 10);
+    ck_assert(minmea_scan("20,30", "i;i", &value, &extra) == true);
+    ck_assert_int_eq(value, 20);
+    ck_assert_int_eq(extra, 30);
+    ck_assert(minmea_scan("42,foo", "i;i", &value, &extra) == false);
 }
 END_TEST
 
@@ -138,9 +190,16 @@ START_TEST(test_minmea_scan_s)
 {
     char value[MINMEA_MAX_LENGTH];
 
+    ck_assert(minmea_scan(",bar,baz", "s", value) == true);
+    ck_assert_str_eq(value, "");
     ck_assert(minmea_scan("foo,bar,baz", "s", value) == true);
     ck_assert_str_eq(value, "foo");
-    ck_assert(minmea_scan(",bar,baz", "s", value) == true);
+
+    ck_assert(minmea_scan("dummy", "_;s", value) == true);
+    ck_assert_str_eq(value, "");
+    ck_assert(minmea_scan("dummy,foo", "_;s", value) == true);
+    ck_assert_str_eq(value, "foo");
+    ck_assert(minmea_scan("dummy,", "_;s", value) == true);
     ck_assert_str_eq(value, "");
 }
 END_TEST
@@ -164,6 +223,9 @@ START_TEST(test_minmea_scan_D)
 {
     struct minmea_date date;
 
+    ck_assert(minmea_scan("$GPXXX,3112", "_D", &date) == false);
+    ck_assert(minmea_scan("$GPXXX,foobar", "_D", &date) == false);
+
     ck_assert(minmea_scan("$GPXXX,311299", "_D", &date) == true);
     ck_assert_int_eq(date.day, 31);
     ck_assert_int_eq(date.month, 12);
@@ -180,6 +242,9 @@ START_TEST(test_minmea_scan_T)
 {
     struct minmea_time time;
 
+    ck_assert(minmea_scan("$GPXXX,2359", "_T", &time) == false);
+    ck_assert(minmea_scan("$GPXXX,foobar", "_T", &time) == false);
+
     ck_assert(minmea_scan("$GPXXX,235960", "_T", &time) == true);
     ck_assert_int_eq(time.hours, 23);
     ck_assert_int_eq(time.minutes, 59);
@@ -482,6 +547,7 @@ Suite *minmea_suite(void)
     tcase_add_test(tc_scan, test_minmea_scan_c);
     tcase_add_test(tc_scan, test_minmea_scan_d);
     tcase_add_test(tc_scan, test_minmea_scan_f);
+    tcase_add_test(tc_scan, test_minmea_scan_i);
     tcase_add_test(tc_scan, test_minmea_scan_s);
     tcase_add_test(tc_scan, test_minmea_scan_t);
     tcase_add_test(tc_scan, test_minmea_scan_D);