1use serde::{Deserialize, Serialize};
2use std::collections::{HashMap, HashSet};
3use trustformers_core::errors::Result;
4use trustformers_core::traits::{TokenizedInput, Tokenizer};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum Language {
9 Rust,
10 Python,
11 JavaScript,
12 TypeScript,
13 Java,
14 CSharp,
15 CPlusPlus,
16 C,
17 Go,
18 Ruby,
19 PHP,
20 Swift,
21 Kotlin,
22 Scala,
23 Haskell,
24 Clojure,
25 SQL,
26 HTML,
27 CSS,
28 JSON,
29 XML,
30 YAML,
31 Markdown,
32 Shell,
33 PowerShell,
34 R,
35 Matlab,
36}
37
38impl Language {
39 pub fn extensions(&self) -> &'static [&'static str] {
41 match self {
42 Language::Rust => &["rs"],
43 Language::Python => &["py", "pyx", "pyi", "pyw"],
44 Language::JavaScript => &["js", "jsx", "mjs", "cjs"],
45 Language::TypeScript => &["ts", "tsx", "d.ts"],
46 Language::Java => &["java"],
47 Language::CSharp => &["cs"],
48 Language::CPlusPlus => &["cpp", "cxx", "cc", "hpp", "hxx", "hh"],
49 Language::C => &["c", "h"],
50 Language::Go => &["go"],
51 Language::Ruby => &["rb", "rbx", "rjs", "gemspec"],
52 Language::PHP => &["php", "phtml", "php3", "php4", "php5"],
53 Language::Swift => &["swift"],
54 Language::Kotlin => &["kt", "kts"],
55 Language::Scala => &["scala", "sc"],
56 Language::Haskell => &["hs", "lhs"],
57 Language::Clojure => &["clj", "cljs", "cljc", "edn"],
58 Language::SQL => &["sql"],
59 Language::HTML => &["html", "htm", "xhtml"],
60 Language::CSS => &["css", "scss", "sass", "less"],
61 Language::JSON => &["json", "jsonl", "ndjson"],
62 Language::XML => &["xml", "xsd", "xsl", "xslt"],
63 Language::YAML => &["yaml", "yml"],
64 Language::Markdown => &["md", "markdown", "mdown", "mkd"],
65 Language::Shell => &["sh", "bash", "zsh", "fish"],
66 Language::PowerShell => &["ps1", "psm1", "psd1"],
67 Language::R => &["r", "R"],
68 Language::Matlab => &["m"],
69 }
70 }
71
72 pub fn keywords(&self) -> &'static [&'static str] {
74 match self {
75 Language::Rust => &[
76 "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false",
77 "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut",
78 "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait",
79 "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn",
80 ],
81 Language::Python => &[
82 "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class",
83 "continue", "def", "del", "elif", "else", "except", "finally", "for", "from",
84 "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass",
85 "raise", "return", "try", "while", "with", "yield",
86 ],
87 Language::JavaScript | Language::TypeScript => &[
88 "break",
89 "case",
90 "catch",
91 "class",
92 "const",
93 "continue",
94 "debugger",
95 "default",
96 "delete",
97 "do",
98 "else",
99 "export",
100 "extends",
101 "false",
102 "finally",
103 "for",
104 "function",
105 "if",
106 "import",
107 "in",
108 "instanceof",
109 "new",
110 "null",
111 "return",
112 "super",
113 "switch",
114 "this",
115 "throw",
116 "true",
117 "try",
118 "typeof",
119 "var",
120 "void",
121 "while",
122 "with",
123 "yield",
124 "let",
125 "static",
126 "enum",
127 "implements",
128 "package",
129 "protected",
130 "interface",
131 "private",
132 "public",
133 "async",
134 "await",
135 ],
136 Language::Java => &[
137 "abstract",
138 "assert",
139 "boolean",
140 "break",
141 "byte",
142 "case",
143 "catch",
144 "char",
145 "class",
146 "const",
147 "continue",
148 "default",
149 "do",
150 "double",
151 "else",
152 "enum",
153 "extends",
154 "final",
155 "finally",
156 "float",
157 "for",
158 "goto",
159 "if",
160 "implements",
161 "import",
162 "instanceof",
163 "int",
164 "interface",
165 "long",
166 "native",
167 "new",
168 "package",
169 "private",
170 "protected",
171 "public",
172 "return",
173 "short",
174 "static",
175 "strictfp",
176 "super",
177 "switch",
178 "synchronized",
179 "this",
180 "throw",
181 "throws",
182 "transient",
183 "try",
184 "void",
185 "volatile",
186 "while",
187 ],
188 Language::CSharp => &[
189 "abstract",
190 "as",
191 "base",
192 "bool",
193 "break",
194 "byte",
195 "case",
196 "catch",
197 "char",
198 "checked",
199 "class",
200 "const",
201 "continue",
202 "decimal",
203 "default",
204 "delegate",
205 "do",
206 "double",
207 "else",
208 "enum",
209 "event",
210 "explicit",
211 "extern",
212 "false",
213 "finally",
214 "fixed",
215 "float",
216 "for",
217 "foreach",
218 "goto",
219 "if",
220 "implicit",
221 "in",
222 "int",
223 "interface",
224 "internal",
225 "is",
226 "lock",
227 "long",
228 "namespace",
229 "new",
230 "null",
231 "object",
232 "operator",
233 "out",
234 "override",
235 "params",
236 "private",
237 "protected",
238 "public",
239 "readonly",
240 "ref",
241 "return",
242 "sbyte",
243 "sealed",
244 "short",
245 "sizeof",
246 "stackalloc",
247 "static",
248 "string",
249 "struct",
250 "switch",
251 "this",
252 "throw",
253 "true",
254 "try",
255 "typeof",
256 "uint",
257 "ulong",
258 "unchecked",
259 "unsafe",
260 "ushort",
261 "using",
262 "virtual",
263 "void",
264 "volatile",
265 "while",
266 ],
267 Language::Go => &[
268 "break",
269 "case",
270 "chan",
271 "const",
272 "continue",
273 "default",
274 "defer",
275 "else",
276 "fallthrough",
277 "for",
278 "func",
279 "go",
280 "goto",
281 "if",
282 "import",
283 "interface",
284 "map",
285 "package",
286 "range",
287 "return",
288 "select",
289 "struct",
290 "switch",
291 "type",
292 "var",
293 ],
294 _ => &[], }
296 }
297
298 pub fn comment_patterns(&self) -> CommentPatterns {
300 match self {
301 Language::Rust
302 | Language::JavaScript
303 | Language::TypeScript
304 | Language::Java
305 | Language::CSharp
306 | Language::CPlusPlus
307 | Language::Go
308 | Language::Swift
309 | Language::Kotlin
310 | Language::Scala => CommentPatterns {
311 line_comment: Some("//"),
312 block_comment: Some(("/*", "*/")),
313 doc_comment: Some("///"),
314 },
315 Language::Python | Language::Ruby | Language::Shell => CommentPatterns {
316 line_comment: Some("#"),
317 block_comment: None,
318 doc_comment: Some("#"),
319 },
320 Language::C => CommentPatterns {
321 line_comment: None,
322 block_comment: Some(("/*", "*/")),
323 doc_comment: None,
324 },
325 Language::HTML | Language::XML => CommentPatterns {
326 line_comment: None,
327 block_comment: Some(("<!--", "-->")),
328 doc_comment: None,
329 },
330 Language::CSS => CommentPatterns {
331 line_comment: None,
332 block_comment: Some(("/*", "*/")),
333 doc_comment: None,
334 },
335 Language::SQL => CommentPatterns {
336 line_comment: Some("--"),
337 block_comment: Some(("/*", "*/")),
338 doc_comment: None,
339 },
340 Language::Haskell => CommentPatterns {
341 line_comment: Some("--"),
342 block_comment: Some(("{-", "-}")),
343 doc_comment: Some("-- |"),
344 },
345 _ => CommentPatterns {
346 line_comment: None,
347 block_comment: None,
348 doc_comment: None,
349 },
350 }
351 }
352
353 pub fn from_extension(ext: &str) -> Option<Language> {
355 let ext = ext.to_lowercase();
356 [
357 Language::Rust,
358 Language::Python,
359 Language::JavaScript,
360 Language::TypeScript,
361 Language::Java,
362 Language::CSharp,
363 Language::CPlusPlus,
364 Language::C,
365 Language::Go,
366 Language::Ruby,
367 Language::PHP,
368 Language::Swift,
369 Language::Kotlin,
370 Language::Scala,
371 Language::Haskell,
372 Language::Clojure,
373 Language::SQL,
374 Language::HTML,
375 Language::CSS,
376 Language::JSON,
377 Language::XML,
378 Language::YAML,
379 Language::Markdown,
380 Language::Shell,
381 Language::PowerShell,
382 Language::R,
383 Language::Matlab,
384 ]
385 .into_iter()
386 .find(|&lang| lang.extensions().contains(&ext.as_str()))
387 }
388}
389
390#[derive(Debug, Clone)]
392pub struct CommentPatterns {
393 pub line_comment: Option<&'static str>,
394 pub block_comment: Option<(&'static str, &'static str)>,
395 pub doc_comment: Option<&'static str>,
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
400pub enum CodeTokenType {
401 Keyword,
402 Identifier,
403 Literal(LiteralType),
404 Operator,
405 Punctuation,
406 Comment,
407 Whitespace,
408 String,
409 Number,
410 Unknown,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
415pub enum LiteralType {
416 String,
417 Character,
418 Integer,
419 Float,
420 Boolean,
421 Null,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct CodeToken {
427 pub text: String,
428 pub token_type: CodeTokenType,
429 pub position: TokenPosition,
430 pub language: Language,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct TokenPosition {
436 pub line: usize,
437 pub column: usize,
438 pub start_offset: usize,
439 pub end_offset: usize,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct CodeTokenizerConfig {
445 pub language: Option<Language>,
446 pub preserve_whitespace: bool,
447 pub preserve_comments: bool,
448 pub include_position_info: bool,
449 pub normalize_identifiers: bool,
450 pub max_token_length: Option<usize>,
451 pub custom_keywords: Option<HashSet<String>>,
452}
453
454impl Default for CodeTokenizerConfig {
455 fn default() -> Self {
456 Self {
457 language: None,
458 preserve_whitespace: false,
459 preserve_comments: true,
460 include_position_info: false,
461 normalize_identifiers: false,
462 max_token_length: Some(128),
463 custom_keywords: None,
464 }
465 }
466}
467
468pub struct CodeTokenizer {
470 config: CodeTokenizerConfig,
471 keywords: HashSet<String>,
472 token_to_id: HashMap<String, u32>,
473 id_to_token: HashMap<u32, String>,
474 special_tokens: HashMap<String, u32>,
475}
476
477impl CodeTokenizer {
478 pub fn new(config: CodeTokenizerConfig) -> Self {
480 let mut tokenizer = Self {
481 config,
482 keywords: HashSet::new(),
483 token_to_id: HashMap::new(),
484 id_to_token: HashMap::new(),
485 special_tokens: HashMap::new(),
486 };
487
488 tokenizer.initialize_vocabulary();
489 tokenizer
490 }
491
492 pub fn for_language(language: Language) -> Self {
494 let config = CodeTokenizerConfig {
495 language: Some(language),
496 ..Default::default()
497 };
498 Self::new(config)
499 }
500
501 fn initialize_vocabulary(&mut self) {
503 let mut next_id = 0u32;
504
505 for special in &[
507 "[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]", "[BOS]", "[EOS]",
508 ] {
509 self.add_token(special, &mut next_id);
510 self.special_tokens.insert(special.to_string(), next_id - 1);
511 }
512
513 if let Some(language) = self.config.language {
515 for keyword in language.keywords() {
516 self.keywords.insert(keyword.to_string());
517 self.add_token(keyword, &mut next_id);
518 }
519 }
520
521 if let Some(custom_keywords) = &self.config.custom_keywords {
523 let keywords_to_add: Vec<String> = custom_keywords.iter().cloned().collect();
524 for keyword in keywords_to_add {
525 self.keywords.insert(keyword.clone());
526 self.add_token(&keyword, &mut next_id);
527 }
528 }
529
530 for op in &[
532 "+", "-", "*", "/", "%", "=", "==", "!=", "<", ">", "<=", ">=", "&&", "||", "!", "&",
533 "|", "^", "~", "<<", ">>", "++", "--", "+=", "-=", "*=", "/=", "%=", "(", ")", "[",
534 "]", "{", "}", ";", ",", ".", ":", "::", "->", "=>", "?",
535 ] {
536 self.add_token(op, &mut next_id);
537 }
538
539 for literal in &["true", "false", "null", "undefined", "nil", "None"] {
541 self.add_token(literal, &mut next_id);
542 }
543 }
544
545 fn add_token(&mut self, token: &str, next_id: &mut u32) {
547 if !self.token_to_id.contains_key(token) {
548 self.token_to_id.insert(token.to_string(), *next_id);
549 self.id_to_token.insert(*next_id, token.to_string());
550 *next_id += 1;
551 }
552 }
553
554 pub fn tokenize_code(&self, code: &str) -> Result<Vec<CodeToken>> {
556 let language = self.config.language.unwrap_or(Language::JavaScript);
557 let comment_patterns = language.comment_patterns();
558
559 let mut tokens = Vec::new();
560 let mut current_line = 1;
561 let mut current_column = 1;
562 let mut char_indices = code.char_indices().peekable();
563
564 while let Some((start_offset, ch)) = char_indices.next() {
565 let token_start_line = current_line;
566 let token_start_column = current_column;
567
568 if ch == '\n' {
570 current_line += 1;
571 current_column = 1;
572 } else {
573 current_column += 1;
574 }
575
576 if ch.is_whitespace() {
578 if self.config.preserve_whitespace {
579 let (text, end_offset) =
580 self.consume_whitespace(&mut char_indices, start_offset, ch);
581 tokens.push(CodeToken {
582 text,
583 token_type: CodeTokenType::Whitespace,
584 position: TokenPosition {
585 line: token_start_line,
586 column: token_start_column,
587 start_offset,
588 end_offset,
589 },
590 language,
591 });
592 }
593 continue;
594 }
595
596 if let Some(token) = self.try_parse_comment(
598 &mut char_indices,
599 start_offset,
600 ch,
601 &comment_patterns,
602 token_start_line,
603 token_start_column,
604 language,
605 )? {
606 if self.config.preserve_comments {
607 tokens.push(token);
608 }
609 continue;
610 }
611
612 if ch == '"'
614 || ch == '\''
615 || (ch == '`' && matches!(language, Language::JavaScript | Language::TypeScript))
616 {
617 let token = self.parse_string_literal(
618 &mut char_indices,
619 start_offset,
620 ch,
621 token_start_line,
622 token_start_column,
623 language,
624 )?;
625 tokens.push(token);
626 continue;
627 }
628
629 if ch.is_ascii_digit()
631 || (ch == '.'
632 && char_indices.peek().map(|(_, c)| c.is_ascii_digit()).unwrap_or(false))
633 {
634 let token = self.parse_numeric_literal(
635 &mut char_indices,
636 start_offset,
637 ch,
638 token_start_line,
639 token_start_column,
640 language,
641 )?;
642 tokens.push(token);
643 continue;
644 }
645
646 if ch.is_alphabetic() || ch == '_' || ch == '$' {
648 let token = self.parse_identifier(
649 &mut char_indices,
650 start_offset,
651 ch,
652 token_start_line,
653 token_start_column,
654 language,
655 )?;
656 tokens.push(token);
657 continue;
658 }
659
660 let token = self.parse_operator_or_punctuation(
662 &mut char_indices,
663 start_offset,
664 ch,
665 token_start_line,
666 token_start_column,
667 language,
668 )?;
669 tokens.push(token);
670 }
671
672 Ok(tokens)
673 }
674
675 fn consume_whitespace(
677 &self,
678 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
679 start_offset: usize,
680 first_char: char,
681 ) -> (String, usize) {
682 let mut text = String::new();
683 text.push(first_char);
684 let mut end_offset = start_offset;
685
686 while let Some((offset, ch)) = char_indices.peek() {
687 if ch.is_whitespace() {
688 text.push(*ch);
689 end_offset = *offset;
690 char_indices.next();
691 } else {
692 break;
693 }
694 }
695
696 (text, end_offset)
697 }
698
699 fn try_parse_comment(
701 &self,
702 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
703 start_offset: usize,
704 first_char: char,
705 patterns: &CommentPatterns,
706 token_start_line: usize,
707 token_start_column: usize,
708 language: Language,
709 ) -> Result<Option<CodeToken>> {
710 if let Some(line_comment) = patterns.line_comment {
712 if line_comment.starts_with(first_char) {
714 if let Some(token) = self.try_parse_line_comment(
715 char_indices,
716 start_offset,
717 line_comment,
718 token_start_line,
719 token_start_column,
720 language,
721 )? {
722 return Ok(Some(token));
723 }
724 }
725 }
726
727 if let Some((start_delim, end_delim)) = patterns.block_comment {
729 if start_delim.starts_with(first_char) {
731 if let Some(token) = self.try_parse_block_comment(
732 char_indices,
733 start_offset,
734 start_delim,
735 end_delim,
736 token_start_line,
737 token_start_column,
738 language,
739 )? {
740 return Ok(Some(token));
741 }
742 }
743 }
744
745 Ok(None)
746 }
747
748 fn try_parse_line_comment(
750 &self,
751 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
752 start_offset: usize,
753 comment_start: &str,
754 token_start_line: usize,
755 token_start_column: usize,
756 language: Language,
757 ) -> Result<Option<CodeToken>> {
758 let mut text = String::new();
759 text.push_str(comment_start);
760
761 for _ in 1..comment_start.len() {
763 if let Some((_, ch)) = char_indices.next() {
764 text.push(ch);
765 }
766 }
767
768 let mut end_offset = start_offset;
770 while let Some((offset, ch)) = char_indices.peek() {
771 if *ch == '\n' {
772 break;
773 }
774 text.push(*ch);
775 end_offset = *offset;
776 char_indices.next();
777 }
778
779 Ok(Some(CodeToken {
780 text,
781 token_type: CodeTokenType::Comment,
782 position: TokenPosition {
783 line: token_start_line,
784 column: token_start_column,
785 start_offset,
786 end_offset,
787 },
788 language,
789 }))
790 }
791
792 fn try_parse_block_comment(
794 &self,
795 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
796 start_offset: usize,
797 start_delim: &str,
798 end_delim: &str,
799 token_start_line: usize,
800 token_start_column: usize,
801 language: Language,
802 ) -> Result<Option<CodeToken>> {
803 let mut text = String::new();
804 text.push_str(start_delim);
805
806 for _ in 1..start_delim.len() {
808 if let Some((_, ch)) = char_indices.next() {
809 text.push(ch);
810 }
811 }
812
813 let mut end_offset = start_offset;
815 let end_chars: Vec<char> = end_delim.chars().collect();
816 let mut buffer = Vec::new();
817
818 for (offset, ch) in char_indices.by_ref() {
819 text.push(ch);
820 end_offset = offset;
821 buffer.push(ch);
822
823 if buffer.len() > end_chars.len() {
825 buffer.remove(0);
826 }
827
828 if buffer.len() == end_chars.len() && buffer == end_chars {
830 break;
831 }
832 }
833
834 Ok(Some(CodeToken {
835 text,
836 token_type: CodeTokenType::Comment,
837 position: TokenPosition {
838 line: token_start_line,
839 column: token_start_column,
840 start_offset,
841 end_offset,
842 },
843 language,
844 }))
845 }
846
847 fn parse_string_literal(
849 &self,
850 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
851 start_offset: usize,
852 quote_char: char,
853 token_start_line: usize,
854 token_start_column: usize,
855 language: Language,
856 ) -> Result<CodeToken> {
857 let mut text = String::new();
858 text.push(quote_char);
859 let mut end_offset = start_offset;
860 let mut escaped = false;
861
862 for (offset, ch) in char_indices.by_ref() {
863 text.push(ch);
864 end_offset = offset;
865
866 if escaped {
867 escaped = false;
868 continue;
869 }
870
871 if ch == '\\' {
872 escaped = true;
873 continue;
874 }
875
876 if ch == quote_char {
877 break;
878 }
879 }
880
881 Ok(CodeToken {
882 text,
883 token_type: CodeTokenType::String,
884 position: TokenPosition {
885 line: token_start_line,
886 column: token_start_column,
887 start_offset,
888 end_offset,
889 },
890 language,
891 })
892 }
893
894 fn parse_numeric_literal(
896 &self,
897 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
898 start_offset: usize,
899 first_char: char,
900 token_start_line: usize,
901 token_start_column: usize,
902 language: Language,
903 ) -> Result<CodeToken> {
904 let mut text = String::new();
905 text.push(first_char);
906 let mut end_offset = start_offset;
907 let mut has_dot = first_char == '.';
908
909 while let Some((offset, ch)) = char_indices.peek() {
910 if ch.is_ascii_digit()
911 || (*ch == '.' && !has_dot)
912 || (*ch == 'e' || *ch == 'E')
913 || (*ch == 'x' || *ch == 'X')
914 || (*ch == '_')
915 || ch.is_ascii_hexdigit()
916 {
917 if *ch == '.' {
918 has_dot = true;
919 }
920 text.push(*ch);
921 end_offset = *offset;
922 char_indices.next();
923 } else {
924 break;
925 }
926 }
927
928 Ok(CodeToken {
929 text,
930 token_type: CodeTokenType::Number,
931 position: TokenPosition {
932 line: token_start_line,
933 column: token_start_column,
934 start_offset,
935 end_offset,
936 },
937 language,
938 })
939 }
940
941 fn parse_identifier(
943 &self,
944 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
945 start_offset: usize,
946 first_char: char,
947 token_start_line: usize,
948 token_start_column: usize,
949 language: Language,
950 ) -> Result<CodeToken> {
951 let mut text = String::new();
952 text.push(first_char);
953 let mut end_offset = start_offset;
954
955 while let Some((offset, ch)) = char_indices.peek() {
956 if ch.is_alphanumeric() || *ch == '_' || *ch == '$' {
957 text.push(*ch);
958 end_offset = *offset;
959 char_indices.next();
960 } else {
961 break;
962 }
963 }
964
965 let token_type = if self.keywords.contains(&text) {
966 CodeTokenType::Keyword
967 } else {
968 CodeTokenType::Identifier
969 };
970
971 Ok(CodeToken {
972 text,
973 token_type,
974 position: TokenPosition {
975 line: token_start_line,
976 column: token_start_column,
977 start_offset,
978 end_offset,
979 },
980 language,
981 })
982 }
983
984 fn parse_operator_or_punctuation(
986 &self,
987 char_indices: &mut std::iter::Peekable<std::str::CharIndices>,
988 start_offset: usize,
989 first_char: char,
990 token_start_line: usize,
991 token_start_column: usize,
992 language: Language,
993 ) -> Result<CodeToken> {
994 let mut text = String::new();
995 text.push(first_char);
996 let mut end_offset = start_offset;
997
998 let operators = [
1000 "==", "!=", "<=", ">=", "&&", "||", "++", "--", "+=", "-=", "*=", "/=", "%=", "<<",
1001 ">>", "::", "->", "=>", "**", "//", "...", "..", ":=", "<=>",
1002 ];
1003
1004 for op in &operators {
1005 if op.starts_with(first_char) && op.len() > 1 {
1006 let chars = op.chars().skip(1);
1007 let mut matched = true;
1008 let mut lookahead = Vec::new();
1009
1010 for expected_char in chars {
1011 if let Some((offset, ch)) = char_indices.peek() {
1012 if *ch == expected_char {
1013 lookahead.push((*offset, *ch));
1014 char_indices.next();
1015 } else {
1016 matched = false;
1017 break;
1018 }
1019 } else {
1020 matched = false;
1021 break;
1022 }
1023 }
1024
1025 if matched {
1026 text = op.to_string();
1027 if let Some((offset, _)) = lookahead.last() {
1028 end_offset = *offset;
1029 }
1030 break;
1031 } else {
1032 for (_, _ch) in lookahead.into_iter().rev() {
1034 }
1037 }
1038 }
1039 }
1040
1041 let token_type = match first_char {
1042 '(' | ')' | '[' | ']' | '{' | '}' | ';' | ',' | '.' | ':' => CodeTokenType::Punctuation,
1043 _ => CodeTokenType::Operator,
1044 };
1045
1046 Ok(CodeToken {
1047 text,
1048 token_type,
1049 position: TokenPosition {
1050 line: token_start_line,
1051 column: token_start_column,
1052 start_offset,
1053 end_offset,
1054 },
1055 language,
1056 })
1057 }
1058
1059 pub fn vocab_size(&self) -> usize {
1061 self.token_to_id.len()
1062 }
1063
1064 pub fn token_to_id(&self, token: &str) -> Option<u32> {
1066 self.token_to_id.get(token).copied()
1067 }
1068
1069 pub fn id_to_token(&self, id: u32) -> Option<String> {
1071 self.id_to_token.get(&id).cloned()
1072 }
1073}
1074
1075impl Tokenizer for CodeTokenizer {
1076 fn encode(&self, text: &str) -> Result<TokenizedInput> {
1077 let code_tokens = self.tokenize_code(text)?;
1078 let mut input_ids = Vec::new();
1079
1080 for token in code_tokens {
1081 let token_text = if self.config.normalize_identifiers
1082 && token.token_type == CodeTokenType::Identifier
1083 {
1084 "[IDENTIFIER]".to_string()
1085 } else {
1086 token.text
1087 };
1088
1089 if let Some(id) = self.token_to_id(&token_text) {
1090 input_ids.push(id);
1091 } else if let Some(&unk_id) = self.special_tokens.get("[UNK]") {
1092 input_ids.push(unk_id);
1093 }
1094 }
1095
1096 let attention_mask = vec![1u8; input_ids.len()];
1097
1098 Ok(TokenizedInput {
1099 input_ids,
1100 attention_mask,
1101 token_type_ids: None,
1102 special_tokens_mask: None,
1103 offset_mapping: None,
1104 overflowing_tokens: None,
1105 })
1106 }
1107
1108 fn decode(&self, ids: &[u32]) -> Result<String> {
1109 let tokens: Vec<String> = ids.iter().filter_map(|&id| self.id_to_token(id)).collect();
1110 Ok(tokens.join(" "))
1111 }
1112
1113 fn encode_pair(&self, text_a: &str, text_b: &str) -> Result<TokenizedInput> {
1114 let combined = format!("{}\n{}", text_a, text_b);
1115 self.encode(&combined)
1116 }
1117
1118 fn vocab_size(&self) -> usize {
1119 self.token_to_id.len()
1120 }
1121
1122 fn get_vocab(&self) -> HashMap<String, u32> {
1123 self.token_to_id.clone()
1124 }
1125
1126 fn token_to_id(&self, token: &str) -> Option<u32> {
1127 self.token_to_id.get(token).copied()
1128 }
1129
1130 fn id_to_token(&self, id: u32) -> Option<String> {
1131 self.id_to_token.get(&id).cloned()
1132 }
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use super::*;
1138
1139 #[test]
1140 fn test_language_detection() {
1141 assert_eq!(Language::from_extension("rs"), Some(Language::Rust));
1142 assert_eq!(Language::from_extension("py"), Some(Language::Python));
1143 assert_eq!(Language::from_extension("js"), Some(Language::JavaScript));
1144 assert_eq!(Language::from_extension("unknown"), None);
1145 }
1146
1147 #[test]
1148 fn test_rust_tokenization() {
1149 let tokenizer = CodeTokenizer::for_language(Language::Rust);
1150 let code = "fn main() { let x = 42; }";
1151 let tokens = tokenizer.tokenize_code(code).expect("Operation failed in test");
1152
1153 assert!(!tokens.is_empty());
1154
1155 let fn_token = tokens.iter().find(|t| t.text == "fn").expect("Operation failed in test");
1157 assert_eq!(fn_token.token_type, CodeTokenType::Keyword);
1158
1159 let let_token = tokens.iter().find(|t| t.text == "let").expect("Operation failed in test");
1160 assert_eq!(let_token.token_type, CodeTokenType::Keyword);
1161 }
1162
1163 #[test]
1164 fn test_string_literal_parsing() {
1165 let tokenizer = CodeTokenizer::for_language(Language::JavaScript);
1166 let code = r#"let name = "Hello \"World\"";"#;
1167 let tokens = tokenizer.tokenize_code(code).expect("Operation failed in test");
1168
1169 let string_token = tokens
1170 .iter()
1171 .find(|t| t.token_type == CodeTokenType::String)
1172 .expect("Operation failed in test");
1173 assert!(string_token.text.starts_with('"'));
1174 assert!(string_token.text.ends_with('"'));
1175 }
1176
1177 #[test]
1178 fn test_comment_parsing() {
1179 let config = CodeTokenizerConfig {
1180 language: Some(Language::Rust),
1181 preserve_comments: true,
1182 ..Default::default()
1183 };
1184 let tokenizer = CodeTokenizer::new(config);
1185 let code = "// This is a comment\nfn main() {}";
1186 let tokens = tokenizer.tokenize_code(code).expect("Operation failed in test");
1187
1188 let comment_token = tokens
1189 .iter()
1190 .find(|t| t.token_type == CodeTokenType::Comment)
1191 .expect("Operation failed in test");
1192 assert!(comment_token.text.starts_with("//"));
1193 }
1194
1195 #[test]
1196 fn test_numeric_literals() {
1197 let tokenizer = CodeTokenizer::for_language(Language::Python);
1198 let code = "x = 42; y = 3.14; z = 0xFF;";
1199 let tokens = tokenizer.tokenize_code(code).expect("Operation failed in test");
1200
1201 let numeric_tokens: Vec<_> =
1202 tokens.iter().filter(|t| t.token_type == CodeTokenType::Number).collect();
1203
1204 assert!(numeric_tokens.len() >= 3);
1205 }
1206
1207 #[test]
1208 fn test_code_tokenizer_encode() {
1209 let tokenizer = CodeTokenizer::for_language(Language::Python);
1210 let code = "def hello(): return 42";
1211 let result = tokenizer.encode(code).expect("Encoding failed");
1212
1213 assert!(!result.input_ids.is_empty());
1214 assert_eq!(result.input_ids.len(), result.attention_mask.len());
1215 }
1216}