use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum KeywordKind {
Const = 0,
Let = 1,
Var = 2,
True = 3,
False = 4,
Null = 5,
Undefined = 6,
Number = 7,
String = 8,
Boolean = 9,
Any = 10,
Void = 11,
Never = 12,
Unknown = 13,
Object = 14,
Symbol = 15,
Bigint = 16,
New = 17,
Instanceof = 18,
In = 19,
Return = 20,
If = 30,
Else = 31,
For = 32,
While = 33,
Do = 34,
Switch = 35,
Case = 36,
Default = 37,
Break = 38,
Continue = 39,
Try = 40,
Catch = 41,
Finally = 42,
Throw = 43,
Function = 21,
Class = 22,
Enum = 49,
Typeof = 23,
Delete = 24,
Async = 25,
Await = 26,
This = 50,
Super = 27,
Extends = 28,
Export = 29,
Import = 44,
From = 45,
As = 46,
Satisfies = 47,
Yield = 48,
Debugger = 51,
With = 52,
}
impl KeywordKind {
#[inline]
pub const fn as_str(self) -> &'static str {
match self {
KeywordKind::Const => "const",
KeywordKind::Let => "let",
KeywordKind::Var => "var",
KeywordKind::True => "true",
KeywordKind::False => "false",
KeywordKind::Null => "null",
KeywordKind::Undefined => "undefined",
KeywordKind::Number => "number",
KeywordKind::String => "string",
KeywordKind::Boolean => "boolean",
KeywordKind::Any => "any",
KeywordKind::Void => "void",
KeywordKind::Never => "never",
KeywordKind::Unknown => "unknown",
KeywordKind::Object => "object",
KeywordKind::Symbol => "symbol",
KeywordKind::Bigint => "bigint",
KeywordKind::New => "new",
KeywordKind::Instanceof => "instanceof",
KeywordKind::In => "in",
KeywordKind::Return => "return",
KeywordKind::If => "if",
KeywordKind::Else => "else",
KeywordKind::For => "for",
KeywordKind::While => "while",
KeywordKind::Do => "do",
KeywordKind::Switch => "switch",
KeywordKind::Case => "case",
KeywordKind::Default => "default",
KeywordKind::Break => "break",
KeywordKind::Continue => "continue",
KeywordKind::Try => "try",
KeywordKind::Catch => "catch",
KeywordKind::Finally => "finally",
KeywordKind::Throw => "throw",
KeywordKind::Function => "function",
KeywordKind::Class => "class",
KeywordKind::Enum => "enum",
KeywordKind::Typeof => "typeof",
KeywordKind::Delete => "delete",
KeywordKind::Async => "async",
KeywordKind::Await => "await",
KeywordKind::This => "this",
KeywordKind::Super => "super",
KeywordKind::Extends => "extends",
KeywordKind::Export => "export",
KeywordKind::Import => "import",
KeywordKind::From => "from",
KeywordKind::As => "as",
KeywordKind::Satisfies => "satisfies",
KeywordKind::Yield => "yield",
KeywordKind::Debugger => "debugger",
KeywordKind::With => "with",
}
}
#[inline]
pub const fn is_type_keyword(self) -> bool {
matches!(
self,
KeywordKind::Number
| KeywordKind::String
| KeywordKind::Boolean
| KeywordKind::Any
| KeywordKind::Void
| KeywordKind::Never
| KeywordKind::Unknown
| KeywordKind::Object
| KeywordKind::Symbol
| KeywordKind::Bigint
| KeywordKind::Null
| KeywordKind::Undefined
)
}
#[inline]
pub const fn starts_expression(self) -> bool {
!matches!(
self,
KeywordKind::Const
| KeywordKind::Var
| KeywordKind::If
| KeywordKind::Else
| KeywordKind::For
| KeywordKind::While
| KeywordKind::Do
| KeywordKind::Switch
| KeywordKind::Case
| KeywordKind::Default
| KeywordKind::Break
| KeywordKind::Continue
| KeywordKind::Try
| KeywordKind::Catch
| KeywordKind::Finally
| KeywordKind::Throw
| KeywordKind::Return
| KeywordKind::Export
| KeywordKind::Extends
| KeywordKind::Instanceof
| KeywordKind::In
| KeywordKind::Enum
| KeywordKind::Debugger
| KeywordKind::With
)
}
#[inline]
pub const fn can_be_identifier(self) -> bool {
matches!(
self,
KeywordKind::Async
| KeywordKind::Await
| KeywordKind::From
| KeywordKind::As
| KeywordKind::Satisfies
| KeywordKind::Let
| KeywordKind::Yield
| KeywordKind::Number
| KeywordKind::String
| KeywordKind::Boolean
| KeywordKind::Any
| KeywordKind::Never
| KeywordKind::Unknown
| KeywordKind::Object
| KeywordKind::Symbol
| KeywordKind::Bigint
| KeywordKind::Undefined
)
}
#[inline]
pub const fn can_be_binding_name(self) -> bool {
matches!(
self,
KeywordKind::Async
| KeywordKind::From
| KeywordKind::As
| KeywordKind::Satisfies
| KeywordKind::Let
| KeywordKind::Yield
| KeywordKind::Number
| KeywordKind::String
| KeywordKind::Boolean
| KeywordKind::Any
| KeywordKind::Never
| KeywordKind::Unknown
| KeywordKind::Object
| KeywordKind::Symbol
| KeywordKind::Bigint
| KeywordKind::Undefined
)
}
}
impl fmt::Display for KeywordKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TokenKind {
Number,
String,
Identifier,
Keyword(KeywordKind),
Equals,
Colon,
Semicolon,
Comma,
BraceOpen, BraceClose, BracketOpen, BracketClose, ParenOpen, ParenClose, Arrow, Dot, DotDotDot, Minus, MinusMinus, Plus, PlusPlus, Star, StarStar, Slash, Percent, Caret, Tilde, LeftShift, RightShift, UnsignedRightShift, LessThan, GreaterThan, LessThanEquals, GreaterThanEquals, EqualsEquals, EqualsEqualsEquals, BangEquals, BangEqualsEquals, Ampersand, AmpersandAmpersand, Pipe, PipePipe, QuestionQuestion, QuestionDot, Bang, Question, PlusEquals, MinusEquals, StarEquals, SlashEquals, PercentEquals, StarStarEquals, LeftShiftEquals, RightShiftEquals, UnsignedRightShiftEquals, AmpersandEquals, PipeEquals, CaretEquals, AmpersandAmpersandEquals, PipePipeEquals, QuestionQuestionEquals, Comment {
is_block: bool,
content_start: u32,
},
NoSubstitutionTemplate,
TemplateHead,
TemplateMiddle,
TemplateTail,
RegexLiteral,
At, Hash, Eof,
}
impl TokenKind {
#[inline]
pub(crate) fn is_binding_name_word(&self) -> bool {
match self {
TokenKind::Identifier => true,
TokenKind::Keyword(kw) => kw.can_be_binding_name(),
_ => false,
}
}
}
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenKind::Number => write!(f, "number"),
TokenKind::String => write!(f, "string"),
TokenKind::Identifier => write!(f, "identifier"),
TokenKind::Keyword(kw) => write!(f, "'{kw}'"),
TokenKind::Equals => write!(f, "'='"),
TokenKind::Colon => write!(f, "':'"),
TokenKind::Semicolon => write!(f, "';'"),
TokenKind::Comma => write!(f, "','"),
TokenKind::BraceOpen => write!(f, "'{{'"),
TokenKind::BraceClose => write!(f, "'}}'"),
TokenKind::BracketOpen => write!(f, "'['"),
TokenKind::BracketClose => write!(f, "']'"),
TokenKind::ParenOpen => write!(f, "'('"),
TokenKind::ParenClose => write!(f, "')'"),
TokenKind::Arrow => write!(f, "'=>'"),
TokenKind::Dot => write!(f, "'.'"),
TokenKind::DotDotDot => write!(f, "'...'"),
TokenKind::Minus => write!(f, "'-'"),
TokenKind::MinusMinus => write!(f, "'--'"),
TokenKind::Plus => write!(f, "'+'"),
TokenKind::PlusPlus => write!(f, "'++'"),
TokenKind::Star => write!(f, "'*'"),
TokenKind::StarStar => write!(f, "'**'"),
TokenKind::Slash => write!(f, "'/'"),
TokenKind::Percent => write!(f, "'%'"),
TokenKind::Caret => write!(f, "'^'"),
TokenKind::Tilde => write!(f, "'~'"),
TokenKind::LeftShift => write!(f, "'<<'"),
TokenKind::RightShift => write!(f, "'>>'"),
TokenKind::UnsignedRightShift => write!(f, "'>>>'"),
TokenKind::LessThan => write!(f, "'<'"),
TokenKind::GreaterThan => write!(f, "'>'"),
TokenKind::LessThanEquals => write!(f, "'<='"),
TokenKind::GreaterThanEquals => write!(f, "'>='"),
TokenKind::EqualsEquals => write!(f, "'=='"),
TokenKind::EqualsEqualsEquals => write!(f, "'==='"),
TokenKind::BangEquals => write!(f, "'!='"),
TokenKind::BangEqualsEquals => write!(f, "'!=='"),
TokenKind::Ampersand => write!(f, "'&'"),
TokenKind::AmpersandAmpersand => write!(f, "'&&'"),
TokenKind::Pipe => write!(f, "'|'"),
TokenKind::PipePipe => write!(f, "'||'"),
TokenKind::QuestionQuestion => write!(f, "'??'"),
TokenKind::QuestionDot => write!(f, "'?.'"),
TokenKind::Bang => write!(f, "'!'"),
TokenKind::Question => write!(f, "'?'"),
TokenKind::PlusEquals => write!(f, "'+='"),
TokenKind::MinusEquals => write!(f, "'-='"),
TokenKind::StarEquals => write!(f, "'*='"),
TokenKind::SlashEquals => write!(f, "'/='"),
TokenKind::PercentEquals => write!(f, "'%='"),
TokenKind::StarStarEquals => write!(f, "'**='"),
TokenKind::LeftShiftEquals => write!(f, "'<<='"),
TokenKind::RightShiftEquals => write!(f, "'>>='"),
TokenKind::UnsignedRightShiftEquals => write!(f, "'>>>='"),
TokenKind::AmpersandEquals => write!(f, "'&='"),
TokenKind::PipeEquals => write!(f, "'|='"),
TokenKind::CaretEquals => write!(f, "'^='"),
TokenKind::AmpersandAmpersandEquals => write!(f, "'&&='"),
TokenKind::PipePipeEquals => write!(f, "'||='"),
TokenKind::QuestionQuestionEquals => write!(f, "'??='"),
TokenKind::Comment { is_block, .. } => {
if *is_block {
write!(f, "block comment")
} else {
write!(f, "line comment")
}
}
TokenKind::NoSubstitutionTemplate => write!(f, "template literal"),
TokenKind::TemplateHead => write!(f, "template head"),
TokenKind::TemplateMiddle => write!(f, "template middle"),
TokenKind::TemplateTail => write!(f, "template tail"),
TokenKind::RegexLiteral => write!(f, "regular expression"),
TokenKind::At => write!(f, "'@'"),
TokenKind::Hash => write!(f, "'#'"),
TokenKind::Eof => write!(f, "end of input"),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Token {
pub kind: TokenKind,
pub start: u32,
pub end: u32,
}
#[cfg(test)]
const _: () = assert!(size_of::<Token>() == 16);
#[cfg(test)]
pub(super) static KEYWORDS: &[(&str, KeywordKind)] = &[
("const", KeywordKind::Const),
("let", KeywordKind::Let),
("var", KeywordKind::Var),
("true", KeywordKind::True),
("false", KeywordKind::False),
("null", KeywordKind::Null),
("undefined", KeywordKind::Undefined),
("number", KeywordKind::Number),
("string", KeywordKind::String),
("boolean", KeywordKind::Boolean),
("any", KeywordKind::Any),
("void", KeywordKind::Void),
("never", KeywordKind::Never),
("unknown", KeywordKind::Unknown),
("object", KeywordKind::Object),
("symbol", KeywordKind::Symbol),
("bigint", KeywordKind::Bigint),
("new", KeywordKind::New),
("instanceof", KeywordKind::Instanceof),
("in", KeywordKind::In),
("return", KeywordKind::Return),
("if", KeywordKind::If),
("else", KeywordKind::Else),
("for", KeywordKind::For),
("while", KeywordKind::While),
("do", KeywordKind::Do),
("switch", KeywordKind::Switch),
("case", KeywordKind::Case),
("default", KeywordKind::Default),
("break", KeywordKind::Break),
("continue", KeywordKind::Continue),
("try", KeywordKind::Try),
("catch", KeywordKind::Catch),
("finally", KeywordKind::Finally),
("throw", KeywordKind::Throw),
("function", KeywordKind::Function),
("class", KeywordKind::Class),
("enum", KeywordKind::Enum),
("typeof", KeywordKind::Typeof),
("delete", KeywordKind::Delete),
("async", KeywordKind::Async),
("await", KeywordKind::Await),
("this", KeywordKind::This),
("super", KeywordKind::Super),
("extends", KeywordKind::Extends),
("export", KeywordKind::Export),
("import", KeywordKind::Import),
("from", KeywordKind::From),
("as", KeywordKind::As),
("satisfies", KeywordKind::Satisfies),
("yield", KeywordKind::Yield),
("debugger", KeywordKind::Debugger),
("with", KeywordKind::With),
];
const KEYWORD_MIN_LEN: usize = 2;
const KEYWORD_MAX_LEN: usize = 10;
const _: () = assert!(KEYWORD_MAX_LEN < u16::BITS as usize);
#[rustfmt::skip]
const KEYWORD_LENGTHS_BY_FIRST_LETTER: [u16; 26] = {
const fn lens(lengths: &[usize]) -> u16 {
let mut m = 0u16;
let mut i = 0;
while i < lengths.len() {
m |= 1 << lengths[i];
i += 1;
}
m
}
[
lens(&[2, 3, 5]), lens(&[5, 6, 7]), lens(&[4, 5, 8]), lens(&[2, 6, 7, 8]), lens(&[4, 6, 7]), lens(&[3, 4, 5, 7, 8]), 0, 0, lens(&[2, 6, 10]), 0, 0, lens(&[3]), 0, lens(&[3, 4, 5, 6]), lens(&[6]), 0, 0, lens(&[6]), lens(&[5, 6, 9]), lens(&[3, 4, 5, 6]), lens(&[7, 9]), lens(&[3, 4]), lens(&[4, 5]), 0, lens(&[5]), 0, ]
};
const fn keyword_low_mask(len: u32) -> u64 {
u64::MAX >> ((8 - len) * 8)
}
const fn keyword_encode(s: &str) -> u64 {
let b = s.as_bytes();
let mut w = 0u64;
let mut i = 0;
while i < b.len() {
w |= (b[i] as u64) << (i * 8);
i += 1;
}
w
}
const fn keyword_encode_wide(s: &str) -> u128 {
let b = s.as_bytes();
let mut w = 0u128;
let mut i = 0;
while i < b.len() {
w |= (b[i] as u128) << (i * 8);
i += 1;
}
w
}
#[inline(always)]
#[allow(clippy::enum_glob_use)] fn keyword_swar(word: u64, len: usize) -> Option<KeywordKind> {
use KeywordKind::*;
match len {
2 => {
let word = word & const { keyword_low_mask(2) };
if word == const { keyword_encode("in") } {
Some(In)
} else if word == const { keyword_encode("if") } {
Some(If)
} else if word == const { keyword_encode("do") } {
Some(Do)
} else if word == const { keyword_encode("as") } {
Some(As)
} else {
None
}
}
3 => {
let word = word & const { keyword_low_mask(3) };
if word == const { keyword_encode("let") } {
Some(Let)
} else if word == const { keyword_encode("var") } {
Some(Var)
} else if word == const { keyword_encode("any") } {
Some(Any)
} else if word == const { keyword_encode("new") } {
Some(New)
} else if word == const { keyword_encode("for") } {
Some(For)
} else if word == const { keyword_encode("try") } {
Some(Try)
} else {
None
}
}
4 => {
let word = word & const { keyword_low_mask(4) };
if word == const { keyword_encode("true") } {
Some(True)
} else if word == const { keyword_encode("null") } {
Some(Null)
} else if word == const { keyword_encode("void") } {
Some(Void)
} else if word == const { keyword_encode("this") } {
Some(This)
} else if word == const { keyword_encode("from") } {
Some(From)
} else if word == const { keyword_encode("enum") } {
Some(Enum)
} else if word == const { keyword_encode("case") } {
Some(Case)
} else if word == const { keyword_encode("else") } {
Some(Else)
} else if word == const { keyword_encode("with") } {
Some(With)
} else {
None
}
}
5 => {
let word = word & const { keyword_low_mask(5) };
if word == const { keyword_encode("const") } {
Some(Const)
} else if word == const { keyword_encode("false") } {
Some(False)
} else if word == const { keyword_encode("never") } {
Some(Never)
} else if word == const { keyword_encode("super") } {
Some(Super)
} else if word == const { keyword_encode("yield") } {
Some(Yield)
} else if word == const { keyword_encode("while") } {
Some(While)
} else if word == const { keyword_encode("break") } {
Some(Break)
} else if word == const { keyword_encode("throw") } {
Some(Throw)
} else if word == const { keyword_encode("class") } {
Some(Class)
} else if word == const { keyword_encode("async") } {
Some(Async)
} else if word == const { keyword_encode("await") } {
Some(Await)
} else if word == const { keyword_encode("catch") } {
Some(Catch)
} else {
None
}
}
6 => {
let word = word & const { keyword_low_mask(6) };
if word == const { keyword_encode("number") } {
Some(Number)
} else if word == const { keyword_encode("string") } {
Some(String)
} else if word == const { keyword_encode("object") } {
Some(Object)
} else if word == const { keyword_encode("symbol") } {
Some(Symbol)
} else if word == const { keyword_encode("bigint") } {
Some(Bigint)
} else if word == const { keyword_encode("return") } {
Some(Return)
} else if word == const { keyword_encode("switch") } {
Some(Switch)
} else if word == const { keyword_encode("typeof") } {
Some(Typeof)
} else if word == const { keyword_encode("delete") } {
Some(Delete)
} else if word == const { keyword_encode("export") } {
Some(Export)
} else if word == const { keyword_encode("import") } {
Some(Import)
} else {
None
}
}
7 => {
let word = word & const { keyword_low_mask(7) };
if word == const { keyword_encode("boolean") } {
Some(Boolean)
} else if word == const { keyword_encode("unknown") } {
Some(Unknown)
} else if word == const { keyword_encode("default") } {
Some(Default)
} else if word == const { keyword_encode("finally") } {
Some(Finally)
} else if word == const { keyword_encode("extends") } {
Some(Extends)
} else {
None
}
}
8 => {
if word == const { keyword_encode("continue") } {
Some(Continue)
} else if word == const { keyword_encode("function") } {
Some(Function)
} else if word == const { keyword_encode("debugger") } {
Some(Debugger)
} else {
None
}
}
_ => None,
}
}
#[inline]
fn keyword_swar_long(word: u128, len: usize) -> Option<KeywordKind> {
use KeywordKind::{Instanceof, Satisfies, Undefined};
match len {
9 => {
if word == const { keyword_encode_wide("undefined") } {
Some(Undefined)
} else if word == const { keyword_encode_wide("satisfies") } {
Some(Satisfies)
} else {
None
}
}
10 => {
if word == const { keyword_encode_wide("instanceof") } {
Some(Instanceof)
} else {
None
}
}
_ => None,
}
}
#[inline]
fn read_keyword_word(bytes: &[u8], start: usize, len: usize) -> u64 {
if let Some(chunk) = bytes.get(start..).and_then(|tail| tail.first_chunk::<8>()) {
u64::from_le_bytes(*chunk)
} else {
let mut w = 0u64;
let mut i = 0;
while i < len {
w |= (bytes[start + i] as u64) << (i * 8);
i += 1;
}
w
}
}
#[inline]
pub(crate) fn keyword_at(bytes: &[u8], start: usize, len: usize) -> Option<KeywordKind> {
if !matches!(len, KEYWORD_MIN_LEN..=KEYWORD_MAX_LEN)
|| !keyword_prefilter_admits(bytes[start], len)
{
return None;
}
if len <= 8 {
keyword_swar(read_keyword_word(bytes, start, len), len)
} else {
long_keyword(&bytes[start..start + len])
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum WordLookup {
Settled(Option<KeywordKind>),
Deferred,
Long,
}
#[inline]
pub(super) fn keyword_at_in_word(bytes: &[u8], start: usize, len: usize) -> WordLookup {
if !matches!(len, KEYWORD_MIN_LEN..=KEYWORD_MAX_LEN) {
return WordLookup::Settled(None);
}
let Some(chunk) = bytes.get(start..).and_then(|tail| tail.first_chunk::<8>()) else {
return WordLookup::Deferred;
};
let word = u64::from_le_bytes(*chunk);
if !keyword_prefilter_admits(word as u8, len) {
return WordLookup::Settled(None);
}
if len > 8 {
return WordLookup::Long;
}
WordLookup::Settled(keyword_swar(word, len))
}
#[inline]
pub(super) fn long_keyword(name: &[u8]) -> Option<KeywordKind> {
let (head, rest) = name.split_first_chunk::<8>()?;
let tail = match *rest {
[ninth] => u16::from(ninth),
[ninth, tenth] => u16::from_le_bytes([ninth, tenth]),
_ => return None,
};
keyword_swar_long(
u128::from(u64::from_le_bytes(*head)) | (u128::from(tail) << 64),
name.len(),
)
}
#[inline]
fn keyword_prefilter_admits(first: u8, len: usize) -> bool {
debug_assert!(matches!(len, KEYWORD_MIN_LEN..=KEYWORD_MAX_LEN));
let idx = first.wrapping_sub(b'a');
idx < 26 && (KEYWORD_LENGTHS_BY_FIRST_LETTER[idx as usize] >> len) & 1 != 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefilter_admits_every_keyword() {
let mut derived = [0u16; 26];
for &(kw, kind) in KEYWORDS {
assert_eq!(
keyword_at(kw.as_bytes(), 0, kw.len()),
Some(kind),
"keyword_at failed to recognize reserved word `{kw}`"
);
let len = kw.len();
assert!(
(KEYWORD_MIN_LEN..=KEYWORD_MAX_LEN).contains(&len),
"reserved word `{kw}` (len {len}) is outside the pre-filter bound \
{KEYWORD_MIN_LEN}..={KEYWORD_MAX_LEN}"
);
let first = kw.as_bytes()[0];
assert!(
first.is_ascii_lowercase(),
"reserved word `{kw}` does not start lowercase-ASCII"
);
derived[(first - b'a') as usize] |= 1u16 << len;
}
assert_eq!(
derived, KEYWORD_LENGTHS_BY_FIRST_LETTER,
"KEYWORD_LENGTHS_BY_FIRST_LETTER is out of sync with the keyword set"
);
}
#[test]
fn keyword_at_matches_the_oracle_on_every_near_miss() {
fn oracle(s: &str) -> Option<KeywordKind> {
KEYWORDS
.iter()
.find(|(kw, _)| *kw == s)
.map(|&(_, kind)| kind)
}
let alphabet: Vec<u8> = (b'a'..=b'z')
.chain(b'A'..=b'Z')
.chain(b'0'..=b'9')
.chain([b'_', b'$'])
.collect();
let mut cases = 0_u32;
let check = |s: &str| {
let expected = oracle(s);
assert_eq!(
keyword_at(s.as_bytes(), 0, s.len()),
expected,
"keyword_at disagrees with the KEYWORDS oracle on `{s}`"
);
let padded = format!("{s});\n\t\t\t\t\t\t");
for bytes in [padded.as_bytes(), s.as_bytes()] {
match keyword_at_in_word(bytes, 0, s.len()) {
WordLookup::Settled(settled) => assert_eq!(
settled, expected,
"keyword_at_in_word disagrees with the KEYWORDS oracle on `{s}`"
),
WordLookup::Long => {
assert!(
matches!(s.len(), 9 | 10),
"keyword_at_in_word handed on `{s}` as a long name"
);
assert_eq!(
long_keyword(s.as_bytes()),
expected,
"long_keyword disagrees with the KEYWORDS oracle on `{s}`"
);
}
WordLookup::Deferred => assert!(
bytes.len() < 8,
"keyword_at_in_word deferred `{s}` with a whole word in bounds"
),
}
}
if !matches!(s.len(), 9 | 10) {
assert_eq!(long_keyword(s.as_bytes()), None, "long_keyword on `{s}`");
}
};
for &(kw, _) in KEYWORDS {
check(kw);
for position in 0..kw.len() {
for &byte in &alphabet {
let mut bytes = kw.as_bytes().to_vec();
bytes[position] = byte;
check(std::str::from_utf8(&bytes).unwrap());
cases += 1;
}
}
for cut in 1..kw.len() {
check(&kw[..cut]);
}
for &byte in &alphabet {
let mut extended = kw.to_string();
extended.push(byte as char);
check(&extended);
}
}
assert!(
cases > 15_000,
"the near-miss sweep collapsed to {cases} cases"
);
}
#[test]
fn prefilter_rejects_non_keywords() {
for s in [
"Foo",
"_private",
"$x",
"x",
"",
"interface",
"readonly",
"namespace",
"get",
"kind",
"map",
] {
assert_eq!(
keyword_at(s.as_bytes(), 0, s.len()),
None,
"`{s}` should not be a keyword"
);
}
}
#[test]
fn swar_matches_keyword_table() {
for &(kw, kind) in KEYWORDS {
if kw.len() <= 8 {
assert_eq!(
keyword_swar(keyword_encode(kw), kw.len()),
Some(kind),
"keyword_swar misclassified reserved word `{kw}`"
);
assert_eq!(
keyword_swar_long(keyword_encode_wide(kw), kw.len()),
None,
"keyword_swar_long should reject length-{} word `{kw}`",
kw.len()
);
} else {
assert_eq!(
keyword_swar_long(keyword_encode_wide(kw), kw.len()),
Some(kind),
"keyword_swar_long misclassified reserved word `{kw}`"
);
assert_eq!(
keyword_swar(0, kw.len()),
None,
"keyword_swar should reject length-{} word `{kw}`",
kw.len()
);
}
}
}
#[test]
fn read_keyword_word_matches_keyword_encode() {
for &(kw, kind) in KEYWORDS {
if kw.len() > 8 {
assert_eq!(
long_keyword(kw.as_bytes()),
Some(kind),
"long_keyword's key disagrees with keyword_encode_wide for `{kw}`"
);
continue;
}
let mask = keyword_low_mask(kw.len() as u32);
let mut padded = kw.as_bytes().to_vec();
padded.extend_from_slice(b"________");
assert_eq!(
read_keyword_word(&padded, 0, kw.len()) & mask,
keyword_encode(kw),
"fast-path read_keyword_word disagrees with keyword_encode for `{kw}`"
);
assert_eq!(
read_keyword_word(kw.as_bytes(), 0, kw.len()) & mask,
keyword_encode(kw),
"EOF-path read_keyword_word disagrees with keyword_encode for `{kw}`"
);
}
}
#[test]
fn keyword_swar_ignores_lanes_past_len() {
const FILLS: [u64; 6] = [
0,
u64::MAX,
0x5a5a_5a5a_5a5a_5a5a,
0xa5a5_a5a5_a5a5_a5a5,
0x0102_0304_0506_0708,
0x8080_8080_8080_8080,
];
for &(kw, kind) in KEYWORDS {
if kw.len() > 8 {
continue;
}
let mask = keyword_low_mask(kw.len() as u32);
for fill in FILLS {
assert_eq!(
keyword_swar(keyword_encode(kw) | (fill & !mask), kw.len()),
Some(kind),
"keyword_swar lost `{kw}` under trailing fill {fill:#x}"
);
}
for trailing in 0u8..=255 {
let mut bytes = kw.as_bytes().to_vec();
bytes.extend_from_slice(&[trailing; 8]);
assert_eq!(
keyword_at(&bytes, 0, kw.len()),
Some(kind),
"keyword_at lost `{kw}` followed by {trailing:#04x}"
);
}
}
for s in [
"value", "index", "props", "fromm", "iff", "clas", "functio", "co",
] {
for trailing in 0u8..=255 {
let mut bytes = s.as_bytes().to_vec();
bytes.extend_from_slice(&[trailing; 8]);
assert_eq!(
keyword_at(&bytes, 0, s.len()),
None,
"keyword_at promoted `{s}` followed by {trailing:#04x}"
);
}
}
}
#[test]
fn swar_rejects_non_keywords() {
for s in [
"value", "index", "props", "Foo", "fromm", "iff", "clas", "functio",
] {
assert_eq!(
keyword_swar(keyword_encode(s), s.len()),
None,
"`{s}` should not be a SWAR keyword"
);
}
}
}