diff --git a/src/tests/build.tests b/src/tests/build.tests index f736572..eefceb9 100644 --- a/src/tests/build.tests +++ b/src/tests/build.tests @@ -25,9 +25,14 @@ then fi done + case "${TEST}" in + obj/tests/manip) TEST_EXTRAS="-lm" ;; + *) TEST_EXTRAS="" ;; + esac + if [ ${MODIFIED} -ne 0 ] then - do_cmd ${CC} -Iobj ${CFLAGS} -o ${TEST} ${SRC} ${LIBS} ${EXTRAS} || return 1 + do_cmd ${CC} -Iobj ${CFLAGS} -o ${TEST} ${SRC} ${LIBS} ${EXTRAS} ${TEST_EXTRAS} || return 1 print_success "Built ${TEST}" else print_success "${TEST} is up to date" diff --git a/src/tests/manip.c b/src/tests/manip.c new file mode 100644 index 0000000..61fab4c --- /dev/null +++ b/src/tests/manip.c @@ -0,0 +1,299 @@ +/* libiso8601/src/tests/manip.c + * + * (c)2007, Laurence Withers, . + * Released under the GNU GPLv2. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + +#include "iso8601.h" + +#include +#include +#include +#include +#include + + + +/* safe_strtod() + * Converts `str' into a double, which it will store in `out'. If an error occurs, returns -1, + * else 0 on success. + */ +int safe_strtod(const char* str, double* out) +{ + char* endp = 0; + + errno = 0; + *out = strtod(str, &endp); + if(errno || !endp || *endp) return -1; + + return 0; +} + + + +/* date_stack + * We use a stack of dates and/or periods to implement effectively an RPN manipulator. + */ +enum date_stack_type { + date_stack_type_date, + date_stack_type_per +}; + +struct date_stack { + enum date_stack_type type; + + union { + struct { + struct iso8601_date d; + struct iso8601_details det; + }date; + + struct iso8601_elapsed per; + }u; + + struct date_stack* next; +}; + +struct date_stack* date_stack_top = 0; +int date_stack_size = 0; + + + +/* date_stack_push_*() + * Routines for pushing an object onto the date stack. + */ +void date_stack_push_date(const struct iso8601_date* d, const struct iso8601_details* det) +{ + struct date_stack* s; + + s = malloc(sizeof(struct date_stack)); + s->type = date_stack_type_date; + s->u.date.d = *d; + s->u.date.det = *det; + s->next = date_stack_top; + + date_stack_top = s; + ++date_stack_size; +} + +void date_stack_push_elapsed(const struct iso8601_elapsed* per) +{ + struct date_stack* s; + + s = malloc(sizeof(struct date_stack)); + s->type = date_stack_type_per; + s->u.per = *per; + s->next = date_stack_top; + + date_stack_top = s; + ++date_stack_size; +} + + + +/* date_stack_pop_date() + * Pops a date from the stack, returning non-0 on error (underflow or type mismatch). + */ +int date_stack_pop_date(struct iso8601_date* d, struct iso8601_details* det) +{ + struct date_stack* next; + + if(!date_stack_top) { + fputs("Stack underflow.\n", stderr); + return -1; + } + if(date_stack_top->type != date_stack_type_date) { + fputs("Type mismatch.\n", stderr); + return -1; + } + + *d = date_stack_top->u.date.d; + *det = date_stack_top->u.date.det; + + --date_stack_size; + next = date_stack_top->next; + free(date_stack_top); + date_stack_top = next; + + return 0; +} + + + +/* date_stack_pop_elapsed() + * Pops an elapsed period from the stack, returning non-0 on error (underflow or type mismatch). + */ +int date_stack_pop_elapsed(struct iso8601_elapsed* per) +{ + struct date_stack* next; + + if(!date_stack_top) { + fputs("Stack underflow.\n", stderr); + return -1; + } + if(date_stack_top->type != date_stack_type_per) { + fputs("Type mismatch.\n", stderr); + return -1; + } + + *per = date_stack_top->u.per; + + --date_stack_size; + next = date_stack_top->next; + free(date_stack_top); + date_stack_top = next; + + return 0; +} + + + +/* usage() + * Displays help. + */ +void usage(void) +{ + fputs("Push a date or elapsed time onto the stack by entering it, e.g.:\n" + " 2007-07-29 12345.67\n" + "Operators:\n" + " + (date, period) Advance date by elapsed time, push date.\n" + " - (date, period) Regress date by elapsed time, push date.\n" + " < (date, date) Prints 1 if first date less than second.\n" + " <= (date, date) Prints 1 if first date less than or equal to second.\n" + " == (date, date) Prints 1 if dates equal.\n" + " p (date) Prints a date.\n" + "\n" + "Type help for this screen at any point.\n" + "\n", stdout); +} + + + +int parse_command(char* cmd) +{ + struct iso8601_date date, date2; + struct iso8601_details details, details2; + struct iso8601_elapsed period; + double period_d; + char date_str[40]; + + int ret = 0; + + cmd = strtok(cmd, " \n"); + while(cmd) { + if(!iso8601_parse(cmd, &date, 0, &details)) { + date_stack_push_date(&date, &details); + + } else if(!safe_strtod(cmd, &period_d) && period_d >= 0) { + period.sec = trunc(period_d); + period.nsec = trunc((period_d - period.sec) * 1e9); + date_stack_push_elapsed(&period); + + } else if(!strcmp(cmd, "+")) { + if(date_stack_pop_elapsed(&period) || date_stack_pop_date(&date, &details)) { + ret = 1; + + } else { + iso8601_add_elapsed(&date, &period); + date_stack_push_date(&date, &details); + } + + } else if(!strcmp(cmd, "-")) { + if(date_stack_pop_elapsed(&period) || date_stack_pop_date(&date, &details)) { + ret = 1; + + } else { + iso8601_subtract_elapsed(&date, &period); + date_stack_push_date(&date, &details); + + } + + } else if(!strcmp(cmd, "<")) { + if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { + ret = 1; + + } else { + printf("%d\n", iso8601_lt(&date2, &date)); + + } + + } else if(!strcmp(cmd, "<=")) { + if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { + ret = 1; + + } else { + printf("%d\n", iso8601_lte(&date2, &date)); + + } + + } else if(!strcmp(cmd, "==")) { + if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { + ret = 1; + + } else { + printf("%d\n", iso8601_eq(&date2, &date)); + + } + + } else if(!strcmp(cmd, "p")) { + if(date_stack_pop_date(&date, &details)) { + ret = 1; + + } else { + iso8601_print(date_str, sizeof(date_str), &date, &details); + fputs(date_str, stdout); + putc('\n', stdout); + } + + } else { + fputs("Unrecognised command or argument.\n", stderr); + ret = 1; + + } + + cmd = strtok(0, " \n"); + } + + return ret; +} + + + +int main(int argc, char* argv[]) +{ + int ret = 0; + char* buf = 0; + size_t buf_len = 0; + + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + printf("Interactive date manipulator. Also evaluates commandline.\n"); + return 0; + } + + /* commandline -- evaluate arguments as though interactive */ + if(argc > 1) { + for(ret = 1; ret < argc; ++ret) { + if(parse_command(argv[ret])) return 1; + } + return 0; + } + + usage(); + while(!feof(stdin)) { + printf("%d >> ", date_stack_size); + ret = getline(&buf, &buf_len, stdin); + if(ret == -1) break; + + parse_command(buf); + } + + return 0; +} + + + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/