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:
parent
07f01d932f
commit
cfb8f2de05
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue