Skip to main content

aura_lang/lexer/
token.rs

1use crate::span::Span;
2
3#[derive(Debug, Clone, PartialEq)]
4pub struct Token<'a> {
5    pub kind: TokenKind<'a>,
6    pub span: Span,
7}
8
9/// SPEC ยง2.1. Zero-copy invariant: not a single `String` - only slices of the source.
10#[derive(Debug, Clone, PartialEq)]
11pub enum TokenKind<'a> {
12    // Literals
13    Ident(&'a str),
14    Int(i64),
15    Float(f64),
16    /// Content without quotes; escape sequences are raw (lazily unescaped during eval).
17    Str(&'a str),
18    InterpStr(Vec<StrPart<'a>>),
19    /// github/actions/rust-cache@v1.2 โ†’ path="github/actions/rust-cache", version="v1.2"
20    ImportPath {
21        path: &'a str,
22        version: &'a str,
23    },
24    True,
25    False,
26    Null,
27    // Keywords
28    Import,
29    As,
30    Type,
31    Enum,
32    Def,
33    End,
34    Domain,
35    New,
36    Assert,
37    Shadow,
38    Pub,
39    Cond,
40    Else,
41    // Delimiters
42    Newline,
43    LParen,
44    RParen,
45    LBracket,
46    RBracket,
47    Colon,
48    Comma,
49    Dot,
50    Assign,
51    Arrow,
52    Question,
53    // Operators
54    Plus,
55    Minus,
56    Star,
57    Slash,
58    Percent,
59    EqEq,
60    NotEq,
61    Lt,
62    Gt,
63    LtEq,
64    GtEq,
65    And,
66    Or,
67    Not,
68    Eof,
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub enum StrPart<'a> {
73    Lit(&'a str),
74    /// A raw expression slice from `#{...}`; parsed by Phase 2.
75    Interp(&'a str),
76}
77
78/// Every reserved keyword. The single source of truth for tooling (the LSP,
79/// grammars): a keyword added to `keyword()` below must be listed here too, and
80/// a test enforces that. `text` is intentionally absent โ€” it is a contextual
81/// block-string opener (D16), not a reserved word.
82pub const KEYWORDS: &[&str] = &[
83    "import", "as", "type", "enum", "def", "end", "domain", "new", "assert", "shadow", "pub",
84    "cond", "else", "true", "false", "null",
85];
86
87impl TokenKind<'_> {
88    pub(crate) fn keyword(ident: &str) -> Option<TokenKind<'static>> {
89        Some(match ident {
90            "import" => TokenKind::Import,
91            "as" => TokenKind::As,
92            "type" => TokenKind::Type,
93            "enum" => TokenKind::Enum,
94            "def" => TokenKind::Def,
95            "end" => TokenKind::End,
96            "domain" => TokenKind::Domain,
97            "new" => TokenKind::New,
98            "assert" => TokenKind::Assert,
99            "shadow" => TokenKind::Shadow,
100            "pub" => TokenKind::Pub,
101            "cond" => TokenKind::Cond,
102            "else" => TokenKind::Else,
103            "true" => TokenKind::True,
104            "false" => TokenKind::False,
105            "null" => TokenKind::Null,
106            _ => return None,
107        })
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn keywords_const_matches_the_recognizer() {
117        // Every listed keyword is recognized, and a non-keyword is not: the const
118        // and `keyword()` cannot drift apart without this test going red.
119        for kw in KEYWORDS {
120            assert!(
121                TokenKind::keyword(kw).is_some(),
122                "`{kw}` is in KEYWORDS but not recognized by keyword()"
123            );
124        }
125        assert!(TokenKind::keyword("text").is_none(), "text is contextual");
126        assert!(TokenKind::keyword("nope").is_none());
127    }
128}