Tokenizer¶
Header: libxs_token.h
Fixed-width lexical tokenizer with stable vocabulary IDs, lightweight classification flags, optional caller-owned normalization tables, and loadable text/lexical rules.
Token Format¶
Every token is exactly 8 bytes:
Bytes 0-3: vocabulary ID
Bytes 4-5: source token byte length
Bytes 6-7: class flags
The vocabulary ID indexes normalized token text in a libxs_lexicon_t.
The original source bytes are not stored in the token; callers that need text
use the lexicon for normalized ID-to-text mapping.
Flags mark word, number, punctuation, markup, sentence, question, stopword, entity, and break classes.
Stream Encoding¶
void libxs_token_stream_init(libxs_token_stream_t *stream);
int libxs_token_stream_encode(libxs_lexicon_t *lexicon,
libxs_token_stream_t *stream, const unsigned char *text, size_t size,
const libxs_lexrule_t *rules, int nrules,
const libxs_lexnorm_t *norms, int nnorms, int create);
Initialize a stream with libxs_token_stream_init, create or load a lexicon,
then encode text into fixed-width lexical tokens. Words are lowercased first.
If norms is non-NULL, each normalized word can be rewritten by the caller's
normalization table before lexicon lookup. This keeps language-specific
inflection, stemming, or spelling policy outside the tokenizer core. Pass
NULL, 0 for no language normalization.
Lexical rules (rules, nrules) classify normalized tokens after optional
normalization. The create flag controls whether missing normalized text is
interned into the lexicon or yields ID 0.
Flags are set automatically during tokenization:
- Word/number/punctuation flags: set by basic token shape.
- Break flag: set when whitespace preceded the token.
- Sentence/question flags: set for
.,?, and!punctuation tokens. - Markup flag: set when punctuation content is structural markup.
- Stop/entity and other semantic flags: assigned by lexical rules.
Returns EXIT_SUCCESS or EXIT_FAILURE.
Token Info¶
typedef struct libxs_token_info_t {
unsigned int id;
size_t length;
unsigned int flags;
int is_word;
int is_number;
int is_punct;
int has_break;
int is_sentence;
int is_question;
int is_stop;
int is_entity;
int is_markup;
} libxs_token_info_t;
void libxs_token_info(const libxs_token_t *token,
libxs_token_info_t *info);
Decode all token properties in one call. Fields:
id: normalized vocabulary ID, or 0 for unknown.length: source byte length of the token.flags: raw class flag bitset.is_word,is_number,is_punct: basic token class.has_break: non-zero if whitespace preceded the token.is_sentence: non-zero if the token is a sentence terminator.is_question: non-zero if the token is?.is_stop: non-zero if lexical rules classify it as a stopword.is_entity: non-zero if lexical rules classify it as an entity marker.is_markup: non-zero if the token is structural markup.
Stream Management¶
void libxs_token_stream_init(libxs_token_stream_t *stream);
int libxs_token_stream_reserve(libxs_token_stream_t *stream, size_t cap);
int libxs_token_stream_push(libxs_token_stream_t *stream, const libxs_token_t *t);
void libxs_token_stream_release(libxs_token_stream_t *stream);
The stream grows geometrically (doubling). reserve pre-allocates
capacity. release frees all memory but does not free the stream
struct itself (it may be stack-allocated), then resets the fields.
Text Rules¶
Text rules refine raw token signals using loadable knowledge. The
primary use case is sentence-boundary detection: the raw
sentence-end flag fires on every . ? !, but rules can
suppress false positives (e.g., "Mr." is not a sentence end).
Rule Structure¶
typedef struct libxs_textrule_t {
unsigned char tmpl; /* template ID */
unsigned char action; /* SUPPRESS or CONFIRM */
unsigned char reserved[2];
unsigned int argument; /* template-specific parameter */
unsigned char pad[8];
} libxs_textrule_t;
Each rule is 16 bytes, directly storable in a registry. The tmpl
field selects a compiled match pattern (template); argument
parameterizes it; action determines the outcome when matched.
Templates¶
| ID | Name | Matches when... |
|---|---|---|
| 1 | PREV_WORD_SHORT_UPPER |
Word before position is 1-4 chars, starts upper |
| 2 | NEXT_CHAR_UPPER |
First non-space char after position is uppercase |
| 3 | NEXT_CHAR_LOWER |
First non-space char after position is lowercase |
| 4 | PREV_TOKEN_BREAK |
Previous token has the BREAK flag |
| 5 | INSIDE_DELIMITERS |
Position is inside paired delimiters |
For template 1, if argument is 0 it matches any qualifying word;
if non-zero it must equal libxs_textrule_wordhash(word, len).
For template 5, argument & 0xFF is the opening delimiter character.
Evaluation¶
int libxs_textrule_eval(const libxs_textrule_ctx_t *ctx,
const libxs_textrule_t *rules, int nrules);
Returns 1 if the position is a sentence boundary, 0 if suppressed. If the token lacks the sentence-end flag, returns 0 without consulting rules. Rules are evaluated in order; last match wins.
The context struct provides the evaluation window:
typedef struct libxs_textrule_ctx_t {
const unsigned char *text;
size_t text_size;
int byte_pos; /* position of the sentence-end character */
const libxs_token_t *token;
const libxs_token_t *prev_token;
} libxs_textrule_ctx_t;
Persistence¶
int libxs_textrule_load(const libxs_registry_t *reg,
libxs_textrule_t *rules, int max_rules);
int libxs_textrule_save(libxs_registry_t *reg,
const libxs_textrule_t *rules, int nrules);
Rules are stored in a registry under key prefix TRULE: followed
by a 2-byte sequence number. Load returns the number of rules
read; save writes them sequentially.
Defaults¶
int libxs_textrule_defaults(libxs_textrule_t *rules, int max_rules);
Populates rules[] with built-in knowledge (abbreviation
suppression). Returns the number of rules written. Use this as a
starting point; additional rules can be appended or loaded from a
registry.
Word Hash¶
unsigned int libxs_textrule_wordhash(const unsigned char *word, int len);
Produces a 32-bit hash for use as rule argument. Pass this to
argument in a PREV_WORD_SHORT_UPPER rule to match a specific
word rather than any short uppercase-initial word.
Properties¶
- Fixed-width: every token is 8 bytes regardless of content, enabling direct indexing and compact storage.
- UTF-8 safe: multi-byte sequences are never split mid-codepoint.
- Deterministic: identical input, lexicon state, lexical rules, and normalization table produce identical IDs.
- Vocabulary-backed: normalized token text is stored once in a lexicon and referenced by stable token IDs.
- Language-swappable: caller-owned normalization tables decide whether and how inflections or spelling variants collapse to a common vocabulary entry.
- Rule-extensible: lexical classes and sentence boundary refinements are data rules, not hard-coded language policy.
Break Detection¶
The break flag is set when whitespace preceded the token. Downstream code can combine this with punctuation and lexical class flags to recover token spacing or detect phrase boundaries.
Sentence Detection¶
The sentence flag fires on punctuation tokens for ., ?, and !; the
question flag is also set for ?. This is the raw signal; use
libxs_textrule_eval with a ruleset to suppress false positives
(abbreviations, initials, delimiters, etc).
Constants¶
| Name | Value | Meaning |
|---|---|---|
LIBXS_TOKEN_BYTES |
8 | Fixed token size in bytes |
LIBXS_TOKEN_MAXBYTES |
63 | Maximum normalized text bytes |
LIBXS_TOKEN_WORD |
0x0001 | Word token |
LIBXS_TOKEN_NUMBER |
0x0002 | Number token |
LIBXS_TOKEN_PUNCT |
0x0004 | Punctuation token |
LIBXS_TOKEN_MARKUP |
0x0008 | Markup-like punctuation |
LIBXS_TOKEN_SENTENCE |
0x0010 | Sentence terminator |
LIBXS_TOKEN_QUESTION |
0x0020 | Question mark |
LIBXS_TOKEN_STOP |
0x0040 | Stopword class |
LIBXS_TOKEN_ENTITY |
0x0080 | Entity class |
LIBXS_TOKEN_BREAK |
0x0100 | Whitespace before token |
Example¶
libxs_token_stream_t stream;
libxs_lexicon_t *lexicon;
libxs_lexrule_t rules[96];
libxs_lexnorm_t norms[1] = { { "arrived", "arrive" } };
int nrules;
size_t i;
libxs_token_stream_init(&stream);
lexicon = libxs_lexicon_create();
nrules = libxs_lexrule_defaults(rules, 96);
libxs_token_stream_encode(lexicon, &stream,
(const unsigned char*)"Dr. Smith arrived.", 18,
rules, nrules, norms, 1, 1);
/* iterate normalized lexical tokens */
for (i = 0; i < stream.size; ++i) {
int len = 0;
const char *txt = libxs_lexicon_text(lexicon, stream.data[i].id,
&len, NULL);
/* "arrived" is stored as "arrive" because norms was supplied. */
}
libxs_token_stream_release(&stream);
libxs_lexicon_destroy(lexicon);