use quantalang::lexer::{
tokenize, tokenize_file, Delimiter, IntBase, Keyword, LiteralKind, SourceFile, Token,
TokenKind, Lexer, LexerConfig,
};
fn lex(source: &str) -> Vec<Token> {
tokenize(source).expect("lexer should succeed")
}
fn lex_kinds(source: &str) -> Vec<TokenKind> {
lex(source).into_iter().map(|t| t.kind).collect()
}
fn expect_single_token(source: &str, expected: TokenKind) {
let tokens = lex(source);
assert!(tokens.len() >= 1, "expected at least one token");
assert_eq!(
tokens[0].kind, expected,
"source: {:?}, got: {:?}",
source, tokens[0].kind
);
}
fn expect_error(source: &str) {
let result = tokenize(source);
assert!(result.is_err(), "expected error for source: {:?}", source);
}
#[test]
fn test_delimiters() {
expect_single_token("(", TokenKind::OpenDelim(Delimiter::Paren));
expect_single_token(")", TokenKind::CloseDelim(Delimiter::Paren));
expect_single_token("[", TokenKind::OpenDelim(Delimiter::Bracket));
expect_single_token("]", TokenKind::CloseDelim(Delimiter::Bracket));
expect_single_token("{", TokenKind::OpenDelim(Delimiter::Brace));
expect_single_token("}", TokenKind::CloseDelim(Delimiter::Brace));
}
#[test]
fn test_punctuation() {
expect_single_token(",", TokenKind::Comma);
expect_single_token(";", TokenKind::Semi);
expect_single_token(":", TokenKind::Colon);
expect_single_token(".", TokenKind::Dot);
expect_single_token("?", TokenKind::Question);
expect_single_token("@", TokenKind::At);
expect_single_token("#", TokenKind::Pound);
expect_single_token("$", TokenKind::Dollar);
expect_single_token("~", TokenKind::Tilde);
}
#[test]
fn test_arithmetic_operators() {
expect_single_token("+", TokenKind::Plus);
expect_single_token("-", TokenKind::Minus);
expect_single_token("*", TokenKind::Star);
expect_single_token("/", TokenKind::Slash);
expect_single_token("%", TokenKind::Percent);
expect_single_token("^", TokenKind::Caret);
}
#[test]
fn test_comparison_operators() {
expect_single_token("==", TokenKind::EqEq);
expect_single_token("!=", TokenKind::Ne);
expect_single_token("<", TokenKind::Lt);
expect_single_token("<=", TokenKind::Le);
expect_single_token(">", TokenKind::Gt);
expect_single_token(">=", TokenKind::Ge);
}
#[test]
fn test_logical_operators() {
expect_single_token("&&", TokenKind::AndAnd);
expect_single_token("||", TokenKind::OrOr);
expect_single_token("!", TokenKind::Not);
}
#[test]
fn test_bitwise_operators() {
expect_single_token("&", TokenKind::And);
expect_single_token("|", TokenKind::Or);
expect_single_token("<<", TokenKind::Shl);
expect_single_token(">>", TokenKind::Shr);
}
#[test]
fn test_assignment_operators() {
expect_single_token("=", TokenKind::Eq);
expect_single_token("+=", TokenKind::PlusEq);
expect_single_token("-=", TokenKind::MinusEq);
expect_single_token("*=", TokenKind::StarEq);
expect_single_token("/=", TokenKind::SlashEq);
expect_single_token("%=", TokenKind::PercentEq);
expect_single_token("^=", TokenKind::CaretEq);
expect_single_token("&=", TokenKind::AndEq);
expect_single_token("|=", TokenKind::OrEq);
expect_single_token("<<=", TokenKind::ShlEq);
expect_single_token(">>=", TokenKind::ShrEq);
}
#[test]
fn test_special_operators() {
expect_single_token("->", TokenKind::Arrow);
expect_single_token("=>", TokenKind::FatArrow);
expect_single_token("|>", TokenKind::Pipe);
expect_single_token("::", TokenKind::ColonColon);
expect_single_token("..", TokenKind::DotDot);
expect_single_token("...", TokenKind::DotDotDot);
expect_single_token("..=", TokenKind::DotDotEq);
}
#[test]
fn test_decimal_integers() {
let check = |s| {
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Int {
base: IntBase::Decimal,
empty_int: false,
},
suffix: None,
},
);
};
check("0");
check("1");
check("42");
check("123456789");
check("1_000_000");
check("1_2_3_4");
}
#[test]
fn test_hexadecimal_integers() {
let check = |s| {
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Int {
base: IntBase::Hexadecimal,
empty_int: false,
},
suffix: None,
},
);
};
check("0x0");
check("0xFF");
check("0xDEAD_BEEF");
check("0x123abc");
check("0X123ABC");
}
#[test]
fn test_octal_integers() {
let check = |s| {
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Int {
base: IntBase::Octal,
empty_int: false,
},
suffix: None,
},
);
};
check("0o0");
check("0o755");
check("0o777");
check("0O123");
}
#[test]
fn test_binary_integers() {
let check = |s| {
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Int {
base: IntBase::Binary,
empty_int: false,
},
suffix: None,
},
);
};
check("0b0");
check("0b1");
check("0b1010");
check("0b1111_0000");
check("0B1010");
}
#[test]
fn test_integer_suffixes() {
let tokens = lex("42i32");
assert!(matches!(
&tokens[0].kind,
TokenKind::Literal { suffix: Some(s), .. } if s.as_ref() == "i32"
));
let tokens = lex("0xFFu8");
assert!(matches!(
&tokens[0].kind,
TokenKind::Literal { suffix: Some(s), .. } if s.as_ref() == "u8"
));
}
#[test]
fn test_float_literals() {
let check = |s| {
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Float {
empty_exponent: false,
},
suffix: None,
},
);
};
check("0.0");
check("3.14");
check("3.14159265");
check("1.0e10");
check("1.0E10");
check("1.5e-3");
check("2.5E+10");
check("1e10");
check("1E10");
check("1_000.000_001");
}
#[test]
fn test_float_suffixes() {
let tokens = lex("3.14f32");
assert!(matches!(
&tokens[0].kind,
TokenKind::Literal { suffix: Some(s), .. } if s.as_ref() == "f32"
));
let tokens = lex("1.0f64");
assert!(matches!(
&tokens[0].kind,
TokenKind::Literal { suffix: Some(s), .. } if s.as_ref() == "f64"
));
}
#[test]
fn test_string_literals() {
expect_single_token(
r#""""#,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true },
suffix: None,
},
);
expect_single_token(
r#""hello""#,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true },
suffix: None,
},
);
expect_single_token(
r#""hello world""#,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true },
suffix: None,
},
);
}
#[test]
fn test_string_escapes() {
let cases = [
r#""hello\nworld""#,
r#""tab\there""#,
r#""quote\"here""#,
r#""backslash\\here""#,
r#""\x41\x42\x43""#,
r#""\u{0041}\u{1F600}""#,
];
for case in cases {
expect_single_token(
case,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true },
suffix: None,
},
);
}
}
#[test]
fn test_multiline_strings() {
let s = "\"line1\nline2\nline3\"";
expect_single_token(
s,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true },
suffix: None,
},
);
}
#[test]
fn test_unterminated_string() {
expect_error(r#""unterminated"#);
}
#[test]
fn test_raw_strings() {
expect_single_token(
r##"r"raw""##,
TokenKind::Literal {
kind: LiteralKind::RawStr { n_hashes: Some(0) },
suffix: None,
},
);
expect_single_token(
r###"r#"raw with "quotes""#"###,
TokenKind::Literal {
kind: LiteralKind::RawStr { n_hashes: Some(1) },
suffix: None,
},
);
expect_single_token(
r####"r##"even more ##"## raw"##"####,
TokenKind::Literal {
kind: LiteralKind::RawStr { n_hashes: Some(2) },
suffix: None,
},
);
}
#[test]
fn test_char_literals() {
expect_single_token(
"'a'",
TokenKind::Literal {
kind: LiteralKind::Char { terminated: true },
suffix: None,
},
);
expect_single_token(
"'\\n'",
TokenKind::Literal {
kind: LiteralKind::Char { terminated: true },
suffix: None,
},
);
expect_single_token(
"'\\x41'",
TokenKind::Literal {
kind: LiteralKind::Char { terminated: true },
suffix: None,
},
);
expect_single_token(
"'\\u{1F600}'",
TokenKind::Literal {
kind: LiteralKind::Char { terminated: true },
suffix: None,
},
);
}
#[test]
fn test_empty_char_literal() {
expect_error("''");
}
#[test]
fn test_byte_literals() {
expect_single_token(
"b'a'",
TokenKind::Literal {
kind: LiteralKind::Byte { terminated: true },
suffix: None,
},
);
expect_single_token(
"b'\\xFF'",
TokenKind::Literal {
kind: LiteralKind::Byte { terminated: true },
suffix: None,
},
);
}
#[test]
fn test_byte_strings() {
expect_single_token(
r#"b"hello""#,
TokenKind::Literal {
kind: LiteralKind::ByteStr { terminated: true },
suffix: None,
},
);
expect_single_token(
r#"b"\xFF\x00""#,
TokenKind::Literal {
kind: LiteralKind::ByteStr { terminated: true },
suffix: None,
},
);
}
#[test]
fn test_lifetimes() {
expect_single_token("'a", TokenKind::Lifetime);
expect_single_token("'static", TokenKind::Lifetime);
expect_single_token("'lifetime", TokenKind::Lifetime);
expect_single_token("'_", TokenKind::Lifetime);
}
#[test]
fn test_declaration_keywords() {
expect_single_token("fn", TokenKind::Keyword(Keyword::Fn));
expect_single_token("struct", TokenKind::Keyword(Keyword::Struct));
expect_single_token("enum", TokenKind::Keyword(Keyword::Enum));
expect_single_token("trait", TokenKind::Keyword(Keyword::Trait));
expect_single_token("impl", TokenKind::Keyword(Keyword::Impl));
expect_single_token("type", TokenKind::Keyword(Keyword::Type));
expect_single_token("const", TokenKind::Keyword(Keyword::Const));
expect_single_token("static", TokenKind::Keyword(Keyword::Static));
expect_single_token("let", TokenKind::Keyword(Keyword::Let));
expect_single_token("mut", TokenKind::Keyword(Keyword::Mut));
expect_single_token("pub", TokenKind::Keyword(Keyword::Pub));
expect_single_token("mod", TokenKind::Keyword(Keyword::Mod));
expect_single_token("use", TokenKind::Keyword(Keyword::Use));
}
#[test]
fn test_control_flow_keywords() {
expect_single_token("if", TokenKind::Keyword(Keyword::If));
expect_single_token("else", TokenKind::Keyword(Keyword::Else));
expect_single_token("match", TokenKind::Keyword(Keyword::Match));
expect_single_token("loop", TokenKind::Keyword(Keyword::Loop));
expect_single_token("while", TokenKind::Keyword(Keyword::While));
expect_single_token("for", TokenKind::Keyword(Keyword::For));
expect_single_token("in", TokenKind::Keyword(Keyword::In));
expect_single_token("break", TokenKind::Keyword(Keyword::Break));
expect_single_token("continue", TokenKind::Keyword(Keyword::Continue));
expect_single_token("return", TokenKind::Keyword(Keyword::Return));
}
#[test]
fn test_boolean_keywords() {
expect_single_token(
"true",
TokenKind::Literal {
kind: LiteralKind::Bool(true),
suffix: None,
},
);
expect_single_token(
"false",
TokenKind::Literal {
kind: LiteralKind::Bool(false),
suffix: None,
},
);
}
#[test]
fn test_quanta_specific_keywords() {
expect_single_token("ai", TokenKind::Keyword(Keyword::AI));
expect_single_token("neural", TokenKind::Keyword(Keyword::Neural));
expect_single_token("infer", TokenKind::Keyword(Keyword::Infer));
expect_single_token("effect", TokenKind::Keyword(Keyword::Effect));
expect_single_token("handle", TokenKind::Keyword(Keyword::Handle));
expect_single_token("with", TokenKind::Keyword(Keyword::With));
}
#[test]
fn test_identifiers() {
expect_single_token("foo", TokenKind::Ident);
expect_single_token("bar123", TokenKind::Ident);
expect_single_token("_underscore", TokenKind::Ident);
expect_single_token("__double", TokenKind::Ident);
expect_single_token("CamelCase", TokenKind::Ident);
expect_single_token("snake_case", TokenKind::Ident);
expect_single_token("SCREAMING_SNAKE", TokenKind::Ident);
}
#[test]
fn test_unicode_identifiers() {
expect_single_token("", TokenKind::Ident);
expect_single_token("cafe", TokenKind::Ident);
expect_single_token("", TokenKind::Ident);
expect_single_token("", TokenKind::Ident);
expect_single_token("nombre", TokenKind::Ident);
}
#[test]
fn test_raw_identifiers() {
expect_single_token("r#fn", TokenKind::RawIdent);
expect_single_token("r#type", TokenKind::RawIdent);
expect_single_token("r#match", TokenKind::RawIdent);
}
#[test]
fn test_line_comments_skipped() {
let tokens = lex("// comment\nlet");
assert_eq!(tokens.len(), 2); assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Let)));
}
#[test]
fn test_block_comments_skipped() {
let tokens = lex("/* comment */ let");
assert_eq!(tokens.len(), 2); assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Let)));
}
#[test]
fn test_nested_block_comments() {
let tokens = lex("/* outer /* inner */ still outer */ let");
assert_eq!(tokens.len(), 2); assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Let)));
}
#[test]
fn test_unterminated_block_comment() {
expect_error("/* unterminated");
}
#[test]
fn test_sql_dsl() {
expect_single_token(
"sql! { SELECT * FROM users }",
TokenKind::DslBlock { name: "sql".into() },
);
}
#[test]
fn test_regex_dsl() {
expect_single_token(
"regex! { [a-z]+ }",
TokenKind::DslBlock { name: "regex".into() },
);
}
#[test]
fn test_json_dsl() {
expect_single_token(
r#"json! { "key": "value" }"#,
TokenKind::DslBlock { name: "json".into() },
);
}
#[test]
fn test_nested_dsl_delimiters() {
expect_single_token(
"sql! { SELECT { nested } FROM users }",
TokenKind::DslBlock { name: "sql".into() },
);
}
#[test]
fn test_whitespace_ignored() {
let tokens = lex(" let x = 42 ");
assert_eq!(tokens.len(), 5); }
#[test]
fn test_newlines_ignored() {
let tokens = lex("let\nx\n=\n42");
assert_eq!(tokens.len(), 5); }
#[test]
fn test_tabs_ignored() {
let tokens = lex("let\t\tx\t=\t42");
assert_eq!(tokens.len(), 5); }
#[test]
fn test_simple_function() {
let tokens = lex("fn main() { let x = 42; }");
let kinds: Vec<_> = tokens.iter().map(|t| &t.kind).collect();
assert!(matches!(kinds[0], TokenKind::Keyword(Keyword::Fn)));
assert!(matches!(kinds[1], TokenKind::Ident));
assert!(matches!(kinds[2], TokenKind::OpenDelim(Delimiter::Paren)));
assert!(matches!(kinds[3], TokenKind::CloseDelim(Delimiter::Paren)));
assert!(matches!(kinds[4], TokenKind::OpenDelim(Delimiter::Brace)));
assert!(matches!(kinds[5], TokenKind::Keyword(Keyword::Let)));
assert!(matches!(kinds[6], TokenKind::Ident));
assert!(matches!(kinds[7], TokenKind::Eq));
assert!(matches!(kinds[8], TokenKind::Literal { .. }));
assert!(matches!(kinds[9], TokenKind::Semi));
assert!(matches!(kinds[10], TokenKind::CloseDelim(Delimiter::Brace)));
assert!(matches!(kinds[11], TokenKind::Eof));
}
#[test]
fn test_struct_definition() {
let tokens = lex("pub struct Point { x: f32, y: f32 }");
assert!(tokens.len() > 10);
}
#[test]
fn test_generic_function() {
let tokens = lex("fn swap<T>(a: &mut T, b: &mut T) { }");
assert!(tokens.len() > 10);
}
#[test]
fn test_closure() {
let tokens = lex("|x, y| x + y");
assert!(matches!(tokens[0].kind, TokenKind::Or));
assert!(matches!(tokens[1].kind, TokenKind::Ident));
}
#[test]
fn test_match_expression() {
let tokens = lex("match x { Some(v) => v, None => 0 }");
assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Match)));
}
#[test]
fn test_span_accuracy() {
let source = "let x = 42";
let file = SourceFile::anonymous(source);
let mut lexer = Lexer::new(&file);
let tokens = lexer.tokenize().unwrap();
assert_eq!(tokens[0].span.start.0, 0);
assert_eq!(tokens[0].span.end.0, 3);
assert_eq!(tokens[1].span.start.0, 4);
assert_eq!(tokens[1].span.end.0, 5);
}
#[test]
fn test_unicode_span() {
let source = "let = 42"; let file = SourceFile::anonymous(source);
let mut lexer = Lexer::new(&file);
let tokens = lexer.tokenize().unwrap();
assert_eq!(tokens[1].span.len(), 3);
}
#[test]
fn test_recover_after_bad_char() {
let file = SourceFile::anonymous("let ` x = 42");
let mut lexer = Lexer::new(&file);
let result = lexer.tokenize();
assert!(result.is_err());
assert!(!lexer.errors().is_empty());
}