use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TokenType {
Heading = 0,
Bullet = 1,
CodeBlock = 2,
Text = 3,
Whitespace = 4,
Comment = 5,
KeyValue = 6,
End = 255,
}
#[derive(Debug, Clone, Copy)]
pub struct Token {
pub ty: TokenType,
pub start: u32,
pub len: u32,
}
impl Token {
pub fn new(ty: TokenType, start: u32, len: u32) -> Self {
Self { ty, start, len }
}
pub fn end(&self) -> u32 {
self.start + self.len
}
pub fn text<'a>(&self, input: &'a [u8]) -> Option<&'a str> {
let start = self.start as usize;
let end = self.end() as usize;
if end > input.len() {
return None;
}
std::str::from_utf8(&input[start..end]).ok()
}
}
#[derive(Debug)]
pub struct SimdTokenizer<'a> {
input: &'a [u8],
pos: usize,
line: u32,
col: u32,
}
impl<'a> SimdTokenizer<'a> {
pub fn new(input: &'a [u8]) -> Self {
Self {
input,
pos: 0,
line: 1,
col: 1,
}
}
pub fn next_token(&mut self) -> Result<Token> {
self.skip_whitespace();
if self.pos >= self.input.len() {
return Ok(Token::new(TokenType::End, self.pos as u32, 0));
}
let start = self.pos as u32;
if self.starts_with(b"#") {
return Ok(self.tokenize_heading(start));
}
if self.starts_with(b"- ") || self.starts_with(b"* ") || self.starts_with(b"\xE2\x80\xA2 ")
{
return Ok(self.tokenize_bullet(start));
}
if self.starts_with(b"```") {
return Ok(self.tokenize_code_block(start));
}
if let Some(token) = self.try_tokenize_key_value(start) {
return Ok(token);
}
Ok(self.tokenize_text(start))
}
pub fn tokenize_all(&mut self) -> Result<Vec<Token>> {
let mut tokens = Vec::new();
loop {
let token = self.next_token()?;
let is_end = token.ty == TokenType::End;
tokens.push(token);
if is_end {
break;
}
}
Ok(tokens)
}
pub fn find_all(&self, pattern: &[u8]) -> Vec<usize> {
if pattern.len() == 1 {
return memchr::memchr_iter(pattern[0], self.input).collect();
}
let mut positions = Vec::new();
let mut pos = 0;
while pos + pattern.len() <= self.input.len() {
if &self.input[pos..pos + pattern.len()] == pattern {
positions.push(pos);
pos += pattern.len();
} else {
pos += 1;
}
}
positions
}
pub fn count_lines(&self) -> usize {
memchr::memchr_iter(b'\n', self.input).count() + 1
}
fn starts_with(&self, pattern: &[u8]) -> bool {
self.input[self.pos..].starts_with(pattern)
}
fn skip_whitespace(&mut self) {
while self.pos < self.input.len() {
match self.input[self.pos] {
b' ' | b'\t' => {
self.pos += 1;
self.col += 1;
}
b'\n' => {
self.pos += 1;
self.line += 1;
self.col = 1;
}
b'\r' => {
self.pos += 1;
if self.pos < self.input.len() && self.input[self.pos] == b'\n' {
self.pos += 1;
}
self.line += 1;
self.col = 1;
}
_ => break,
}
}
}
fn advance_to_newline(&mut self) {
while self.pos < self.input.len() && self.input[self.pos] != b'\n' {
self.pos += 1;
self.col += 1;
}
}
fn tokenize_heading(&mut self, start: u32) -> Token {
self.advance_to_newline();
Token::new(TokenType::Heading, start, (self.pos as u32) - start)
}
fn tokenize_bullet(&mut self, start: u32) -> Token {
self.advance_to_newline();
Token::new(TokenType::Bullet, start, (self.pos as u32) - start)
}
fn tokenize_code_block(&mut self, start: u32) -> Token {
self.pos += 3;
self.advance_to_newline();
if self.pos < self.input.len() {
self.pos += 1; }
while self.pos + 3 <= self.input.len() {
if &self.input[self.pos..self.pos + 3] == b"```" {
self.pos += 3;
self.advance_to_newline();
break;
}
self.pos += 1;
}
Token::new(TokenType::CodeBlock, start, (self.pos as u32) - start)
}
fn try_tokenize_key_value(&mut self, start: u32) -> Option<Token> {
let line_end = self.input[self.pos..]
.iter()
.position(|&b| b == b'\n')
.map(|p| self.pos + p)
.unwrap_or(self.input.len());
let line = &self.input[self.pos..line_end];
if line.contains(&b':') {
self.pos = line_end;
return Some(Token::new(
TokenType::KeyValue,
start,
(self.pos as u32) - start,
));
}
None
}
fn tokenize_text(&mut self, start: u32) -> Token {
self.advance_to_newline();
Token::new(TokenType::Text, start, (self.pos as u32) - start)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokenize_heading() {
let input = b"## Test Heading\nsome text";
let mut tokenizer = SimdTokenizer::new(input);
let token = tokenizer.next_token().unwrap();
assert_eq!(token.ty, TokenType::Heading);
assert_eq!(token.text(input), Some("## Test Heading"));
}
#[test]
fn test_tokenize_bullet() {
let input = b"- Item one\n- Item two";
let mut tokenizer = SimdTokenizer::new(input);
let token = tokenizer.next_token().unwrap();
assert_eq!(token.ty, TokenType::Bullet);
assert_eq!(token.text(input), Some("- Item one"));
}
#[test]
fn test_find_all() {
let input = b"hello world hello rust hello";
let tokenizer = SimdTokenizer::new(input);
let positions = tokenizer.find_all(b"hello");
assert_eq!(positions.len(), 3);
}
#[test]
fn test_count_lines() {
let input = b"line 1\nline 2\nline 3";
let tokenizer = SimdTokenizer::new(input);
assert_eq!(tokenizer.count_lines(), 3);
}
}