stride-align 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/NOTICE +255 -0
- data/README.md +107 -0
- data/data/bmpm_data/gen_approx_any.txt +131 -0
- data/data/bmpm_data/gen_approx_arabic.txt +26 -0
- data/data/bmpm_data/gen_approx_common.txt +233 -0
- data/data/bmpm_data/gen_approx_cyrillic.txt +18 -0
- data/data/bmpm_data/gen_approx_czech.txt +18 -0
- data/data/bmpm_data/gen_approx_dutch.txt +18 -0
- data/data/bmpm_data/gen_approx_english.txt +47 -0
- data/data/bmpm_data/gen_approx_french.txt +25 -0
- data/data/bmpm_data/gen_approx_german.txt +73 -0
- data/data/bmpm_data/gen_approx_greek.txt +18 -0
- data/data/bmpm_data/gen_approx_greeklatin.txt +20 -0
- data/data/bmpm_data/gen_approx_hebrew.txt +18 -0
- data/data/bmpm_data/gen_approx_hungarian.txt +18 -0
- data/data/bmpm_data/gen_approx_italian.txt +18 -0
- data/data/bmpm_data/gen_approx_polish.txt +84 -0
- data/data/bmpm_data/gen_approx_portuguese.txt +18 -0
- data/data/bmpm_data/gen_approx_romanian.txt +18 -0
- data/data/bmpm_data/gen_approx_russian.txt +48 -0
- data/data/bmpm_data/gen_approx_spanish.txt +21 -0
- data/data/bmpm_data/gen_approx_turkish.txt +18 -0
- data/data/bmpm_data/gen_exact_any.txt +40 -0
- data/data/bmpm_data/gen_exact_approx_common.txt +79 -0
- data/data/bmpm_data/gen_exact_arabic.txt +18 -0
- data/data/bmpm_data/gen_exact_common.txt +32 -0
- data/data/bmpm_data/gen_exact_cyrillic.txt +18 -0
- data/data/bmpm_data/gen_exact_czech.txt +18 -0
- data/data/bmpm_data/gen_exact_dutch.txt +18 -0
- data/data/bmpm_data/gen_exact_english.txt +18 -0
- data/data/bmpm_data/gen_exact_french.txt +18 -0
- data/data/bmpm_data/gen_exact_german.txt +18 -0
- data/data/bmpm_data/gen_exact_greek.txt +18 -0
- data/data/bmpm_data/gen_exact_greeklatin.txt +18 -0
- data/data/bmpm_data/gen_exact_hebrew.txt +18 -0
- data/data/bmpm_data/gen_exact_hungarian.txt +18 -0
- data/data/bmpm_data/gen_exact_italian.txt +18 -0
- data/data/bmpm_data/gen_exact_polish.txt +23 -0
- data/data/bmpm_data/gen_exact_portuguese.txt +18 -0
- data/data/bmpm_data/gen_exact_romanian.txt +18 -0
- data/data/bmpm_data/gen_exact_russian.txt +19 -0
- data/data/bmpm_data/gen_exact_spanish.txt +19 -0
- data/data/bmpm_data/gen_exact_turkish.txt +18 -0
- data/data/bmpm_data/gen_hebrew_common.txt +113 -0
- data/data/bmpm_data/gen_lang.txt +295 -0
- data/data/bmpm_data/gen_languages.txt +36 -0
- data/data/bmpm_data/gen_rules_any.txt +367 -0
- data/data/bmpm_data/gen_rules_arabic.txt +76 -0
- data/data/bmpm_data/gen_rules_cyrillic.txt +99 -0
- data/data/bmpm_data/gen_rules_czech.txt +67 -0
- data/data/bmpm_data/gen_rules_dutch.txt +78 -0
- data/data/bmpm_data/gen_rules_english.txt +113 -0
- data/data/bmpm_data/gen_rules_french.txt +114 -0
- data/data/bmpm_data/gen_rules_german.txt +129 -0
- data/data/bmpm_data/gen_rules_greek.txt +97 -0
- data/data/bmpm_data/gen_rules_greeklatin.txt +118 -0
- data/data/bmpm_data/gen_rules_hebrew.txt +62 -0
- data/data/bmpm_data/gen_rules_hungarian.txt +83 -0
- data/data/bmpm_data/gen_rules_italian.txt +77 -0
- data/data/bmpm_data/gen_rules_polish.txt +185 -0
- data/data/bmpm_data/gen_rules_portuguese.txt +105 -0
- data/data/bmpm_data/gen_rules_romanian.txt +64 -0
- data/data/bmpm_data/gen_rules_russian.txt +142 -0
- data/data/bmpm_data/gen_rules_spanish.txt +85 -0
- data/data/bmpm_data/gen_rules_turkish.txt +50 -0
- data/data/keyboard_data/qwerty.npy +0 -0
- data/data/matrix_data/BLOSUM100 +31 -0
- data/data/matrix_data/BLOSUM30 +31 -0
- data/data/matrix_data/BLOSUM35 +31 -0
- data/data/matrix_data/BLOSUM40 +31 -0
- data/data/matrix_data/BLOSUM45 +25 -0
- data/data/matrix_data/BLOSUM50 +25 -0
- data/data/matrix_data/BLOSUM55 +31 -0
- data/data/matrix_data/BLOSUM60 +31 -0
- data/data/matrix_data/BLOSUM62 +25 -0
- data/data/matrix_data/BLOSUM65 +31 -0
- data/data/matrix_data/BLOSUM70 +31 -0
- data/data/matrix_data/BLOSUM75 +31 -0
- data/data/matrix_data/BLOSUM80 +25 -0
- data/data/matrix_data/BLOSUM85 +31 -0
- data/data/matrix_data/BLOSUM90 +25 -0
- data/data/matrix_data/NUC.4.4 +25 -0
- data/data/matrix_data/PAM10 +34 -0
- data/data/matrix_data/PAM100 +34 -0
- data/data/matrix_data/PAM110 +34 -0
- data/data/matrix_data/PAM120 +34 -0
- data/data/matrix_data/PAM130 +34 -0
- data/data/matrix_data/PAM140 +34 -0
- data/data/matrix_data/PAM150 +34 -0
- data/data/matrix_data/PAM160 +34 -0
- data/data/matrix_data/PAM170 +34 -0
- data/data/matrix_data/PAM180 +34 -0
- data/data/matrix_data/PAM190 +34 -0
- data/data/matrix_data/PAM20 +34 -0
- data/data/matrix_data/PAM200 +34 -0
- data/data/matrix_data/PAM210 +34 -0
- data/data/matrix_data/PAM220 +34 -0
- data/data/matrix_data/PAM230 +34 -0
- data/data/matrix_data/PAM240 +34 -0
- data/data/matrix_data/PAM250 +25 -0
- data/data/matrix_data/PAM260 +34 -0
- data/data/matrix_data/PAM270 +34 -0
- data/data/matrix_data/PAM280 +34 -0
- data/data/matrix_data/PAM290 +34 -0
- data/data/matrix_data/PAM30 +25 -0
- data/data/matrix_data/PAM300 +34 -0
- data/data/matrix_data/PAM310 +34 -0
- data/data/matrix_data/PAM320 +34 -0
- data/data/matrix_data/PAM330 +34 -0
- data/data/matrix_data/PAM340 +34 -0
- data/data/matrix_data/PAM350 +34 -0
- data/data/matrix_data/PAM360 +34 -0
- data/data/matrix_data/PAM370 +34 -0
- data/data/matrix_data/PAM380 +34 -0
- data/data/matrix_data/PAM390 +34 -0
- data/data/matrix_data/PAM40 +34 -0
- data/data/matrix_data/PAM400 +34 -0
- data/data/matrix_data/PAM410 +34 -0
- data/data/matrix_data/PAM420 +34 -0
- data/data/matrix_data/PAM430 +34 -0
- data/data/matrix_data/PAM440 +34 -0
- data/data/matrix_data/PAM450 +34 -0
- data/data/matrix_data/PAM460 +34 -0
- data/data/matrix_data/PAM470 +34 -0
- data/data/matrix_data/PAM480 +34 -0
- data/data/matrix_data/PAM490 +34 -0
- data/data/matrix_data/PAM50 +34 -0
- data/data/matrix_data/PAM500 +34 -0
- data/data/matrix_data/PAM60 +34 -0
- data/data/matrix_data/PAM70 +25 -0
- data/data/matrix_data/PAM80 +34 -0
- data/data/matrix_data/PAM90 +34 -0
- data/ext/stride_align/backend_avx2.cpp +2 -0
- data/ext/stride_align/backend_avx512bwvl.cpp +2 -0
- data/ext/stride_align/backend_generic.cpp +3 -0
- data/ext/stride_align/backend_impl.hpp +983 -0
- data/ext/stride_align/backend_lasx.cpp +2 -0
- data/ext/stride_align/backend_lsx.cpp +2 -0
- data/ext/stride_align/backend_neon.cpp +2 -0
- data/ext/stride_align/backend_rvv.cpp +2 -0
- data/ext/stride_align/backend_sse41.cpp +2 -0
- data/ext/stride_align/backend_sve.cpp +2 -0
- data/ext/stride_align/backend_sve2.cpp +2 -0
- data/ext/stride_align/backend_vsx.cpp +2 -0
- data/ext/stride_align/beider_morse_impl.cpp +5 -0
- data/ext/stride_align/cpu_detect.cpp +172 -0
- data/ext/stride_align/cpu_detect.hpp +6 -0
- data/ext/stride_align/extconf.rb +114 -0
- data/ext/stride_align/target_profile.hpp +82 -0
- data/ext/stride_align/vendor/beider_morse_impl.cpp +1467 -0
- data/ext/stride_align/vendor/stride_align/alignment.hpp +199 -0
- data/ext/stride_align/vendor/stride_align/batch.hpp +812 -0
- data/ext/stride_align/vendor/stride_align/beider_morse.hpp +121 -0
- data/ext/stride_align/vendor/stride_align/caverphone.hpp +222 -0
- data/ext/stride_align/vendor/stride_align/cologne_phonetic.hpp +202 -0
- data/ext/stride_align/vendor/stride_align/core.hpp +731 -0
- data/ext/stride_align/vendor/stride_align/daitch_mokotoff.hpp +631 -0
- data/ext/stride_align/vendor/stride_align/double_metaphone.hpp +796 -0
- data/ext/stride_align/vendor/stride_align/dtw.hpp +300 -0
- data/ext/stride_align/vendor/stride_align/encoded.hpp +235 -0
- data/ext/stride_align/vendor/stride_align/hamming.hpp +55 -0
- data/ext/stride_align/vendor/stride_align/indel.hpp +1200 -0
- data/ext/stride_align/vendor/stride_align/jaro.hpp +517 -0
- data/ext/stride_align/vendor/stride_align/lcs.hpp +159 -0
- data/ext/stride_align/vendor/stride_align/levenshtein.hpp +1247 -0
- data/ext/stride_align/vendor/stride_align/levenshtein_prepared.hpp +193 -0
- data/ext/stride_align/vendor/stride_align/match_rating.hpp +168 -0
- data/ext/stride_align/vendor/stride_align/metaphone.hpp +291 -0
- data/ext/stride_align/vendor/stride_align/ngram.hpp +176 -0
- data/ext/stride_align/vendor/stride_align/nysiis.hpp +199 -0
- data/ext/stride_align/vendor/stride_align/pairwise_alignment.hpp +465 -0
- data/ext/stride_align/vendor/stride_align/partial_ratio.hpp +486 -0
- data/ext/stride_align/vendor/stride_align/ratcliff_obershelp.hpp +101 -0
- data/ext/stride_align/vendor/stride_align/soundex.hpp +108 -0
- data/ext/stride_align/vendor/stride_align/token_ratios.hpp +445 -0
- data/ext/stride_align/vendor/stride_align/types.hpp +16 -0
- data/ext/stride_align/vendor/stride_align/utf8.hpp +512 -0
- data/ext/stride_align/vendor/stride_align/wratio.hpp +363 -0
- data/lib/stride_align/algorithms.rb +296 -0
- data/lib/stride_align/alignment_path.rb +217 -0
- data/lib/stride_align/backend.rb +47 -0
- data/lib/stride_align/batch.rb +705 -0
- data/lib/stride_align/core.rb +180 -0
- data/lib/stride_align/keyboard.rb +200 -0
- data/lib/stride_align/matrices.rb +403 -0
- data/lib/stride_align/version.rb +5 -0
- data/lib/stride_align.rb +87 -0
- metadata +231 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Daitch-Mokotoff Soundex (Daitch & Mokotoff, 1985).
|
|
4
|
+
//
|
|
5
|
+
// A refinement of American Soundex tuned for Slavic and Yiddish
|
|
6
|
+
// surnames: 6-digit output (vs Soundex's letter-and-three-digits),
|
|
7
|
+
// the leading letter is encoded (not preserved verbatim), and rules
|
|
8
|
+
// fire on multi-character clusters (``sch``, ``tsch``, ``schtsch``,
|
|
9
|
+
// ``rz``, ``cz``) before any single-letter fallback. Several rules
|
|
10
|
+
// emit branched alternatives separated by ``|``; the returned string
|
|
11
|
+
// is a ``|``-joined set of code candidates.
|
|
12
|
+
//
|
|
13
|
+
// Public API:
|
|
14
|
+
// * ``daitch_mokotoff(s)`` — branching enabled, returns
|
|
15
|
+
// ``code1|code2|...``.
|
|
16
|
+
// * ``daitch_mokotoff(s, branching=false)`` — single-code form.
|
|
17
|
+
// * Optional ``folding=true`` (default) applies the ASCII fold table
|
|
18
|
+
// so common accented characters (è/é/ê/ë/ï/ö/ü/ß ...) collapse to
|
|
19
|
+
// their ASCII bases before encoding.
|
|
20
|
+
//
|
|
21
|
+
// Source attribution:
|
|
22
|
+
// * Daitch, R. & Mokotoff, G. (1985). Original publication via
|
|
23
|
+
// Avotaynu Inc.
|
|
24
|
+
// * Apache Commons Codec ``DaitchMokotoffSoundex`` (Apache 2.0) and
|
|
25
|
+
// its companion resource file ``dmrules.txt`` for the rule table
|
|
26
|
+
// and algorithm. The rule data is vendored verbatim under
|
|
27
|
+
// ``docs/upstream/apache-commons-codec-dmrules.txt`` with its
|
|
28
|
+
// original ASF header intact and embedded below as the
|
|
29
|
+
// ``kRules`` raw string literal.
|
|
30
|
+
// * https://www.avotaynu.com/soundex.htm
|
|
31
|
+
// * https://en.wikipedia.org/wiki/Daitch-Mokotoff_Soundex
|
|
32
|
+
//
|
|
33
|
+
// Cross-checked against the canonical ``DaitchMokotoffSoundexTest``
|
|
34
|
+
// vectors (Apache Commons Codec).
|
|
35
|
+
|
|
36
|
+
#include <algorithm>
|
|
37
|
+
#include <array>
|
|
38
|
+
#include <cstdint>
|
|
39
|
+
#include <cstring>
|
|
40
|
+
#include <mutex>
|
|
41
|
+
#include <string>
|
|
42
|
+
#include <string_view>
|
|
43
|
+
#include <unordered_map>
|
|
44
|
+
#include <vector>
|
|
45
|
+
|
|
46
|
+
namespace stride_align::phonetic {
|
|
47
|
+
|
|
48
|
+
namespace daitch_mokotoff_detail {
|
|
49
|
+
|
|
50
|
+
constexpr std::size_t kMaxLength = 6;
|
|
51
|
+
|
|
52
|
+
// Rule data — vendored verbatim from
|
|
53
|
+
// ``docs/upstream/apache-commons-codec-dmrules.txt`` (Apache 2.0,
|
|
54
|
+
// Apache Commons Codec). The ASF header from that file is reproduced
|
|
55
|
+
// inside the raw string so the derivative-work attribution survives
|
|
56
|
+
// even if this single header is read in isolation.
|
|
57
|
+
inline constexpr std::string_view kRules = R"DM(
|
|
58
|
+
/*
|
|
59
|
+
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
60
|
+
* contributor license agreements. See the NOTICE file distributed with
|
|
61
|
+
* this work for additional information regarding copyright ownership.
|
|
62
|
+
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
63
|
+
* (the "License"); you may not use this file except in compliance with
|
|
64
|
+
* the License. You may obtain a copy of the License at
|
|
65
|
+
*
|
|
66
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
67
|
+
*
|
|
68
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
69
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
70
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
71
|
+
* See the License for the specific language governing permissions and
|
|
72
|
+
* limitations under the License.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
// Vowels
|
|
76
|
+
"a" "0" "" ""
|
|
77
|
+
"e" "0" "" ""
|
|
78
|
+
"i" "0" "" ""
|
|
79
|
+
"o" "0" "" ""
|
|
80
|
+
"u" "0" "" ""
|
|
81
|
+
|
|
82
|
+
// Consonants
|
|
83
|
+
"b" "7" "7" "7"
|
|
84
|
+
"d" "3" "3" "3"
|
|
85
|
+
"f" "7" "7" "7"
|
|
86
|
+
"g" "5" "5" "5"
|
|
87
|
+
"h" "5" "5" ""
|
|
88
|
+
"k" "5" "5" "5"
|
|
89
|
+
"l" "8" "8" "8"
|
|
90
|
+
"m" "6" "6" "6"
|
|
91
|
+
"n" "6" "6" "6"
|
|
92
|
+
"p" "7" "7" "7"
|
|
93
|
+
"q" "5" "5" "5"
|
|
94
|
+
"r" "9" "9" "9"
|
|
95
|
+
"s" "4" "4" "4"
|
|
96
|
+
"t" "3" "3" "3"
|
|
97
|
+
"v" "7" "7" "7"
|
|
98
|
+
"w" "7" "7" "7"
|
|
99
|
+
"x" "5" "54" "54"
|
|
100
|
+
"y" "1" "" ""
|
|
101
|
+
"z" "4" "4" "4"
|
|
102
|
+
|
|
103
|
+
"ţ" "3|4" "3|4" "3|4"
|
|
104
|
+
"ț" "3|4" "3|4" "3|4"
|
|
105
|
+
|
|
106
|
+
"ę" "" "" "|6"
|
|
107
|
+
"ą" "" "" "|6"
|
|
108
|
+
|
|
109
|
+
"schtsch" "2" "4" "4"
|
|
110
|
+
"schtsh" "2" "4" "4"
|
|
111
|
+
"schtch" "2" "4" "4"
|
|
112
|
+
"shtch" "2" "4" "4"
|
|
113
|
+
"shtsh" "2" "4" "4"
|
|
114
|
+
"stsch" "2" "4" "4"
|
|
115
|
+
"ttsch" "4" "4" "4"
|
|
116
|
+
"zhdzh" "2" "4" "4"
|
|
117
|
+
"shch" "2" "4" "4"
|
|
118
|
+
"scht" "2" "43" "43"
|
|
119
|
+
"schd" "2" "43" "43"
|
|
120
|
+
"stch" "2" "4" "4"
|
|
121
|
+
"strz" "2" "4" "4"
|
|
122
|
+
"strs" "2" "4" "4"
|
|
123
|
+
"stsh" "2" "4" "4"
|
|
124
|
+
"szcz" "2" "4" "4"
|
|
125
|
+
"szcs" "2" "4" "4"
|
|
126
|
+
"ttch" "4" "4" "4"
|
|
127
|
+
"tsch" "4" "4" "4"
|
|
128
|
+
"ttsz" "4" "4" "4"
|
|
129
|
+
"zdzh" "2" "4" "4"
|
|
130
|
+
"zsch" "4" "4" "4"
|
|
131
|
+
"chs" "5" "54" "54"
|
|
132
|
+
"csz" "4" "4" "4"
|
|
133
|
+
"czs" "4" "4" "4"
|
|
134
|
+
"drz" "4" "4" "4"
|
|
135
|
+
"drs" "4" "4" "4"
|
|
136
|
+
"dsh" "4" "4" "4"
|
|
137
|
+
"dsz" "4" "4" "4"
|
|
138
|
+
"dzh" "4" "4" "4"
|
|
139
|
+
"dzs" "4" "4" "4"
|
|
140
|
+
"sch" "4" "4" "4"
|
|
141
|
+
"sht" "2" "43" "43"
|
|
142
|
+
"szt" "2" "43" "43"
|
|
143
|
+
"shd" "2" "43" "43"
|
|
144
|
+
"szd" "2" "43" "43"
|
|
145
|
+
"tch" "4" "4" "4"
|
|
146
|
+
"trz" "4" "4" "4"
|
|
147
|
+
"trs" "4" "4" "4"
|
|
148
|
+
"tsh" "4" "4" "4"
|
|
149
|
+
"tts" "4" "4" "4"
|
|
150
|
+
"ttz" "4" "4" "4"
|
|
151
|
+
"tzs" "4" "4" "4"
|
|
152
|
+
"tsz" "4" "4" "4"
|
|
153
|
+
"zdz" "2" "4" "4"
|
|
154
|
+
"zhd" "2" "43" "43"
|
|
155
|
+
"zsh" "4" "4" "4"
|
|
156
|
+
"ai" "0" "1" ""
|
|
157
|
+
"aj" "0" "1" ""
|
|
158
|
+
"ay" "0" "1" ""
|
|
159
|
+
"au" "0" "7" ""
|
|
160
|
+
"cz" "4" "4" "4"
|
|
161
|
+
"cs" "4" "4" "4"
|
|
162
|
+
"ds" "4" "4" "4"
|
|
163
|
+
"dz" "4" "4" "4"
|
|
164
|
+
"dt" "3" "3" "3"
|
|
165
|
+
"ei" "0" "1" ""
|
|
166
|
+
"ej" "0" "1" ""
|
|
167
|
+
"ey" "0" "1" ""
|
|
168
|
+
"eu" "1" "1" ""
|
|
169
|
+
"fb" "7" "7" "7"
|
|
170
|
+
"ia" "1" "" ""
|
|
171
|
+
"ie" "1" "" ""
|
|
172
|
+
"io" "1" "" ""
|
|
173
|
+
"iu" "1" "" ""
|
|
174
|
+
"ks" "5" "54" "54"
|
|
175
|
+
"kh" "5" "5" "5"
|
|
176
|
+
"mn" "66" "66" "66"
|
|
177
|
+
"nm" "66" "66" "66"
|
|
178
|
+
"oi" "0" "1" ""
|
|
179
|
+
"oj" "0" "1" ""
|
|
180
|
+
"oy" "0" "1" ""
|
|
181
|
+
"pf" "7" "7" "7"
|
|
182
|
+
"ph" "7" "7" "7"
|
|
183
|
+
"sh" "4" "4" "4"
|
|
184
|
+
"sc" "2" "4" "4"
|
|
185
|
+
"st" "2" "43" "43"
|
|
186
|
+
"sd" "2" "43" "43"
|
|
187
|
+
"sz" "4" "4" "4"
|
|
188
|
+
"th" "3" "3" "3"
|
|
189
|
+
"ts" "4" "4" "4"
|
|
190
|
+
"tc" "4" "4" "4"
|
|
191
|
+
"tz" "4" "4" "4"
|
|
192
|
+
"ui" "0" "1" ""
|
|
193
|
+
"uj" "0" "1" ""
|
|
194
|
+
"uy" "0" "1" ""
|
|
195
|
+
"ue" "0" "1" ""
|
|
196
|
+
"zd" "2" "43" "43"
|
|
197
|
+
"zh" "4" "4" "4"
|
|
198
|
+
"zs" "4" "4" "4"
|
|
199
|
+
|
|
200
|
+
"c" "4|5" "4|5" "4|5"
|
|
201
|
+
"ch" "4|5" "4|5" "4|5"
|
|
202
|
+
"ck" "5|45" "5|45" "5|45"
|
|
203
|
+
"rs" "4|94" "4|94" "4|94"
|
|
204
|
+
"rz" "4|94" "4|94" "4|94"
|
|
205
|
+
"j" "1|4" "|4" "|4"
|
|
206
|
+
|
|
207
|
+
ß=s
|
|
208
|
+
à=a
|
|
209
|
+
á=a
|
|
210
|
+
â=a
|
|
211
|
+
ã=a
|
|
212
|
+
ä=a
|
|
213
|
+
å=a
|
|
214
|
+
æ=a
|
|
215
|
+
ç=c
|
|
216
|
+
è=e
|
|
217
|
+
é=e
|
|
218
|
+
ê=e
|
|
219
|
+
ë=e
|
|
220
|
+
ì=i
|
|
221
|
+
í=i
|
|
222
|
+
î=i
|
|
223
|
+
ï=i
|
|
224
|
+
ð=d
|
|
225
|
+
ñ=n
|
|
226
|
+
ò=o
|
|
227
|
+
ó=o
|
|
228
|
+
ô=o
|
|
229
|
+
õ=o
|
|
230
|
+
ö=o
|
|
231
|
+
ø=o
|
|
232
|
+
ù=u
|
|
233
|
+
ú=u
|
|
234
|
+
û=u
|
|
235
|
+
ý=y
|
|
236
|
+
þ=b
|
|
237
|
+
ÿ=y
|
|
238
|
+
ć=c
|
|
239
|
+
ł=l
|
|
240
|
+
ś=s
|
|
241
|
+
ż=z
|
|
242
|
+
ź=z
|
|
243
|
+
)DM";
|
|
244
|
+
|
|
245
|
+
// One rule row. ``pattern`` is a UTF-8 byte string (1–7 bytes for the
|
|
246
|
+
// Romanian / Polish entries). ``r_start`` / ``r_vowel`` / ``r_other``
|
|
247
|
+
// hold ``|``-separated alternative replacements; an empty alternative
|
|
248
|
+
// is a valid value (silent in that context).
|
|
249
|
+
struct Rule {
|
|
250
|
+
std::string pattern;
|
|
251
|
+
std::vector<std::string> r_start;
|
|
252
|
+
std::vector<std::string> r_vowel;
|
|
253
|
+
std::vector<std::string> r_other;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Forward declaration — defined further down. Needed here so the
|
|
257
|
+
// folding-table parser can decode the accented ``<from>`` side of
|
|
258
|
+
// each folding line to a codepoint key.
|
|
259
|
+
inline std::uint32_t decode_one(std::string_view s, std::size_t pos,
|
|
260
|
+
std::size_t& consumed);
|
|
261
|
+
|
|
262
|
+
struct CompiledTables {
|
|
263
|
+
// Rules grouped by the first byte of their pattern; within each
|
|
264
|
+
// bucket sorted by descending pattern length so longest-match wins.
|
|
265
|
+
std::array<std::vector<Rule>, 256> by_first_byte;
|
|
266
|
+
// ASCII folding: maps a single codepoint to a single ASCII byte.
|
|
267
|
+
// Keying directly on the codepoint means ``cleanup`` can look up
|
|
268
|
+
// the fold result without encoding the codepoint to UTF-8 just to
|
|
269
|
+
// build the search key.
|
|
270
|
+
std::unordered_map<std::uint32_t, char> foldings;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
inline std::vector<std::string> split_alts(std::string_view s) {
|
|
274
|
+
std::vector<std::string> out;
|
|
275
|
+
std::size_t start = 0;
|
|
276
|
+
for (std::size_t i = 0; i <= s.size(); ++i) {
|
|
277
|
+
if (i == s.size() || s[i] == '|') {
|
|
278
|
+
out.emplace_back(s.substr(start, i - start));
|
|
279
|
+
start = i + 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
inline std::string_view trim(std::string_view s) {
|
|
286
|
+
while (!s.empty() && (s.front() == ' ' || s.front() == '\t' || s.front() == '\r')) {
|
|
287
|
+
s.remove_prefix(1);
|
|
288
|
+
}
|
|
289
|
+
while (!s.empty() && (s.back() == ' ' || s.back() == '\t' || s.back() == '\r')) {
|
|
290
|
+
s.remove_suffix(1);
|
|
291
|
+
}
|
|
292
|
+
return s;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Tokenise a rule line: whitespace-split, then strip exactly one
|
|
296
|
+
// leading and one trailing ``"`` per token (mirrors Apache Commons
|
|
297
|
+
// Codec ``DaitchMokotoffSoundex.stripQuotes``).
|
|
298
|
+
inline std::vector<std::string> tokenise(std::string_view line) {
|
|
299
|
+
std::vector<std::string> out;
|
|
300
|
+
std::size_t i = 0;
|
|
301
|
+
while (i < line.size()) {
|
|
302
|
+
while (i < line.size() && (line[i] == ' ' || line[i] == '\t')) ++i;
|
|
303
|
+
if (i >= line.size()) break;
|
|
304
|
+
const std::size_t start = i;
|
|
305
|
+
while (i < line.size() && line[i] != ' ' && line[i] != '\t') ++i;
|
|
306
|
+
std::string tok(line.substr(start, i - start));
|
|
307
|
+
if (!tok.empty() && tok.front() == '"') tok.erase(0, 1);
|
|
308
|
+
if (!tok.empty() && tok.back() == '"') tok.pop_back();
|
|
309
|
+
out.push_back(std::move(tok));
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
inline CompiledTables parse_rules() {
|
|
315
|
+
CompiledTables t;
|
|
316
|
+
std::string_view src = kRules;
|
|
317
|
+
bool in_block = false;
|
|
318
|
+
std::size_t i = 0;
|
|
319
|
+
while (i < src.size()) {
|
|
320
|
+
std::size_t end = src.find('\n', i);
|
|
321
|
+
if (end == std::string_view::npos) end = src.size();
|
|
322
|
+
std::string_view line = src.substr(i, end - i);
|
|
323
|
+
i = end + 1;
|
|
324
|
+
|
|
325
|
+
if (in_block) {
|
|
326
|
+
if (line.find("*/") != std::string_view::npos) in_block = false;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
auto trimmed = trim(line);
|
|
330
|
+
if (trimmed.size() >= 2 && trimmed[0] == '/' && trimmed[1] == '*') {
|
|
331
|
+
// single- or multi-line block comment
|
|
332
|
+
if (trimmed.find("*/") == std::string_view::npos) in_block = true;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
// strip ``// ...`` end-of-line comment
|
|
336
|
+
auto cmt = trimmed.find("//");
|
|
337
|
+
if (cmt != std::string_view::npos) trimmed = trimmed.substr(0, cmt);
|
|
338
|
+
trimmed = trim(trimmed);
|
|
339
|
+
if (trimmed.empty()) continue;
|
|
340
|
+
|
|
341
|
+
if (trimmed.find('=') != std::string_view::npos) {
|
|
342
|
+
// folding: ``<accented>=<ascii>`` where ``<accented>`` is a single
|
|
343
|
+
// codepoint encoded in the rule file as UTF-8 — decode it to a
|
|
344
|
+
// codepoint key at parse time.
|
|
345
|
+
const auto eq = trimmed.find('=');
|
|
346
|
+
const auto from = trimmed.substr(0, eq);
|
|
347
|
+
const auto to = trimmed.substr(eq + 1);
|
|
348
|
+
if (!from.empty() && to.size() == 1) {
|
|
349
|
+
std::size_t consumed = 0;
|
|
350
|
+
const std::uint32_t cp = decode_one(from, 0, consumed);
|
|
351
|
+
if (consumed == from.size()) {
|
|
352
|
+
t.foldings.emplace(cp, to.front());
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const auto parts = tokenise(trimmed);
|
|
359
|
+
if (parts.size() != 4) continue;
|
|
360
|
+
Rule r;
|
|
361
|
+
r.pattern = parts[0];
|
|
362
|
+
r.r_start = split_alts(parts[1]);
|
|
363
|
+
r.r_vowel = split_alts(parts[2]);
|
|
364
|
+
r.r_other = split_alts(parts[3]);
|
|
365
|
+
if (r.pattern.empty()) continue;
|
|
366
|
+
const auto first = static_cast<unsigned char>(r.pattern.front());
|
|
367
|
+
t.by_first_byte[first].push_back(std::move(r));
|
|
368
|
+
}
|
|
369
|
+
for (auto& bucket : t.by_first_byte) {
|
|
370
|
+
std::stable_sort(bucket.begin(), bucket.end(),
|
|
371
|
+
[](const Rule& a, const Rule& b) {
|
|
372
|
+
return a.pattern.size() > b.pattern.size();
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return t;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
inline const CompiledTables& tables() {
|
|
379
|
+
static const CompiledTables t = parse_rules();
|
|
380
|
+
return t;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Lower-case ASCII fold, mirroring Apache Commons Codec
|
|
384
|
+
// ``Character.toLowerCase`` for the ASCII range.
|
|
385
|
+
inline char to_lower_ascii(char c) {
|
|
386
|
+
return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - ('A' - 'A') + 32) : c;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
inline bool is_ascii_letter(char c) {
|
|
390
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Decode the next UTF-8 codepoint at ``pos``. Returns the codepoint
|
|
394
|
+
// and updates ``consumed`` with the byte count; on malformed input
|
|
395
|
+
// consumes one byte and returns the raw byte value (so non-letter
|
|
396
|
+
// junk is silently dropped by the caller's letter check).
|
|
397
|
+
inline std::uint32_t decode_one(std::string_view s, std::size_t pos,
|
|
398
|
+
std::size_t& consumed) {
|
|
399
|
+
const auto b0 = static_cast<std::uint8_t>(s[pos]);
|
|
400
|
+
if (b0 < 0x80) { consumed = 1; return b0; }
|
|
401
|
+
if ((b0 & 0xE0) == 0xC0 && pos + 1 < s.size()) {
|
|
402
|
+
consumed = 2;
|
|
403
|
+
return ((b0 & 0x1F) << 6) |
|
|
404
|
+
(static_cast<std::uint8_t>(s[pos + 1]) & 0x3F);
|
|
405
|
+
}
|
|
406
|
+
if ((b0 & 0xF0) == 0xE0 && pos + 2 < s.size()) {
|
|
407
|
+
consumed = 3;
|
|
408
|
+
return ((b0 & 0x0F) << 12) |
|
|
409
|
+
((static_cast<std::uint8_t>(s[pos + 1]) & 0x3F) << 6) |
|
|
410
|
+
(static_cast<std::uint8_t>(s[pos + 2]) & 0x3F);
|
|
411
|
+
}
|
|
412
|
+
if ((b0 & 0xF8) == 0xF0 && pos + 3 < s.size()) {
|
|
413
|
+
consumed = 4;
|
|
414
|
+
return ((b0 & 0x07) << 18) |
|
|
415
|
+
((static_cast<std::uint8_t>(s[pos + 1]) & 0x3F) << 12) |
|
|
416
|
+
((static_cast<std::uint8_t>(s[pos + 2]) & 0x3F) << 6) |
|
|
417
|
+
(static_cast<std::uint8_t>(s[pos + 3]) & 0x3F);
|
|
418
|
+
}
|
|
419
|
+
consumed = 1;
|
|
420
|
+
return b0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
inline bool is_letter_cp(std::uint32_t cp) {
|
|
424
|
+
// ASCII letters first; then a permissive sweep over the codepoints
|
|
425
|
+
// the upstream rule / folding files care about. Anything not in
|
|
426
|
+
// either set is treated as non-letter and dropped during cleanup —
|
|
427
|
+
// matches Apache Commons Codec's ``Character.isLetter`` for the
|
|
428
|
+
// surnames this encoder targets.
|
|
429
|
+
if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) return true;
|
|
430
|
+
return cp >= 0x00C0; // Latin Supplement and beyond
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
inline bool is_whitespace_cp(std::uint32_t cp) {
|
|
434
|
+
return cp == ' ' || cp == '\t' || cp == '\n' || cp == '\r';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// To-lower for Latin-1 / Latin-Extended-A characters that the
|
|
438
|
+
// folding table keys are lower-case for already. Returns the new
|
|
439
|
+
// codepoint.
|
|
440
|
+
inline std::uint32_t lower_cp(std::uint32_t cp) {
|
|
441
|
+
if (cp >= 'A' && cp <= 'Z') return cp + 32;
|
|
442
|
+
if (cp >= 0x00C0 && cp <= 0x00DE && cp != 0x00D7) return cp + 32;
|
|
443
|
+
// Latin-Extended-A even codepoints upper, odd lower (mostly).
|
|
444
|
+
if (cp >= 0x0100 && cp <= 0x017F && (cp & 1) == 0) return cp + 1;
|
|
445
|
+
return cp;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Encode one codepoint as UTF-8 onto ``out``. Pure helper, no state.
|
|
449
|
+
inline void append_cp_as_utf8(std::string& out, std::uint32_t cp) {
|
|
450
|
+
if (cp < 0x80) {
|
|
451
|
+
out.push_back(static_cast<char>(cp));
|
|
452
|
+
} else if (cp < 0x800) {
|
|
453
|
+
out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
|
|
454
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
455
|
+
} else if (cp < 0x10000) {
|
|
456
|
+
out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
|
|
457
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
458
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
459
|
+
} else {
|
|
460
|
+
out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
|
|
461
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
|
|
462
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
463
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Cleanup takes the codepoint stream the dispatch layer already
|
|
468
|
+
// produced (no UTF-8 round-trip on the input side). The output is a
|
|
469
|
+
// UTF-8 byte string because the rule matching loop is byte-level —
|
|
470
|
+
// it walks ``cleaned[pos]`` looking up the per-first-byte bucket.
|
|
471
|
+
// Folding turns each codepoint in the fold table into a single ASCII
|
|
472
|
+
// byte; unfolded codepoints stay as their natural UTF-8 encoding so
|
|
473
|
+
// the few non-ASCII rule patterns (``ţ``, ``ț``, ``ę``, ``ą``)
|
|
474
|
+
// match.
|
|
475
|
+
inline std::string cleanup(const std::vector<std::uint32_t>& input,
|
|
476
|
+
bool folding) {
|
|
477
|
+
const auto& t = tables();
|
|
478
|
+
std::string out;
|
|
479
|
+
out.reserve(input.size());
|
|
480
|
+
for (auto cp : input) {
|
|
481
|
+
if (is_whitespace_cp(cp) || !is_letter_cp(cp)) continue;
|
|
482
|
+
cp = lower_cp(cp);
|
|
483
|
+
if (folding) {
|
|
484
|
+
auto it = t.foldings.find(cp);
|
|
485
|
+
if (it != t.foldings.end()) {
|
|
486
|
+
out.push_back(it->second);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
append_cp_as_utf8(out, cp);
|
|
491
|
+
}
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Returns true if the byte at ``pos`` in ``s`` is an ASCII vowel.
|
|
496
|
+
// The encoder peels accents via folding before rule application, so
|
|
497
|
+
// the only vowels that reach the next-char check are ASCII.
|
|
498
|
+
inline bool is_ascii_vowel(char c) {
|
|
499
|
+
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
struct Branch {
|
|
503
|
+
std::string text;
|
|
504
|
+
std::string last_replacement;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
inline void process_replacement(Branch& b, const std::string& replacement,
|
|
508
|
+
bool force_append) {
|
|
509
|
+
const bool append = b.last_replacement.empty() ||
|
|
510
|
+
!b.last_replacement.ends_with(replacement) ||
|
|
511
|
+
force_append;
|
|
512
|
+
if (append && b.text.size() < kMaxLength) {
|
|
513
|
+
b.text.append(replacement);
|
|
514
|
+
if (b.text.size() > kMaxLength) b.text.resize(kMaxLength);
|
|
515
|
+
}
|
|
516
|
+
b.last_replacement = replacement;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
inline std::string daitch_mokotoff_impl(const std::vector<std::uint32_t>& input,
|
|
520
|
+
bool branching, bool folding) {
|
|
521
|
+
const auto& t = tables();
|
|
522
|
+
const std::string cleaned = cleanup(input, folding);
|
|
523
|
+
std::vector<Branch> current;
|
|
524
|
+
current.push_back({}); // single empty branch
|
|
525
|
+
|
|
526
|
+
char last_char = '\0';
|
|
527
|
+
std::size_t pos = 0;
|
|
528
|
+
while (pos < cleaned.size()) {
|
|
529
|
+
const auto first = static_cast<unsigned char>(cleaned[pos]);
|
|
530
|
+
const auto& bucket = t.by_first_byte[first];
|
|
531
|
+
if (bucket.empty()) {
|
|
532
|
+
// Stay on this position one byte; matches Apache Commons Codec's
|
|
533
|
+
// behaviour of advancing one char on no-rule (it then re-loops
|
|
534
|
+
// — net effect is the same as skip).
|
|
535
|
+
++pos;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
bool matched = false;
|
|
539
|
+
for (const auto& rule : bucket) {
|
|
540
|
+
if (rule.pattern.size() > cleaned.size() - pos) continue;
|
|
541
|
+
if (std::memcmp(cleaned.data() + pos, rule.pattern.data(),
|
|
542
|
+
rule.pattern.size()) != 0) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
// Pick the replacement column.
|
|
546
|
+
const std::size_t next_index = pos + rule.pattern.size();
|
|
547
|
+
const bool at_start = (last_char == '\0');
|
|
548
|
+
const std::vector<std::string>* reps;
|
|
549
|
+
if (at_start) {
|
|
550
|
+
reps = &rule.r_start;
|
|
551
|
+
} else if (next_index < cleaned.size() &&
|
|
552
|
+
is_ascii_vowel(cleaned[next_index])) {
|
|
553
|
+
reps = &rule.r_vowel;
|
|
554
|
+
} else {
|
|
555
|
+
reps = &rule.r_other;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ``mn`` / ``nm`` always emit both digits, even when adjacent
|
|
559
|
+
// to a duplicate emission (Apache Commons Codec rule).
|
|
560
|
+
const char this_char = cleaned[pos];
|
|
561
|
+
const bool force = (last_char == 'm' && this_char == 'n') ||
|
|
562
|
+
(last_char == 'n' && this_char == 'm');
|
|
563
|
+
|
|
564
|
+
const bool branching_required = branching && reps->size() > 1;
|
|
565
|
+
std::vector<Branch> next;
|
|
566
|
+
if (branching) next.reserve(current.size() * reps->size());
|
|
567
|
+
for (auto& b : current) {
|
|
568
|
+
for (const auto& rep : *reps) {
|
|
569
|
+
if (branching_required) {
|
|
570
|
+
Branch nb = b;
|
|
571
|
+
process_replacement(nb, rep, force);
|
|
572
|
+
next.push_back(std::move(nb));
|
|
573
|
+
} else {
|
|
574
|
+
process_replacement(b, rep, force);
|
|
575
|
+
if (!branching) break;
|
|
576
|
+
next.push_back(b);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (branching) {
|
|
581
|
+
// Dedupe by current text (LinkedHashSet semantics in Java).
|
|
582
|
+
std::vector<Branch> uniq;
|
|
583
|
+
uniq.reserve(next.size());
|
|
584
|
+
for (auto& nb : next) {
|
|
585
|
+
bool found = false;
|
|
586
|
+
for (const auto& u : uniq) if (u.text == nb.text) { found = true; break; }
|
|
587
|
+
if (!found) uniq.push_back(std::move(nb));
|
|
588
|
+
}
|
|
589
|
+
current.swap(uniq);
|
|
590
|
+
}
|
|
591
|
+
pos += rule.pattern.size();
|
|
592
|
+
last_char = this_char;
|
|
593
|
+
matched = true;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
if (!matched) {
|
|
597
|
+
++pos;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Pad each branch with '0' to length kMaxLength and join with '|'.
|
|
602
|
+
std::string out;
|
|
603
|
+
for (std::size_t k = 0; k < current.size(); ++k) {
|
|
604
|
+
while (current[k].text.size() < kMaxLength) current[k].text.push_back('0');
|
|
605
|
+
if (k != 0) out.push_back('|');
|
|
606
|
+
out.append(current[k].text);
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
} // namespace daitch_mokotoff_detail
|
|
612
|
+
|
|
613
|
+
// Encode ``input`` as a Daitch-Mokotoff Soundex set. The engine runs
|
|
614
|
+
// in codepoint space — Python ``str`` storage is fixed-width per
|
|
615
|
+
// string (``PyUnicode_KIND`` is 1/2/4 bytes per codepoint), so the
|
|
616
|
+
// dispatch wrapper widens that storage straight into
|
|
617
|
+
// ``std::vector<std::uint32_t>`` without UTF-8 round-tripping on the
|
|
618
|
+
// input side. The output is a UTF-8 byte string of 6-digit codes.
|
|
619
|
+
//
|
|
620
|
+
// When ``branching`` is true (default) the result is a ``|``-
|
|
621
|
+
// separated list of distinct codes; otherwise a single code.
|
|
622
|
+
// ``folding`` (default true) applies the ASCII fold table for
|
|
623
|
+
// accented characters before encoding.
|
|
624
|
+
inline std::string daitch_mokotoff(const std::vector<std::uint32_t>& input,
|
|
625
|
+
bool branching = true,
|
|
626
|
+
bool folding = true) {
|
|
627
|
+
return daitch_mokotoff_detail::daitch_mokotoff_impl(
|
|
628
|
+
input, branching, folding);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
} // namespace stride_align::phonetic
|