├── .gitignore ├── .vim └── coc-settings.json ├── Makefile ├── Readme.md ├── c └── fuzzy.c ├── compile_flags.txt ├── index.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.wat 3 | *.bc 4 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SDK=/usr/local/share/wasi-sdk-16.0 2 | SYSROOT=$(SDK)/share/wasi-sysroot 3 | 4 | CC=$(SDK)/bin/clang 5 | LD=$(SDK)/bin/wasm-ld 6 | LDFLAGS=-s -S -O3 --export=malloc --export=free --no-entry --allow-undefined -L$(SYSROOT)/lib/wasm32-wasi -lc -lm 7 | 8 | WASM2WAT=/usr/local/share/wabt-1.0.29/bin/wasm2wat 9 | 10 | sources = c/fuzzy.c 11 | objects = $(sources:%.c=%.bc) 12 | target = fuzzy 13 | 14 | .PHONY: all 15 | 16 | all: $(target).wat 17 | 18 | $(target).wat: $(target).wasm 19 | $(WASM2WAT) $(target).wasm -o $(target).wat 20 | 21 | $(target).wasm: $(objects) 22 | $(LD) $(objects) $(LDFLAGS) -o $(target).wasm 23 | 24 | c/%.bc: c/%.c 25 | $(CC) -c -nostdlib -emit-llvm -Oz $< -o $@ 26 | 27 | clean: 28 | rm -f $(target).wasm 29 | rm -f $(target).wat 30 | rm -f $(objects) 31 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Wasm-fuzzy 2 | 3 | Wasm module for fuzzy match with native performance. 4 | 5 | C code mostly form source code of vim in `src/search.c`. 6 | 7 | ## Build 8 | 9 | Requires [wasi](https://github.com/WebAssembly/wasi-sdk) and optional [wabt](https://github.com/WebAssembly/wabt). 10 | 11 | Binary file also provided from [release page](https://github.com/neoclide/wasm-fuzzy/releases) 12 | 13 | Change the SDK part in Makefile. 14 | 15 | Run command: 16 | 17 | ```sh 18 | make 19 | node index.js 20 | ``` 21 | 22 | Result should looks like: 23 | 24 | ``` 25 | Match fb => fooBar 26 | Matched score & positions 135 Uint32Array(2) [ 0, 3 ] 27 | Match fb => fobbbdefo/Bar 28 | Matched score & positions 114 Uint32Array(2) [ 0, 10 ] 29 | Match fb => foot, No match 30 | Done 31 | ``` 32 | 33 | ## LICENSE 34 | 35 | MIT 36 | -------------------------------------------------------------------------------- /c/fuzzy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define NUL '\000' 6 | #define MAX_FUZZY_MATCHES 256 7 | #define FUZZY_MATCH_RECURSION_LIMIT 10 8 | #define HT_INIT_SIZE 16 9 | #define NUMBUFLEN 65 10 | #define MAX_FUZZY_MATCHES 256 11 | 12 | #ifndef TRUE 13 | #define FALSE 0 // note: this is an int, not a long! 14 | #define TRUE 1 15 | #endif 16 | 17 | #ifndef mch_memmove 18 | #define mch_memmove(to, from, len) \ 19 | memmove((char *)(to), (char *)(from), (size_t)(len)) 20 | #endif 21 | 22 | #define VIM_ISWHITE(x) ((x) == ' ' || (x) == '\t') 23 | #define TOUPPER_ASC(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - ('a' - 'A')) 24 | #define TOLOWER_ASC(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + ('a' - 'A')) 25 | #define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0])) 26 | #define STRLEN(s) strlen((char *)(s)) 27 | 28 | // bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that 29 | // matching a whole word is preferred. 30 | #define SEQUENTIAL_BONUS 40 31 | // bonus if match occurs after a path separator 32 | #define PATH_SEPARATOR_BONUS 30 33 | // bonus if match occurs after a word separator 34 | #define WORD_SEPARATOR_BONUS 25 35 | // bonus if match is uppercase and prev is lower 36 | #define CAMEL_BONUS 30 37 | // bonus if the first letter is matched 38 | #define FIRST_LETTER_BONUS 15 39 | // penalty applied for every letter in str before the first match 40 | #define LEADING_LETTER_PENALTY (-5) 41 | // maximum penalty for leading letters 42 | #define MAX_LEADING_LETTER_PENALTY (-15) 43 | // penalty for every letter that doesn't match 44 | #define UNMATCHED_LETTER_PENALTY (-1) 45 | // penalty for gap in matching positions (-2 * k) 46 | #define GAP_PENALTY (-2) 47 | // Score for a string that doesn't fuzzy match the pattern 48 | #define SCORE_NONE (-9999) 49 | #define FUZZY_MATCH_RECURSION_LIMIT 10 50 | 51 | typedef unsigned int int_u; 52 | typedef long long varnumber_T; 53 | typedef unsigned char char_u; 54 | typedef double float_T; 55 | // On rare systems "char" is unsigned, sometimes we really want a signed 8-bit 56 | // value. 57 | typedef signed char int8_T; 58 | typedef unsigned long long_u; 59 | 60 | /* 61 | * The following tables are built by ../runtime/tools/unicode.vim. 62 | * They must be in numeric order, because we use binary search. 63 | * An entry such as {0x41,0x5a,1,32} means that Unicode characters in the 64 | * range from 0x41 to 0x5a inclusive, stepping by 1, are changed to 65 | * folded/upper/lower by adding 32. 66 | */ 67 | typedef struct { 68 | int rangeStart; 69 | int rangeEnd; 70 | int step; 71 | int offset; 72 | } convertStruct; 73 | 74 | static convertStruct toLower[] = { 75 | {0x41, 0x5a, 1, 32}, {0xc0, 0xd6, 1, 32}, 76 | {0xd8, 0xde, 1, 32}, {0x100, 0x12e, 2, 1}, 77 | {0x130, 0x130, -1, -199}, {0x132, 0x136, 2, 1}, 78 | {0x139, 0x147, 2, 1}, {0x14a, 0x176, 2, 1}, 79 | {0x178, 0x178, -1, -121}, {0x179, 0x17d, 2, 1}, 80 | {0x181, 0x181, -1, 210}, {0x182, 0x184, 2, 1}, 81 | {0x186, 0x186, -1, 206}, {0x187, 0x187, -1, 1}, 82 | {0x189, 0x18a, 1, 205}, {0x18b, 0x18b, -1, 1}, 83 | {0x18e, 0x18e, -1, 79}, {0x18f, 0x18f, -1, 202}, 84 | {0x190, 0x190, -1, 203}, {0x191, 0x191, -1, 1}, 85 | {0x193, 0x193, -1, 205}, {0x194, 0x194, -1, 207}, 86 | {0x196, 0x196, -1, 211}, {0x197, 0x197, -1, 209}, 87 | {0x198, 0x198, -1, 1}, {0x19c, 0x19c, -1, 211}, 88 | {0x19d, 0x19d, -1, 213}, {0x19f, 0x19f, -1, 214}, 89 | {0x1a0, 0x1a4, 2, 1}, {0x1a6, 0x1a6, -1, 218}, 90 | {0x1a7, 0x1a7, -1, 1}, {0x1a9, 0x1a9, -1, 218}, 91 | {0x1ac, 0x1ac, -1, 1}, {0x1ae, 0x1ae, -1, 218}, 92 | {0x1af, 0x1af, -1, 1}, {0x1b1, 0x1b2, 1, 217}, 93 | {0x1b3, 0x1b5, 2, 1}, {0x1b7, 0x1b7, -1, 219}, 94 | {0x1b8, 0x1bc, 4, 1}, {0x1c4, 0x1c4, -1, 2}, 95 | {0x1c5, 0x1c5, -1, 1}, {0x1c7, 0x1c7, -1, 2}, 96 | {0x1c8, 0x1c8, -1, 1}, {0x1ca, 0x1ca, -1, 2}, 97 | {0x1cb, 0x1db, 2, 1}, {0x1de, 0x1ee, 2, 1}, 98 | {0x1f1, 0x1f1, -1, 2}, {0x1f2, 0x1f4, 2, 1}, 99 | {0x1f6, 0x1f6, -1, -97}, {0x1f7, 0x1f7, -1, -56}, 100 | {0x1f8, 0x21e, 2, 1}, {0x220, 0x220, -1, -130}, 101 | {0x222, 0x232, 2, 1}, {0x23a, 0x23a, -1, 10795}, 102 | {0x23b, 0x23b, -1, 1}, {0x23d, 0x23d, -1, -163}, 103 | {0x23e, 0x23e, -1, 10792}, {0x241, 0x241, -1, 1}, 104 | {0x243, 0x243, -1, -195}, {0x244, 0x244, -1, 69}, 105 | {0x245, 0x245, -1, 71}, {0x246, 0x24e, 2, 1}, 106 | {0x370, 0x372, 2, 1}, {0x376, 0x376, -1, 1}, 107 | {0x37f, 0x37f, -1, 116}, {0x386, 0x386, -1, 38}, 108 | {0x388, 0x38a, 1, 37}, {0x38c, 0x38c, -1, 64}, 109 | {0x38e, 0x38f, 1, 63}, {0x391, 0x3a1, 1, 32}, 110 | {0x3a3, 0x3ab, 1, 32}, {0x3cf, 0x3cf, -1, 8}, 111 | {0x3d8, 0x3ee, 2, 1}, {0x3f4, 0x3f4, -1, -60}, 112 | {0x3f7, 0x3f7, -1, 1}, {0x3f9, 0x3f9, -1, -7}, 113 | {0x3fa, 0x3fa, -1, 1}, {0x3fd, 0x3ff, 1, -130}, 114 | {0x400, 0x40f, 1, 80}, {0x410, 0x42f, 1, 32}, 115 | {0x460, 0x480, 2, 1}, {0x48a, 0x4be, 2, 1}, 116 | {0x4c0, 0x4c0, -1, 15}, {0x4c1, 0x4cd, 2, 1}, 117 | {0x4d0, 0x52e, 2, 1}, {0x531, 0x556, 1, 48}, 118 | {0x10a0, 0x10c5, 1, 7264}, {0x10c7, 0x10cd, 6, 7264}, 119 | {0x13a0, 0x13ef, 1, 38864}, {0x13f0, 0x13f5, 1, 8}, 120 | {0x1c90, 0x1cba, 1, -3008}, {0x1cbd, 0x1cbf, 1, -3008}, 121 | {0x1e00, 0x1e94, 2, 1}, {0x1e9e, 0x1e9e, -1, -7615}, 122 | {0x1ea0, 0x1efe, 2, 1}, {0x1f08, 0x1f0f, 1, -8}, 123 | {0x1f18, 0x1f1d, 1, -8}, {0x1f28, 0x1f2f, 1, -8}, 124 | {0x1f38, 0x1f3f, 1, -8}, {0x1f48, 0x1f4d, 1, -8}, 125 | {0x1f59, 0x1f5f, 2, -8}, {0x1f68, 0x1f6f, 1, -8}, 126 | {0x1f88, 0x1f8f, 1, -8}, {0x1f98, 0x1f9f, 1, -8}, 127 | {0x1fa8, 0x1faf, 1, -8}, {0x1fb8, 0x1fb9, 1, -8}, 128 | {0x1fba, 0x1fbb, 1, -74}, {0x1fbc, 0x1fbc, -1, -9}, 129 | {0x1fc8, 0x1fcb, 1, -86}, {0x1fcc, 0x1fcc, -1, -9}, 130 | {0x1fd8, 0x1fd9, 1, -8}, {0x1fda, 0x1fdb, 1, -100}, 131 | {0x1fe8, 0x1fe9, 1, -8}, {0x1fea, 0x1feb, 1, -112}, 132 | {0x1fec, 0x1fec, -1, -7}, {0x1ff8, 0x1ff9, 1, -128}, 133 | {0x1ffa, 0x1ffb, 1, -126}, {0x1ffc, 0x1ffc, -1, -9}, 134 | {0x2126, 0x2126, -1, -7517}, {0x212a, 0x212a, -1, -8383}, 135 | {0x212b, 0x212b, -1, -8262}, {0x2132, 0x2132, -1, 28}, 136 | {0x2160, 0x216f, 1, 16}, {0x2183, 0x2183, -1, 1}, 137 | {0x24b6, 0x24cf, 1, 26}, {0x2c00, 0x2c2e, 1, 48}, 138 | {0x2c60, 0x2c60, -1, 1}, {0x2c62, 0x2c62, -1, -10743}, 139 | {0x2c63, 0x2c63, -1, -3814}, {0x2c64, 0x2c64, -1, -10727}, 140 | {0x2c67, 0x2c6b, 2, 1}, {0x2c6d, 0x2c6d, -1, -10780}, 141 | {0x2c6e, 0x2c6e, -1, -10749}, {0x2c6f, 0x2c6f, -1, -10783}, 142 | {0x2c70, 0x2c70, -1, -10782}, {0x2c72, 0x2c75, 3, 1}, 143 | {0x2c7e, 0x2c7f, 1, -10815}, {0x2c80, 0x2ce2, 2, 1}, 144 | {0x2ceb, 0x2ced, 2, 1}, {0x2cf2, 0xa640, 31054, 1}, 145 | {0xa642, 0xa66c, 2, 1}, {0xa680, 0xa69a, 2, 1}, 146 | {0xa722, 0xa72e, 2, 1}, {0xa732, 0xa76e, 2, 1}, 147 | {0xa779, 0xa77b, 2, 1}, {0xa77d, 0xa77d, -1, -35332}, 148 | {0xa77e, 0xa786, 2, 1}, {0xa78b, 0xa78b, -1, 1}, 149 | {0xa78d, 0xa78d, -1, -42280}, {0xa790, 0xa792, 2, 1}, 150 | {0xa796, 0xa7a8, 2, 1}, {0xa7aa, 0xa7aa, -1, -42308}, 151 | {0xa7ab, 0xa7ab, -1, -42319}, {0xa7ac, 0xa7ac, -1, -42315}, 152 | {0xa7ad, 0xa7ad, -1, -42305}, {0xa7ae, 0xa7ae, -1, -42308}, 153 | {0xa7b0, 0xa7b0, -1, -42258}, {0xa7b1, 0xa7b1, -1, -42282}, 154 | {0xa7b2, 0xa7b2, -1, -42261}, {0xa7b3, 0xa7b3, -1, 928}, 155 | {0xa7b4, 0xa7be, 2, 1}, {0xa7c2, 0xa7c2, -1, 1}, 156 | {0xa7c4, 0xa7c4, -1, -48}, {0xa7c5, 0xa7c5, -1, -42307}, 157 | {0xa7c6, 0xa7c6, -1, -35384}, {0xa7c7, 0xa7c9, 2, 1}, 158 | {0xa7f5, 0xa7f5, -1, 1}, {0xff21, 0xff3a, 1, 32}, 159 | {0x10400, 0x10427, 1, 40}, {0x104b0, 0x104d3, 1, 40}, 160 | {0x10c80, 0x10cb2, 1, 64}, {0x118a0, 0x118bf, 1, 32}, 161 | {0x16e40, 0x16e5f, 1, 32}, {0x1e900, 0x1e921, 1, 34}}; 162 | 163 | static convertStruct toUpper[] = { 164 | {0x61, 0x7a, 1, -32}, {0xb5, 0xb5, -1, 743}, 165 | {0xe0, 0xf6, 1, -32}, {0xf8, 0xfe, 1, -32}, 166 | {0xff, 0xff, -1, 121}, {0x101, 0x12f, 2, -1}, 167 | {0x131, 0x131, -1, -232}, {0x133, 0x137, 2, -1}, 168 | {0x13a, 0x148, 2, -1}, {0x14b, 0x177, 2, -1}, 169 | {0x17a, 0x17e, 2, -1}, {0x17f, 0x17f, -1, -300}, 170 | {0x180, 0x180, -1, 195}, {0x183, 0x185, 2, -1}, 171 | {0x188, 0x18c, 4, -1}, {0x192, 0x192, -1, -1}, 172 | {0x195, 0x195, -1, 97}, {0x199, 0x199, -1, -1}, 173 | {0x19a, 0x19a, -1, 163}, {0x19e, 0x19e, -1, 130}, 174 | {0x1a1, 0x1a5, 2, -1}, {0x1a8, 0x1ad, 5, -1}, 175 | {0x1b0, 0x1b4, 4, -1}, {0x1b6, 0x1b9, 3, -1}, 176 | {0x1bd, 0x1bd, -1, -1}, {0x1bf, 0x1bf, -1, 56}, 177 | {0x1c5, 0x1c5, -1, -1}, {0x1c6, 0x1c6, -1, -2}, 178 | {0x1c8, 0x1c8, -1, -1}, {0x1c9, 0x1c9, -1, -2}, 179 | {0x1cb, 0x1cb, -1, -1}, {0x1cc, 0x1cc, -1, -2}, 180 | {0x1ce, 0x1dc, 2, -1}, {0x1dd, 0x1dd, -1, -79}, 181 | {0x1df, 0x1ef, 2, -1}, {0x1f2, 0x1f2, -1, -1}, 182 | {0x1f3, 0x1f3, -1, -2}, {0x1f5, 0x1f9, 4, -1}, 183 | {0x1fb, 0x21f, 2, -1}, {0x223, 0x233, 2, -1}, 184 | {0x23c, 0x23c, -1, -1}, {0x23f, 0x240, 1, 10815}, 185 | {0x242, 0x247, 5, -1}, {0x249, 0x24f, 2, -1}, 186 | {0x250, 0x250, -1, 10783}, {0x251, 0x251, -1, 10780}, 187 | {0x252, 0x252, -1, 10782}, {0x253, 0x253, -1, -210}, 188 | {0x254, 0x254, -1, -206}, {0x256, 0x257, 1, -205}, 189 | {0x259, 0x259, -1, -202}, {0x25b, 0x25b, -1, -203}, 190 | {0x25c, 0x25c, -1, 42319}, {0x260, 0x260, -1, -205}, 191 | {0x261, 0x261, -1, 42315}, {0x263, 0x263, -1, -207}, 192 | {0x265, 0x265, -1, 42280}, {0x266, 0x266, -1, 42308}, 193 | {0x268, 0x268, -1, -209}, {0x269, 0x269, -1, -211}, 194 | {0x26a, 0x26a, -1, 42308}, {0x26b, 0x26b, -1, 10743}, 195 | {0x26c, 0x26c, -1, 42305}, {0x26f, 0x26f, -1, -211}, 196 | {0x271, 0x271, -1, 10749}, {0x272, 0x272, -1, -213}, 197 | {0x275, 0x275, -1, -214}, {0x27d, 0x27d, -1, 10727}, 198 | {0x280, 0x280, -1, -218}, {0x282, 0x282, -1, 42307}, 199 | {0x283, 0x283, -1, -218}, {0x287, 0x287, -1, 42282}, 200 | {0x288, 0x288, -1, -218}, {0x289, 0x289, -1, -69}, 201 | {0x28a, 0x28b, 1, -217}, {0x28c, 0x28c, -1, -71}, 202 | {0x292, 0x292, -1, -219}, {0x29d, 0x29d, -1, 42261}, 203 | {0x29e, 0x29e, -1, 42258}, {0x345, 0x345, -1, 84}, 204 | {0x371, 0x373, 2, -1}, {0x377, 0x377, -1, -1}, 205 | {0x37b, 0x37d, 1, 130}, {0x3ac, 0x3ac, -1, -38}, 206 | {0x3ad, 0x3af, 1, -37}, {0x3b1, 0x3c1, 1, -32}, 207 | {0x3c2, 0x3c2, -1, -31}, {0x3c3, 0x3cb, 1, -32}, 208 | {0x3cc, 0x3cc, -1, -64}, {0x3cd, 0x3ce, 1, -63}, 209 | {0x3d0, 0x3d0, -1, -62}, {0x3d1, 0x3d1, -1, -57}, 210 | {0x3d5, 0x3d5, -1, -47}, {0x3d6, 0x3d6, -1, -54}, 211 | {0x3d7, 0x3d7, -1, -8}, {0x3d9, 0x3ef, 2, -1}, 212 | {0x3f0, 0x3f0, -1, -86}, {0x3f1, 0x3f1, -1, -80}, 213 | {0x3f2, 0x3f2, -1, 7}, {0x3f3, 0x3f3, -1, -116}, 214 | {0x3f5, 0x3f5, -1, -96}, {0x3f8, 0x3fb, 3, -1}, 215 | {0x430, 0x44f, 1, -32}, {0x450, 0x45f, 1, -80}, 216 | {0x461, 0x481, 2, -1}, {0x48b, 0x4bf, 2, -1}, 217 | {0x4c2, 0x4ce, 2, -1}, {0x4cf, 0x4cf, -1, -15}, 218 | {0x4d1, 0x52f, 2, -1}, {0x561, 0x586, 1, -48}, 219 | {0x10d0, 0x10fa, 1, 3008}, {0x10fd, 0x10ff, 1, 3008}, 220 | {0x13f8, 0x13fd, 1, -8}, {0x1c80, 0x1c80, -1, -6254}, 221 | {0x1c81, 0x1c81, -1, -6253}, {0x1c82, 0x1c82, -1, -6244}, 222 | {0x1c83, 0x1c84, 1, -6242}, {0x1c85, 0x1c85, -1, -6243}, 223 | {0x1c86, 0x1c86, -1, -6236}, {0x1c87, 0x1c87, -1, -6181}, 224 | {0x1c88, 0x1c88, -1, 35266}, {0x1d79, 0x1d79, -1, 35332}, 225 | {0x1d7d, 0x1d7d, -1, 3814}, {0x1d8e, 0x1d8e, -1, 35384}, 226 | {0x1e01, 0x1e95, 2, -1}, {0x1e9b, 0x1e9b, -1, -59}, 227 | {0x1ea1, 0x1eff, 2, -1}, {0x1f00, 0x1f07, 1, 8}, 228 | {0x1f10, 0x1f15, 1, 8}, {0x1f20, 0x1f27, 1, 8}, 229 | {0x1f30, 0x1f37, 1, 8}, {0x1f40, 0x1f45, 1, 8}, 230 | {0x1f51, 0x1f57, 2, 8}, {0x1f60, 0x1f67, 1, 8}, 231 | {0x1f70, 0x1f71, 1, 74}, {0x1f72, 0x1f75, 1, 86}, 232 | {0x1f76, 0x1f77, 1, 100}, {0x1f78, 0x1f79, 1, 128}, 233 | {0x1f7a, 0x1f7b, 1, 112}, {0x1f7c, 0x1f7d, 1, 126}, 234 | {0x1f80, 0x1f87, 1, 8}, {0x1f90, 0x1f97, 1, 8}, 235 | {0x1fa0, 0x1fa7, 1, 8}, {0x1fb0, 0x1fb1, 1, 8}, 236 | {0x1fb3, 0x1fb3, -1, 9}, {0x1fbe, 0x1fbe, -1, -7205}, 237 | {0x1fc3, 0x1fc3, -1, 9}, {0x1fd0, 0x1fd1, 1, 8}, 238 | {0x1fe0, 0x1fe1, 1, 8}, {0x1fe5, 0x1fe5, -1, 7}, 239 | {0x1ff3, 0x1ff3, -1, 9}, {0x214e, 0x214e, -1, -28}, 240 | {0x2170, 0x217f, 1, -16}, {0x2184, 0x2184, -1, -1}, 241 | {0x24d0, 0x24e9, 1, -26}, {0x2c30, 0x2c5e, 1, -48}, 242 | {0x2c61, 0x2c61, -1, -1}, {0x2c65, 0x2c65, -1, -10795}, 243 | {0x2c66, 0x2c66, -1, -10792}, {0x2c68, 0x2c6c, 2, -1}, 244 | {0x2c73, 0x2c76, 3, -1}, {0x2c81, 0x2ce3, 2, -1}, 245 | {0x2cec, 0x2cee, 2, -1}, {0x2cf3, 0x2cf3, -1, -1}, 246 | {0x2d00, 0x2d25, 1, -7264}, {0x2d27, 0x2d2d, 6, -7264}, 247 | {0xa641, 0xa66d, 2, -1}, {0xa681, 0xa69b, 2, -1}, 248 | {0xa723, 0xa72f, 2, -1}, {0xa733, 0xa76f, 2, -1}, 249 | {0xa77a, 0xa77c, 2, -1}, {0xa77f, 0xa787, 2, -1}, 250 | {0xa78c, 0xa791, 5, -1}, {0xa793, 0xa793, -1, -1}, 251 | {0xa794, 0xa794, -1, 48}, {0xa797, 0xa7a9, 2, -1}, 252 | {0xa7b5, 0xa7bf, 2, -1}, {0xa7c3, 0xa7c8, 5, -1}, 253 | {0xa7ca, 0xa7f6, 44, -1}, {0xab53, 0xab53, -1, -928}, 254 | {0xab70, 0xabbf, 1, -38864}, {0xff41, 0xff5a, 1, -32}, 255 | {0x10428, 0x1044f, 1, -40}, {0x104d8, 0x104fb, 1, -40}, 256 | {0x10cc0, 0x10cf2, 1, -64}, {0x118c0, 0x118df, 1, -32}, 257 | {0x16e60, 0x16e7f, 1, -32}, {0x1e922, 0x1e943, 1, -34}}; 258 | 259 | /* 260 | * Get byte length of character at "*p". Returns zero when "*p" is NUL. 261 | * Used for mb_ptr2len() when 'encoding' latin. 262 | */ 263 | int latin_ptr2len(char_u *p) { return *p == NUL ? 0 : 1; } 264 | 265 | /* 266 | * Lookup table to quickly get the length in bytes of a UTF-8 character from 267 | * the first byte of a UTF-8 string. 268 | * Bytes which are illegal when used as the first byte have a 1. 269 | * The NUL byte has length 1. 270 | */ 271 | static char utf8len_tab[256] = { 272 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 273 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 274 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 275 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 276 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 277 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 278 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 279 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 280 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 281 | 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 282 | 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, 283 | }; 284 | 285 | /* 286 | * Like utf8len_tab above, but using a zero for illegal lead bytes. 287 | */ 288 | static char utf8len_tab_zero[256] = { 289 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 290 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 291 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 292 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 293 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 294 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 295 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 297 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 298 | 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 299 | 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, 300 | }; 301 | 302 | /* 303 | * Get the length of a UTF-8 byte sequence, not including any following 304 | * composing characters. 305 | * Returns 0 for "". 306 | * Returns 1 for an illegal byte sequence. 307 | */ 308 | int utf_ptr2len(char_u *p) { 309 | int len; 310 | int i; 311 | 312 | if (*p == NUL) 313 | return 0; 314 | len = utf8len_tab[*p]; 315 | for (i = 1; i < len; ++i) 316 | if ((p[i] & 0xc0) != 0x80) 317 | return 1; 318 | return len; 319 | } 320 | 321 | /* 322 | * Convert a UTF-8 byte sequence to a character number. 323 | * If the sequence is illegal or truncated by a NUL the first byte is 324 | * returned. 325 | * For an overlong sequence this may return zero. 326 | * Does not include composing characters, of course. 327 | */ 328 | int utf_ptr2char(char_u *p) { 329 | int len; 330 | 331 | if (p[0] < 0x80) // be quick for ASCII 332 | return p[0]; 333 | 334 | len = utf8len_tab_zero[p[0]]; 335 | if (len > 1 && (p[1] & 0xc0) == 0x80) { 336 | if (len == 2) 337 | return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); 338 | if ((p[2] & 0xc0) == 0x80) { 339 | if (len == 3) 340 | return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + (p[2] & 0x3f); 341 | if ((p[3] & 0xc0) == 0x80) { 342 | if (len == 4) 343 | return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + 344 | ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); 345 | if ((p[4] & 0xc0) == 0x80) { 346 | if (len == 5) 347 | return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + 348 | ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + (p[4] & 0x3f); 349 | if ((p[5] & 0xc0) == 0x80 && len == 6) 350 | return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + 351 | ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + 352 | ((p[4] & 0x3f) << 6) + (p[5] & 0x3f); 353 | } 354 | } 355 | } 356 | } 357 | // Illegal value, just return the first byte 358 | return p[0]; 359 | } 360 | 361 | /* 362 | * Return the character length of "str". Each multi-byte character (with 363 | * following composing characters) counts as one. 364 | */ 365 | int mb_charlen(char_u *str) { 366 | char_u *p = str; 367 | int count; 368 | 369 | if (p == NULL) 370 | return 0; 371 | 372 | for (count = 0; *p != NUL; count++) 373 | p += (*utf_ptr2len)(p); 374 | 375 | return count; 376 | } 377 | 378 | /* 379 | * Generic conversion function for case operations. 380 | * Return the converted equivalent of "a", which is a UCS-4 character. Use 381 | * the given conversion "table". Uses binary search on "table". 382 | */ 383 | static int utf_convert(int a, convertStruct table[], int tableSize) { 384 | int start, mid, end; // indices into table 385 | int entries = tableSize / sizeof(convertStruct); 386 | 387 | start = 0; 388 | end = entries; 389 | while (start < end) { 390 | // need to search further 391 | mid = (end + start) / 2; 392 | if (table[mid].rangeEnd < a) 393 | start = mid + 1; 394 | else 395 | end = mid; 396 | } 397 | if (start < entries && table[start].rangeStart <= a && 398 | a <= table[start].rangeEnd && 399 | (a - table[start].rangeStart) % table[start].step == 0) 400 | return (a + table[start].offset); 401 | else 402 | return a; 403 | } 404 | 405 | /* 406 | * Return the lower-case equivalent of "a", which is a UCS-4 character. Use 407 | * simple case folding. 408 | */ 409 | int utf_tolower(int a) { 410 | // If 'casemap' contains "keepascii" use ASCII style tolower(). 411 | if (a < 128) 412 | return TOLOWER_ASC(a); 413 | 414 | // For any other characters use the above mapping table. 415 | return utf_convert(a, toLower, (int)sizeof(toLower)); 416 | } 417 | 418 | /* 419 | * Return the upper-case equivalent of "a", which is a UCS-4 character. Use 420 | * simple case folding. 421 | */ 422 | int utf_toupper(int a) { 423 | // If 'casemap' contains "keepascii" use ASCII style toupper(). 424 | if (a < 128) 425 | return TOUPPER_ASC(a); 426 | // For any other characters use the above mapping table. 427 | return utf_convert(a, toUpper, (int)sizeof(toUpper)); 428 | } 429 | 430 | int utf_islower(int a) { 431 | // German sharp s is lower case but has no upper case equivalent. 432 | return (utf_toupper(a) != a) || a == 0xdf; 433 | } 434 | 435 | int utf_isupper(int a) { return (utf_tolower(a) != a); } 436 | /* 437 | * Allocate memory and set all bytes to zero. 438 | */ 439 | void *alloc_clear(size_t size) { 440 | void *p; 441 | p = malloc(size); 442 | return p; 443 | } 444 | 445 | /* 446 | * The normal way to allocate memory. This handles an out-of-memory situation 447 | * as well as possible, still returns NULL when we're completely out. 448 | */ 449 | void *alloc(size_t size) { return malloc(size); } 450 | 451 | /* 452 | * Replacement for free() that ignores NULL pointers. 453 | * Also skip free() when exiting for sure, this helps when we caught a deadly 454 | * signal that was caused by a crash in free(). 455 | * If you want to set NULL after calling this function, you should use 456 | * VIM_CLEAR() instead. 457 | */ 458 | void vim_free(void *x) { 459 | if (x != NULL) { 460 | #ifdef MEM_PROFILE 461 | mem_pre_free(&x); 462 | #endif 463 | free(x); 464 | } 465 | } 466 | 467 | /* 468 | * Copy "string" into newly allocated memory. 469 | */ 470 | char_u *vim_strsave(char_u *string) { 471 | char_u *p; 472 | size_t len; 473 | 474 | len = STRLEN(string) + 1; 475 | p = alloc(len); 476 | if (p != NULL) 477 | mch_memmove(p, string, len); 478 | return p; 479 | } 480 | 481 | /* 482 | * Skip over ' ' and '\t'. 483 | */ 484 | char_u *skipwhite(char_u *q) { 485 | char_u *p = q; 486 | 487 | while (VIM_ISWHITE(*p)) 488 | ++p; 489 | return p; 490 | } 491 | 492 | int vim_tolower(int c) { 493 | if (c <= '@') 494 | return c; 495 | if (c >= 0x80) { 496 | return utf_tolower(c); 497 | } 498 | return TOLOWER_ASC(c); 499 | } 500 | 501 | int vim_islower(int c) { 502 | if (c <= '@') 503 | return FALSE; 504 | if (c >= 0x80) { 505 | return utf_islower(c); 506 | } 507 | return islower(c); 508 | } 509 | 510 | int vim_isupper(int c) { 511 | if (c <= '@') 512 | return FALSE; 513 | if (c >= 0x80) { 514 | return utf_isupper(c); 515 | } 516 | return isupper(c); 517 | } 518 | 519 | /* 520 | * Compute a score for a fuzzy matched string. The matching character locations 521 | * are in 'matches'. 522 | */ 523 | static int fuzzy_match_compute_score(char_u *str, int strSz, int_u *matches, 524 | int numMatches) { 525 | int score; 526 | int penalty; 527 | int unmatched; 528 | int i; 529 | char_u *p = str; 530 | int_u sidx = 0; 531 | 532 | // Initialize score 533 | score = 100; 534 | 535 | // Apply leading letter penalty 536 | penalty = LEADING_LETTER_PENALTY * matches[0]; 537 | if (penalty < MAX_LEADING_LETTER_PENALTY) 538 | penalty = MAX_LEADING_LETTER_PENALTY; 539 | score += penalty; 540 | 541 | // Apply unmatched penalty 542 | unmatched = strSz - numMatches; 543 | score += UNMATCHED_LETTER_PENALTY * unmatched; 544 | 545 | // Apply ordering bonuses 546 | for (i = 0; i < numMatches; ++i) { 547 | int_u currIdx = matches[i]; 548 | 549 | if (i > 0) { 550 | int_u prevIdx = matches[i - 1]; 551 | 552 | // Sequential 553 | if (currIdx == (prevIdx + 1)) 554 | score += SEQUENTIAL_BONUS; 555 | else 556 | score += GAP_PENALTY * (currIdx - prevIdx); 557 | } 558 | 559 | // Check for bonuses based on neighbor character value 560 | if (currIdx > 0) { 561 | // Camel case 562 | int neighbor = ' '; 563 | int curr; 564 | 565 | while (sidx < currIdx) { 566 | neighbor = (*utf_ptr2char)(p); 567 | p += (*utf_ptr2len)(p); 568 | sidx++; 569 | } 570 | curr = (*utf_ptr2char)(p); 571 | 572 | if (vim_islower(neighbor) && vim_isupper(curr)) 573 | score += CAMEL_BONUS; 574 | 575 | // Bonus if the match follows a separator character 576 | if (neighbor == '/' || neighbor == '\\') 577 | score += PATH_SEPARATOR_BONUS; 578 | else if (neighbor == ' ' || neighbor == '_') 579 | score += WORD_SEPARATOR_BONUS; 580 | } else { 581 | // First letter 582 | score += FIRST_LETTER_BONUS; 583 | } 584 | } 585 | return score; 586 | } 587 | 588 | /* 589 | * Perform a recursive search for fuzzy matching 'fuzpat' in 'str'. 590 | * Return the number of matching characters. 591 | */ 592 | static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, 593 | int *outScore, char_u *strBegin, int strLen, 594 | int_u *srcMatches, int_u *matches, 595 | int maxMatches, int nextMatch, 596 | int *recursionCount) { 597 | // Recursion params 598 | int recursiveMatch = FALSE; 599 | int_u bestRecursiveMatches[MAX_FUZZY_MATCHES]; 600 | int bestRecursiveScore = 0; 601 | int first_match; 602 | int matched; 603 | 604 | // Count recursions 605 | ++*recursionCount; 606 | if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) 607 | return 0; 608 | 609 | // Detect end of strings 610 | if (*fuzpat == NUL || *str == NUL) 611 | return 0; 612 | 613 | // Loop through fuzpat and str looking for a match 614 | first_match = TRUE; 615 | while (*fuzpat != NUL && *str != NUL) { 616 | int c1; 617 | int c2; 618 | 619 | c1 = utf_ptr2char(fuzpat); 620 | c2 = utf_ptr2char(str); 621 | 622 | // Found match 623 | if (vim_tolower(c1) == vim_tolower(c2)) { 624 | int_u recursiveMatches[MAX_FUZZY_MATCHES]; 625 | int recursiveScore = 0; 626 | char_u *next_char; 627 | 628 | // Supplied matches buffer was too short 629 | if (nextMatch >= maxMatches) 630 | return 0; 631 | 632 | // "Copy-on-Write" srcMatches into matches 633 | if (first_match && srcMatches) { 634 | memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0])); 635 | first_match = FALSE; 636 | } 637 | 638 | // Recursive call that "skips" this match 639 | next_char = str + (*utf_ptr2len)(str); 640 | if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, 641 | strBegin, strLen, matches, recursiveMatches, 642 | ARRAY_LENGTH(recursiveMatches), nextMatch, 643 | recursionCount)) { 644 | // Pick best recursive score 645 | if (!recursiveMatch || recursiveScore > bestRecursiveScore) { 646 | memcpy(bestRecursiveMatches, recursiveMatches, 647 | MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0])); 648 | bestRecursiveScore = recursiveScore; 649 | } 650 | recursiveMatch = TRUE; 651 | } 652 | 653 | // Advance 654 | matches[nextMatch++] = strIdx; 655 | fuzpat += (*utf_ptr2len)(fuzpat); 656 | } 657 | str += (*utf_ptr2len)(str); 658 | strIdx++; 659 | } 660 | 661 | // Determine if full fuzpat was matched 662 | matched = *fuzpat == NUL ? TRUE : FALSE; 663 | 664 | // Calculate score 665 | if (matched) 666 | *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch); 667 | 668 | // Return best result 669 | if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) { 670 | // Recursive score is better than "this" 671 | memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0])); 672 | *outScore = bestRecursiveScore; 673 | return nextMatch; 674 | } else if (matched) 675 | return nextMatch; // "this" score is better than recursive 676 | 677 | return 0; // no match 678 | } 679 | 680 | /* 681 | * fuzzy_match() 682 | * 683 | * Performs exhaustive search via recursion to find all possible matches and 684 | * match with highest score. 685 | * Scores values have no intrinsic meaning. Possible score range is not 686 | * normalized and varies with pattern. 687 | * Recursion is limited internally (default=10) to prevent degenerate cases 688 | * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"). 689 | * Uses char_u for match indices. Therefore patterns are limited to 690 | * MAX_FUZZY_MATCHES characters. 691 | * If 'matchseq' is TRUE, then for multi-word search strings, match all the 692 | * words in sequence. 693 | * 694 | * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in 695 | * 'outScore' and the matching character positions in 'matches'. 696 | */ 697 | int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, 698 | int_u *matches, int maxMatches) { 699 | int recursionCount = 0; 700 | int len = mb_charlen(str); 701 | char_u *save_pat; 702 | char_u *pat; 703 | char_u *p; 704 | int complete = FALSE; 705 | int score = 0; 706 | int numMatches = 0; 707 | int matchCount; 708 | 709 | *outScore = 0; 710 | 711 | save_pat = vim_strsave(pat_arg); 712 | if (save_pat == NULL) 713 | return FALSE; 714 | pat = save_pat; 715 | p = pat; 716 | 717 | // Try matching each word in 'pat_arg' in 'str' 718 | while (TRUE) { 719 | if (matchseq) 720 | complete = TRUE; 721 | else { 722 | // Extract one word from the pattern (separated by space) 723 | p = skipwhite(p); 724 | if (*p == NUL) 725 | break; 726 | pat = p; 727 | while (*p != NUL && !VIM_ISWHITE(utf_ptr2char(p))) { 728 | p += (*utf_ptr2len)(p); 729 | } 730 | if (*p == NUL) // processed all the words 731 | complete = TRUE; 732 | *p = NUL; 733 | } 734 | 735 | score = 0; 736 | recursionCount = 0; 737 | matchCount = fuzzy_match_recursive( 738 | pat, str, 0, &score, str, len, NULL, matches + numMatches, 739 | maxMatches - numMatches, 0, &recursionCount); 740 | if (matchCount == 0) { 741 | numMatches = 0; 742 | break; 743 | } 744 | 745 | // Accumulate the match score and the number of matches 746 | *outScore += score; 747 | numMatches += matchCount; 748 | 749 | if (complete) 750 | break; 751 | 752 | // try matching the next word 753 | ++p; 754 | } 755 | 756 | vim_free(save_pat); 757 | return numMatches != 0; 758 | } 759 | 760 | __attribute__((export_name("fuzzyMatch"))) int 761 | fuzzyMatch(char_u *str, char_u *pattern, int_u *matches, int matchseq) { 762 | int score = 0; 763 | int res = 0; 764 | res = fuzzy_match(str, pattern, matchseq, &score, matches, MAX_FUZZY_MATCHES); 765 | if (!res) 766 | return 0; 767 | return score; 768 | } 769 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | --target=wasm32-unknown-unknown-wasm 2 | --sysroot=/usr/local/share/wasi-sdk-16.0/share/wasi-sysroot 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | async function setupWasi(fileName) { 4 | // Read the wasm file. 5 | const buf = fs.readFileSync(fileName) 6 | // Instantiate the wasm module. 7 | const res = await WebAssembly.instantiate(buf, { 8 | env: { 9 | // This function is exported to the web assembly. 10 | consoleLog: function (ptr, length) { 11 | let memory = res.instance.exports.memory 12 | // This converts the pointer to a string and frees he memory. 13 | const array = new Uint8Array(memory.buffer, ptr, length) 14 | const decoder = new TextDecoder() 15 | const string = decoder.decode(array) 16 | console.log(string) 17 | } 18 | } 19 | }) 20 | return res 21 | } 22 | 23 | async function main() { 24 | // Setup the WASI instance. 25 | const wasi = await setupWasi('./fuzzy.wasm') 26 | 27 | // Get the functions exported from the WebAssembly 28 | const { 29 | fuzzyMatch, 30 | malloc, 31 | memory 32 | } = wasi.instance.exports 33 | let contentPtr = malloc(2048) 34 | let patternPtr = malloc(1024) 35 | let resultPtr = malloc(1024) 36 | 37 | const changePattern = (pattern) => { 38 | let buf = Buffer.from(pattern, 'utf8') 39 | let len = buf.length 40 | if (len > 1024) throw new Error('pattern too long') 41 | let bytes = new Uint8Array(memory.buffer, patternPtr, len + 1) 42 | bytes.set(buf) 43 | bytes[len] = 0 44 | } 45 | 46 | const changeContent = (text) => { 47 | let buf = Buffer.from(text, 'utf8') 48 | let len = buf.length 49 | if (len > 2048) throw new Error('content too long') 50 | let contentBytes = new Uint8Array(memory.buffer, contentPtr, len + 1) 51 | contentBytes.set(buf) 52 | contentBytes[len] = 0 53 | } 54 | 55 | let pat = 'fb' 56 | changePattern(pat) 57 | const matchResults = text => { 58 | changeContent(text) 59 | let score = fuzzyMatch(contentPtr, patternPtr, resultPtr, 0) 60 | if (score) { 61 | const u32 = new Uint32Array(memory.buffer, resultPtr, 4) 62 | let arr = u32.slice(0, pat.length) 63 | console.log(`Match ${pat} => ${text}`) 64 | console.log(`Matched score & positions`, score, arr) 65 | } else { 66 | console.log(`Match ${pat} => ${text}, No match`) 67 | } 68 | } 69 | matchResults('fooBar') 70 | matchResults('fobbbdefo/Bar') 71 | matchResults('foot') 72 | pat = '好' 73 | changePattern(pat) 74 | matchResults('abc你好') 75 | } 76 | 77 | main().then(() => console.log('Done')) 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-fuzzy", 3 | "version": "1.0.0", 4 | "description": "Wasm fuzzy match module", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "author": "chemzqm@gmail.com", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "@types/node": "^18.7.18" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^18.7.18": 6 | version "18.7.18" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" 8 | integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== 9 | --------------------------------------------------------------------------------