#[cfg(test)]
mod tests {
use crate::{Lexer, Token, TokenType};
#[test]
fn test_docstring_basic() {
let source = r#"fn test1() {
"""test1 doc"""
print("Hello, world!");
}"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "test1 doc");
}
#[test]
fn test_docstring_multiline() {
let source = r#"fn test2() {
"""
This is a
multiline docstring
"""
print("Hello");
}"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "This is a\n multiline docstring");
}
#[test]
fn test_docstring_whitespace() {
let source = r#"""" spaces before and after """"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "spaces before and after");
}
#[test]
fn test_docstring_unterminated() {
let source = r#"fn test() {
"""unterminated docstring
"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let error_token = tokens
.iter()
.find(|t| t.kind == TokenType::Invalid)
.expect("Error token not found");
assert_eq!(error_token.lexeme, "Unterminated docstring.");
}
#[test]
fn test_empty_docstring() {
let source = r#"
""" """
"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = &tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "");
let source = r#""""""""#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = &tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "");
}
#[test]
fn test_docstring() {
let source = r#"fn test() {
"""
This is a
multiline docstring
"""
print("Hello");
}"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let doc_token = tokens
.iter()
.find(|t| t.kind == TokenType::Doc)
.expect("Docstring token not found");
assert_eq!(doc_token.lexeme, "This is a\n multiline docstring");
}
#[test]
fn test_unterminated_docstring() {
let source = r#"fn test() {
"""
Unterminated docstring
"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let error_token = tokens
.iter()
.find(|t| t.kind == TokenType::Invalid)
.expect("Error token not found");
assert_eq!(error_token.lexeme, "Unterminated docstring.");
}
#[test]
fn test_string_tokens() {
let source = r#"print("Hello World");"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
assert_eq!(tokens[0].kind, TokenType::Identifier);
assert_eq!(tokens[1].kind, TokenType::OpenParen);
assert_eq!(tokens[2].kind, TokenType::String);
assert_eq!(tokens[3].kind, TokenType::CloseParen);
assert_eq!(tokens[4].kind, TokenType::Semicolon);
assert_eq!(tokens[2].lexeme, "Hello World");
}
#[test]
fn test_numbers() {
let source = "123 123.456 0.1";
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let number_tokens: Vec<&Token> = tokens
.iter()
.filter(|t| t.kind == TokenType::Number)
.collect();
assert_eq!(number_tokens.len(), 3);
assert_eq!(number_tokens[0].lexeme, "123");
assert_eq!(number_tokens[1].lexeme, "123.456");
assert_eq!(number_tokens[2].lexeme, "0.1");
}
#[test]
fn test_keywords() {
let source = "fn let if else while for";
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let keywords: Vec<TokenType> = tokens
.iter()
.map(|t| t.kind)
.filter(|k| *k != TokenType::Eof)
.collect();
assert_eq!(
keywords,
vec![
TokenType::Fn,
TokenType::Let,
TokenType::If,
TokenType::Else,
TokenType::While,
TokenType::For,
]
);
}
#[test]
fn test_operators() {
let source = "+ - * / >= <= == != ->";
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let operators: Vec<TokenType> = tokens
.iter()
.map(|t| t.kind)
.filter(|k| *k != TokenType::Eof)
.collect();
assert_eq!(
operators,
vec![
TokenType::Plus,
TokenType::Minus,
TokenType::Star,
TokenType::Slash,
TokenType::GreaterEqual,
TokenType::LessEqual,
TokenType::EqualEqual,
TokenType::NotEqual,
TokenType::Arrow,
]
);
}
#[test]
fn test_line_counting() {
let source = "line1\nline2\n\nline4";
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let identifiers: Vec<(String, u32)> = tokens
.iter()
.filter(|t| t.kind == TokenType::Identifier)
.map(|t| (t.lexeme.to_string(), t.line))
.collect();
assert_eq!(
identifiers,
vec![
("line1".to_string(), 1),
("line2".to_string(), 2),
("line4".to_string(), 4),
]
);
}
#[test]
fn test_comments() {
let source = r#"// This is a comment
fn test() { // Another comment
print("Hello"); // Comment after code
}"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let token_types: Vec<TokenType> = tokens
.iter()
.map(|t| t.kind)
.filter(|k| *k != TokenType::Eof)
.collect();
assert_eq!(
token_types,
vec![
TokenType::Fn,
TokenType::Identifier,
TokenType::OpenParen,
TokenType::CloseParen,
TokenType::OpenBrace,
TokenType::Identifier,
TokenType::OpenParen,
TokenType::String,
TokenType::CloseParen,
TokenType::Semicolon,
TokenType::CloseBrace,
]
);
}
#[test]
fn test_ai_keywords() {
let source = "ai agent prompt";
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let keywords: Vec<TokenType> = tokens
.iter()
.map(|t| t.kind)
.filter(|k| *k != TokenType::Eof)
.collect();
assert_eq!(
keywords,
vec![TokenType::AI, TokenType::Agent, TokenType::Prompt,]
);
}
#[test]
fn test_mixed_tokens() {
let source = r#"fn calculate(x: number) {
let result = x * 2;
return result;
}"#;
let scanner = Lexer::new(source);
let tokens: Vec<Token> = scanner.collect();
let token_types: Vec<TokenType> = tokens
.iter()
.map(|t| t.kind)
.filter(|k| *k != TokenType::Eof)
.collect();
assert_eq!(
token_types,
vec![
TokenType::Fn,
TokenType::Identifier, TokenType::OpenParen,
TokenType::Identifier, TokenType::Colon,
TokenType::Identifier, TokenType::CloseParen,
TokenType::OpenBrace,
TokenType::Let,
TokenType::Identifier, TokenType::Equal,
TokenType::Identifier, TokenType::Star,
TokenType::Number, TokenType::Semicolon,
TokenType::Return,
TokenType::Identifier, TokenType::Semicolon,
TokenType::CloseBrace,
]
);
}
}