Implement conversion from NFA to DFA
This commit is contained in:
parent
6b52d4d9cd
commit
557ab451a8
@ -1,5 +1,6 @@
|
|||||||
add_library(lib
|
add_library(lib
|
||||||
construct.c
|
construct.c
|
||||||
|
convert.c
|
||||||
desugar.c
|
desugar.c
|
||||||
fsa.c
|
fsa.c
|
||||||
min_heap.c
|
min_heap.c
|
||||||
|
275
lib/convert.c
Normal file
275
lib/convert.c
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "convert.h"
|
||||||
|
|
||||||
|
#include "min_heap.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define BUFFER_START_CAPACITY 8
|
||||||
|
|
||||||
|
#define TABLE_START_CAPACITY 32
|
||||||
|
#define TABLE_START_SHIFT 27 // 32 - log_2(TABLE_START_CAPACITY)
|
||||||
|
#define TABLE_WRAP_COEFF 2654435769 // Closest odd number to 2^32 / φ
|
||||||
|
#define TABLE_DOUBLING_THRESHOLD 6
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int count, capacity, *states;
|
||||||
|
} buffer_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int probe_count, nfa_state_count, dfa_state, *nfa_states;
|
||||||
|
} table_entry_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int capacity, shift, max_probe_count;
|
||||||
|
table_entry_t *entries;
|
||||||
|
} table_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const fsa_t *nfa;
|
||||||
|
fsa_t *dfa;
|
||||||
|
buffer_t buffer;
|
||||||
|
table_t table;
|
||||||
|
} conversion_context_t;
|
||||||
|
|
||||||
|
static bool add_state(buffer_t *buffer, int nfa_state)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < buffer->count; ++i) {
|
||||||
|
if (nfa_state == buffer->states[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (buffer->capacity < buffer->count + 1) {
|
||||||
|
buffer->capacity *= 2;
|
||||||
|
buffer->states
|
||||||
|
= realloc(buffer->states, buffer->capacity * sizeof(int));
|
||||||
|
assert(NULL != buffer->states);
|
||||||
|
}
|
||||||
|
buffer->states[buffer->count++] = nfa_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_epsilon_closure(conversion_context_t *ctx, int nfa_state)
|
||||||
|
{
|
||||||
|
if (!add_state(&ctx->buffer, nfa_state))
|
||||||
|
return;
|
||||||
|
for (int i = 0; i < ctx->nfa->states[nfa_state].count; ++i) {
|
||||||
|
const fsa_rule_t *rule = &ctx->nfa->states[nfa_state].rules[i];
|
||||||
|
if (EPSILON == rule->input)
|
||||||
|
get_epsilon_closure(ctx, rule->next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int *move_buffer_sorted(buffer_t *buffer)
|
||||||
|
{
|
||||||
|
int *states, *p;
|
||||||
|
p = states = malloc(buffer->count * sizeof(int));
|
||||||
|
assert(NULL != states);
|
||||||
|
|
||||||
|
min_heap_heapify(buffer->states, buffer->count);
|
||||||
|
do
|
||||||
|
*p++ = min_heap_pop(buffer->states, &buffer->count);
|
||||||
|
while (0 < buffer->count);
|
||||||
|
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t hash(const int *states, int count)
|
||||||
|
{
|
||||||
|
assert(count > 0);
|
||||||
|
uint32_t x = states[0];
|
||||||
|
for (int i = 1; i < count; ++i)
|
||||||
|
x ^= (uint32_t)states[i];
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t wrap(uint32_t hash, int probe_count, int shift)
|
||||||
|
{
|
||||||
|
hash += probe_count;
|
||||||
|
hash *= TABLE_WRAP_COEFF;
|
||||||
|
return hash >> shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lookup(
|
||||||
|
const table_t *table, const int *nfa_states, int count,
|
||||||
|
int *dfa_state_out)
|
||||||
|
{
|
||||||
|
const uint32_t h = hash(nfa_states, count);
|
||||||
|
for (int i = 0; i <= table->max_probe_count; ++i) {
|
||||||
|
const uint32_t loc = wrap(h, i, table->shift);
|
||||||
|
const table_entry_t *entry = &table->entries[loc];
|
||||||
|
if (entry->nfa_state_count != count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int size = count * sizeof(int);
|
||||||
|
if (memcmp(entry->nfa_states, nfa_states, size) == 0) {
|
||||||
|
*dfa_state_out = entry->dfa_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insert(table_t *table, int *nfa_states, int count, int dfa_state)
|
||||||
|
{
|
||||||
|
uint32_t h = hash(nfa_states, count);
|
||||||
|
for (int i = 0; i < TABLE_DOUBLING_THRESHOLD; ++i) {
|
||||||
|
const uint32_t loc = wrap(h, i, table->shift);
|
||||||
|
table_entry_t *entry = &table->entries[loc];
|
||||||
|
if (0 == entry->nfa_state_count) {
|
||||||
|
// Slot is empty: insert the entry here.
|
||||||
|
entry->nfa_states = nfa_states;
|
||||||
|
entry->nfa_state_count = count;
|
||||||
|
entry->dfa_state = dfa_state;
|
||||||
|
entry->probe_count = i;
|
||||||
|
if (entry->probe_count > table->max_probe_count)
|
||||||
|
table->max_probe_count = entry->probe_count;
|
||||||
|
return;
|
||||||
|
} else if (entry->probe_count < i) {
|
||||||
|
// Slot contains entry with lesser probe count: steal the
|
||||||
|
// slot for the current entry.
|
||||||
|
table_entry_t tmp;
|
||||||
|
memcpy(&tmp, entry, sizeof(table_entry_t));
|
||||||
|
entry->nfa_states = nfa_states;
|
||||||
|
entry->nfa_state_count = count;
|
||||||
|
entry->dfa_state = dfa_state;
|
||||||
|
entry->probe_count = i;
|
||||||
|
if (entry->probe_count > table->max_probe_count)
|
||||||
|
table->max_probe_count = entry->probe_count;
|
||||||
|
|
||||||
|
// Continue with the slot's previous entry.
|
||||||
|
nfa_states = tmp.nfa_states;
|
||||||
|
count = tmp.nfa_state_count;
|
||||||
|
dfa_state = tmp.dfa_state;
|
||||||
|
i = tmp.probe_count;
|
||||||
|
h = hash(nfa_states, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double the capacity of the table.
|
||||||
|
table_entry_t *entries = table->entries;
|
||||||
|
const int old_capacity = table->capacity;
|
||||||
|
--table->shift;
|
||||||
|
table->capacity *= 2;
|
||||||
|
table->entries = calloc(table->capacity, sizeof(table_entry_t));
|
||||||
|
assert(NULL != table->entries);
|
||||||
|
for (int i = 0; i < old_capacity; ++i) {
|
||||||
|
if (0 != entries[i].nfa_state_count)
|
||||||
|
continue;
|
||||||
|
insert(
|
||||||
|
table, entries[i].nfa_states, entries[i].nfa_state_count,
|
||||||
|
entries[i].dfa_state);
|
||||||
|
}
|
||||||
|
free(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lookup_or_create(
|
||||||
|
conversion_context_t *ctx, int *nfa_states, int count,
|
||||||
|
int *dfa_state_out)
|
||||||
|
{
|
||||||
|
// Check if the DFA state for these NFA states already exists.
|
||||||
|
if (lookup(&ctx->table, nfa_states, count, dfa_state_out))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Create the DFA state, marking it as final if any of the NFA
|
||||||
|
// states are final.
|
||||||
|
const int dfa_state = fsa_add_state(ctx->dfa);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (ctx->nfa->states[nfa_states[i]].final) {
|
||||||
|
ctx->dfa->states[dfa_state].final = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the DFA state into the table under the NFA states.
|
||||||
|
insert(&ctx->table, nfa_states, count, dfa_state);
|
||||||
|
|
||||||
|
*dfa_state_out = dfa_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int convert_step(conversion_context_t *ctx)
|
||||||
|
{
|
||||||
|
assert(0 != ctx->buffer.count);
|
||||||
|
|
||||||
|
int count = ctx->buffer.count;
|
||||||
|
int *nfa_states = move_buffer_sorted(&ctx->buffer);
|
||||||
|
int dfa_state;
|
||||||
|
if (!lookup_or_create(ctx, nfa_states, count, &dfa_state)) {
|
||||||
|
// Base case: state already exists.
|
||||||
|
free(nfa_states);
|
||||||
|
return dfa_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handled[CHAR_COUNT] = { 0 };
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
const fsa_state_t *nfa_state = &ctx->nfa->states[nfa_states[i]];
|
||||||
|
for (int j = 0; j < nfa_state->count; ++j) {
|
||||||
|
const int input = nfa_state->rules[j].input;
|
||||||
|
if (EPSILON == input || handled[input])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Get epsilon closure of the target of this rule.
|
||||||
|
get_epsilon_closure(ctx, nfa_state->rules[j].next);
|
||||||
|
|
||||||
|
// Get epsilon closure for targets of any other rules the
|
||||||
|
// current state has with this input.
|
||||||
|
for (int k = j + 1; k < nfa_state->count; ++k) {
|
||||||
|
if (input == nfa_state->rules[k].input)
|
||||||
|
get_epsilon_closure(ctx, nfa_state->rules[k].next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same for all states after this one (we have
|
||||||
|
// already done them if they came before).
|
||||||
|
for (int k = i + 1; k < count; ++k) {
|
||||||
|
const fsa_state_t *nfa_state
|
||||||
|
= &ctx->nfa->states[nfa_states[k]];
|
||||||
|
for (int l = 0; l < nfa_state->count; ++l) {
|
||||||
|
if (input == nfa_state->rules[l].input)
|
||||||
|
get_epsilon_closure(ctx, nfa_state->rules[l].next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The buffer now contains the all states reachable via
|
||||||
|
// epsilon move or the given input -- recurse.
|
||||||
|
int new_dfa_state = convert_step(ctx);
|
||||||
|
|
||||||
|
fsa_add_rule(ctx->dfa, dfa_state, new_dfa_state, input);
|
||||||
|
handled[input] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dfa_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_to_dfa(const fsa_t *nfa, fsa_t *dfa_out)
|
||||||
|
{
|
||||||
|
fsa_init(dfa_out);
|
||||||
|
|
||||||
|
conversion_context_t ctx = { .nfa = nfa, .dfa = dfa_out };
|
||||||
|
|
||||||
|
ctx.buffer.count = 0;
|
||||||
|
ctx.buffer.capacity = BUFFER_START_CAPACITY;
|
||||||
|
ctx.buffer.states = malloc(ctx.buffer.capacity * sizeof(int));
|
||||||
|
assert(NULL != ctx.buffer.states);
|
||||||
|
|
||||||
|
ctx.table.capacity = TABLE_START_CAPACITY;
|
||||||
|
ctx.table.shift = TABLE_START_SHIFT;
|
||||||
|
ctx.table.max_probe_count = 0;
|
||||||
|
ctx.table.entries = calloc(ctx.table.capacity, sizeof(table_entry_t));
|
||||||
|
assert(NULL != ctx.table.entries);
|
||||||
|
|
||||||
|
get_epsilon_closure(&ctx, nfa->initial);
|
||||||
|
ctx.dfa->initial = convert_step(&ctx);
|
||||||
|
|
||||||
|
free(ctx.buffer.states);
|
||||||
|
for (int i = 0; i < ctx.table.capacity; ++i)
|
||||||
|
free(ctx.table.entries[i].nfa_states);
|
||||||
|
free(ctx.table.entries);
|
||||||
|
}
|
13
lib/include/convert.h
Normal file
13
lib/include/convert.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONVERT_H
|
||||||
|
#define CONVERT_H
|
||||||
|
|
||||||
|
#include "fsa.h"
|
||||||
|
|
||||||
|
void convert_to_dfa(const fsa_t *nfa, fsa_t *dfa_out);
|
||||||
|
|
||||||
|
#endif
|
@ -18,6 +18,7 @@ endfunction()
|
|||||||
|
|
||||||
add_test_suites(
|
add_test_suites(
|
||||||
construct_tests.c
|
construct_tests.c
|
||||||
|
convert_tests.c
|
||||||
desugar_tests.c
|
desugar_tests.c
|
||||||
fsa_tests.c
|
fsa_tests.c
|
||||||
min_heap_tests.c
|
min_heap_tests.c
|
||||||
|
271
tests/convert_tests.c
Normal file
271
tests/convert_tests.c
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "convert.h"
|
||||||
|
#include "testing.h"
|
||||||
|
|
||||||
|
static bool is_deterministic(const fsa_t *fsa)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < fsa->count; ++i) {
|
||||||
|
bool seen[CHAR_COUNT] = { 0 };
|
||||||
|
fsa_state_t *state = &fsa->states[i];
|
||||||
|
for (int j = 0; j < state->count; ++j) {
|
||||||
|
const int input = state->rules[j].input;
|
||||||
|
if (EPSILON == input)
|
||||||
|
return false;
|
||||||
|
if (seen[input])
|
||||||
|
return false;
|
||||||
|
seen[input] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool accepts(const fsa_t *dfa, const char *input)
|
||||||
|
{
|
||||||
|
int current = dfa->initial;
|
||||||
|
while ('\0' != *input) {
|
||||||
|
bool found = false;
|
||||||
|
const fsa_rule_t *rules = dfa->states[current].rules;
|
||||||
|
for (int i = 0; i < dfa->states[current].count; ++i) {
|
||||||
|
if (rules[i].input == *input) {
|
||||||
|
current = rules[i].next;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
return false;
|
||||||
|
++input;
|
||||||
|
}
|
||||||
|
return dfa->states[current].final;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_trivial_case(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
nfa.states[b].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, 'a');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aa"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "b"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_epsilon_move(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
const int c = fsa_add_state(&nfa);
|
||||||
|
nfa.states[c].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, a, c, 'a');
|
||||||
|
fsa_add_rule(&nfa, b, c, 'b');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "b"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aa"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "bb"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "ab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "ba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "c"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_branch(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
const int c = fsa_add_state(&nfa);
|
||||||
|
const int d = fsa_add_state(&nfa);
|
||||||
|
nfa.states[d].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, a, c, 'a');
|
||||||
|
fsa_add_rule(&nfa, b, d, 'b');
|
||||||
|
fsa_add_rule(&nfa, c, d, 'a');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aa"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "ab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaa"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "abb"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "c"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "ac"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_nfa_a(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
const int c = fsa_add_state(&nfa);
|
||||||
|
const int d = fsa_add_state(&nfa);
|
||||||
|
nfa.states[c].final = true;
|
||||||
|
nfa.states[d].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, a, c, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, b, b, 'b');
|
||||||
|
fsa_add_rule(&nfa, b, d, 'b');
|
||||||
|
fsa_add_rule(&nfa, c, b, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, c, d, 'a');
|
||||||
|
fsa_add_rule(&nfa, d, c, 'a');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
|
||||||
|
ASSERT_TRUE(accepts(&dfa, ""));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "b"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "ab"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "ba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaaab"));
|
||||||
|
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaabb"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaaba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaabb"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_nfa_b(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
const int c = fsa_add_state(&nfa);
|
||||||
|
const int d = fsa_add_state(&nfa);
|
||||||
|
nfa.states[c].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, a, c, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, b, c, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, c, b, 'b');
|
||||||
|
fsa_add_rule(&nfa, c, d, 'a');
|
||||||
|
fsa_add_rule(&nfa, d, b, 'a');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
|
||||||
|
ASSERT_TRUE(accepts(&dfa, ""));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaaaaa"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "b"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "bbbbb"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaaaaa"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaaaabaa"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaaaabaab"));
|
||||||
|
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "ba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "abab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaaba"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaabaaa"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aaaaabbaabbaaa"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_nfa_c(void)
|
||||||
|
{
|
||||||
|
fsa_t nfa;
|
||||||
|
fsa_init(&nfa);
|
||||||
|
const int a = nfa.initial;
|
||||||
|
const int b = fsa_add_state(&nfa);
|
||||||
|
const int c = fsa_add_state(&nfa);
|
||||||
|
const int d = fsa_add_state(&nfa);
|
||||||
|
const int e = fsa_add_state(&nfa);
|
||||||
|
nfa.states[e].final = true;
|
||||||
|
fsa_add_rule(&nfa, a, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, a, c, 'a');
|
||||||
|
fsa_add_rule(&nfa, a, d, 'b');
|
||||||
|
fsa_add_rule(&nfa, b, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, b, d, 'b');
|
||||||
|
fsa_add_rule(&nfa, b, e, EPSILON);
|
||||||
|
fsa_add_rule(&nfa, d, b, 'a');
|
||||||
|
fsa_add_rule(&nfa, d, c, 'b');
|
||||||
|
fsa_add_rule(&nfa, d, d, 'a');
|
||||||
|
fsa_add_rule(&nfa, e, a, 'b');
|
||||||
|
|
||||||
|
fsa_t dfa;
|
||||||
|
convert_to_dfa(&nfa, &dfa);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_deterministic(&dfa));
|
||||||
|
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "a"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "aaba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "abaaba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "ba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "babba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "baaa"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "baba"));
|
||||||
|
ASSERT_TRUE(accepts(&dfa, "babaa"));
|
||||||
|
|
||||||
|
ASSERT_FALSE(accepts(&dfa, ""));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "ab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "abbab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "b"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "bb"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "baaabab"));
|
||||||
|
ASSERT_FALSE(accepts(&dfa, "aabababab"));
|
||||||
|
|
||||||
|
fsa_free(&nfa);
|
||||||
|
fsa_free(&dfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TESTING_BEGIN();
|
||||||
|
|
||||||
|
// Base cases
|
||||||
|
test_trivial_case();
|
||||||
|
test_epsilon_move();
|
||||||
|
test_branch();
|
||||||
|
|
||||||
|
// Compound cases
|
||||||
|
test_nfa_a();
|
||||||
|
test_nfa_b();
|
||||||
|
test_nfa_c();
|
||||||
|
|
||||||
|
return TESTING_END();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user