use std::collections::BTreeMap;
use crate::SymbolKind;
use brink_syntax::{SyntaxKind, SyntaxNode};
use rowan::TextRange;
use crate::hir::projection::range_key;
use crate::line_index::LineIndex;
pub fn token_type_names() -> &'static [&'static str] {
&[
"namespace", "function", "variable", "string", "number", "keyword", "operator", "comment", "enum", "enumMember", "parameter", "decorator", "label", "struct", "property", "marker", "divert", "halt", "escape", ]
}
pub fn token_modifier_names() -> &'static [&'static str] {
&[
"declaration", "definition", "readonly", "deprecated", ]
}
pub const TT_NAMESPACE: u32 = 0;
pub const TT_FUNCTION: u32 = 1;
pub const TT_VARIABLE: u32 = 2;
pub const TT_STRING: u32 = 3;
pub const TT_NUMBER: u32 = 4;
pub const TT_KEYWORD: u32 = 5;
pub const TT_OPERATOR: u32 = 6;
pub const TT_COMMENT: u32 = 7;
pub const TT_ENUM: u32 = 8;
pub const TT_ENUM_MEMBER: u32 = 9;
pub const TT_PARAMETER: u32 = 10;
pub const TT_DECORATOR: u32 = 11;
pub const TT_LABEL: u32 = 12;
pub const TT_STRUCT: u32 = 13;
pub const TT_PROPERTY: u32 = 14;
pub const TT_MARKER: u32 = 15;
pub const TT_DIVERT: u32 = 16;
pub const TT_HALT: u32 = 17;
pub const TT_ESCAPE: u32 = 18;
pub const MOD_DECLARATION: u32 = 1 << 0;
pub const MOD_READONLY: u32 = 1 << 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawToken {
pub line: u32,
pub start_char: u32,
pub length: u32,
pub token_type: u32,
pub modifiers: u32,
}
#[derive(Debug, Clone)]
pub struct DeltaToken {
pub delta_line: u32,
pub delta_start: u32,
pub length: u32,
pub token_type: u32,
pub token_modifiers: u32,
}
pub struct Classification {
pub token_type: u32,
pub modifiers: u32,
}
#[expect(
clippy::too_many_lines,
reason = "flat classifier dispatch — one arm per token family"
)]
pub fn classify_token(
token: &brink_syntax::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Option<Classification> {
let kind = token.kind();
if kind == SyntaxKind::BACKSLASH {
return token
.parent()
.is_some_and(|p| p.kind() == SyntaxKind::ESCAPE)
.then_some(Classification {
token_type: TT_ESCAPE,
modifiers: 0,
});
}
if matches!(
kind,
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::EOF
| SyntaxKind::ERROR_TOKEN
| SyntaxKind::L_PAREN
| SyntaxKind::R_PAREN
| SyntaxKind::COMMA
| SyntaxKind::DOT
| SyntaxKind::COLON
| SyntaxKind::DOLLAR
) {
return None;
}
if token
.parent()
.is_some_and(|p| matches!(p.kind(), SyntaxKind::TEXT | SyntaxKind::ESCAPE))
{
return None;
}
if matches!(
kind,
SyntaxKind::L_BRACE | SyntaxKind::R_BRACE | SyntaxKind::PIPE
) {
return Some(Classification {
token_type: TT_OPERATOR,
modifiers: 0,
});
}
let parent_kind = token.parent().map(|p| p.kind());
if matches!(kind, SyntaxKind::STAR | SyntaxKind::PLUS)
&& parent_kind == Some(SyntaxKind::CHOICE_BULLETS)
{
return Some(Classification {
token_type: TT_MARKER,
modifiers: 0,
});
}
if kind == SyntaxKind::MINUS && parent_kind == Some(SyntaxKind::GATHER_DASHES) {
return Some(Classification {
token_type: TT_MARKER,
modifiers: 0,
});
}
if matches!(kind, SyntaxKind::L_BRACKET | SyntaxKind::R_BRACKET) {
return if matches!(
parent_kind,
Some(
SyntaxKind::CHOICE
| SyntaxKind::CHOICE_START_CONTENT
| SyntaxKind::CHOICE_BRACKET_CONTENT
| SyntaxKind::CHOICE_INNER_CONTENT
)
) {
Some(Classification {
token_type: TT_MARKER,
modifiers: 0,
})
} else {
None
};
}
if matches!(kind, SyntaxKind::EQ | SyntaxKind::EQ_EQ) {
if parent_kind == Some(SyntaxKind::KNOT_HEADER) {
return Some(Classification {
token_type: TT_NAMESPACE,
modifiers: 0,
});
}
if parent_kind == Some(SyntaxKind::STITCH_HEADER) {
return Some(Classification {
token_type: TT_FUNCTION,
modifiers: 0,
});
}
}
if matches!(
kind,
SyntaxKind::DIVERT | SyntaxKind::THREAD | SyntaxKind::TUNNEL_ONWARDS | SyntaxKind::GLUE
) {
return Some(Classification {
token_type: TT_DIVERT,
modifiers: 0,
});
}
if matches!(kind, SyntaxKind::KW_END | SyntaxKind::KW_DONE) {
return Some(Classification {
token_type: TT_HALT,
modifiers: 0,
});
}
if kind == SyntaxKind::LINE_COMMENT || kind == SyntaxKind::BLOCK_COMMENT {
return Some(Classification {
token_type: TT_COMMENT,
modifiers: 0,
});
}
if kind.is_keyword() {
return Some(Classification {
token_type: TT_KEYWORD,
modifiers: 0,
});
}
if kind == SyntaxKind::INTEGER || kind == SyntaxKind::FLOAT {
return Some(Classification {
token_type: TT_NUMBER,
modifiers: 0,
});
}
if matches!(
kind,
SyntaxKind::STRING_TEXT | SyntaxKind::STRING_ESCAPE | SyntaxKind::QUOTE
) {
return Some(Classification {
token_type: TT_STRING,
modifiers: 0,
});
}
if matches!(
kind,
SyntaxKind::TILDE
| SyntaxKind::EQ
| SyntaxKind::EQ_EQ
| SyntaxKind::BANG_EQ
| SyntaxKind::LT
| SyntaxKind::GT
| SyntaxKind::LT_EQ
| SyntaxKind::GT_EQ
| SyntaxKind::PLUS
| SyntaxKind::MINUS
| SyntaxKind::STAR
| SyntaxKind::SLASH
| SyntaxKind::PERCENT
| SyntaxKind::CARET
| SyntaxKind::BANG
| SyntaxKind::QUESTION
| SyntaxKind::BANG_QUESTION
| SyntaxKind::AMP
| SyntaxKind::AMP_AMP
| SyntaxKind::PLUS_EQ
| SyntaxKind::MINUS_EQ
) {
return Some(Classification {
token_type: TT_OPERATOR,
modifiers: 0,
});
}
if kind == SyntaxKind::HASH {
return Some(Classification {
token_type: TT_DECORATOR,
modifiers: 0,
});
}
if kind == SyntaxKind::IDENT {
return classify_ident(token, resolution_index);
}
None
}
fn classify_ident(
token: &brink_syntax::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Option<Classification> {
let parent = token.parent()?;
let parent_kind = parent.kind();
if parent_kind == SyntaxKind::LIST_MEMBER_ON || parent_kind == SyntaxKind::LIST_MEMBER_OFF {
return Some(Classification {
token_type: TT_ENUM_MEMBER,
modifiers: MOD_DECLARATION,
});
}
if parent_kind == SyntaxKind::IDENTIFIER
&& let Some(grandparent) = parent.parent()
{
let gp_kind = grandparent.kind();
return match gp_kind {
SyntaxKind::KNOT_HEADER => Some(Classification {
token_type: TT_NAMESPACE,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::STITCH_HEADER | SyntaxKind::EXTERNAL_DECL => Some(Classification {
token_type: TT_FUNCTION,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::KNOT_PARAM_DECL => Some(Classification {
token_type: TT_PARAMETER,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::LABEL => Some(Classification {
token_type: TT_LABEL,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::VAR_DECL | SyntaxKind::TEMP_DECL => Some(Classification {
token_type: TT_VARIABLE,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::CONST_DECL => Some(Classification {
token_type: TT_VARIABLE,
modifiers: MOD_DECLARATION | MOD_READONLY,
}),
SyntaxKind::LIST_DECL => Some(Classification {
token_type: TT_ENUM,
modifiers: MOD_DECLARATION,
}),
SyntaxKind::FUNCTION_CALL => Some(Classification {
token_type: TT_FUNCTION,
modifiers: 0,
}),
_ => Some(classify_ident_by_resolution(token, resolution_index)),
};
}
Some(classify_ident_by_resolution(token, resolution_index))
}
fn classify_ident_by_resolution(
token: &brink_syntax::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Classification {
if let Some(&sym_kind) = resolution_index.get(&range_key(token.text_range())) {
return symbol_kind_to_classification(sym_kind);
}
let Some(parent) = token.parent() else {
return GENERIC_VARIABLE;
};
if parent.kind() == SyntaxKind::IDENTIFIER
&& let Some(&sym_kind) = resolution_index.get(&range_key(parent.text_range()))
{
return symbol_kind_to_classification(sym_kind);
}
let path = if parent.kind() == SyntaxKind::PATH {
Some(parent.clone())
} else if parent.kind() == SyntaxKind::IDENTIFIER {
parent.parent().filter(|gp| gp.kind() == SyntaxKind::PATH)
} else {
None
};
if let Some(path) = path
&& let Some(&sym_kind) = resolution_index.get(&range_key(path.text_range()))
{
return classify_path_segment(&path, token, sym_kind);
}
GENERIC_VARIABLE
}
const GENERIC_VARIABLE: Classification = Classification {
token_type: TT_VARIABLE,
modifiers: 0,
};
fn classify_path_segment(
path: &SyntaxNode,
token: &brink_syntax::SyntaxToken,
sym_kind: SymbolKind,
) -> Classification {
let named_by_tail = matches!(
sym_kind,
SymbolKind::Stitch | SymbolKind::ListItem | SymbolKind::Label
);
let segments: Vec<TextRange> = path
.descendants_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text_range())
.collect();
let naming = if named_by_tail {
segments.last()
} else {
segments.first()
};
if naming == Some(&token.text_range()) {
return symbol_kind_to_classification(sym_kind);
}
if named_by_tail {
return GENERIC_VARIABLE;
}
Classification {
token_type: TT_PROPERTY,
modifiers: 0,
}
}
fn symbol_kind_to_classification(kind: SymbolKind) -> Classification {
match kind {
SymbolKind::Knot => Classification {
token_type: TT_NAMESPACE,
modifiers: 0,
},
SymbolKind::Stitch | SymbolKind::External => Classification {
token_type: TT_FUNCTION,
modifiers: 0,
},
SymbolKind::Variable | SymbolKind::Temp => Classification {
token_type: TT_VARIABLE,
modifiers: 0,
},
SymbolKind::Constant => Classification {
token_type: TT_VARIABLE,
modifiers: MOD_READONLY,
},
SymbolKind::List => Classification {
token_type: TT_ENUM,
modifiers: 0,
},
SymbolKind::ListItem => Classification {
token_type: TT_ENUM_MEMBER,
modifiers: 0,
},
SymbolKind::Label => Classification {
token_type: TT_LABEL,
modifiers: 0,
},
SymbolKind::Param => Classification {
token_type: TT_PARAMETER,
modifiers: 0,
},
SymbolKind::Struct => Classification {
token_type: TT_STRUCT,
modifiers: 0,
},
}
}
fn is_prose_run_container(kind: brink_syntax_native::SyntaxKind) -> bool {
use brink_syntax_native::SyntaxKind as NK;
matches!(
kind,
NK::TEXT | NK::CUE_NAME | NK::TAG | NK::SCENE_TITLE | NK::ESCAPE
)
}
#[expect(
clippy::too_many_lines,
reason = "flat classifier dispatch — one arm per token family"
)]
pub fn classify_native_token(
token: &brink_syntax_native::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Option<Classification> {
use brink_syntax_native::SyntaxKind as NK;
let kind = token.kind();
if token
.parent()
.is_some_and(|p| matches!(p.kind(), NK::STRING_LIT | NK::SPAN_ATTR_VALUE))
{
return Some(Classification {
token_type: TT_STRING,
modifiers: 0,
});
}
if kind == NK::BACKSLASH {
return token
.parent()
.is_some_and(|p| p.kind() == NK::ESCAPE)
.then_some(Classification {
token_type: TT_ESCAPE,
modifiers: 0,
});
}
if matches!(
kind,
NK::WHITESPACE
| NK::NEWLINE
| NK::EOF
| NK::ERROR_TOKEN
| NK::L_PAREN
| NK::R_PAREN
| NK::L_BRACKET
| NK::R_BRACKET
| NK::COMMA
| NK::DOT
| NK::COLON
| NK::COLON_COLON
| NK::SEMICOLON
) {
return None;
}
if matches!(kind, NK::L_BRACE | NK::R_BRACE | NK::PIPE) {
if token
.parent()
.is_some_and(|p| is_prose_run_container(p.kind()))
{
return None;
}
return Some(Classification {
token_type: TT_OPERATOR,
modifiers: 0,
});
}
let parent_kind = token.parent().map(|p| p.kind());
if matches!(kind, NK::STAR | NK::PLUS) && parent_kind == Some(NK::CHOICE_BULLET) {
return Some(Classification {
token_type: TT_MARKER,
modifiers: 0,
});
}
if matches!(kind, NK::L_BRACKET | NK::R_BRACKET)
&& matches!(
parent_kind,
Some(
NK::CHOICE
| NK::CHOICE_START_CONTENT
| NK::CHOICE_BRACKET_CONTENT
| NK::CHOICE_INNER_CONTENT
)
)
{
return Some(Classification {
token_type: TT_MARKER,
modifiers: 0,
});
}
if matches!(kind, NK::DIVERT | NK::THREAD | NK::GLUE) {
return Some(Classification {
token_type: TT_DIVERT,
modifiers: 0,
});
}
if matches!(kind, NK::KW_END | NK::KW_DONE) {
if token
.parent()
.is_some_and(|p| is_prose_run_container(p.kind()))
{
return None;
}
return Some(Classification {
token_type: TT_HALT,
modifiers: 0,
});
}
if matches!(
kind,
NK::LINE_COMMENT | NK::BLOCK_COMMENT | NK::DOC_COMMENT_OUTER | NK::DOC_COMMENT_INNER
) {
return Some(Classification {
token_type: TT_COMMENT,
modifiers: 0,
});
}
if kind.is_keyword() {
if token
.parent()
.is_some_and(|p| is_prose_run_container(p.kind()))
{
return None;
}
return Some(Classification {
token_type: TT_KEYWORD,
modifiers: 0,
});
}
if kind == NK::AT {
return token
.parent()
.filter(|p| matches!(p.kind(), NK::CUE | NK::COMPACT_CUE))
.map(|_| Classification {
token_type: TT_DECORATOR,
modifiers: 0,
});
}
if kind == NK::IDENT {
return classify_native_ident(token, resolution_index);
}
if !matches!(kind, NK::HASH | NK::AT_L_BRACKET)
&& token
.parent()
.is_some_and(|p| is_prose_run_container(p.kind()))
{
return None;
}
classify_native_fixed_kind(kind)
}
fn classify_native_fixed_kind(kind: brink_syntax_native::SyntaxKind) -> Option<Classification> {
use brink_syntax_native::SyntaxKind as NK;
if matches!(kind, NK::INTEGER | NK::FLOAT) {
return Some(Classification {
token_type: TT_NUMBER,
modifiers: 0,
});
}
if matches!(kind, NK::STRING_TEXT | NK::STRING_ESCAPE | NK::QUOTE) {
return Some(Classification {
token_type: TT_STRING,
modifiers: 0,
});
}
if matches!(
kind,
NK::TILDE
| NK::EQ
| NK::EQ_EQ
| NK::BANG_EQ
| NK::LT
| NK::GT
| NK::LT_EQ
| NK::GT_EQ
| NK::PLUS
| NK::MINUS
| NK::STAR
| NK::SLASH
| NK::PERCENT
| NK::CARET
| NK::BANG
| NK::QUESTION
| NK::AMP
| NK::AMP_AMP
| NK::PLUS_EQ
| NK::MINUS_EQ
| NK::STAR_EQ
| NK::SLASH_EQ
| NK::FAT_ARROW
) {
return Some(Classification {
token_type: TT_OPERATOR,
modifiers: 0,
});
}
if kind == NK::HASH || kind == NK::AT_L_BRACKET {
return Some(Classification {
token_type: TT_DECORATOR,
modifiers: 0,
});
}
None
}
fn classify_native_ident(
token: &brink_syntax_native::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Option<Classification> {
use brink_syntax_native::SyntaxKind as NK;
let parent = token.parent()?;
match parent.kind() {
NK::STRUCT_DECL => Some(Classification {
token_type: TT_STRUCT,
modifiers: MOD_DECLARATION,
}),
NK::FLOW_DECL | NK::MODULE_DECL => Some(Classification {
token_type: TT_NAMESPACE,
modifiers: MOD_DECLARATION,
}),
NK::FN_DECL | NK::EXTERN_DECL | NK::SCENE_SLUG => Some(Classification {
token_type: TT_FUNCTION,
modifiers: MOD_DECLARATION,
}),
NK::PARAM => Some(Classification {
token_type: TT_PARAMETER,
modifiers: MOD_DECLARATION,
}),
NK::VAR_DECL | NK::LET_STMT => Some(Classification {
token_type: TT_VARIABLE,
modifiers: MOD_DECLARATION,
}),
NK::CONST_DECL => Some(Classification {
token_type: TT_VARIABLE,
modifiers: MOD_DECLARATION | MOD_READONLY,
}),
NK::FLAGS_DECL => Some(Classification {
token_type: TT_ENUM,
modifiers: MOD_DECLARATION,
}),
NK::FLAGS_MEMBER => Some(Classification {
token_type: TT_ENUM_MEMBER,
modifiers: MOD_DECLARATION,
}),
NK::STRUCT_FIELD => Some(Classification {
token_type: TT_PROPERTY,
modifiers: MOD_DECLARATION,
}),
NK::LABEL | NK::CUE_NAME => Some(Classification {
token_type: TT_LABEL,
modifiers: MOD_DECLARATION,
}),
NK::TYPE_NAME | NK::TYPE_GENERIC => Some(Classification {
token_type: TT_STRUCT,
modifiers: 0,
}),
NK::DISPATCH_NAME => Some(Classification {
token_type: TT_FUNCTION,
modifiers: 0,
}),
NK::ANNOTATION_LINE => Some(Classification {
token_type: TT_DECORATOR,
modifiers: 0,
}),
NK::ANNOTATION_ARG => Some(Classification {
token_type: TT_PARAMETER,
modifiers: 0,
}),
NK::TEXT | NK::TAG | NK::SCENE_TITLE => None,
_ => Some(classify_native_ident_by_resolution(token, resolution_index)),
}
}
fn classify_native_ident_by_resolution(
token: &brink_syntax_native::SyntaxToken,
resolution_index: &BTreeMap<(u32, u32), SymbolKind>,
) -> Classification {
use brink_syntax_native::SyntaxKind as NK;
if let Some(&sym_kind) = resolution_index.get(&range_key(token.text_range())) {
return symbol_kind_to_classification(sym_kind);
}
let Some(parent) = token.parent() else {
return GENERIC_VARIABLE;
};
if parent.kind() == NK::PATH_SEGMENT
&& let Some(&sym_kind) = resolution_index.get(&range_key(parent.text_range()))
{
return symbol_kind_to_classification(sym_kind);
}
let path = if parent.kind() == NK::PATH {
Some(parent.clone())
} else if parent.kind() == NK::PATH_SEGMENT {
parent.parent().filter(|gp| gp.kind() == NK::PATH)
} else {
None
};
if let Some(path) = path
&& let Some(&sym_kind) = resolution_index.get(&range_key(path.text_range()))
{
return classify_native_path_segment(&path, token, sym_kind);
}
GENERIC_VARIABLE
}
fn classify_native_path_segment(
path: &brink_syntax_native::SyntaxNode,
token: &brink_syntax_native::SyntaxToken,
sym_kind: SymbolKind,
) -> Classification {
let named_by_tail = matches!(
sym_kind,
SymbolKind::Stitch | SymbolKind::ListItem | SymbolKind::Label
);
let segments: Vec<TextRange> = path
.descendants_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|t| t.kind() == brink_syntax_native::SyntaxKind::IDENT)
.map(|t| t.text_range())
.collect();
let naming = if named_by_tail {
segments.last()
} else {
segments.first()
};
if naming == Some(&token.text_range()) {
return symbol_kind_to_classification(sym_kind);
}
if named_by_tail {
return GENERIC_VARIABLE;
}
Classification {
token_type: TT_PROPERTY,
modifiers: 0,
}
}
pub fn emit_token<L: rowan::Language>(
token: &rowan::SyntaxToken<L>,
classification: &Classification,
idx: &LineIndex,
out: &mut Vec<RawToken>,
) {
let text = token.text();
let start_offset = token.text_range().start();
if !text.contains('\n') {
let (line, start_char) = idx.line_col(start_offset);
let length = utf16_len(text);
out.push(RawToken {
line,
start_char,
length,
token_type: classification.token_type,
modifiers: classification.modifiers,
});
return;
}
let segments: Vec<&str> = text.split('\n').collect();
let num_segments = segments.len();
let mut byte_offset = u32::from(start_offset);
for (i, segment) in segments.iter().enumerate() {
if segment.is_empty() && i > 0 {
byte_offset += 1; continue;
}
let (line, start_char) = idx.line_col(rowan::TextSize::from(byte_offset));
let length = utf16_len(segment);
if length > 0 {
out.push(RawToken {
line,
start_char,
length,
token_type: classification.token_type,
modifiers: classification.modifiers,
});
}
byte_offset += u32::try_from(segment.len()).unwrap_or(u32::MAX);
if i < num_segments - 1 {
byte_offset += 1; }
}
}
fn utf16_len(s: &str) -> u32 {
s.chars()
.map(|c| u32::try_from(c.len_utf16()).unwrap_or(1))
.sum()
}
pub fn delta_encode(raw_tokens: &[RawToken]) -> Vec<DeltaToken> {
let mut result = Vec::with_capacity(raw_tokens.len());
let mut prev_line = 0u32;
let mut prev_start = 0u32;
for tok in raw_tokens {
let delta_line = tok.line - prev_line;
let delta_start = if delta_line == 0 {
tok.start_char - prev_start
} else {
tok.start_char
};
result.push(DeltaToken {
delta_line,
delta_start,
length: tok.length,
token_type: tok.token_type,
token_modifiers: tok.modifiers,
});
prev_line = tok.line;
prev_start = tok.start_char;
}
result
}
#[must_use]
pub fn tokens_with_kinds(
source: &str,
root: &brink_syntax::SyntaxNode,
kinds: &BTreeMap<(u32, u32), SymbolKind>,
) -> Vec<RawToken> {
let idx = LineIndex::new(source);
let mut raw_tokens = Vec::new();
for element in root.descendants_with_tokens() {
let token = match element {
rowan::NodeOrToken::Token(t) => t,
rowan::NodeOrToken::Node(_) => continue,
};
if let Some(classification) = classify_token(&token, kinds) {
emit_token(&token, &classification, &idx, &mut raw_tokens);
}
}
raw_tokens
}
#[must_use]
pub fn tokens_with_kinds_native(
source: &str,
root: &brink_syntax_native::SyntaxNode,
kinds: &BTreeMap<(u32, u32), SymbolKind>,
) -> Vec<RawToken> {
let idx = LineIndex::new(source);
let mut raw_tokens = Vec::new();
for element in root.descendants_with_tokens() {
let token = match element {
rowan::NodeOrToken::Token(t) => t,
rowan::NodeOrToken::Node(_) => continue,
};
if let Some(classification) = classify_native_token(&token, kinds) {
emit_token(&token, &classification, &idx, &mut raw_tokens);
}
}
raw_tokens
}