@@ -29,7 +29,7 @@ import {
2929 left , right , none , noUnaryLhs ,
3030 op , prefix , postfix , sameLine ,
3131 sep , opt , many , many1 , alt , exclude , not ,
32- lit , seq , oneOf , noneOf , range , anyChar , star , plus , repeat , notFollowedBy , start ,
32+ altPattern , optPattern , seq , oneOf , noneOf , range , anyChar , star , plus , repeat , notFollowedBy , start ,
3333} from './src/api.ts' ;
3434
3535// ── Tokens ──
@@ -46,9 +46,9 @@ const idStart = oneOf(range('a', 'z'), range('A', 'Z'), '_', '$');
4646const idCont = oneOf ( range ( 'a' , 'z' ) , range ( 'A' , 'Z' ) , digit , '_' , '$' ) ;
4747const lineTerminator = oneOf ( '\n' , '\r' , '\u2028' , '\u2029' ) ;
4848const hspace = oneOf ( ' ' , '\t' ) ;
49- const uEsc = alt ( seq ( '\\u' , repeat ( hexDigit , 4 , 4 ) ) , seq ( '\\u{' , plus ( hexDigit ) , '}' ) ) ;
50- const identStart = alt ( idStart , uEsc ) ;
51- const identPart = alt ( idCont , uEsc ) ;
49+ const uEsc = altPattern ( seq ( '\\u' , repeat ( hexDigit , 4 , 4 ) ) , seq ( '\\u{' , plus ( hexDigit ) , '}' ) ) ;
50+ const identStart = altPattern ( idStart , uEsc ) ;
51+ const identPart = altPattern ( idCont , uEsc ) ;
5252const numericTailGuard = notFollowedBy ( oneOf ( range ( '0' , '9' ) , range ( 'A' , 'Z' ) , range ( 'a' , 'z' ) , '_' , '$' , '\\' ) ) ;
5353const digits = seq ( plus ( digit ) , star ( seq ( '_' , plus ( digit ) ) ) ) ;
5454const Ident = token ( seq ( identStart , star ( identPart ) ) , { identifier : true } ) ;
@@ -63,39 +63,39 @@ const Ident = token(seq(identStart, star(identPart)), { identifier: true
6363// only valid here (radix forms have no fractional part), so it lives on each radix token rather
6464// than on the decimal `BigInt_` below. The shared `(?!IdentifierStart|DecimalDigit)` tail still
6565// rejects a stray trailing identifier (`0x5anabc`).
66- const HexNumber = token ( seq ( '0' , oneOf ( 'x' , 'X' ) , plus ( hexDigit ) , star ( seq ( '_' , plus ( hexDigit ) ) ) , opt ( lit ( 'n' ) ) , numericTailGuard ) , { scope : 'constant.numeric.hex' } ) ;
67- const OctalNumber = token ( seq ( '0' , oneOf ( 'o' , 'O' ) , plus ( range ( '0' , '7' ) ) , star ( seq ( '_' , plus ( range ( '0' , '7' ) ) ) ) , opt ( lit ( 'n' ) ) , numericTailGuard ) , { scope : 'constant.numeric.octal' } ) ;
68- const BinaryNumber = token ( seq ( '0' , oneOf ( 'b' , 'B' ) , plus ( oneOf ( '0' , '1' ) ) , star ( seq ( '_' , plus ( oneOf ( '0' , '1' ) ) ) ) , opt ( lit ( 'n' ) ) , numericTailGuard ) , { scope : 'constant.numeric.binary' } ) ;
66+ const HexNumber = token ( seq ( '0' , oneOf ( 'x' , 'X' ) , plus ( hexDigit ) , star ( seq ( '_' , plus ( hexDigit ) ) ) , optPattern ( 'n' ) , numericTailGuard ) , { scope : 'constant.numeric.hex' } ) ;
67+ const OctalNumber = token ( seq ( '0' , oneOf ( 'o' , 'O' ) , plus ( range ( '0' , '7' ) ) , star ( seq ( '_' , plus ( range ( '0' , '7' ) ) ) ) , optPattern ( 'n' ) , numericTailGuard ) , { scope : 'constant.numeric.octal' } ) ;
68+ const BinaryNumber = token ( seq ( '0' , oneOf ( 'b' , 'B' ) , plus ( oneOf ( '0' , '1' ) ) , star ( seq ( '_' , plus ( oneOf ( '0' , '1' ) ) ) ) , optPattern ( 'n' ) , numericTailGuard ) , { scope : 'constant.numeric.binary' } ) ;
6969const BigInt_ = token ( seq ( digits , 'n' , numericTailGuard ) , { scope : 'constant.numeric.bigint' } ) ;
7070// DecimalLiteral, including the leading-dot form (`.5`, `.0e1`): an integer part with optional
7171// fraction/exponent, OR a bare fraction `.digits` with optional exponent. Same trailing guard.
7272// Scope is set explicitly (not inferred from a `[0-9]`-leading pattern) because the leading-dot
7373// alternative makes the pattern start with `(?:` — gen-tm's decimal-numeric detector keys on a
7474// `[0-9]`/`\d` prefix, so without this the token would lose its `constant.numeric` scope.
7575const fracTail = seq ( '.' , star ( digit ) , star ( seq ( '_' , plus ( digit ) ) ) ) ;
76- const expTail = seq ( oneOf ( 'e' , 'E' ) , opt ( oneOf ( '+' , '-' ) ) , digits ) ;
77- const Number_ = token ( seq ( alt ( seq ( digits , opt ( fracTail ) ) , seq ( '.' , digits ) ) , opt ( expTail ) , numericTailGuard ) , { scope : 'constant.numeric.decimal' } ) ;
76+ const expTail = seq ( oneOf ( 'e' , 'E' ) , optPattern ( oneOf ( '+' , '-' ) ) , digits ) ;
77+ const Number_ = token ( seq ( altPattern ( seq ( digits , optPattern ( fracTail ) ) , seq ( '.' , digits ) ) , optPattern ( expTail ) , numericTailGuard ) , { scope : 'constant.numeric.decimal' } ) ;
7878// A well-formed JS escape, used in the string-body pattern below. `\u`/`\x` must
7979// match their strict forms — a `\u{cp}` with cp ≤ 0x10FFFF, a 4-hex `\uXXXX`, or a
8080// 2-hex `\xXX` — while `\` + any *other* char (\n, \\, \q non-escape, line
8181// continuation) stays valid via `[^ux]`. A malformed `\u`/`\x` (e.g. `\u{110000}`,
8282// `\u{r}`, `\u{}`, `\u{67`) matches no escape, so the string matches no token and the
8383// lexer throws — TS's exact rejection. The in-range codepoint is `0*` leading zeros
8484// then 1–5 hex (0–0xFFFFF) or `10`+4 hex (0x100000–0x10FFFF).
85- const codePoint = seq ( star ( '0' ) , alt ( repeat ( hexDigit , 1 , 5 ) , seq ( '10' , repeat ( hexDigit , 4 , 4 ) ) ) ) ;
86- const escape = seq ( '\\' , alt ( seq ( 'u{' , codePoint , '}' ) , seq ( 'u' , repeat ( hexDigit , 4 , 4 ) ) , seq ( 'x' , repeat ( hexDigit , 2 , 2 ) ) , noneOf ( 'u' , 'x' ) ) ) ;
87- const highlightedEscape = seq ( '\\' , alt (
85+ const codePoint = seq ( star ( '0' ) , altPattern ( repeat ( hexDigit , 1 , 5 ) , seq ( '10' , repeat ( hexDigit , 4 , 4 ) ) ) ) ;
86+ const escape = seq ( '\\' , altPattern ( seq ( 'u{' , codePoint , '}' ) , seq ( 'u' , repeat ( hexDigit , 4 , 4 ) ) , seq ( 'x' , repeat ( hexDigit , 2 , 2 ) ) , noneOf ( 'u' , 'x' ) ) ) ;
87+ const highlightedEscape = seq ( '\\' , altPattern (
8888 oneOf ( 'n' , 'r' , 't' , 'b' , 'f' , 'v' , '0' , "'" , '"' , '\\' ) ,
8989 seq ( 'x' , repeat ( hexDigit , 2 , 2 ) ) ,
9090 seq ( 'u' , repeat ( hexDigit , 4 , 4 ) ) ,
9191 seq ( 'u{' , plus ( hexDigit ) , '}' ) ,
9292) ) ;
93- const String_ = token ( alt ( seq ( '"' , star ( alt ( noneOf ( '"' , '\\' ) , escape ) ) , '"' ) , seq ( "'" , star ( alt ( noneOf ( "'" , '\\' ) , escape ) ) , "'" ) ) , {
93+ const String_ = token ( altPattern ( seq ( '"' , star ( altPattern ( noneOf ( '"' , '\\' ) , escape ) ) , '"' ) , seq ( "'" , star ( altPattern ( noneOf ( "'" , '\\' ) , escape ) ) , "'" ) ) , {
9494 string : true ,
9595 escape : highlightedEscape ,
9696} ) ;
97- const Template = token ( seq ( '`' , star ( alt ( noneOf ( '`' , '\\' , '$' ) , seq ( '\\' , noneOf ( lineTerminator ) ) , seq ( '$' , notFollowedBy ( '{' ) ) ) ) , '`' ) , {
98- escape : seq ( '\\' , alt (
97+ const Template = token ( seq ( '`' , star ( altPattern ( noneOf ( '`' , '\\' , '$' ) , seq ( '\\' , noneOf ( lineTerminator ) ) , seq ( '$' , notFollowedBy ( '{' ) ) ) ) , '`' ) , {
98+ escape : seq ( '\\' , altPattern (
9999 oneOf ( 'n' , 'r' , 't' , 'b' , 'f' , 'v' , '0' , "'" , '"' , '\\' , '`' , '$' ) ,
100100 seq ( 'x' , repeat ( hexDigit , 2 , 2 ) ) ,
101101 seq ( 'u' , repeat ( hexDigit , 4 , 4 ) ) ,
@@ -107,8 +107,8 @@ const Template = token(seq('`', star(alt(noneOf('`', '\\', '$'), seq('\\', n
107107 template : { open : '`' , interpOpen : '${' , interpClose : '}' } ,
108108} ) ;
109109const regexEscape = seq ( '\\' , noneOf ( lineTerminator ) ) ;
110- const regexClassBody = star ( alt ( noneOf ( ']' , '\\' , '\n' ) , regexEscape ) ) ;
111- const Regex_ = token ( seq ( '/' , plus ( alt ( noneOf ( '/' , '\\' , '[' , '\n' ) , regexEscape , seq ( '[' , regexClassBody , ']' ) ) ) , '/' , star ( oneOf ( 'g' , 'i' , 'm' , 's' , 'u' , 'y' , 'd' , 'v' ) ) ) , {
110+ const regexClassBody = star ( altPattern ( noneOf ( ']' , '\\' , '\n' ) , regexEscape ) ) ;
111+ const Regex_ = token ( seq ( '/' , plus ( altPattern ( noneOf ( '/' , '\\' , '[' , '\n' ) , regexEscape , seq ( '[' , regexClassBody , ']' ) ) ) , '/' , star ( oneOf ( 'g' , 'i' , 'm' , 's' , 'u' , 'y' , 'd' , 'v' ) ) ) , {
112112 regex : true ,
113113 regexContext : {
114114 divisionAfterTypes : [ 'Ident' , 'Number' , 'String' , 'Template' , 'BigInt' ] ,
@@ -131,7 +131,7 @@ const Regex_ = token(seq('/', plus(alt(noneOf('/', '\\', '[', '\n'), regex
131131} ) ;
132132// `@name` / `@ns.name` — each dotted segment is an IdentifierName, so it admits the same
133133// `\u`-escape forms as `Ident` (`@℘`, `@ZW__NJ`); the parser owns the `(args)` tail.
134- const Decorator = token ( seq ( '@' , opt ( seq ( identStart , star ( alt ( identPart , '.' ) ) ) ) ) , { scope : 'entity.name.function.decorator' } ) ;
134+ const Decorator = token ( seq ( '@' , optPattern ( seq ( identStart , star ( altPattern ( identPart , '.' ) ) ) ) ) , { scope : 'entity.name.function.decorator' } ) ;
135135// PrivateIdentifier: `#` + an IdentifierName, so it admits the same `\u`-escape forms as `Ident`
136136// (`#\u{6F}_`). A non-ASCII literal `#name` (`#℘`, `#ZWNJ`) is handled by the lexer's Unicode
137137// fallback, which recognises this token's leading `#` as a name prefix.
0 commit comments