lang: Recognize explicit generic delimiters

4501081c8d025f0c1df6c44ce468b829f433781ca231d9682a04ec7c3d6e85c5
Teach the scanner and editor definitions about the dedicated angle delimiters. Accept them inside delimited input so a scanner-aware bootstrap compiler can compile later generic tests and diagnostics.

Assisted-by: Codex:gpt-5.6
Alexis Sellier committed ago 1 parent f18cc5f0
lib/std/lang/scanner.rad +27 -3
22 22
    RParen,     // )
23 23
    LBrace,     // {
24 24
    RBrace,     // }
25 25
    LBracket,   // [
26 26
    RBracket,   // ]
27 +
    LAngle,     // U+27E8
28 +
    RAngle,     // U+27E9
27 29
    Comma,      // ,
28 30
    Dot,        // .
29 31
    DotDot,     // ..
30 32
    Minus,      // -
31 33
    Plus,       // +
98 100
    Mod, Use, Super,
99 101
100 102
    // Type or function attributes.
101 103
    Export, Static,
102 104
103 -
    // Trait-related tokens.
104 -
    Trait, Instance,
105 +
    // Trait and generic-related tokens.
106 +
    Trait, Instance, Instantiate,
105 107
106 108
    // Type-related tokens.
107 109
    I8, I16, I32, I64, U8, U16, U32, U64,
108 110
    Opaque, Fn, Bool, Union, Record, As, Unsafe
109 111
}
115 117
    /// Corresponding token.
116 118
    tok: TokenKind,
117 119
}
118 120
119 121
/// Sorted keyword table for binary search.
120 -
constant KEYWORDS: [Keyword; 52] = [
122 +
constant KEYWORDS: [Keyword; 53] = [
121 123
    { name: "align", tok: TokenKind::Align },
122 124
    { name: "and", tok: TokenKind::And },
123 125
    { name: "as", tok: TokenKind::As },
124 126
    { name: "assert", tok: TokenKind::Assert },
125 127
    { name: "bool", tok: TokenKind::Bool },
138 140
    { name: "i64", tok: TokenKind::I64 },
139 141
    { name: "i8", tok: TokenKind::I8 },
140 142
    { name: "if", tok: TokenKind::If },
141 143
    { name: "in", tok: TokenKind::In },
142 144
    { name: "instance", tok: TokenKind::Instance },
145 +
    { name: "instantiate", tok: TokenKind::Instantiate },
143 146
    { name: "let", tok: TokenKind::Let },
144 147
    { name: "log", tok: TokenKind::Log },
145 148
    { name: "loop", tok: TokenKind::Loop },
146 149
    { name: "match", tok: TokenKind::Match },
147 150
    { name: "mod", tok: TokenKind::Mod },
345 348
        }
346 349
    }
347 350
    return tok(s, TokenKind::Number);
348 351
}
349 352
353 +
/// Scan a token through its closing delimiter.
350 354
fn scanDelimited(s: *mut Scanner, delim: u8, kind: TokenKind) -> ?Token {
351 355
    while let ch = current(s); ch <> delim {
356 +
        if ch == 0xe2 and s.cursor + 2 < s.source.len and
357 +
           s.source[s.cursor + 1] == 0x9f and
358 +
           (s.source[s.cursor + 2] == 0xa8 or s.source[s.cursor + 2] == 0xa9)
359 +
        {
360 +
            set s.cursor += 3;
361 +
            continue;
362 +
        }
352 363
        if not char::isPrint(ch) {
353 364
            return invalid(s.token, "invalid character");
354 365
        }
355 366
        if consume(s, '\\') { // Consume escapes
356 367
            if isEof(s) {
438 449
        case ')'  => return tok(s, TokenKind::RParen),
439 450
        case '{'  => return tok(s, TokenKind::LBrace),
440 451
        case '}'  => return tok(s, TokenKind::RBrace),
441 452
        case '['  => return tok(s, TokenKind::LBracket),
442 453
        case ']'  => return tok(s, TokenKind::RBracket),
454 +
        case 0xe2 => {
455 +
            // Scan one of the dedicated UTF-8 generic delimiters.
456 +
            if not consume(s, 0x9f) {
457 +
                return invalid(s.token, "unexpected UTF-8 character");
458 +
            }
459 +
            if consume(s, 0xa8) {
460 +
                return tok(s, TokenKind::LAngle);
461 +
            }
462 +
            if consume(s, 0xa9) {
463 +
                return tok(s, TokenKind::RAngle);
464 +
            }
465 +
            return invalid(s.token, "unexpected UTF-8 character");
466 +
        }
443 467
        case ';'  => return tok(s, TokenKind::Semicolon),
444 468
        case ','  => return tok(s, TokenKind::Comma),
445 469
        case '.'  => {
446 470
            if consume(s, '.') {
447 471
                return tok(s, TokenKind::DotDot);
sublime/radiance.sublime-syntax +2 -0
107 107
      scope: punctuation.section.block.radiance
108 108
    - match: '[()]'
109 109
      scope: punctuation.section.group.radiance
110 110
    - match: '[\[\]]'
111 111
      scope: punctuation.section.brackets.radiance
112 +
    - match: '[⟨⟩]'
113 +
      scope: punctuation.section.generic.radiance
112 114
    - match: '[,;]'
113 115
      scope: punctuation.separator.radiance
vim/radiance.vim +3 -3
15 15
syntax keyword radianceTodo TODO FIXME contained containedin=radianceComment
16 16
17 17
" Keywords
18 18
syntax keyword radianceKeyword mod fn return if else while true false and or not case align static
19 19
syntax keyword radianceKeyword export break continue use loop in for match nil undefined
20 -
syntax keyword radianceKeyword let mut set as register device constant log record union trait instance
20 +
syntax keyword radianceKeyword let mut set as register device constant instantiate log record union trait instance
21 21
syntax keyword radianceKeyword throws throw try catch panic assert super
22 22
syntax keyword radianceType i8 i16 i32 i64 u8 u16 u32 u64 f32 void bool bit opaque
23 23
24 24
" Double-quoted strings
25 25
syntax region radianceString start=/"/ skip=/\\"/ end=/"/ contains=radianceEscape
45 45
46 46
" Namespaced identifiers.
47 47
syntax match radianceNamespaceSep "::" contained
48 48
syntax match radianceNamespaceAccess "\<\u\w*\%(::\u\w*\)\+" contains=radianceNamespaceSep
49 49
50 -
" Braces, parentheses and brackets
50 +
" Braces, parentheses, brackets, and generic angles
51 51
syntax match radianceBraces "[{}]"
52 52
syntax match radianceParens "[()]"
53 -
syntax match radianceBrackets "[\[\]]"
53 +
syntax match radianceBrackets "[\[\]⟨⟩]"
54 54
55 55
" Define highlighting
56 56
highlight default link radianceKeyword Keyword
57 57
highlight default link radianceBuiltin Special
58 58
highlight default link radianceType Type