Here's an evil C state-machine-based time conversion routine. It
walks through the text backwards and when it hits a digit, it fires
the state machine off. If it matches, the result gets copied into the
buffer, otherwise it continues the char-by-char copy until the next
candidate for a match comes around. I imagine it finishes its job
before the perl, ruby, or javascript runtimes even get a chance to
start. ;) It did take quite a bit longer to write and debug, though.
I make no claims about the quality of the code; I did it in a hurry
and don't have any more time to put into it. It does pass a lot more
test cases than I included in the source, though.
--Levi
#include <stdio.h>
#include <string.h>
char *ritoa(int n, char *s) {
int i;
i = 0;
do {
*(s--) = "0123456789"[n % 10];
} while ( n /= 10 );
return s;
}
/*
A state machine to convert strings from hours:minutes:seconds format
to seconds
*/
enum {
SECOND_OR_FRAC,
SECOND,
SECOND_2,
MINUTE,
MINUTE_2,
HOUR,
HOUR_2,
FINISHED
};
#define CASE_DIGIT case '0': case '1': case '2': case '3': case '4': \
case '5': case '6': case '7': case '8': case '9'
#define ACCUM(x) acc += ((x)-'0') * placeval; placeval *= 10
#define ACCUM2(x) if (placeval > 10) goto reject; ACCUM(x)
#define RESET() acc = 0; placeval = 1
#define STORE_FRAC() frac += acc; RESET()
#define STORE_SEC() if (acc > 60 || placeval != 100) goto reject; \
seconds += acc; RESET()
#define STORE_MIN() if (acc > 60 || placeval != 100) goto reject; \
seconds += (acc*60); RESET()
#define STORE_HOUR() seconds += (acc * 3600); RESET()
char *convert(char *str, char *begin, char **out) {
int state = SECOND_OR_FRAC;
int acc = 0;
int frac = 0;
int seconds = 0;
int placeval = 1;
char *p = str;
char *q;
while (state != FINISHED) {
switch (state) {
case SECOND_OR_FRAC:
switch (*p) {
CASE_DIGIT: ACCUM(*p); break;
case '.': STORE_FRAC(); state = SECOND; break;
case ':': STORE_SEC(); state = MINUTE; break;
default: STORE_SEC(); state = FINISHED;
}
if (p == begin) { STORE_SEC(); state = FINISHED; }
break;
case SECOND:
switch (*p) {
CASE_DIGIT: ACCUM2(*p); state = SECOND_2; break;
default: goto reject;
}
if (p == begin) { STORE_SEC(); state = FINISHED; }
break;
case SECOND_2:
switch (*p) {
CASE_DIGIT: ACCUM2(*p); break;
case '.': goto reject;
case ':': STORE_SEC(); state = MINUTE; break;
default: STORE_SEC(); state = FINISHED;
}
if (p == begin) { STORE_SEC(); state = FINISHED; }
break;
case MINUTE:
switch (*p) {
CASE_DIGIT: ACCUM2(*p); state = MINUTE_2; break;
default: goto reject;
}
if (p == begin) { STORE_MIN(); state = FINISHED; }
break;
case MINUTE_2:
switch (*p) {
CASE_DIGIT: ACCUM2(*p); break;
case '.': goto reject;
case ':': STORE_MIN(); state = HOUR; break;
default: STORE_MIN(); state = FINISHED;
}
if (p == begin) { STORE_MIN(); state = FINISHED; }
break;
case HOUR:
switch (*p) {
CASE_DIGIT: ACCUM(*p); state = HOUR_2; break;
default: goto reject;
}
if (p == begin) { STORE_HOUR(); state = FINISHED; }
break;
case HOUR_2:
switch (*p) {
CASE_DIGIT: ACCUM(*p); break;
case '.': goto reject;
case ':': goto reject;
default: STORE_HOUR(); state = FINISHED;
}
if (p == begin) { STORE_HOUR(); state = FINISHED; }
break;
}
p--;
}
if (frac) {
q = ritoa(frac, *out);
*(q--) = '.';
q = ritoa(seconds, q);
} else {
q = ritoa(seconds, *out);
}
*out = q;
return p+1;
reject:
return 0;
}
void convert_expression(char *str, char *begin, char **out) {
char *p;
int done = 0;
while (str >= begin) {
/* seek a digit, copying from str to *out */
switch (*str) {
CASE_DIGIT:
p = convert(str, begin, out);
if (p) {
str = p;
if (p == begin) str--;
} else {
/* seek something that isn't a deformed time pattern */
done = 0;
while (!done) {
switch (*str) {
CASE_DIGIT:
case '.':
case ':':
*((*out)--) = *(str--); break;
default:
done = 1;
}
}
}
break;
default:
*((*out)--) = *(str--);
}
}
}
int main(void) {
char output[10000] = {};
char test[] = "23 +12 /14:25:35.124 - 16*125/cat";
char *p;
p = output + 9999;
*(p--) = 0;
convert_expression(test + strlen(test) - 1, test, &p);
printf("%s\n", p+1);
}
/*
PLUG: http://plug.org, #utah on irc.freenode.net
Unsubscribe: http://plug.org/mailman/options/plug
Don't fear the penguin.
*/