2 * Copyright (C) 2012, 2013
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is furnished to do
11 * so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * This is a very clever method for correcting mistakes in QuakeC code
28 * most notably when invalid identifiers are used or inproper assignments;
29 * we can proprly lookup in multiple dictonaries (depening on the rules
30 * of what the task is trying to acomplish) to find the best possible
34 * A little about how it works, and probability theory:
36 * When given an identifier (which we will denote I), we're essentially
37 * just trying to choose the most likely correction for that identifier.
38 * (the actual "correction" can very well be the identifier itself).
39 * There is actually no way to know for sure that certian identifers
40 * such as "lates", need to be corrected to "late" or "latest" or any
41 * other permutations that look lexically the same. This is why we
42 * must advocate the usage of probabilities. This means that instead of
43 * just guessing, instead we're trying to find the correction for C,
44 * out of all possible corrections that maximizes the probability of C
45 * for the original identifer I.
47 * Thankfully there exists some theroies for probalistic interpretations
48 * of data. Since we're operating on two distictive intepretations, the
49 * transposition from I to C. We need something that can express how much
50 * degree of I should rationally change to become C. this is called the
51 * Bayesian interpretation. You can read more about it from here:
52 * http://www.celiagreen.com/charlesmccreery/statistics/bayestutorial.pdf
53 * (which is probably the only good online documentation for bayes theroy
54 * no lie. Everything else just sucks ..)
56 * Bayes' Thereom suggests something like the following:
57 * AC P(I|C) P(C) / P(I)
59 * However since P(I) is the same for every possibility of I, we can
60 * completley ignore it giving just:
63 * This greatly helps visualize how the parts of the expression are performed
64 * there is essentially three, from right to left we perform the following:
66 * 1: P(C), the probability that a proposed correction C will stand on its
67 * own. This is called the language model.
69 * 2: P(I|C), the probability that I would be used, when the programmer
70 * really meant C. This is the error model.
72 * 3: AC, the control mechanisim, an enumerator if you will, one that
73 * enumerates all feasible values of C, to determine the one that
74 * gives the greatest probability score.
76 * In reality the requirement for a more complex expression involving
77 * two seperate models is considerably a waste. But one must recognize
78 * that P(C|I) is already conflating two factors. It's just much simpler
79 * to seperate the two models and deal with them explicitaly. To properly
80 * estimate P(C|I) you have to consider both the probability of C and
81 * probability of the transposition from C to I. It's simply much more
82 * cleaner, and direct to seperate the two factors.
84 * Research tells us that 80% to 95% of all spelling errors have an edit
85 * distance no greater than one. Knowing this we can optimize for most
86 * cases of mistakes without taking a performance hit. Which is what we
87 * base longer edit distances off of. Opposed to the original method of
88 * I had concieved of checking everything.
90 * A little information on additional algorithms used:
92 * Initially when I implemented this corrector, it was very slow.
93 * Need I remind you this is essentially a brute force attack on strings,
94 * and since every transformation requires dynamic memory allocations,
95 * you can easily imagine where most of the runtime conflated. Yes
96 * It went right to malloc. More than THREE MILLION malloc calls are
97 * performed for an identifier about 16 bytes long. This was such a
98 * shock to me. A forward allocator (or as some call it a bump-point
99 * allocator, or just a memory pool) was implemented. To combat this.
101 * But of course even other factors were making it slow. Initially
102 * this used a hashtable. And hashtables have a good constant lookup
103 * time complexity. But the problem wasn't in the hashtable, it was
104 * in the hashing (despite having one of the fastest hash functions
105 * known). Remember those 3 million mallocs? Well for every malloc
106 * there is also a hash. After 3 million hashes .. you start to get
107 * very slow. To combat this I had suggested burst tries to Blub.
108 * The next day he had implemented them. Sure enough this brought
109 * down the runtime by a factory > 100%
111 * Future Work (If we really need it)
113 * Currently we can only distinguishes one source of error in the
114 * language model we use. This could become an issue for identifiers
115 * that have close colliding rates, e.g colate->coat yields collate.
117 * Currently the error model has been fairly trivial, the smaller the
118 * edit distance the smaller the error. This usually causes some un-
119 * expected problems. e.g reciet->recite yields recipt. For QuakeC
120 * this could become a problem when lots of identifiers are involved.
122 * Our control mechanisim could use a limit, i.e limit the number of
123 * sets of edits for distance X. This would also increase execution
124 * speed considerably.
128 #define CORRECT_POOL_SIZE (128*1024*1024)
130 * A forward allcator for the corrector. This corrector requires a lot
131 * of allocations. This forward allocator combats all those allocations
132 * and speeds us up a little. It also saves us space in a way since each
133 * allocation isn't wasting a little header space for when NOTRACK isn't
136 static unsigned char **correct_pool_data = NULL;
137 static unsigned char *correct_pool_this = NULL;
138 static size_t correct_pool_addr = 0;
140 static GMQCC_INLINE void correct_pool_new(void) {
141 correct_pool_addr = 0;
142 correct_pool_this = (unsigned char *)mem_a(CORRECT_POOL_SIZE);
144 vec_push(correct_pool_data, correct_pool_this);
147 static GMQCC_INLINE void *correct_pool_alloc(size_t bytes) {
149 if (correct_pool_addr + bytes>= CORRECT_POOL_SIZE)
152 data = (void*)correct_pool_this;
153 correct_pool_this += bytes;
154 correct_pool_addr += bytes;
158 static GMQCC_INLINE void correct_pool_delete(void) {
160 for (i = 0; i < vec_size(correct_pool_data); ++i)
161 mem_d(correct_pool_data[i]);
163 correct_pool_data = NULL;
164 correct_pool_this = NULL;
165 correct_pool_addr = 0;
169 static GMQCC_INLINE char *correct_pool_claim(const char *data) {
170 char *claim = util_strdup(data);
171 correct_pool_delete();
176 * _ is valid in identifiers. I've yet to implement numerics however
177 * because they're only valid after the first character is of a _, or
180 static const char correct_alpha[] = "abcdefghijklmnopqrstuvwxyz"
181 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
182 "_"; /* TODO: Numbers ... */
184 static const size_t correct_alpha_index[0x80] = {
185 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
186 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
187 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
188 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
189 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
190 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 52,
191 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
192 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0
196 * A fast space efficent trie for a dictionary of identifiers. This is
197 * faster than a hashtable for one reason. A hashtable itself may have
198 * fast constant lookup time, but the hash itself must be very fast. We
199 * have one of the fastest hash functions for strings, but if you do a
200 * lost of hashing (which we do, almost 3 million hashes per identifier)
201 * a hashtable becomes slow.
203 correct_trie_t* correct_trie_new() {
204 correct_trie_t *t = (correct_trie_t*)mem_a(sizeof(correct_trie_t));
210 void correct_trie_del_sub(correct_trie_t *t) {
214 for (i = 0; i < sizeof(correct_alpha)-1; ++i) {
215 correct_trie_del_sub(&t->entries[i]);
220 void correct_trie_del(correct_trie_t *t) {
223 for (i = 0; i < sizeof(correct_alpha)-1; ++i)
224 correct_trie_del_sub(&t->entries[i]);
230 void* correct_trie_get(const correct_trie_t *t, const char *key) {
231 const unsigned char *data = (const unsigned char*)key;
236 t = t->entries + correct_alpha_index[*data];
242 void correct_trie_set(correct_trie_t *t, const char *key, void * const value) {
243 const unsigned char *data = (const unsigned char*)key;
246 t->entries = (correct_trie_t*)mem_a(sizeof(correct_trie_t)*(sizeof(correct_alpha)-1));
247 memset(t->entries, 0, sizeof(correct_trie_t)*(sizeof(correct_alpha)-1));
249 t = t->entries + correct_alpha_index[*data];
257 * Implementation of the corrector algorithm commences. A very efficent
258 * brute-force attack (thanks to tries and mempool :-)).
260 static GMQCC_INLINE size_t *correct_find(correct_trie_t *table, const char *word) {
261 return (size_t*)correct_trie_get(table, word);
264 static GMQCC_INLINE bool correct_update(correct_trie_t* *table, const char *word) {
265 size_t *data = correct_find(*table, word);
273 void correct_add(correct_trie_t* table, size_t ***size, const char *ident) {
275 const char *add = ident;
277 if (!correct_update(&table, add)) {
278 data = (size_t*)mem_a(sizeof(size_t));
281 vec_push((*size), data);
282 correct_trie_set(table, add, data);
286 void correct_del(correct_trie_t* dictonary, size_t **data) {
288 const size_t vs = vec_size(data);
290 for (i = 0; i < vs; i++)
294 correct_trie_del(dictonary);
298 * correcting logic for the following forms of transformations:
304 * These functions could take an additional size_t **size paramater
305 * and store back the results of their new length in an array that
306 * is the same as **array for the memcmp in correct_exists. I'm just
307 * not able to figure out how to do that just yet. As my brain is
308 * not in the mood to figure out that logic. This is a reminder to
309 * do it, or for someone else to :-) correct_edit however would also
310 * need to take a size_t ** to carry it along (would all the argument
311 * overhead be worth it?)
313 static size_t correct_deletion(const char *ident, char **array) {
315 const size_t len = strlen(ident);
317 for (; itr < len; itr++) {
318 char *a = (char*)correct_pool_alloc(len+1);
319 memcpy(a, ident, itr);
320 memcpy(a + itr, ident + itr + 1, len - itr);
327 static size_t correct_transposition(const char *ident, char **array) {
329 const size_t len = strlen(ident);
331 for (; itr < len - 1; itr++) {
333 char *a = (char*)correct_pool_alloc(len+1);
334 memcpy(a, ident, len+1);
344 static size_t correct_alteration(const char *ident, char **array) {
348 const size_t len = strlen(ident);
350 for (; itr < len; itr++) {
351 for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++, ktr++) {
352 char *a = (char*)correct_pool_alloc(len+1);
353 memcpy(a, ident, len+1);
354 a[itr] = correct_alpha[jtr];
362 static size_t correct_insertion(const char *ident, char **array) {
365 const size_t len = strlen(ident);
367 for (; itr <= len; itr++) {
368 for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++) {
369 char *a = (char*)correct_pool_alloc(len+2);
370 memcpy(a, ident, itr);
371 memcpy(a + itr + 1, ident + itr, len - itr + 1);
372 a[itr] = correct_alpha[jtr];
373 array[itr * (sizeof(correct_alpha)-1) + jtr] = a;
377 return (len+1)*(sizeof(correct_alpha)-1);
380 static GMQCC_INLINE size_t correct_size(const char *ident) {
383 * transposition = len - 1
384 * alteration = len * sizeof(correct_alpha)
385 * insertion = (len + 1) * sizeof(correct_alpha)
388 register size_t len = strlen(ident);
389 return (len) + (len - 1) + (len * (sizeof(correct_alpha)-1)) + ((len + 1) * (sizeof(correct_alpha)-1));
392 static char **correct_edit(const char *ident) {
394 char **find = (char**)correct_pool_alloc(correct_size(ident) * sizeof(char*));
399 next = correct_deletion (ident, find);
400 next += correct_transposition(ident, find+next);
401 next += correct_alteration (ident, find+next);
402 /*****/ correct_insertion (ident, find+next);
408 * We could use a hashtable but the space complexity isn't worth it
409 * since we're only going to determine the "did you mean?" identifier
412 static int correct_exist(char **array, size_t rows, char *ident) {
415 * As an experiment I tried the following assembly for memcmp here:
418 * incl %eax ; eax = LHS
419 * incl %edx ; edx = LRS
420 * cmpl %eax, %ebx ; ebx = &LHS[END_POS]
423 * movb (%edx), %cl ; micro-optimized even on atoms :-)
424 * cmpb %cl, (%eax) ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
426 * jge correct_cmp_loop
429 * Despite how much optimization went in to this, the speed was the
430 * being conflicted by the strlen(ident) used for &LHS[END_POS]
431 * If we could eliminate the strlen with what I suggested on line
432 * 311 ... we can accelerate this whole damn thing quite a bit.
434 * However there is still something we can do here that does give
435 * us a little more speed. Although one more branch, we know for
436 * sure there is at least one byte to compare, if that one byte
437 * simply isn't the same we can skip the full check. Which means
438 * we skip a whole strlen call.
440 for (itr = 0; itr < rows; itr++) {
441 if (!memcmp(array[itr], ident, strlen(ident)))
448 static GMQCC_INLINE char **correct_known_resize(char **res, size_t *allocated, size_t size) {
449 size_t oldallocated = *allocated;
451 if (size < oldallocated)
454 out = correct_pool_alloc(sizeof(*res) * oldallocated + 32);
455 memcpy(out, res, sizeof(*res) * oldallocated);
461 static char **correct_known(correct_trie_t* table, char **array, size_t rows, size_t *next) {
467 char **res = correct_pool_alloc(sizeof(char *) * nxt);
470 for (; itr < rows; itr++) {
471 end = correct_edit(array[itr]);
472 row = correct_size(array[itr]);
474 /* removing jtr=0 here speeds it up by 100ms O_o */
475 for (jtr = 0; jtr < row; jtr++) {
476 if (correct_find(table, end[jtr]) && !correct_exist(res, len, end[jtr])) {
477 res = correct_known_resize(res, &nxt, len+1);
478 res[len++] = end[jtr];
487 static char *correct_maximum(correct_trie_t* table, char **array, size_t rows) {
493 for (; itr < rows; itr++) {
494 if ((itm = correct_find(table, array[itr])) && (*itm > top)) {
504 * This is the exposed interface:
505 * takes a table for the dictonary a vector of sizes (used for internal
506 * probability calculation), and an identifier to "correct".
508 char *correct_str(correct_trie_t* table, const char *ident) {
511 char *e1ident = NULL;
512 char *e2ident = NULL;
518 /* needs to be allocated for free later */
519 if (correct_find(table, ident))
520 return correct_pool_claim(ident);
522 if ((e1rows = correct_size(ident))) {
523 e1 = correct_edit(ident);
525 if ((e1ident = correct_maximum(table, e1, e1rows)))
526 return correct_pool_claim(e1ident);
529 e2 = correct_known(table, e1, e1rows, &e2rows);
530 if (e2rows && ((e2ident = correct_maximum(table, e2, e2rows))))
531 return correct_pool_claim(e2ident);
534 correct_pool_delete();
535 return util_strdup(ident);