Skip to main content

leekscript_core/
syntax.rs

1//! Syntax and token kinds for the `LeekScript` grammar.
2//!
3//! Uses an enum with [`sipha::SyntaxKinds`] so discriminants are 0, 1, 2, … automatically.
4
5use sipha::types::FromSyntaxKind;
6use sipha::SyntaxKinds;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, SyntaxKinds)]
9#[repr(u16)]
10pub enum Kind {
11    // Trivia (0–2)
12    TriviaWs,
13    TriviaLineComment,
14    TriviaBlockComment,
15    // Tokens: literals and identifiers (3–5)
16    TokNumber,
17    TokString,
18    TokIdent,
19    // Keywords (6–48)
20    KwAbstract,
21    KwAnd,
22    KwAs,
23    KwBreak,
24    KwClass,
25    KwConst,
26    KwContinue,
27    KwDo,
28    KwElse,
29    KwFalse,
30    KwFor,
31    KwFunction,
32    KwGlobal,
33    KwIf,
34    KwIn,
35    KwInclude,
36    KwLet,
37    KwNew,
38    KwNot,
39    KwNull,
40    KwOr,
41    KwReturn,
42    KwTrue,
43    KwVar,
44    KwWhile,
45    KwXor,
46    KwFinal,
47    KwConstructor,
48    KwExtends,
49    KwStatic,
50    KwPublic,
51    KwPrivate,
52    KwProtected,
53    KwThis,
54    KwSuper,
55    KwInstanceof,
56    KwTry,
57    KwCatch,
58    KwSwitch,
59    KwCase,
60    KwDefault,
61    KwThrow,
62    KwReserved,
63    // Tokens: operators and punctuation (49–63)
64    TokOp,
65    TokArrow,
66    TokDotDot,
67    TokDot,
68    TokColon,
69    TokComma,
70    TokSemi,
71    TokParenL,
72    TokParenR,
73    TokBracketL,
74    TokBracketR,
75    TokBraceL,
76    TokBraceR,
77    TokLemnisate,
78    TokPi,
79    // End of input (64)
80    TokEof,
81    // Nodes (65+)
82    NodeRoot,
83    NodeTokenStream,
84    NodeExpr,
85    NodePrimaryExpr,
86    NodeBinaryExpr,
87    NodeBinaryLevel, // Wraps full "left op right" for one precedence level (add, mul, etc.)
88    NodeUnaryExpr,
89    NodeCallExpr,
90    NodeMemberExpr,
91    NodeIndexExpr,
92    NodeArray,
93    NodeMap,
94    NodeMapPair,
95    NodeObject,
96    NodeObjectPair,
97    NodeSet,
98    NodeStmt,
99    NodeBlock,
100    NodeVarDecl,
101    NodeIfStmt,
102    NodeWhileStmt,
103    NodeForStmt,
104    NodeForInStmt,
105    NodeDoWhileStmt,
106    NodeReturnStmt,
107    NodeBreakStmt,
108    NodeContinueStmt,
109    NodeExprStmt,
110    NodeFunctionDecl,
111    NodeClassDecl,
112    NodeInclude,
113    NodeConstructorDecl,
114    NodeInterval,
115    NodeClassField,
116    NodeAsCast,
117    NodeAnonFn,
118    NodeTypeAnnot,
119    NodeParam,
120    NodeTypeExpr,
121    NodeTypeParams,
122    // Signature file nodes (for stdlib / API signature files, not LeekScript source)
123    NodeSigFile,
124    NodeSigFunction,
125    NodeSigClass,
126    NodeSigMethod,
127    NodeSigConstructor,
128    NodeSigField,
129    NodeSigGlobal,
130    NodeSigParam,
131    /// Doxygen-style doc block after a function/global in .sig (/// lines or /** */ block).
132    NodeSigDocBlock,
133    /// Doc line token: `///` plus rest of line (for `NodeSigDocBlock`).
134    TokSigDocLine,
135    /// Block doc token: `/**` ... `*/` (for `NodeSigDocBlock`).
136    TokSigDocBlock,
137}
138
139/// sipha uses this kind for a wrapper root when the grammar produces a single root node.
140pub const SYNTHETIC_ROOT: sipha::types::SyntaxKind = u16::MAX;
141
142/// `LeekScript` keywords (for completion and tooling). Sorted for display.
143pub const KEYWORDS: &[&str] = &[
144    "abstract",
145    "and",
146    "as",
147    "break",
148    "case",
149    "catch",
150    "class",
151    "const",
152    "constructor",
153    "continue",
154    "default",
155    "do",
156    "else",
157    "extends",
158    "false",
159    "final",
160    "for",
161    "function",
162    "global",
163    "if",
164    "in",
165    "include",
166    "instanceof",
167    "let",
168    "new",
169    "not",
170    "null",
171    "or",
172    "private",
173    "protected",
174    "public",
175    "reserved",
176    "return",
177    "static",
178    "super",
179    "switch",
180    "this",
181    "throw",
182    "true",
183    "try",
184    "var",
185    "while",
186    "xor",
187];
188
189/// Returns true if `name` is a valid `LeekScript` identifier (non-empty, starts with letter or
190/// underscore, rest alphanumeric or underscore). Used e.g. for rename validation in LSP.
191#[must_use]
192pub fn is_valid_identifier(name: &str) -> bool {
193    let mut chars = name.chars();
194    match chars.next() {
195        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
196        _ => return false,
197    }
198    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
199}
200
201#[cfg(test)]
202mod tests {
203    use super::is_valid_identifier;
204
205    #[test]
206    fn valid_identifiers() {
207        assert!(is_valid_identifier("x"));
208        assert!(is_valid_identifier("_private"));
209        assert!(is_valid_identifier("foo_bar"));
210        assert!(is_valid_identifier("Cell"));
211    }
212
213    #[test]
214    fn invalid_identifiers() {
215        assert!(!is_valid_identifier(""));
216        assert!(!is_valid_identifier("123"));
217        assert!(!is_valid_identifier("bad name"));
218        assert!(!is_valid_identifier("x-y"));
219    }
220}
221
222/// Field id for the "rhs" named child (right-hand side of binary expressions in the expression grammar).
223/// Use with [`sipha::red::SyntaxNode::field_by_id`] on a `NodeBinaryExpr` to get the right operand.
224pub const FIELD_RHS: sipha::types::FieldId = 0;
225
226/// Human-readable name for a syntax kind (for diagnostics and debugging).
227pub fn kind_name(kind: sipha::types::SyntaxKind) -> &'static str {
228    if kind == SYNTHETIC_ROOT {
229        return "ROOT";
230    }
231    Kind::from_syntax_kind(kind).map_or("?", kind_name_enum)
232}
233
234fn kind_name_enum(k: Kind) -> &'static str {
235    match k {
236        Kind::TokNumber => "NUMBER",
237        Kind::TokString => "STRING",
238        Kind::TokIdent => "IDENT",
239        Kind::TokOp => "OP",
240        Kind::TokParenL => "(",
241        Kind::TokParenR => ")",
242        Kind::TokDotDot => "..",
243        Kind::NodeTokenStream => "TOKEN_STREAM",
244        Kind::NodeRoot => "ROOT",
245        Kind::NodeExpr => "EXPR",
246        Kind::NodeExprStmt => "EXPR_STMT",
247        Kind::NodeVarDecl => "VAR_DECL",
248        Kind::NodeIfStmt => "IF_STMT",
249        Kind::NodeWhileStmt => "WHILE_STMT",
250        Kind::NodeBlock => "BLOCK",
251        Kind::NodeReturnStmt => "RETURN_STMT",
252        Kind::NodeForStmt => "FOR_STMT",
253        Kind::NodeForInStmt => "FOR_IN_STMT",
254        Kind::NodeDoWhileStmt => "DO_WHILE_STMT",
255        Kind::NodeFunctionDecl => "FUNCTION_DECL",
256        Kind::NodeClassDecl => "CLASS_DECL",
257        Kind::NodeConstructorDecl => "CONSTRUCTOR_DECL",
258        Kind::NodeClassField => "CLASS_FIELD",
259        Kind::NodeInterval => "INTERVAL",
260        Kind::NodeInclude => "INCLUDE",
261        Kind::NodeArray => "ARRAY",
262        Kind::NodeMap => "MAP",
263        Kind::NodeMapPair => "MAP_PAIR",
264        Kind::NodeObject => "OBJECT",
265        Kind::NodeObjectPair => "OBJECT_PAIR",
266        Kind::NodeSet => "SET",
267        Kind::NodeTypeExpr => "TYPE_EXPR",
268        Kind::NodeTypeParams => "TYPE_PARAMS",
269        Kind::NodeAsCast => "AS_CAST",
270        Kind::NodeSigFile => "SIG_FILE",
271        Kind::NodeSigFunction => "SIG_FUNCTION",
272        Kind::NodeSigClass => "SIG_CLASS",
273        Kind::NodeSigMethod => "SIG_METHOD",
274        Kind::NodeSigConstructor => "SIG_CONSTRUCTOR",
275        Kind::NodeSigField => "SIG_FIELD",
276        Kind::NodeSigGlobal => "SIG_GLOBAL",
277        Kind::NodeSigParam => "SIG_PARAM",
278        Kind::NodeSigDocBlock => "SIG_DOC_BLOCK",
279        Kind::TokSigDocLine => "SIG_DOC_LINE",
280        Kind::TokSigDocBlock => "SIG_DOC_BLOCK_TOKEN",
281        _ => "?",
282    }
283}