add support for optional fields (minmea_scan ";" modifier)
This commit is contained in:
parent
5d49c07957
commit
dd2f2f20ff
70
minmea.c
70
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++)) {} \
|
||||
/* 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,6 +141,7 @@ bool minmea_scan(const char *sentence, const char *format, ...)
|
||||
int value = -1;
|
||||
int scale = 0;
|
||||
|
||||
if (field) {
|
||||
while (minmea_isfield(*field)) {
|
||||
if (*field == '+' && !sign && value == -1) {
|
||||
sign = 1;
|
||||
@ -137,13 +156,14 @@ bool minmea_scan(const char *sentence, const char *format, ...)
|
||||
} else if (*field == '.' && scale == 0) {
|
||||
scale = 1;
|
||||
} else {
|
||||
goto end;
|
||||
goto parse_error;
|
||||
}
|
||||
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;
|
||||
|
||||
if (field) {
|
||||
char *endptr;
|
||||
value = strtol(field, &endptr, 10);
|
||||
if (minmea_isfield(*endptr))
|
||||
goto end;
|
||||
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 *);
|
||||
|
||||
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;
|
||||
|
||||
if (field && minmea_isfield(*field)) {
|
||||
// Always six digits.
|
||||
for (int i=0; i<6; i++)
|
||||
if (!isdigit((unsigned char) field[i]))
|
||||
goto end_D;
|
||||
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,10 +241,12 @@ 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;
|
||||
|
||||
if (field && minmea_isfield(*field)) {
|
||||
// Minimum required: integer time.
|
||||
for (int i=0; i<6; i++)
|
||||
if (!isdigit((unsigned char) field[i]))
|
||||
goto end_T;
|
||||
goto parse_error;
|
||||
|
||||
h = strtol((char[]) {field[0], field[1], '\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 {
|
||||
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;
|
||||
}
|
||||
|
70
tests.c
70
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);
|
||||
|
Loading…
Reference in New Issue
Block a user