parser.c -- pequeño motor para parsers/lexers.

El usuario describe estados y transiciones.
Las transiciones contienen una condición, un estado destino y acciones.

El usuario provee al parser con bytes y éste retona eventos que pueden
servir para delimitar tokens o accionar directamente.
This commit is contained in:
Juan F. Codagnone 2017-10-22 19:09:10 -03:00 committed by Santiago Lo Coco
parent 07f01d932f
commit cfb8f2de05
3 changed files with 299 additions and 0 deletions

91
src/parser.c Normal file
View File

@ -0,0 +1,91 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "parser.h"
/* CDT del parser */
struct parser {
/** tipificación para cada caracter */
const unsigned *classes;
/** definición de estados */
const struct parser_definition *def;
/* estado actual */
unsigned state;
/* evento que se retorna */
struct parser_event e1;
/* evento que se retorna */
struct parser_event e2;
};
void
parser_destroy(struct parser *p) {
if(p != NULL) {
free(p);
}
}
struct parser *
parser_init(const unsigned *classes,
const struct parser_definition *def) {
struct parser *ret = malloc(sizeof(*ret));
if(ret != NULL) {
memset(ret, 0, sizeof(*ret));
ret->classes = classes;
ret->def = def;
ret->state = def->start_state;
}
return ret;
}
void
parser_reset(struct parser *p) {
p->state = p->def->start_state;
}
const struct parser_event *
parser_feed(struct parser *p, const uint8_t c) {
const unsigned type = p->classes[c];
p->e1.next = p->e2.next = 0;
const struct parser_state_transition *state = p->def->states[p->state];
const size_t n = p->def->states_n[p->state];
bool matched = false;
for(unsigned i = 0; i < n ; i++) {
const int when = state[i].when;
if (state[i].when <= 0xFF) {
matched = (c == when);
} else if(state[i].when == ANY) {
matched = true;
} else if(state[i].when > 0xFF) {
matched = (type & when);
} else {
matched = false;
}
if(matched) {
state[i].act1(&p->e1, c);
if(state[i].act2 != NULL) {
p->e1.next = &p->e2;
state[i].act2(&p->e2, c);
}
p->state = state[i].dest;
break;
}
}
return &p->e1;
}
static const unsigned classes[0xFF] = {0x00};
const unsigned *
parser_no_classes(void) {
return classes;
}

93
src/parser.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef PARSER_H_00180a6350a1fbe79f133adf0a96eb6685c242b6
#define PARSER_H_00180a6350a1fbe79f133adf0a96eb6685c242b6
/**
* parser.c -- pequeño motor para parsers/lexers.
*
* El usuario describe estados y transiciones.
* Las transiciones contienen una condición, un estado destino y acciones.
*
* El usuario provee al parser con bytes y éste retona eventos que pueden
* servir para delimitar tokens o accionar directamente.
*/
#include <stdint.h>
#include <stddef.h>
/**
* Evento que retorna el parser.
* Cada tipo de evento tendrá sus reglas en relación a data.
*/
struct parser_event {
/** tipo de evento */
unsigned type;
/** caracteres asociados al evento */
uint8_t data[3];
/** cantidad de datos en el buffer `data' */
uint8_t n;
/** lista de eventos: si es diferente de null ocurrieron varios eventos */
struct parser_event *next;
};
/** describe una transición entre estados */
struct parser_state_transition {
/* condición: un caracter o una clase de caracter. Por ej: '\r' */
int when;
/** descriptor del estado destino cuando se cumple la condición */
unsigned dest;
/** acción 1 que se ejecuta cuando la condición es verdadera. requerida. */
void (*act1)(struct parser_event *ret, const uint8_t c);
/** otra acción opcional */
void (*act2)(struct parser_event *ret, const uint8_t c);
};
/** predicado para utilizar en `when' que retorna siempre true */
static const unsigned ANY = 1 << 9;
/** declaración completa de una máquina de estados */
struct parser_definition {
/** cantidad de estados */
const unsigned states_count;
/** por cada estado, sus transiciones */
const struct parser_state_transition **states;
/** cantidad de estados por transición */
const size_t *states_n;
/** estado inicial */
const unsigned start_state;
};
/**
* inicializa el parser.
*
* `classes`: caracterización de cada caracter (256 elementos)
*/
struct parser *
parser_init (const unsigned *classes,
const struct parser_definition *def);
/** destruye el parser */
void
parser_destroy (struct parser *p);
/** permite resetear el parser al estado inicial */
void
parser_reset (struct parser *p);
/**
* el usuario alimenta el parser con un caracter, y el parser retorna un evento
* de parsing. Los eventos son reusado entre llamadas por lo que si se desea
* capturar los datos se debe clonar.
*/
const struct parser_event *
parser_feed (struct parser *p, const uint8_t c);
/**
* En caso de la aplicacion no necesite clases caracteres, se
* provee dicho arreglo para ser usando en `parser_init'
*/
const unsigned *
parser_no_classes(void);
#endif

115
src/parser_test.c Normal file
View File

@ -0,0 +1,115 @@
#include <stdio.h>
#include <stdlib.h>
#include <check.h>
#include "parser.h"
// definición de maquina
enum states {
S0,
S1
};
enum event_type {
FOO,
BAR,
};
static void
foo(struct parser_event *ret, const uint8_t c) {
ret->type = FOO;
ret->n = 1;
ret->data[0] = c;
}
static void
bar(struct parser_event *ret, const uint8_t c) {
ret->type = BAR;
ret->n = 1;
ret->data[0] = c;
}
static const struct parser_state_transition ST_S0 [] = {
{.when = 'F', .dest = S0, .act1 = foo,},
{.when = 'f', .dest = S0, .act1 = foo,},
{.when = ANY, .dest = S1, .act1 = bar,},
};
static const struct parser_state_transition ST_S1 [] = {
{.when = 'F', .dest = S0, .act1 = foo,},
{.when = 'f', .dest = S0, .act1 = foo,},
{.when = ANY, .dest = S1, .act1 = bar,},
};
static const struct parser_state_transition *states [] = {
ST_S0,
ST_S1,
};
#define N(x) (sizeof(x)/sizeof((x)[0]))
static const size_t states_n [] = {
N(ST_S0),
N(ST_S1),
};
static struct parser_definition definition = {
.states_count = N(states),
.states = states,
.states_n = states_n,
.start_state = S0,
};
//// TEST
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_basic) {
struct parser *parser = parser_init(parser_no_classes(), &definition);
assert_eq(FOO, 'f', parser_feed(parser, 'f'));
assert_eq(FOO, 'F', parser_feed(parser, 'F'));
assert_eq(BAR, 'B', parser_feed(parser, 'B'));
assert_eq(BAR, 'b', parser_feed(parser, 'b'));
parser_destroy(parser);
}
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_basic);
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;
}