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

This commit is contained in:
Kosma Moczek 2014-04-23 18:15:26 +02:00
parent 5d49c07957
commit dd2f2f20ff
2 changed files with 163 additions and 65 deletions

View File

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

70
tests.c
View File

@ -61,7 +61,7 @@ END_TEST
START_TEST(test_minmea_scan_c) START_TEST(test_minmea_scan_c)
{ {
char ch; char ch, extra;
ck_assert(minmea_scan("A,123.45", "c", &ch) == true); ck_assert(minmea_scan("A,123.45", "c", &ch) == true);
ck_assert_int_eq(ch, 'A'); 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(minmea_scan(",123.45", "c", &ch) == true);
ck_assert_int_eq(ch, '\0'); 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 END_TEST
@ -131,6 +144,45 @@ START_TEST(test_minmea_scan_f)
ck_assert(minmea_scan("-18000.00000", "f", &value, &scale) == true); ck_assert(minmea_scan("-18000.00000", "f", &value, &scale) == true);
ck_assert_int_eq(value, -1800000000); ck_assert_int_eq(value, -1800000000);
ck_assert_int_eq(scale, 100000); 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 END_TEST
@ -138,9 +190,16 @@ START_TEST(test_minmea_scan_s)
{ {
char value[MINMEA_MAX_LENGTH]; 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(minmea_scan("foo,bar,baz", "s", value) == true);
ck_assert_str_eq(value, "foo"); 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, ""); ck_assert_str_eq(value, "");
} }
END_TEST END_TEST
@ -164,6 +223,9 @@ START_TEST(test_minmea_scan_D)
{ {
struct minmea_date date; 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(minmea_scan("$GPXXX,311299", "_D", &date) == true);
ck_assert_int_eq(date.day, 31); ck_assert_int_eq(date.day, 31);
ck_assert_int_eq(date.month, 12); ck_assert_int_eq(date.month, 12);
@ -180,6 +242,9 @@ START_TEST(test_minmea_scan_T)
{ {
struct minmea_time time; 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(minmea_scan("$GPXXX,235960", "_T", &time) == true);
ck_assert_int_eq(time.hours, 23); ck_assert_int_eq(time.hours, 23);
ck_assert_int_eq(time.minutes, 59); 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_c);
tcase_add_test(tc_scan, test_minmea_scan_d); 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_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_s);
tcase_add_test(tc_scan, test_minmea_scan_t); tcase_add_test(tc_scan, test_minmea_scan_t);
tcase_add_test(tc_scan, test_minmea_scan_D); tcase_add_test(tc_scan, test_minmea_scan_D);