1use std::path::Path;
11use std::str::FromStr;
12use std::sync::{Mutex, MutexGuard};
13
14use serde_json::Value;
15
16use lindera::dictionary::{Dictionary, UserDictionary};
17use lindera::mode::Mode;
18use lindera::segmenter::Segmenter;
19use lindera_analysis::tokenizer::{Tokenizer, TokenizerBuilder};
20use lindera_analysis::worker::AnalysisWorker;
21
22use crate::error::CoreResult;
23use crate::token::{SurfaceView, TokenView};
24
25pub struct CoreTokenizerBuilder {
31 inner: TokenizerBuilder,
33}
34
35impl CoreTokenizerBuilder {
36 pub fn new() -> CoreResult<Self> {
38 Ok(Self {
39 inner: TokenizerBuilder::new()?,
40 })
41 }
42
43 pub fn from_file(file_path: &Path) -> CoreResult<Self> {
45 Ok(Self {
46 inner: TokenizerBuilder::from_file(file_path)?,
47 })
48 }
49
50 pub fn set_mode(&mut self, mode: &str) -> CoreResult<&mut Self> {
52 let mode = Mode::from_str(mode)?;
53 self.inner.set_segmenter_mode(&mode);
54 Ok(self)
55 }
56
57 pub fn set_dictionary(&mut self, uri: &str) -> &mut Self {
59 self.inner.set_segmenter_dictionary(uri);
60 self
61 }
62
63 pub fn set_user_dictionary(&mut self, uri: &str) -> &mut Self {
65 self.inner.set_segmenter_user_dictionary(uri);
66 self
67 }
68
69 pub fn set_keep_whitespace(&mut self, keep_whitespace: bool) -> &mut Self {
71 self.inner.set_segmenter_keep_whitespace(keep_whitespace);
72 self
73 }
74
75 pub fn append_character_filter(&mut self, kind: &str, args: &Value) -> &mut Self {
77 self.inner.append_character_filter(kind, args);
78 self
79 }
80
81 pub fn append_token_filter(&mut self, kind: &str, args: &Value) -> &mut Self {
83 self.inner.append_token_filter(kind, args);
84 self
85 }
86
87 pub fn build(&self) -> CoreResult<CoreTokenizer> {
89 Ok(CoreTokenizer::from_tokenizer(self.inner.build()?))
90 }
91}
92
93pub struct CoreTokenizer {
107 worker: Mutex<AnalysisWorker>,
110}
111
112fn lock_worker(mutex: &Mutex<AnalysisWorker>) -> MutexGuard<'_, AnalysisWorker> {
127 match mutex.lock() {
128 Ok(guard) => guard,
129 Err(poisoned) => {
130 let mut guard = poisoned.into_inner();
131 guard.reset();
132 guard
133 }
134 }
135}
136
137impl CoreTokenizer {
138 pub fn from_segmenter(
140 mode: &str,
141 dictionary: Dictionary,
142 user_dictionary: Option<UserDictionary>,
143 ) -> CoreResult<Self> {
144 let mode = Mode::from_str(mode)?;
145 let segmenter = Segmenter::new(mode, dictionary, user_dictionary);
146 Ok(Self::from_tokenizer(Tokenizer::new(segmenter)))
147 }
148
149 pub fn from_tokenizer(tokenizer: Tokenizer) -> Self {
152 Self {
153 worker: Mutex::new(tokenizer.into_worker()),
154 }
155 }
156
157 pub fn tokenize(&self, text: &str) -> CoreResult<Vec<TokenView>> {
162 let mut worker = lock_worker(&self.worker);
163 let tokens = worker.tokenize(text)?;
164 Ok(tokens.into_iter().map(TokenView::from_token).collect())
165 }
166
167 pub fn tokenize_surfaces(&self, text: &str) -> CoreResult<Vec<SurfaceView>> {
178 let mut worker = lock_worker(&self.worker);
179 let tokens = worker.tokenize(text)?;
180 Ok(tokens.into_iter().map(SurfaceView::from_token).collect())
181 }
182
183 pub fn tokenize_nbest(
188 &self,
189 text: &str,
190 n: usize,
191 unique: bool,
192 cost_threshold: Option<i64>,
193 ) -> CoreResult<Vec<(Vec<TokenView>, i64)>> {
194 let mut worker = lock_worker(&self.worker);
195 let results = worker.tokenize_nbest(text, n, unique, cost_threshold)?;
196 Ok(results
197 .into_iter()
198 .map(|(tokens, cost)| {
199 (
200 tokens.into_iter().map(TokenView::from_token).collect(),
201 cost,
202 )
203 })
204 .collect())
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn builder_new_succeeds() {
214 assert!(CoreTokenizerBuilder::new().is_ok());
215 }
216
217 #[test]
218 fn set_mode_accepts_known_modes() {
219 let mut builder = CoreTokenizerBuilder::new().expect("builder");
220 assert!(builder.set_mode("normal").is_ok());
221 assert!(builder.set_mode("decompose").is_ok());
222 }
223
224 #[test]
225 fn set_mode_rejects_unknown_mode() {
226 let mut builder = CoreTokenizerBuilder::new().expect("builder");
227 assert!(builder.set_mode("definitely-not-a-mode").is_err());
228 }
229
230 #[test]
231 fn infallible_setters_chain() {
232 let mut builder = CoreTokenizerBuilder::new().expect("builder");
233 builder
234 .set_dictionary("embedded://ipadic")
235 .set_keep_whitespace(true)
236 .append_token_filter("japanese_compound_word", &Value::Object(Default::default()));
237 }
239
240 #[test]
246 fn core_tokenizer_is_send_and_sync() {
247 fn assert_send_sync<T: Send + Sync>() {}
248 assert_send_sync::<CoreTokenizer>();
249 assert_send_sync::<CoreTokenizerBuilder>();
250 }
251
252 #[cfg(feature = "embed-ipadic")]
253 mod with_ipadic {
254 use super::super::*;
255
256 fn ipadic_tokenizer() -> CoreTokenizer {
257 let dictionary = match lindera::dictionary::load_dictionary("embedded://ipadic") {
258 Ok(dictionary) => dictionary,
259 Err(err) => panic!("failed to load embedded IPADIC: {err}"),
260 };
261 match CoreTokenizer::from_segmenter("normal", dictionary, None) {
262 Ok(tokenizer) => tokenizer,
263 Err(err) => panic!("failed to build CoreTokenizer: {err}"),
264 }
265 }
266
267 #[test]
270 fn tokenize_repeated_calls_are_stable() {
271 let tokenizer = ipadic_tokenizer();
272 let first = match tokenizer.tokenize("すもももももももものうち") {
273 Ok(tokens) => tokens,
274 Err(err) => panic!("tokenize failed: {err}"),
275 };
276 assert!(!first.is_empty());
277 for _ in 0..100 {
278 let again = match tokenizer.tokenize("すもももももももものうち") {
279 Ok(tokens) => tokens,
280 Err(err) => panic!("tokenize failed: {err}"),
281 };
282 assert_eq!(first.len(), again.len());
283 for (a, b) in first.iter().zip(again.iter()) {
284 assert_eq!(a.surface, b.surface);
285 assert_eq!(a.byte_start, b.byte_start);
286 assert_eq!(a.byte_end, b.byte_end);
287 assert_eq!(a.details, b.details);
288 }
289 }
290 }
291
292 #[test]
294 fn tokenize_nbest_repeated_calls_are_stable() {
295 let tokenizer = ipadic_tokenizer();
296 let first = match tokenizer.tokenize_nbest("すもももももももものうち", 3, false, None)
297 {
298 Ok(results) => results,
299 Err(err) => panic!("tokenize_nbest failed: {err}"),
300 };
301 assert!(!first.is_empty());
302 for _ in 0..10 {
303 let again =
304 match tokenizer.tokenize_nbest("すもももももももものうち", 3, false, None)
305 {
306 Ok(results) => results,
307 Err(err) => panic!("tokenize_nbest failed: {err}"),
308 };
309 assert_eq!(first.len(), again.len());
310 for ((a_tokens, a_cost), (b_tokens, b_cost)) in first.iter().zip(again.iter()) {
311 assert_eq!(a_cost, b_cost);
312 assert_eq!(a_tokens.len(), b_tokens.len());
313 }
314 }
315 }
316
317 #[test]
320 fn tokenize_surfaces_matches_tokenize() {
321 let tokenizer = ipadic_tokenizer();
322 let texts = [
323 "すもももももももものうち",
324 "関西国際空港限定トートバッグ",
325 "",
326 ];
327 for _ in 0..3 {
328 for text in texts {
329 let full = match tokenizer.tokenize(text) {
330 Ok(tokens) => tokens,
331 Err(err) => panic!("tokenize failed: {err}"),
332 };
333 let surfaces = match tokenizer.tokenize_surfaces(text) {
334 Ok(views) => views,
335 Err(err) => panic!("tokenize_surfaces failed: {err}"),
336 };
337 assert_eq!(full.len(), surfaces.len(), "count for {text:?}");
338 for (a, b) in full.iter().zip(surfaces.iter()) {
339 assert_eq!(a.surface, b.surface);
340 assert_eq!(a.byte_start, b.byte_start);
341 assert_eq!(a.byte_end, b.byte_end);
342 }
343 }
344 }
345 }
346
347 #[test]
351 fn tokenize_recovers_from_poisoned_lock() {
352 use std::sync::Arc;
353
354 let tokenizer = Arc::new(ipadic_tokenizer());
355 let expected = match tokenizer.tokenize("すもももももももものうち") {
356 Ok(tokens) => tokens,
357 Err(err) => panic!("tokenize failed: {err}"),
358 };
359
360 let poisoner = Arc::clone(&tokenizer);
362 let result = std::thread::spawn(move || {
363 let _guard = lock_worker(&poisoner.worker);
364 panic!("intentional panic to poison the worker lock");
365 })
366 .join();
367 assert!(result.is_err(), "poisoning thread must have panicked");
368 assert!(tokenizer.worker.is_poisoned(), "lock must be poisoned");
369
370 let after = match tokenizer.tokenize("すもももももももものうち") {
372 Ok(tokens) => tokens,
373 Err(err) => panic!("tokenize after poison failed: {err}"),
374 };
375 assert_eq!(expected.len(), after.len());
376 for (a, b) in expected.iter().zip(after.iter()) {
377 assert_eq!(a.surface, b.surface);
378 }
379 }
380 }
381}