#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
#[expect(non_camel_case_types)]
pub enum SyntaxKind {
WHITESPACE = 0,
NEWLINE,
LINE_COMMENT,
BLOCK_COMMENT,
DOC_COMMENT_OUTER,
DOC_COMMENT_INNER,
KW_FLOW,
KW_FN,
KW_VAR,
KW_CONST,
KW_FLAGS,
KW_STRUCT,
KW_EXTERN,
KW_IMPORT,
KW_USE,
KW_MODULE,
KW_RETURN,
KW_REF,
KW_IF,
KW_MATCH,
KW_ELSE,
KW_AS,
KW_TRUE,
KW_FALSE,
KW_END,
KW_DONE,
EQ,
PLUS_EQ,
MINUS_EQ,
STAR_EQ,
SLASH_EQ,
EQ_EQ,
BANG_EQ,
LT,
GT,
LT_EQ,
GT_EQ,
AMP,
AMP_AMP,
PLUS,
MINUS,
STAR,
SLASH,
PERCENT,
CARET,
BANG,
QUESTION,
L_PAREN,
R_PAREN,
L_BRACE,
R_BRACE,
L_BRACKET,
R_BRACKET,
PIPE,
COMMA,
DOT,
COLON,
SEMICOLON,
COLON_COLON,
HASH,
TILDE,
BACKSLASH,
AT,
AT_L_BRACKET,
GLUE,
DIVERT,
THREAD,
FAT_ARROW,
INTEGER,
FLOAT,
QUOTE,
STRING_TEXT,
STRING_ESCAPE,
IDENT,
ERROR_TOKEN,
EOF,
DOC_COMMENT,
SOURCE_FILE,
FLOW_DECL,
FN_DECL,
PARAM_LIST,
PARAM,
VAR_DECL,
CONST_DECL,
FLAGS_DECL,
FLAGS_MEMBER_LIST,
FLAGS_MEMBER,
STRUCT_DECL,
STRUCT_FIELD,
EXTERN_DECL,
USE_DECL,
USE_TREE,
USE_TREE_LIST,
IMPORT_DECL,
MODULE_DECL,
BLOCK,
CONTENT_LINE,
TEXT,
INTERPOLATION,
GLUE_NODE,
TAG_LINE,
TAG,
CHOICE_POINT,
CHOICE,
CHOICE_BULLET,
LABEL,
CHOICE_GUARD,
CHOICE_START_CONTENT,
CHOICE_BRACKET_CONTENT,
CHOICE_INNER_CONTENT,
CHOICE_BODY,
ELSE_BRANCH,
SPLICE,
CONDITIONAL_BLOCK,
IF_ARM,
MATCH_ARM,
MATCH_PATTERN,
ALTERNATION_BLOCK,
ALTERNATION_MARKER,
ENTRY,
ANNOTATION_LINE,
ANNOTATION_ARGS,
ANNOTATION_ARG,
DIVERT_STMT,
TUNNEL_CALL,
DIVERT_TARGET,
RETURN_STMT,
RETURN_REDIRECT,
PATH,
PATH_SEGMENT,
INTEGER_LIT,
FLOAT_LIT,
STRING_LIT,
BOOLEAN_LIT,
PATH_EXPR,
PAREN_EXPR,
PREFIX_EXPR,
INFIX_EXPR,
CALL_EXPR,
ARG_LIST,
LAMBDA_EXPR,
LAMBDA_PARAMS,
ERROR,
#[doc(hidden)]
__LAST,
}
impl SyntaxKind {
#[must_use]
pub fn is_token(self) -> bool {
matches!(
self,
Self::WHITESPACE
| Self::NEWLINE
| Self::LINE_COMMENT
| Self::BLOCK_COMMENT
| Self::DOC_COMMENT_OUTER
| Self::DOC_COMMENT_INNER
| Self::KW_FLOW
| Self::KW_FN
| Self::KW_VAR
| Self::KW_CONST
| Self::KW_FLAGS
| Self::KW_STRUCT
| Self::KW_EXTERN
| Self::KW_IMPORT
| Self::KW_USE
| Self::KW_MODULE
| Self::KW_RETURN
| Self::KW_REF
| Self::KW_IF
| Self::KW_MATCH
| Self::KW_ELSE
| Self::KW_AS
| Self::KW_TRUE
| Self::KW_FALSE
| Self::KW_END
| Self::KW_DONE
| Self::EQ
| Self::PLUS_EQ
| Self::MINUS_EQ
| Self::STAR_EQ
| Self::SLASH_EQ
| Self::EQ_EQ
| Self::BANG_EQ
| Self::LT
| Self::GT
| Self::LT_EQ
| Self::GT_EQ
| Self::AMP
| Self::AMP_AMP
| Self::PLUS
| Self::MINUS
| Self::STAR
| Self::SLASH
| Self::PERCENT
| Self::CARET
| Self::BANG
| Self::QUESTION
| Self::L_PAREN
| Self::R_PAREN
| Self::L_BRACE
| Self::R_BRACE
| Self::L_BRACKET
| Self::R_BRACKET
| Self::PIPE
| Self::COMMA
| Self::DOT
| Self::COLON
| Self::SEMICOLON
| Self::COLON_COLON
| Self::HASH
| Self::TILDE
| Self::BACKSLASH
| Self::AT
| Self::AT_L_BRACKET
| Self::GLUE
| Self::DIVERT
| Self::THREAD
| Self::FAT_ARROW
| Self::INTEGER
| Self::FLOAT
| Self::QUOTE
| Self::STRING_TEXT
| Self::STRING_ESCAPE
| Self::IDENT
| Self::ERROR_TOKEN
| Self::EOF
)
}
#[must_use]
pub fn is_node(self) -> bool {
!self.is_token() && self != Self::__LAST
}
#[must_use]
pub fn is_trivia(self) -> bool {
matches!(
self,
Self::WHITESPACE | Self::LINE_COMMENT | Self::BLOCK_COMMENT
)
}
#[must_use]
pub fn is_keyword(self) -> bool {
matches!(
self,
Self::KW_FLOW
| Self::KW_FN
| Self::KW_VAR
| Self::KW_CONST
| Self::KW_FLAGS
| Self::KW_STRUCT
| Self::KW_EXTERN
| Self::KW_IMPORT
| Self::KW_USE
| Self::KW_MODULE
| Self::KW_RETURN
| Self::KW_REF
| Self::KW_IF
| Self::KW_MATCH
| Self::KW_ELSE
| Self::KW_AS
| Self::KW_TRUE
| Self::KW_FALSE
| Self::KW_END
| Self::KW_DONE
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NativeLanguage {}
impl rowan::Language for NativeLanguage {
type Kind = SyntaxKind;
fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
assert!(raw.0 < SyntaxKind::__LAST as u16);
#[expect(unsafe_code, reason = "repr(u16) transmute with bounds check")]
unsafe {
std::mem::transmute::<u16, SyntaxKind>(raw.0)
}
}
fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
rowan::SyntaxKind(kind as u16)
}
}
pub type SyntaxNode = rowan::SyntaxNode<NativeLanguage>;
pub type SyntaxToken = rowan::SyntaxToken<NativeLanguage>;
pub type SyntaxElement = rowan::SyntaxElement<NativeLanguage>;
#[cfg(test)]
mod tests {
use super::*;
use rowan::Language;
#[test]
fn roundtrip_through_rowan() {
let mut i = 0u16;
loop {
if i == SyntaxKind::__LAST as u16 {
break;
}
let raw = rowan::SyntaxKind(i);
let kind = NativeLanguage::kind_from_raw(raw);
let back = NativeLanguage::kind_to_raw(kind);
assert_eq!(raw, back, "roundtrip failed for discriminant {i}");
i += 1;
}
}
#[test]
fn token_node_partition() {
let mut i = 0u16;
loop {
if i == SyntaxKind::__LAST as u16 {
break;
}
let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
assert!(
kind.is_token() ^ kind.is_node(),
"{kind:?} is neither token nor node (or both)"
);
i += 1;
}
}
#[test]
fn trivia_is_subset_of_tokens() {
let mut i = 0u16;
loop {
if i == SyntaxKind::__LAST as u16 {
break;
}
let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
if kind.is_trivia() {
assert!(kind.is_token(), "{kind:?} is trivia but not a token");
}
i += 1;
}
}
#[test]
fn newline_is_not_trivia() {
assert!(!SyntaxKind::NEWLINE.is_trivia());
assert!(SyntaxKind::NEWLINE.is_token());
}
#[test]
fn keywords_are_tokens() {
let keywords = [
SyntaxKind::KW_FLOW,
SyntaxKind::KW_FN,
SyntaxKind::KW_VAR,
SyntaxKind::KW_CONST,
SyntaxKind::KW_FLAGS,
SyntaxKind::KW_STRUCT,
SyntaxKind::KW_EXTERN,
SyntaxKind::KW_IMPORT,
SyntaxKind::KW_USE,
SyntaxKind::KW_MODULE,
SyntaxKind::KW_RETURN,
SyntaxKind::KW_REF,
SyntaxKind::KW_IF,
SyntaxKind::KW_MATCH,
SyntaxKind::KW_ELSE,
SyntaxKind::KW_AS,
SyntaxKind::KW_TRUE,
SyntaxKind::KW_FALSE,
SyntaxKind::KW_END,
SyntaxKind::KW_DONE,
];
for kw in keywords {
assert!(kw.is_token(), "{kw:?} should be a token");
assert!(kw.is_keyword(), "{kw:?} should be a keyword");
}
}
#[test]
fn non_keywords_are_not_keywords() {
assert!(!SyntaxKind::IDENT.is_keyword());
assert!(!SyntaxKind::PLUS.is_keyword());
assert!(!SyntaxKind::SOURCE_FILE.is_keyword());
}
}