From aa1c136ad91df1a410b419c7261953932e753aa9 Mon Sep 17 00:00:00 2001 From: "Juan F. Codagnone" Date: Sun, 22 Oct 2017 19:32:41 -0300 Subject: [PATCH] =?UTF-8?q?parser=5Futils.c=20--=20factory=20de=20ciertos?= =?UTF-8?q?=20parsers=20t=C3=ADpicos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provee parsers reusables, como por ejemplo para verificar que un string es igual a otro de forma case insensitive. --- src/foo.dot | 20 ++++++ src/parser_utils.c | 144 ++++++++++++++++++++++++++++++++++++++++ src/parser_utils.h | 39 +++++++++++ src/parser_utils_test.c | 63 ++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 src/foo.dot create mode 100644 src/parser_utils.c create mode 100644 src/parser_utils.h create mode 100644 src/parser_utils_test.c diff --git a/src/foo.dot b/src/foo.dot new file mode 100644 index 0000000..36d1ce4 --- /dev/null +++ b/src/foo.dot @@ -0,0 +1,20 @@ +digraph g { + rankdir=LR; + size= "8.27,11.69"; + + node [shape = circle]; + + S0 [label = "0", shape = doublecircle]; + S1 [label = "1"]; + S2 [label = "2"]; + EQ [label = "EQ", shape = doublecircle]; + NEQ [label = "NEQ", shape = doublecircle]; + + S0 -> S1 [label= "'f', 'F'\neq(c)"]; + S0 -> NEQ [label="ANY\nneq(c)"]; + S1 -> S2 [label= "'o', 'O'\neq(c)"]; + S1 -> NEQ [label="ANY\nneq(c)"]; + S2 -> EQ [label= "'o', 'O\neq(c)'"]; + S2 -> NEQ [label="ANY\nneq(c)"]; + EQ -> NEQ [label= "ANY\nneq(c)"]; +} diff --git a/src/parser_utils.c b/src/parser_utils.c new file mode 100644 index 0000000..f54f9ca --- /dev/null +++ b/src/parser_utils.c @@ -0,0 +1,144 @@ +#include +#include +#include + +#include "parser_utils.h" + +const char * +parser_utils_strcmpi_event(const enum string_cmp_event_types type) { + const char *ret; + + switch(type) { + case STRING_CMP_MAYEQ: + ret = "wait(c)"; + break; + case STRING_CMP_EQ: + ret = "eq(c)"; + break; + case STRING_CMP_NEQ: + ret = "neq(c)"; + break; + } + return ret; +} + +static void +may_eq(struct parser_event *ret, const uint8_t c) { + ret->type = STRING_CMP_MAYEQ; + ret->n = 1; + ret->data[0] = c; +} + +static void +eq(struct parser_event *ret, const uint8_t c) { + ret->type = STRING_CMP_EQ; + ret->n = 1; + ret->data[0] = c; +} + +static void +neq(struct parser_event *ret, const uint8_t c) { + ret->type = STRING_CMP_NEQ; + ret->n = 1; + ret->data[0] = c; +} + +/* + * para comparar "foo" (length 3) necesitamos 3 + 2 estados. + * Los útimos dos, son el sumidero de comparación fallida, y + * el estado donde se llegó a la comparación completa. + * + * static const struct parser_state_transition ST_0 [] = { + * {.when = 'F', .dest = 1, .action1 = may_eq, }, + * {.when = 'f', .dest = 1, .action1 = may_eq, }, + * {.when = ANY, .dest = NEQ, .action1 = neq,}, + * }; + * static const struct parser_state_transition ST_1 [] = { + * {.when = 'O', .dest = 2, .action1 = may_eq, }, + * {.when = 'o', .dest = 2, .action1 = may_eq, }, + * {.when = ANY, .dest = NEQ, .action1 = neq,}, + * }; + * static const struct parser_state_transition ST_2 [] = { + * {.when = 'O', .dest = EQ, .action1 = eq, }, + * {.when = 'o', .dest = EQ, .action1 = eq, }, + * {.when = ANY, .dest = NEQ, .action1 = neq,}, + * }; + * static const struct parser_state_transition ST_EQ (3) [] = { + * {.when = ANY, .dest = NEQ, .action1 = neq,}, + * }; + * static const struct parser_state_transition ST_NEQ (4) [] = { + * {.when = ANY, .dest = NEQ, .action1 = neq,}, + * }; + * + */ +struct parser_definition +parser_utils_strcmpi(const char *s) { + const size_t n = strlen(s); + + struct parser_state_transition **states = calloc(n + 2, sizeof(*states)); + size_t *nstates = calloc(n + 2, sizeof(*nstates)); + struct parser_state_transition *transitions= calloc(3 *(n + 2), + sizeof(*transitions)); + if(states == NULL || nstates == NULL || transitions == NULL) { + free(states); + free(nstates); + free(transitions); + + struct parser_definition def = { + .start_state = 0, + .states_count = 0, + .states = NULL, + .states_n = NULL, + }; + return def; + } + + // estados fijos + const size_t st_eq = n; + const size_t st_neq = n + 1; + + for(size_t i = 0; i < n; i++) { + const size_t dest = (i + 1 == n) ? st_eq : i + 1; + + transitions[i * 3 + 0].when = tolower(s[i]); + transitions[i * 3 + 0].dest = dest; + transitions[i * 3 + 0].act1 = i + 1 == n ? eq : may_eq; + transitions[i * 3 + 1].when = toupper(s[i]); + transitions[i * 3 + 1].dest = dest; + transitions[i * 3 + 1].act1 = i + 1 == n ? eq : may_eq; + transitions[i * 3 + 2].when = ANY; + transitions[i * 3 + 2].dest = st_neq; + transitions[i * 3 + 2].act1 = neq; + states [i] = transitions + (i * 3 + 0); + nstates [i] = 3; + } + // EQ + transitions[(n + 0) * 3].when = ANY; + transitions[(n + 0) * 3].dest = st_neq; + transitions[(n + 0) * 3].act1 = neq; + states [(n + 0)] = transitions + ((n + 0) * 3 + 0); + nstates [(n + 0)] = 1; + // NEQ + transitions[(n + 1) * 3].when = ANY; + transitions[(n + 1) * 3].dest = st_neq; + transitions[(n + 1) * 3].act1 = neq; + states [(n + 1)] = transitions + ((n + 1) * 3 + 0); + nstates [(n + 1)] = 1; + + + struct parser_definition def = { + .start_state = 0, + .states_count = n + 2, + .states = (const struct parser_state_transition **) states, + .states_n = (const size_t *) nstates, + }; + + return def; +} + +void +parser_utils_strcmpi_destroy(const struct parser_definition *p) { + free((void *)p->states[0]); + free((void *)p->states); + free((void *)p->states_n); +} diff --git a/src/parser_utils.h b/src/parser_utils.h new file mode 100644 index 0000000..5e4d30d --- /dev/null +++ b/src/parser_utils.h @@ -0,0 +1,39 @@ +#ifndef PARSER_UTILS_H_c2f29bb6482d34fc6f94a09046bbd65a5f668acf +#define PARSER_UTILS_H_c2f29bb6482d34fc6f94a09046bbd65a5f668acf + +/* + * parser_utils.c -- factory de ciertos parsers típicos + * + * Provee parsers reusables, como por ejemplo para verificar que + * un string es igual a otro de forma case insensitive. + */ +#include "parser.h" + +enum string_cmp_event_types { + STRING_CMP_MAYEQ, + /** hay posibilidades de que el string sea igual */ + STRING_CMP_EQ, + /** NO hay posibilidades de que el string sea igual */ + STRING_CMP_NEQ, +}; + +const char * +parser_utils_strcmpi_event(const enum string_cmp_event_types type); + + +/* + * Crea un parser que verifica que los caracteres recibidos forment el texto + * descripto por `s'. + * + * Si se recibe el evento `STRING_CMP_NEQ' el texto entrado no matchea. + */ +struct parser_definition +parser_utils_strcmpi(const char *s); + +/** + * libera recursos asociado a una llamada de `parser_utils_strcmpi' + */ +void +parser_utils_strcmpi_destroy(const struct parser_definition *p); + +#endif diff --git a/src/parser_utils_test.c b/src/parser_utils_test.c new file mode 100644 index 0000000..86ecab9 --- /dev/null +++ b/src/parser_utils_test.c @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "parser_utils.h" + +static void +assert_eq(const unsigned type, const int c, const struct parser_event *e) { + ck_assert_ptr_eq (0, e->next); + ck_assert_uint_eq(1, e->n); + ck_assert_uint_eq(type, e->type); + ck_assert_uint_eq(c, e->data[0]); + +} + +START_TEST (test_eq) { + const struct parser_definition d = parser_utils_strcmpi("foo"); + + struct parser *parser = parser_init(parser_no_classes(), &d); + assert_eq(STRING_CMP_MAYEQ, 'f', parser_feed(parser, 'f')); + assert_eq(STRING_CMP_MAYEQ, 'O', parser_feed(parser, 'O')); + assert_eq(STRING_CMP_EQ, 'o', parser_feed(parser, 'o')); + assert_eq(STRING_CMP_NEQ, 'X', parser_feed(parser, 'X')); + assert_eq(STRING_CMP_NEQ, 'y', parser_feed(parser, 'y')); + + parser_destroy(parser); + parser_utils_strcmpi_destroy(&d); +} +END_TEST + +Suite * +suite(void) { + Suite *s; + TCase *tc; + + s = suite_create("parser_utils"); + + /* Core test case */ + tc = tcase_create("parser_utils"); + + tcase_add_test(tc, test_eq); + suite_add_tcase(s, tc); + + return s; +} + + + +int +main(void) { + int number_failed; + Suite *s; + SRunner *sr; + + s = suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} +