366 lines
11 KiB
C
366 lines
11 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* rosa.c — Suffix automaton implementation
|
|
*
|
|
* Binary automaton: fixed next[2] transitions.
|
|
* Token automaton: open-addressing hash map transitions.
|
|
*/
|
|
|
|
#include "rosa/rosa.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ================================================================
|
|
* Binary suffix automaton
|
|
* ================================================================ */
|
|
|
|
static void sa_init_state(sa_state *s) {
|
|
s->len = 0;
|
|
s->link = -1;
|
|
s->endpos = -1;
|
|
s->next[0] = -1;
|
|
s->next[1] = -1;
|
|
}
|
|
|
|
static void sa_ensure_capacity(sa_automaton *sa, int needed) {
|
|
if (needed <= sa->capacity) return;
|
|
int new_cap = sa->capacity * 2;
|
|
if (new_cap < needed) new_cap = needed;
|
|
sa->states = (sa_state*)realloc(sa->states, new_cap * sizeof(sa_state));
|
|
sa->capacity = new_cap;
|
|
}
|
|
|
|
sa_automaton* sa_new(int initial_cap) {
|
|
if (initial_cap < 4) initial_cap = 4;
|
|
sa_automaton *sa = (sa_automaton*)calloc(1, sizeof(sa_automaton));
|
|
sa->states = (sa_state*)malloc(initial_cap * sizeof(sa_state));
|
|
sa->capacity = initial_cap;
|
|
sa->n_states = 1;
|
|
sa->last = 0;
|
|
sa->seq_len = 0;
|
|
sa_init_state(&sa->states[0]);
|
|
return sa;
|
|
}
|
|
|
|
void sa_free(sa_automaton *sa) {
|
|
if (!sa) return;
|
|
free(sa->states);
|
|
free(sa);
|
|
}
|
|
|
|
void sa_reset(sa_automaton *sa) {
|
|
if (!sa) return;
|
|
sa->n_states = 1;
|
|
sa->last = 0;
|
|
sa->seq_len = 0;
|
|
sa_init_state(&sa->states[0]);
|
|
}
|
|
|
|
void sa_extend(sa_automaton *sa, int symbol, int position) {
|
|
sa_ensure_capacity(sa, sa->n_states + 2); /* extend may clone */
|
|
|
|
int cur = sa->n_states++;
|
|
sa_init_state(&sa->states[cur]);
|
|
sa->states[cur].len = sa->states[sa->last].len + 1;
|
|
sa->states[cur].endpos = position;
|
|
|
|
int p = sa->last;
|
|
while (p != -1 && sa->states[p].next[symbol] == -1) {
|
|
sa->states[p].next[symbol] = cur;
|
|
p = sa->states[p].link;
|
|
}
|
|
|
|
if (p == -1) {
|
|
sa->states[cur].link = 0;
|
|
} else {
|
|
int q = sa->states[p].next[symbol];
|
|
if (sa->states[p].len + 1 == sa->states[q].len) {
|
|
sa->states[cur].link = q;
|
|
} else {
|
|
/* Clone q */
|
|
sa_ensure_capacity(sa, sa->n_states + 1);
|
|
int clone = sa->n_states++;
|
|
sa->states[clone] = sa->states[q];
|
|
sa->states[clone].len = sa->states[p].len + 1;
|
|
|
|
while (p != -1 && sa->states[p].next[symbol] == q) {
|
|
sa->states[p].next[symbol] = clone;
|
|
p = sa->states[p].link;
|
|
}
|
|
sa->states[q].link = clone;
|
|
sa->states[cur].link = clone;
|
|
}
|
|
}
|
|
|
|
/* Update endpos along suffix link chain */
|
|
int v = cur;
|
|
while (v != -1 && sa->states[v].endpos < position) {
|
|
sa->states[v].endpos = position;
|
|
v = sa->states[v].link;
|
|
}
|
|
|
|
sa->last = cur;
|
|
sa->seq_len++;
|
|
}
|
|
|
|
int sa_query_step(sa_automaton *sa, int symbol, int *state, int *match_len) {
|
|
int v = *state;
|
|
int len = *match_len;
|
|
|
|
while (v != 0 && sa->states[v].next[symbol] == -1) {
|
|
v = sa->states[v].link;
|
|
len = sa->states[v].len;
|
|
}
|
|
|
|
if (sa->states[v].next[symbol] != -1) {
|
|
v = sa->states[v].next[symbol];
|
|
len++;
|
|
}
|
|
|
|
*state = v;
|
|
*match_len = len;
|
|
|
|
if (v != 0 && sa->states[v].endpos >= 0)
|
|
return sa->states[v].endpos;
|
|
return -1;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Token-level suffix automaton (hash-map transitions)
|
|
* ================================================================ */
|
|
|
|
#define SA_TRANS_EMPTY (-1)
|
|
#define SA_TRANS_INIT_CAP 4
|
|
|
|
static void sa_trans_init(sa_token_state *s) {
|
|
s->trans_cap = SA_TRANS_INIT_CAP;
|
|
s->trans_count = 0;
|
|
s->trans = (sa_trans_entry*)malloc(SA_TRANS_INIT_CAP * sizeof(sa_trans_entry));
|
|
for (int i = 0; i < SA_TRANS_INIT_CAP; i++)
|
|
s->trans[i].key = SA_TRANS_EMPTY;
|
|
}
|
|
|
|
static void sa_trans_free(sa_token_state *s) {
|
|
free(s->trans);
|
|
s->trans = NULL;
|
|
s->trans_cap = 0;
|
|
s->trans_count = 0;
|
|
}
|
|
|
|
static int sa_trans_find(const sa_token_state *s, int key) {
|
|
int mask = s->trans_cap - 1;
|
|
int idx = (unsigned)key & mask;
|
|
for (int i = 0; i < s->trans_cap; i++) {
|
|
int k = s->trans[(idx + i) & mask].key;
|
|
if (k == key) return s->trans[(idx + i) & mask].value;
|
|
if (k == SA_TRANS_EMPTY) return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void sa_trans_rehash(sa_token_state *s) {
|
|
int old_cap = s->trans_cap;
|
|
sa_trans_entry *old = s->trans;
|
|
s->trans_cap *= 2;
|
|
s->trans = (sa_trans_entry*)malloc(s->trans_cap * sizeof(sa_trans_entry));
|
|
for (int i = 0; i < s->trans_cap; i++)
|
|
s->trans[i].key = SA_TRANS_EMPTY;
|
|
int mask = s->trans_cap - 1;
|
|
for (int i = 0; i < old_cap; i++) {
|
|
if (old[i].key == SA_TRANS_EMPTY) continue;
|
|
int idx = (unsigned)old[i].key & mask;
|
|
while (s->trans[idx].key != SA_TRANS_EMPTY)
|
|
idx = (idx + 1) & mask;
|
|
s->trans[idx] = old[i];
|
|
}
|
|
free(old);
|
|
}
|
|
|
|
static void sa_trans_set(sa_token_state *s, int key, int value) {
|
|
/* Check if key already exists */
|
|
int mask = s->trans_cap - 1;
|
|
int idx = (unsigned)key & mask;
|
|
for (int i = 0; i < s->trans_cap; i++) {
|
|
int pos = (idx + i) & mask;
|
|
if (s->trans[pos].key == key) {
|
|
s->trans[pos].value = value;
|
|
return;
|
|
}
|
|
if (s->trans[pos].key == SA_TRANS_EMPTY) {
|
|
s->trans[pos].key = key;
|
|
s->trans[pos].value = value;
|
|
s->trans_count++;
|
|
if (s->trans_count * 2 > s->trans_cap)
|
|
sa_trans_rehash(s);
|
|
return;
|
|
}
|
|
}
|
|
/* Table full (shouldn't happen with rehash) */
|
|
sa_trans_rehash(s);
|
|
sa_trans_set(s, key, value);
|
|
}
|
|
|
|
static void sa_trans_copy(sa_token_state *dst, const sa_token_state *src) {
|
|
dst->trans_cap = src->trans_cap;
|
|
dst->trans_count = src->trans_count;
|
|
dst->trans = (sa_trans_entry*)malloc(dst->trans_cap * sizeof(sa_trans_entry));
|
|
memcpy(dst->trans, src->trans, dst->trans_cap * sizeof(sa_trans_entry));
|
|
}
|
|
|
|
static void sa_token_init_state(sa_token_state *s) {
|
|
s->len = 0;
|
|
s->link = -1;
|
|
s->endpos = -1;
|
|
sa_trans_init(s);
|
|
}
|
|
|
|
static void sa_token_ensure_cap(sa_token_automaton *sa, int needed) {
|
|
if (needed <= sa->capacity) return;
|
|
int new_cap = sa->capacity * 2;
|
|
if (new_cap < needed) new_cap = needed;
|
|
sa->states = (sa_token_state*)realloc(sa->states, new_cap * sizeof(sa_token_state));
|
|
sa->capacity = new_cap;
|
|
}
|
|
|
|
sa_token_automaton* sa_token_new(int initial_cap, int alphabet_size) {
|
|
if (initial_cap < 4) initial_cap = 4;
|
|
sa_token_automaton *sa = (sa_token_automaton*)calloc(1, sizeof(sa_token_automaton));
|
|
sa->states = (sa_token_state*)malloc(initial_cap * sizeof(sa_token_state));
|
|
sa->capacity = initial_cap;
|
|
sa->n_states = 1;
|
|
sa->last = 0;
|
|
sa->seq_len = 0;
|
|
sa->alphabet_size = alphabet_size;
|
|
sa_token_init_state(&sa->states[0]);
|
|
return sa;
|
|
}
|
|
|
|
void sa_token_free(sa_token_automaton *sa) {
|
|
if (!sa) return;
|
|
for (int i = 0; i < sa->n_states; i++)
|
|
sa_trans_free(&sa->states[i]);
|
|
free(sa->states);
|
|
free(sa);
|
|
}
|
|
|
|
void sa_token_reset(sa_token_automaton *sa) {
|
|
if (!sa) return;
|
|
for (int i = 0; i < sa->n_states; i++)
|
|
sa_trans_free(&sa->states[i]);
|
|
sa->n_states = 1;
|
|
sa->last = 0;
|
|
sa->seq_len = 0;
|
|
sa_token_init_state(&sa->states[0]);
|
|
}
|
|
|
|
void sa_token_extend(sa_token_automaton *sa, int token, int position) {
|
|
sa_token_ensure_cap(sa, sa->n_states + 2);
|
|
|
|
int cur = sa->n_states++;
|
|
sa_token_init_state(&sa->states[cur]);
|
|
sa->states[cur].len = sa->states[sa->last].len + 1;
|
|
sa->states[cur].endpos = position;
|
|
|
|
int p = sa->last;
|
|
while (p != -1 && sa_trans_find(&sa->states[p], token) == -1) {
|
|
sa_trans_set(&sa->states[p], token, cur);
|
|
p = sa->states[p].link;
|
|
}
|
|
|
|
if (p == -1) {
|
|
sa->states[cur].link = 0;
|
|
} else {
|
|
int q = sa_trans_find(&sa->states[p], token);
|
|
if (sa->states[p].len + 1 == sa->states[q].len) {
|
|
sa->states[cur].link = q;
|
|
} else {
|
|
sa_token_ensure_cap(sa, sa->n_states + 1);
|
|
int clone = sa->n_states++;
|
|
sa->states[clone].len = sa->states[p].len + 1;
|
|
sa->states[clone].link = sa->states[q].link;
|
|
sa->states[clone].endpos = sa->states[q].endpos;
|
|
sa_trans_copy(&sa->states[clone], &sa->states[q]);
|
|
|
|
while (p != -1 && sa_trans_find(&sa->states[p], token) == q) {
|
|
sa_trans_set(&sa->states[p], token, clone);
|
|
p = sa->states[p].link;
|
|
}
|
|
sa->states[q].link = clone;
|
|
sa->states[cur].link = clone;
|
|
}
|
|
}
|
|
|
|
int v = cur;
|
|
while (v != -1 && sa->states[v].endpos < position) {
|
|
sa->states[v].endpos = position;
|
|
v = sa->states[v].link;
|
|
}
|
|
|
|
sa->last = cur;
|
|
sa->seq_len++;
|
|
}
|
|
|
|
int sa_token_query_step(sa_token_automaton *sa, int token,
|
|
int *state, int *match_len) {
|
|
int v = *state;
|
|
int len = *match_len;
|
|
|
|
while (v != 0 && sa_trans_find(&sa->states[v], token) == -1) {
|
|
v = sa->states[v].link;
|
|
len = sa->states[v].len;
|
|
}
|
|
|
|
int next = sa_trans_find(&sa->states[v], token);
|
|
if (next != -1) {
|
|
v = next;
|
|
len++;
|
|
}
|
|
|
|
*state = v;
|
|
*match_len = len;
|
|
|
|
if (v != 0 && sa->states[v].endpos >= 0)
|
|
return sa->states[v].endpos;
|
|
return -1;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Position map
|
|
* ================================================================ */
|
|
|
|
sa_position_map* sa_position_map_new(int initial_cap) {
|
|
if (initial_cap < 16) initial_cap = 16;
|
|
sa_position_map *map = (sa_position_map*)calloc(1, sizeof(sa_position_map));
|
|
map->entries = (sa_position_info*)malloc(initial_cap * sizeof(sa_position_info));
|
|
map->capacity = initial_cap;
|
|
map->count = 0;
|
|
return map;
|
|
}
|
|
|
|
void sa_position_map_free(sa_position_map *map) {
|
|
if (!map) return;
|
|
free(map->entries);
|
|
free(map);
|
|
}
|
|
|
|
int sa_position_map_append(sa_position_map *map, int conv_idx,
|
|
int msg_idx, int token_offset) {
|
|
if (map->count >= map->capacity) {
|
|
map->capacity *= 2;
|
|
map->entries = (sa_position_info*)realloc(map->entries,
|
|
map->capacity * sizeof(sa_position_info));
|
|
}
|
|
int pos = map->count++;
|
|
map->entries[pos].conv_idx = conv_idx;
|
|
map->entries[pos].msg_idx = msg_idx;
|
|
map->entries[pos].token_offset = token_offset;
|
|
return pos;
|
|
}
|
|
|
|
const sa_position_info* sa_position_map_get(const sa_position_map *map,
|
|
int position) {
|
|
if (!map || position < 0 || position >= map->count) return NULL;
|
|
return &map->entries[position];
|
|
}
|