use std::sync::Arc; use super::*; const SURFACE_PROGRAM_TURKISH: u8 = 0; const SURFACE_PROGRAM_CODE: u8 = 1; const CODE_PROGRAM_MAX_UNITS: usize = 256; /// Compact unit used only by the exact surface-ID production path. /// /// Cuts live in one document-local contiguous array instead of one heap /// allocation per unit. Rich analysis metadata remains exclusive to the /// public rich tokenizer path. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct FlatSurfaceUnit { pub(crate) span: ByteSpan, pub(crate) kind: LexicalKind, pub(crate) mode: TokenMode, pub(crate) status: TokenStatus, cut_start: usize, cut_len: usize, } impl FlatSurfaceUnit { fn new(span: ByteSpan, kind: LexicalKind, mode: TokenMode, status: TokenStatus) -> Self { Self { span, kind, mode, status, cut_start: 0, cut_len: 0, } } pub(crate) fn cuts<'a>(&self, cuts: &'a [u64]) -> Result<&'a [u64], TokenizerError> { let end = self .cut_start .checked_add(self.cut_len) .ok_or(TokenizerError::LengthOverflow("flat surface cut range"))?; cuts.get(self.cut_start..end) .ok_or(TokenizerError::InvalidTrainingEncoding( "flat surface cut range is invalid", )) } } #[derive(Clone, Debug)] pub(crate) struct SurfaceProgramUse { pub(crate) start_unit: usize, pub(crate) end_unit: usize, pub(crate) program: Arc, } pub(crate) fn scan_fixed_units( raw: &[u8], requested: TokenizerMode, ) -> Result, TokenizerError> { let mut units = Vec::new(); for_each_fixed_unit(raw, requested, |unit| { units.push(unit); Ok(()) })?; Ok(units) } fn for_each_fixed_unit( raw: &[u8], requested: TokenizerMode, mut consume: impl FnMut(FlatSurfaceUnit) -> Result<(), TokenizerError>, ) -> Result<(), TokenizerError> { if requested == TokenizerMode::Auto { return Err(TokenizerError::InvalidConfiguration( "fixed lexical stream cannot run in auto mode", )); } if requested == TokenizerMode::Turkish { for lexical in nedo_core::scan_lexical_spans(raw) { let (span, kind) = lexical?; let mode = effective_mode(span, kind, &[], requested); for_each_turkish_piece(raw, span, kind, mode, &mut consume)?; } return Ok(()); } let mut boundaries = Vec::with_capacity(8); for lexical in nedo_core::scan_lexical_spans(raw) { let (span, kind) = lexical?; let bytes = unit_bytes(raw, span)?; let mode = effective_mode(span, kind, &[], requested); boundaries.clear(); boundaries.push(span.start); boundaries.push(span.end); if kind == LexicalKind::Punctuation { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } else if mode == TokenMode::Turkish && kind == LexicalKind::Symbol && bytes.iter().all(u8::is_ascii_punctuation) { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } if mode == TokenMode::Code && matches!(kind, LexicalKind::Word | LexicalKind::Number) { let text = std::str::from_utf8(bytes).map_err(|_| TokenizerError::InvalidUtf8Unit)?; for relative in identifier_cuts(text) { boundaries.push( span.start .checked_add( u64::try_from(relative).map_err(|_| { TokenizerError::LengthOverflow("identifier boundary") })?, ) .ok_or(TokenizerError::LengthOverflow("identifier boundary"))?, ); } } if boundaries.len() > 2 { boundaries.sort_unstable(); boundaries.dedup(); } let status = initial_status(mode); for window in boundaries.windows(2) { consume(FlatSurfaceUnit::new( ByteSpan { start: window[0], end: window[1], }, kind, mode, status, ))?; } } Ok(()) } fn for_each_turkish_piece( raw: &[u8], span: ByteSpan, kind: LexicalKind, mode: TokenMode, consume: &mut impl FnMut(FlatSurfaceUnit) -> Result<(), TokenizerError>, ) -> Result<(), TokenizerError> { let bytes = unit_bytes(raw, span)?; let split_scalars = kind == LexicalKind::Punctuation || (mode == TokenMode::Turkish && kind == LexicalKind::Symbol && bytes.iter().all(u8::is_ascii_punctuation)); let status = initial_status(mode); if !split_scalars { return consume(FlatSurfaceUnit::new(span, kind, mode, status)); } let text = std::str::from_utf8(bytes).map_err(|_| TokenizerError::InvalidUtf8Unit)?; let mut starts = text.char_indices().map(|(index, _)| index).peekable(); while let Some(relative_start) = starts.next() { let relative_end = starts.peek().copied().unwrap_or(bytes.len()); let start = span .start .checked_add( u64::try_from(relative_start) .map_err(|_| TokenizerError::LengthOverflow("structural boundary"))?, ) .ok_or(TokenizerError::LengthOverflow("structural boundary"))?; let end = span .start .checked_add( u64::try_from(relative_end) .map_err(|_| TokenizerError::LengthOverflow("structural boundary"))?, ) .ok_or(TokenizerError::LengthOverflow("structural boundary"))?; consume(FlatSurfaceUnit::new( ByteSpan { start, end }, kind, mode, status, ))?; } Ok(()) } #[allow(clippy::too_many_arguments)] pub(crate) fn try_append_cached_turkish_document( raw: &[u8], newline: bool, vocabulary: &SurfaceVocabulary, maximum_sentence_tokens: usize, maximum_chars: usize, cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result { if cache.surface_program_entries == 0 { return Ok(false); } let id_start = ids.len(); let length_output_start = lengths.len(); let length_start = vocabulary.begin_cached_surface_document(ids, lengths)?; let mut stream = CachedTurkishStream { raw, vocabulary, maximum_sentence_tokens, maximum_chars, cache, ids, lengths, segment_start: None, segment_end: 0, segment_tokens: 0, pending_whitespace: None, missed: false, }; for_each_fixed_unit(raw, TokenizerMode::Turkish, |unit| stream.consume(unit))?; stream.finish()?; if stream.missed { stream.ids.truncate(id_start); stream.lengths.truncate(length_output_start); return Ok(false); } vocabulary.finish_cached_surface_document( raw.len(), newline, length_start, stream.ids, stream.lengths, )?; Ok(true) } #[allow(clippy::too_many_arguments)] pub(crate) fn try_append_cached_auto_document( scan: &CompactScanResult, newline: bool, vocabulary: &SurfaceVocabulary, maximum_sentence_tokens: usize, maximum_chars: usize, cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result { if cache.surface_program_entries == 0 { return Ok(false); } let raw = scan.raw(); let id_start = ids.len(); let length_output_start = lengths.len(); let length_start = vocabulary.begin_cached_surface_document(ids, lengths)?; let mut stream = CachedTurkishStream { raw, vocabulary, maximum_sentence_tokens, maximum_chars, cache, ids, lengths, segment_start: None, segment_end: 0, segment_tokens: 0, pending_whitespace: None, missed: false, }; for_each_compact_unit_without_code_spans(scan, TokenizerMode::Auto, |unit| { stream.consume(unit) })?; stream.finish()?; if stream.missed { stream.ids.truncate(id_start); stream.lengths.truncate(length_output_start); return Ok(false); } vocabulary.finish_cached_surface_document( raw.len(), newline, length_start, stream.ids, stream.lengths, )?; Ok(true) } #[allow(clippy::too_many_arguments)] pub(crate) fn try_append_cached_auto_raw_document( raw: &[u8], newline: bool, vocabulary: &SurfaceVocabulary, maximum_sentence_tokens: usize, maximum_chars: usize, detect_unmarked_code: bool, cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result { if cache.surface_program_entries == 0 { return Ok(false); } let id_start = ids.len(); let output_length_start = lengths.len(); let cache_hits_start = cache.hits; let segment_hits_start = cache.segment_program_hits; let length_start = vocabulary.begin_cached_surface_document(ids, lengths)?; let (hints, missed) = { let mut stream = CachedTurkishStream { raw, vocabulary, maximum_sentence_tokens, maximum_chars, cache, ids, lengths, segment_start: None, segment_end: 0, segment_tokens: 0, pending_whitespace: None, missed: false, }; let mut lexical = nedo_core::scan_lexical_spans_with_hints(raw); for item in lexical.by_ref() { let (span, kind) = item?; let mode = effective_mode(span, kind, &[], TokenizerMode::Auto); for_each_turkish_piece(raw, span, kind, mode, &mut |unit| stream.consume(unit))?; } let hints = lexical.into_hints(); stream.finish()?; (hints, stream.missed) }; let has_code = hints.has_backtick() || (detect_unmarked_code && hints.may_contain_unmarked_code()); if missed || has_code { ids.truncate(id_start); lengths.truncate(output_length_start); cache.hits = cache_hits_start; cache.segment_program_hits = segment_hits_start; return Ok(false); } vocabulary.finish_cached_surface_document(raw.len(), newline, length_start, ids, lengths)?; Ok(true) } struct CachedTurkishStream<'a> { raw: &'a [u8], vocabulary: &'a SurfaceVocabulary, maximum_sentence_tokens: usize, maximum_chars: usize, cache: &'a mut FlatAnalysisCache, ids: &'a mut Vec, lengths: &'a mut Vec, segment_start: Option, segment_end: u64, segment_tokens: usize, pending_whitespace: Option, missed: bool, } impl CachedTurkishStream<'_> { fn consume(&mut self, unit: FlatSurfaceUnit) -> Result<(), TokenizerError> { if self.missed { return Ok(()); } if unit.mode != TokenMode::Turkish || matches!( unit.kind, LexicalKind::LineBreak | LexicalKind::Control | LexicalKind::Opaque ) { self.flush_segment()?; if !self.missed { self.encode_direct(unit)?; } return Ok(()); } if unit.kind == LexicalKind::Whitespace { if self.segment_start.is_some() { if let Some(pending) = self.pending_whitespace.as_mut() { if pending.span.end == unit.span.start && pending.kind == unit.kind && pending.mode == unit.mode && pending.status == unit.status { pending.span.end = unit.span.end; } else { return Err(TokenizerError::InvalidTrainingEncoding( "non-contiguous cached trailing whitespace", )); } } else { self.pending_whitespace = Some(unit); } } else { self.encode_direct(unit)?; } return Ok(()); } if self.segment_start.is_none() { self.segment_start = Some(unit.span.start); } else { self.pending_whitespace = None; } self.segment_end = unit.span.end; self.segment_tokens = self.segment_tokens.saturating_add(1); if is_sentence_boundary(unit.kind, self.raw, unit.span)? || self.segment_tokens >= self.maximum_sentence_tokens { self.flush_segment()?; } Ok(()) } fn finish(&mut self) -> Result<(), TokenizerError> { if !self.missed { self.flush_segment()?; } Ok(()) } fn flush_segment(&mut self) -> Result<(), TokenizerError> { let Some(start) = self.segment_start.take() else { return Ok(()); }; let exact = unit_bytes( self.raw, ByteSpan { start, end: self.segment_end, }, )?; if !append_borrowed_surface_program( self.cache, exact, SURFACE_PROGRAM_TURKISH, self.ids, self.lengths, )? { self.missed = true; return Ok(()); } self.cache.segment_program_hits = self.cache.segment_program_hits.saturating_add(1); self.cache.hits = self .cache .hits .saturating_add(u64::try_from(self.segment_tokens).unwrap_or(u64::MAX)); self.segment_end = 0; self.segment_tokens = 0; if let Some(trailing) = self.pending_whitespace.take() { self.encode_direct(trailing)?; } Ok(()) } fn encode_direct(&mut self, unit: FlatSurfaceUnit) -> Result<(), TokenizerError> { self.vocabulary.encode_flat_unit_direct( self.raw, &unit, self.maximum_chars, self.ids, self.lengths, ) } } #[allow(clippy::too_many_arguments)] pub(crate) fn append_cached_code_document( raw: &[u8], newline: bool, vocabulary: &SurfaceVocabulary, maximum_chars: usize, cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result<(), TokenizerError> { if cache.surface_program_entries != 0 { let id_start = ids.len(); let output_length_start = lengths.len(); let length_start = vocabulary.begin_cached_surface_document(ids, lengths)?; if try_append_cached_code_lines(raw, cache, ids, lengths)? { vocabulary.finish_cached_surface_document( raw.len(), newline, length_start, ids, lengths, )?; return Ok(()); } ids.truncate(id_start); lengths.truncate(output_length_start); } let length_start = vocabulary.begin_cached_surface_document(ids, lengths)?; let mut stream = CachedCodeStream { raw, vocabulary, maximum_chars, cache, ids, lengths, units: Vec::with_capacity(64), chunk_start: 0, chunk_end: 0, }; for_each_fixed_unit(raw, TokenizerMode::Code, |unit| stream.consume(unit))?; stream.flush()?; vocabulary.finish_cached_surface_document( raw.len(), newline, length_start, stream.ids, stream.lengths, ) } fn try_append_cached_code_lines( raw: &[u8], cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result { let mut start = 0_usize; let mut hit_lines = 0_u64; while start < raw.len() { let end = next_code_line_end(raw, start); let exact = &raw[start..end]; if !append_borrowed_surface_program(cache, exact, SURFACE_PROGRAM_CODE, ids, lengths)? { return Ok(false); } hit_lines = hit_lines.saturating_add(1); start = end; } cache.segment_program_hits = cache.segment_program_hits.saturating_add(hit_lines); cache.hits = cache.hits.saturating_add(hit_lines); Ok(true) } #[inline] fn next_code_line_end(raw: &[u8], start: usize) -> usize { let mut index = start; while index < raw.len() { match raw[index] { b'\n' => return index + 1, b'\r' => { return index + 1 + usize::from(raw.get(index + 1) == Some(&b'\n')); } 0xc2 if raw.get(index + 1) == Some(&0x85) => return index + 2, 0xe2 if raw.get(index + 1) == Some(&0x80) && matches!(raw.get(index + 2), Some(0xa8 | 0xa9)) => { return index + 3; } _ => index += 1, } } raw.len() } struct CachedCodeStream<'a> { raw: &'a [u8], vocabulary: &'a SurfaceVocabulary, maximum_chars: usize, cache: &'a mut FlatAnalysisCache, ids: &'a mut Vec, lengths: &'a mut Vec, units: Vec, chunk_start: u64, chunk_end: u64, } impl CachedCodeStream<'_> { fn consume(&mut self, unit: FlatSurfaceUnit) -> Result<(), TokenizerError> { if self.units.is_empty() { self.chunk_start = unit.span.start; } self.chunk_end = unit.span.end; let boundary = unit.kind == LexicalKind::LineBreak || unit.mode == TokenMode::Opaque || self.units.len().saturating_add(1) >= CODE_PROGRAM_MAX_UNITS; self.units.push(unit); if boundary { self.flush()?; } Ok(()) } fn flush(&mut self) -> Result<(), TokenizerError> { if self.units.is_empty() { return Ok(()); } let exact = unit_bytes( self.raw, ByteSpan { start: self.chunk_start, end: self.chunk_end, }, )?; if let Some(program) = cached_surface_program(self.cache, exact, SURFACE_PROGRAM_CODE) { if program.surface_ids.len() != program.surface_lengths.len() { return Err(TokenizerError::InvalidTrainingEncoding( "cached code surface program cardinalities differ", )); } self.cache.segment_program_hits = self.cache.segment_program_hits.saturating_add(1); self.cache.hits = self .cache .hits .saturating_add(u64::try_from(self.units.len()).unwrap_or(u64::MAX)); self.ids.extend_from_slice(&program.surface_ids); self.lengths.extend_from_slice(&program.surface_lengths); } else { let mut program_ids = Vec::new(); let mut program_lengths = Vec::new(); self.vocabulary.encode_flat_range_into( self.raw, &self.units, &[], 0..self.units.len(), self.maximum_chars, false, &mut program_ids, &mut program_lengths, )?; self.ids.extend_from_slice(&program_ids); self.lengths.extend_from_slice(&program_lengths); let fingerprint = surface_program_fingerprint(exact, SURFACE_PROGRAM_CODE); let program = FlatSegmentProgram { tokens: Box::new([]), relative_cuts: Box::new([]), surface_ids: program_ids.into_boxed_slice(), surface_lengths: program_lengths.into_boxed_slice(), }; let _ = insert_surface_program( self.cache, SURFACE_PROGRAM_CODE, fingerprint, exact, program, ); } self.units.clear(); self.chunk_start = self.chunk_end; Ok(()) } } pub(crate) fn split_units( lexical_scan: &CompactScanResult, code_spans: &[ByteSpan], requested: TokenizerMode, ) -> Result, TokenizerError> { let spans = lexical_scan.spans(); let kinds = lexical_scan.kinds(); if spans.len() != kinds.len() { return Err(TokenizerError::Scan(ScanError::MetadataLengthMismatch { unit_count: spans.len(), kind_count: kinds.len(), })); } if code_spans.is_empty() { return split_units_without_code_spans(lexical_scan, requested); } let mut boundaries = Vec::new(); for (index, (span, kind)) in spans.iter().copied().zip(kinds.iter().copied()).enumerate() { boundaries.push(span.start); boundaries.push(span.end); let bytes = lexical_scan.unit_bytes(index)?; if kind == LexicalKind::Punctuation { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } let mode = effective_mode(span, kind, code_spans, requested); if mode == TokenMode::Turkish && kind == LexicalKind::Symbol && bytes.iter().all(u8::is_ascii_punctuation) { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } if mode == TokenMode::Code && matches!(kind, LexicalKind::Word | LexicalKind::Number) { let text = std::str::from_utf8(bytes).map_err(|_| TokenizerError::InvalidUtf8Unit)?; for relative in identifier_cuts(text) { boundaries.push( span.start .checked_add( u64::try_from(relative).map_err(|_| { TokenizerError::LengthOverflow("identifier boundary") })?, ) .ok_or(TokenizerError::LengthOverflow("identifier boundary"))?, ); } } } for span in code_spans { boundaries.push(span.start); boundaries.push(span.end); } boundaries.sort_unstable(); boundaries.dedup(); let mut units = Vec::with_capacity(boundaries.len().saturating_sub(1)); let mut source_index = 0_usize; for window in boundaries.windows(2) { let span = ByteSpan { start: window[0], end: window[1], }; while source_index + 1 < spans.len() && spans[source_index].end <= span.start { source_index += 1; } let source = spans .get(source_index) .copied() .ok_or(TokenizerError::InvalidTrainingEncoding( "flat surface source unit is absent", ))?; if span.start < source.start || span.end > source.end { return Err(TokenizerError::InvalidTrainingEncoding( "flat surface split crosses a lexical unit", )); } let kind = kinds[source_index]; let mode = effective_mode(span, kind, code_spans, requested); units.push(FlatSurfaceUnit::new(span, kind, mode, initial_status(mode))); } Ok(units) } fn split_units_without_code_spans( lexical_scan: &CompactScanResult, requested: TokenizerMode, ) -> Result, TokenizerError> { let mut units = Vec::with_capacity(lexical_scan.spans().len()); for_each_compact_unit_without_code_spans(lexical_scan, requested, |unit| { units.push(unit); Ok(()) })?; Ok(units) } fn for_each_compact_unit_without_code_spans( lexical_scan: &CompactScanResult, requested: TokenizerMode, mut consume: impl FnMut(FlatSurfaceUnit) -> Result<(), TokenizerError>, ) -> Result<(), TokenizerError> { let spans = lexical_scan.spans(); let kinds = lexical_scan.kinds(); let mut boundaries = Vec::with_capacity(8); for (index, (span, kind)) in spans.iter().copied().zip(kinds.iter().copied()).enumerate() { let mode = effective_mode(span, kind, &[], requested); boundaries.clear(); boundaries.push(span.start); boundaries.push(span.end); let bytes = lexical_scan.unit_bytes(index)?; if kind == LexicalKind::Punctuation { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } else if mode == TokenMode::Turkish && kind == LexicalKind::Symbol && bytes.iter().all(u8::is_ascii_punctuation) { add_scalar_boundaries(bytes, span.start, &mut boundaries)?; } if mode == TokenMode::Code && matches!(kind, LexicalKind::Word | LexicalKind::Number) { let text = std::str::from_utf8(bytes).map_err(|_| TokenizerError::InvalidUtf8Unit)?; for relative in identifier_cuts(text) { boundaries.push( span.start .checked_add( u64::try_from(relative).map_err(|_| { TokenizerError::LengthOverflow("identifier boundary") })?, ) .ok_or(TokenizerError::LengthOverflow("identifier boundary"))?, ); } } if boundaries.len() > 2 { boundaries.sort_unstable(); boundaries.dedup(); } let status = initial_status(mode); for window in boundaries.windows(2) { consume(FlatSurfaceUnit::new( ByteSpan { start: window[0], end: window[1], }, kind, mode, status, ))?; } } Ok(()) } const fn initial_status(mode: TokenMode) -> TokenStatus { match mode { TokenMode::Code => TokenStatus::Code, TokenMode::Opaque => TokenStatus::Opaque, TokenMode::Turkish => TokenStatus::Structural, } } pub(crate) fn apply_contextual_analysis( tokenizer: &Tokenizer<'_>, raw: &[u8], units: &mut [FlatSurfaceUnit], cuts: &mut Vec, vocabulary: &SurfaceVocabulary, use_morphology: bool, maximum_chars: usize, cache: &mut FlatAnalysisCache, ) -> Result, TokenizerError> { let mut segment = Vec::new(); let mut programs = Vec::new(); for index in 0..units.len() { let unit = units[index]; if unit.mode != TokenMode::Turkish || matches!( unit.kind, LexicalKind::LineBreak | LexicalKind::Control | LexicalKind::Opaque ) { flush_segment( tokenizer, raw, units, cuts, &mut segment, &mut programs, vocabulary, use_morphology, maximum_chars, cache, )?; continue; } if unit.kind == LexicalKind::Whitespace { continue; } segment.push(index); let boundary = is_sentence_boundary(unit.kind, raw, unit.span)?; if boundary || segment.len() >= tokenizer.config.max_sentence_tokens { flush_segment( tokenizer, raw, units, cuts, &mut segment, &mut programs, vocabulary, use_morphology, maximum_chars, cache, )?; } } flush_segment( tokenizer, raw, units, cuts, &mut segment, &mut programs, vocabulary, use_morphology, maximum_chars, cache, )?; Ok(programs) } #[allow(clippy::too_many_arguments)] fn flush_segment( tokenizer: &Tokenizer<'_>, raw: &[u8], units: &mut [FlatSurfaceUnit], cuts: &mut Vec, indices: &mut Vec, programs: &mut Vec, vocabulary: &SurfaceVocabulary, use_morphology: bool, maximum_chars: usize, cache: &mut FlatAnalysisCache, ) -> Result<(), TokenizerError> { if indices.is_empty() { return Ok(()); } let exact = segment_exact_bytes(raw, units, indices)?; let fingerprint = surface_program_fingerprint(exact, SURFACE_PROGRAM_TURKISH); if let Some(program) = cached_surface_program(cache, exact, SURFACE_PROGRAM_TURKISH) { cache.segment_program_hits = cache.segment_program_hits.saturating_add(1); cache.hits = cache .hits .saturating_add(u64::try_from(indices.len()).unwrap_or(u64::MAX)); programs.push(program_use(indices, program)?); indices.clear(); return Ok(()); } cache.segment_program_misses = cache.segment_program_misses.saturating_add(1); #[cfg(feature = "compiled-surface-table")] if let Some(table) = tokenizer.compiled_surface_analysis_table.as_ref() { let mut sources = Vec::with_capacity(indices.len()); for index in indices.iter().copied() { let token = unit_str(raw, units[index].span)?; if let Some(set) = table.get(token) { sources.push(FlatAnalysisSource::Compiled(set)); } else { sources.push(FlatAnalysisSource::Live( cache.analyze(&tokenizer.morphology, token)?, )); } } let sets = sources .iter() .map(FlatAnalysisSource::set) .collect::>(); apply_sets(tokenizer, raw, units, cuts, indices, &sets, cache)?; } else { let mut owned_sets = Vec::with_capacity(indices.len()); for index in indices.iter().copied() { let token = unit_str(raw, units[index].span)?; owned_sets.push(cache.analyze(&tokenizer.morphology, token)?); } let sets = owned_sets.iter().map(AsRef::as_ref).collect::>(); apply_sets(tokenizer, raw, units, cuts, indices, &sets, cache)?; } #[cfg(not(feature = "compiled-surface-table"))] { let mut owned_sets = Vec::with_capacity(indices.len()); for index in indices.iter().copied() { let token = unit_str(raw, units[index].span)?; owned_sets.push(cache.analyze(&tokenizer.morphology, token)?); } let sets = owned_sets.iter().map(AsRef::as_ref).collect::>(); apply_sets(tokenizer, raw, units, cuts, indices, &sets, cache)?; } let program = capture_segment_program( raw, units, cuts, indices, vocabulary, use_morphology, maximum_chars, )?; let program = insert_surface_program(cache, SURFACE_PROGRAM_TURKISH, fingerprint, exact, program); programs.push(program_use(indices, program)?); indices.clear(); Ok(()) } fn program_use( indices: &[usize], program: Arc, ) -> Result { let start_unit = indices .first() .copied() .ok_or(TokenizerError::InvalidTrainingEncoding( "surface segment has no first unit", ))?; let end_unit = indices .last() .copied() .and_then(|index| index.checked_add(1)) .ok_or(TokenizerError::LengthOverflow("surface segment unit end"))?; Ok(SurfaceProgramUse { start_unit, end_unit, program, }) } fn insert_surface_program( cache: &mut FlatAnalysisCache, kind: u8, fingerprint: u64, exact: &[u8], program: FlatSegmentProgram, ) -> Arc { let program = Arc::new(program); if cache.surface_program_entries >= SEGMENT_PROGRAM_CACHE_ENTRIES { return program; } let bytes = exact .len() .saturating_add(std::mem::size_of::()) .saturating_add( program .surface_ids .len() .saturating_mul(std::mem::size_of::()), ) .saturating_add(program.surface_lengths.len()); let Some(slot) = vacant_surface_program_slot(cache, fingerprint) else { return program; }; cache.surface_program_bytes = cache .surface_program_bytes .saturating_add(u64::try_from(bytes).unwrap_or(u64::MAX)); cache.surface_programs.slots[slot] = Some(FlatSurfaceProgramEntry { fingerprint, kind, exact: exact.to_vec().into_boxed_slice(), program: Arc::clone(&program), }); cache.surface_program_entries = cache.surface_program_entries.saturating_add(1); program } #[inline] fn vacant_surface_program_slot(cache: &FlatAnalysisCache, fingerprint: u64) -> Option { let mask = cache.surface_programs.slots.len().checked_sub(1)?; debug_assert!(cache.surface_programs.slots.len().is_power_of_two()); let mut slot = (fingerprint as usize) & mask; for _ in 0..cache.surface_programs.slots.len() { if cache.surface_programs.slots[slot].is_none() { return Some(slot); } slot = (slot + 1) & mask; } None } fn segment_exact_bytes<'a>( raw: &'a [u8], units: &[FlatSurfaceUnit], indices: &[usize], ) -> Result<&'a [u8], TokenizerError> { let first = indices.first().and_then(|index| units.get(*index)).ok_or( TokenizerError::InvalidTrainingEncoding("flat surface segment has no first unit"), )?; let last = indices.last().and_then(|index| units.get(*index)).ok_or( TokenizerError::InvalidTrainingEncoding("flat surface segment has no last unit"), )?; unit_bytes( raw, ByteSpan { start: first.span.start, end: last.span.end, }, ) } fn append_borrowed_surface_program( cache: &FlatAnalysisCache, exact: &[u8], kind: u8, ids: &mut Vec, lengths: &mut Vec, ) -> Result { let fingerprint = surface_program_fingerprint(exact, kind); let Some(entry) = find_surface_program(cache, fingerprint, exact, kind) else { return Ok(false); }; if entry.program.surface_ids.len() != entry.program.surface_lengths.len() { return Err(TokenizerError::InvalidTrainingEncoding( "cached surface program cardinalities differ", )); } ids.extend_from_slice(&entry.program.surface_ids); lengths.extend_from_slice(&entry.program.surface_lengths); Ok(true) } fn cached_surface_program( cache: &FlatAnalysisCache, exact: &[u8], kind: u8, ) -> Option> { let fingerprint = surface_program_fingerprint(exact, kind); find_surface_program(cache, fingerprint, exact, kind).map(|entry| Arc::clone(&entry.program)) } #[inline] fn find_surface_program<'a>( cache: &'a FlatAnalysisCache, fingerprint: u64, exact: &[u8], kind: u8, ) -> Option<&'a FlatSurfaceProgramEntry> { let mask = cache.surface_programs.slots.len().checked_sub(1)?; debug_assert!(cache.surface_programs.slots.len().is_power_of_two()); let mut slot = (fingerprint as usize) & mask; for _ in 0..cache.surface_programs.slots.len() { let entry = cache.surface_programs.slots[slot].as_ref()?; if entry.fingerprint == fingerprint && entry.kind == kind && entry.exact.as_ref() == exact { return Some(entry); } slot = (slot + 1) & mask; } None } #[inline(always)] fn surface_program_fingerprint(bytes: &[u8], kind: u8) -> u64 { const MIX1: u64 = 0x9e37_79b1_85eb_ca87; const MIX2: u64 = 0xc2b2_ae3d_27d4_eb4f; let mut hash = (bytes.len() as u64).wrapping_mul(MIX1) ^ MIX2 ^ u64::from(kind).wrapping_mul(0xd6e8_feb8_6659_fd93); if bytes.len() <= 32 { let mut chunks = bytes.chunks_exact(8); for chunk in &mut chunks { hash = mix_surface_fingerprint(hash, load_surface_word(chunk)); } let remainder = chunks.remainder(); if !remainder.is_empty() { hash = mix_surface_fingerprint(hash, load_surface_tail(remainder)); } } else { let last = bytes.len() - 8; let middle = bytes.len() / 2 - 4; let quarter = bytes.len() / 4; let three_quarters = bytes.len() * 3 / 4 - 4; for offset in [0, quarter, middle, three_quarters, last] { hash = mix_surface_fingerprint(hash, load_surface_word(&bytes[offset..offset + 8])); } } hash ^= hash >> 33; hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd); hash ^= hash >> 33; hash } #[inline(always)] fn mix_surface_fingerprint(hash: u64, word: u64) -> u64 { const MIX1: u64 = 0x9e37_79b1_85eb_ca87; const MIX2: u64 = 0xc2b2_ae3d_27d4_eb4f; (hash ^ word.wrapping_mul(MIX1)) .rotate_left(27) .wrapping_mul(MIX2) .wrapping_add(MIX1) } #[inline(always)] fn load_surface_word(bytes: &[u8]) -> u64 { u64::from_le_bytes( bytes .try_into() .expect("eight-byte surface fingerprint word"), ) } #[inline(always)] fn load_surface_tail(bytes: &[u8]) -> u64 { let mut word = 0_u64; for (shift, byte) in bytes.iter().copied().enumerate() { word |= u64::from(byte) << (shift * 8); } word } pub(crate) fn try_append_cached_document_program( raw: &[u8], newline: bool, cache: &mut FlatAnalysisCache, ids: &mut Vec, lengths: &mut Vec, ) -> Result { if cache.document_program_capacity == 0 || cache.document_program_entries == 0 { if cache.document_program_capacity > 0 { cache.document_program_misses = cache.document_program_misses.saturating_add(1); } return Ok(false); } let fingerprint = document_program_fingerprint(raw, newline); let Some(entry) = find_document_program(cache, fingerprint, raw, newline) else { cache.document_program_misses = cache.document_program_misses.saturating_add(1); return Ok(false); }; if entry.ids.len() != entry.lengths.len() { return Err(TokenizerError::InvalidTrainingEncoding( "cached document program cardinalities differ", )); } ids.extend_from_slice(&entry.ids); lengths.extend_from_slice(&entry.lengths); cache.document_program_hits = cache.document_program_hits.saturating_add(1); Ok(true) } pub(crate) fn insert_document_program( cache: &mut FlatAnalysisCache, exact: Box<[u8]>, newline: bool, ids: &[u16], lengths: &[u8], ) -> Result<(), TokenizerError> { if ids.len() != lengths.len() { return Err(TokenizerError::InvalidTrainingEncoding( "document program cardinalities differ", )); } if cache.document_program_capacity == 0 || cache.document_program_entries >= cache.document_program_capacity { return Ok(()); } let fingerprint = document_program_fingerprint(&exact, newline); if find_document_program(cache, fingerprint, &exact, newline).is_some() { return Ok(()); } let Some(slot) = vacant_document_program_slot(cache, fingerprint) else { return Ok(()); }; let bytes = exact .len() .saturating_add(ids.len().saturating_mul(std::mem::size_of::())) .saturating_add(lengths.len()) .saturating_add(std::mem::size_of::()); cache.document_programs.slots[slot] = Some(FlatDocumentProgramEntry { fingerprint, newline, exact, ids: ids.to_vec().into_boxed_slice(), lengths: lengths.to_vec().into_boxed_slice(), }); cache.document_program_entries = cache.document_program_entries.saturating_add(1); cache.document_program_bytes = cache .document_program_bytes .saturating_add(u64::try_from(bytes).unwrap_or(u64::MAX)); Ok(()) } #[inline] fn find_document_program<'a>( cache: &'a FlatAnalysisCache, fingerprint: u64, exact: &[u8], newline: bool, ) -> Option<&'a FlatDocumentProgramEntry> { let mask = cache.document_programs.slots.len().checked_sub(1)?; debug_assert!(cache.document_programs.slots.len().is_power_of_two()); let mut slot = (fingerprint as usize) & mask; for _ in 0..cache.document_programs.slots.len() { let entry = cache.document_programs.slots[slot].as_ref()?; if entry.fingerprint == fingerprint && entry.newline == newline && entry.exact.as_ref() == exact { return Some(entry); } slot = (slot + 1) & mask; } None } #[inline] fn vacant_document_program_slot(cache: &FlatAnalysisCache, fingerprint: u64) -> Option { let mask = cache.document_programs.slots.len().checked_sub(1)?; debug_assert!(cache.document_programs.slots.len().is_power_of_two()); let mut slot = (fingerprint as usize) & mask; for _ in 0..cache.document_programs.slots.len() { if cache.document_programs.slots[slot].is_none() { return Some(slot); } slot = (slot + 1) & mask; } None } #[inline(always)] fn document_program_fingerprint(raw: &[u8], newline: bool) -> u64 { surface_program_fingerprint(raw, 0xd0 | u8::from(newline)) } fn apply_sets( tokenizer: &Tokenizer<'_>, raw: &[u8], units: &mut [FlatSurfaceUnit], cuts: &mut Vec, indices: &[usize], sets: &[&FlatAnalysisSet], cache: &mut FlatAnalysisCache, ) -> Result<(), TokenizerError> { let output_invariant = sets.iter().all(|set| set.output_invariant); if tokenizer.config.contextual_disambiguation && !output_invariant { let ambiguity = sets .iter() .map(|set| set.ambiguity.as_ref()) .collect::>(); let scoring_codes = sets .iter() .map(|set| set.scoring_codes.as_ref()) .collect::>(); let selected = tokenizer.disambiguator.disambiguate_indices_scored_causal( &ambiguity, &scoring_codes, &mut cache.disambiguation_scores, )?; if selected.len() != indices.len() { return Err(TokenizerError::ContextLengthMismatch); } for ((&index, set), candidate_index) in indices.iter().zip(sets).zip(selected) { apply_candidate(raw, &mut units[index], cuts, set, candidate_index)?; } } else { for (&index, set) in indices.iter().zip(sets) { apply_candidate(raw, &mut units[index], cuts, set, 0)?; } } Ok(()) } fn apply_candidate( raw: &[u8], unit: &mut FlatSurfaceUnit, cuts: &mut Vec, set: &FlatAnalysisSet, candidate_index: usize, ) -> Result<(), TokenizerError> { let relative = set.relative_cuts .get(candidate_index) .ok_or(TokenizerError::InvalidTrainingEncoding( "flat surface disambiguator selected an out-of-range candidate", ))?; let unknown = *set.unknown .get(candidate_index) .ok_or(TokenizerError::InvalidTrainingEncoding( "flat surface candidate status is out of range", ))?; apply_output(raw, unit, cuts, relative, unknown) } fn apply_output( raw: &[u8], unit: &mut FlatSurfaceUnit, cuts: &mut Vec, relative: &[u32], unknown: bool, ) -> Result<(), TokenizerError> { if matches!(unit.kind, LexicalKind::Punctuation | LexicalKind::Symbol) { unit.status = TokenStatus::Structural; unit.cut_start = cuts.len(); unit.cut_len = 0; return Ok(()); } unit.status = if unknown { TokenStatus::Unknown } else { TokenStatus::Morphological }; let start = cuts.len(); for cut in relative { cuts.push( unit.span .start .checked_add(u64::from(*cut)) .ok_or(TokenizerError::LengthOverflow("flat surface absolute cut"))?, ); } if unit.kind == LexicalKind::Number { cuts.extend(numeric_micro_cuts(raw, unit.span)?); cuts[start..].sort_unstable(); let mut write = start; for read in start..cuts.len() { if write == start || cuts[read] != cuts[write - 1] { cuts[write] = cuts[read]; write += 1; } } cuts.truncate(write); } unit.cut_start = start; unit.cut_len = cuts.len() - start; Ok(()) } fn capture_segment_program( raw: &[u8], units: &[FlatSurfaceUnit], cuts: &[u64], indices: &[usize], vocabulary: &SurfaceVocabulary, use_morphology: bool, maximum_chars: usize, ) -> Result { let start_unit = indices .first() .copied() .ok_or(TokenizerError::InvalidTrainingEncoding( "surface segment has no first unit", ))?; let end_unit = indices .last() .copied() .and_then(|index| index.checked_add(1)) .ok_or(TokenizerError::LengthOverflow("surface segment unit end"))?; let mut surface_ids = Vec::new(); let mut surface_lengths = Vec::new(); vocabulary.encode_flat_range_into( raw, units, cuts, start_unit..end_unit, maximum_chars, use_morphology, &mut surface_ids, &mut surface_lengths, )?; Ok(FlatSegmentProgram { tokens: Box::new([]), relative_cuts: Box::new([]), surface_ids: surface_ids.into_boxed_slice(), surface_lengths: surface_lengths.into_boxed_slice(), }) } #[cfg(test)] mod tests { use super::{next_code_line_end, scan_fixed_units, split_units, FlatSurfaceUnit}; use crate::{TokenizerError, TokenizerMode}; use nedo_core::scan_compact; #[test] fn flat_surface_unit_stays_compact() { assert!(std::mem::size_of::() <= 40); } #[test] fn fixed_stream_matches_compact_split_exactly() -> Result<(), TokenizerError> { let mut inputs = vec![ Vec::new(), b"a != b && HTTPServer42::run()".to_vec(), "Ankara'da İstanbul’daydı 2026'da...".as_bytes().to_vec(), (u8::MIN..=u8::MAX).collect(), ]; let mut state = 0xbb67_ae85_84ca_a73b_u64; let mut random = Vec::with_capacity(8192); for _ in 0..8192 { state ^= state << 13; state ^= state >> 7; state ^= state << 17; random.push(state.to_le_bytes()[0]); } inputs.push(random); for raw in inputs { for mode in [TokenizerMode::Turkish, TokenizerMode::Code] { let compact = scan_compact(raw.clone())?; let expected = split_units(&compact, &[], mode)?; let actual = scan_fixed_units(&raw, mode)?; assert_eq!(actual, expected); } } Ok(()) } #[test] fn code_line_end_handles_every_scanner_line_break() { let raw = b"a\r\nb\rc\nd\xc2\x85e\xe2\x80\xa8f\xe2\x80\xa9g"; let mut starts = Vec::new(); let mut start = 0_usize; while start < raw.len() { starts.push(start); start = next_code_line_end(raw, start); } assert_eq!(starts, vec![0, 3, 5, 7, 10, 14, 18]); assert_eq!(next_code_line_end(b"", 0), 0); } }