* Scan next token. Context-sensitive: `cmd` mode treats [ as operator (test * command start), `arg` mode treats [ as word char (glob/subscript).
(L: Lexer, ctx: 'cmd' | 'arg' = 'arg')
| 300 | * command start), `arg` mode treats [ as word char (glob/subscript). |
| 301 | */ |
| 302 | function nextToken(L: Lexer, ctx: 'cmd' | 'arg' = 'arg'): Token { |
| 303 | skipBlanks(L) |
| 304 | const start = L.b |
| 305 | if (L.i >= L.len) return { type: 'EOF', value: '', start, end: start } |
| 306 | |
| 307 | const c = L.src[L.i]! |
| 308 | const c1 = peek(L, 1) |
| 309 | const c2 = peek(L, 2) |
| 310 | |
| 311 | if (c === '\n') { |
| 312 | advance(L) |
| 313 | return { type: 'NEWLINE', value: '\n', start, end: L.b } |
| 314 | } |
| 315 | |
| 316 | if (c === '#') { |
| 317 | const si = L.i |
| 318 | while (L.i < L.len && L.src[L.i] !== '\n') advance(L) |
| 319 | return { |
| 320 | type: 'COMMENT', |
| 321 | value: L.src.slice(si, L.i), |
| 322 | start, |
| 323 | end: L.b, |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | // Multi-char operators (longest match first) |
| 328 | if (c === '&' && c1 === '&') { |
| 329 | advance(L) |
| 330 | advance(L) |
| 331 | return { type: 'OP', value: '&&', start, end: L.b } |
| 332 | } |
| 333 | if (c === '|' && c1 === '|') { |
| 334 | advance(L) |
| 335 | advance(L) |
| 336 | return { type: 'OP', value: '||', start, end: L.b } |
| 337 | } |
| 338 | if (c === '|' && c1 === '&') { |
| 339 | advance(L) |
| 340 | advance(L) |
| 341 | return { type: 'OP', value: '|&', start, end: L.b } |
| 342 | } |
| 343 | if (c === ';' && c1 === ';' && c2 === '&') { |
| 344 | advance(L) |
| 345 | advance(L) |
| 346 | advance(L) |
| 347 | return { type: 'OP', value: ';;&', start, end: L.b } |
| 348 | } |
| 349 | if (c === ';' && c1 === ';') { |
| 350 | advance(L) |
| 351 | advance(L) |
| 352 | return { type: 'OP', value: ';;', start, end: L.b } |
| 353 | } |
| 354 | if (c === ';' && c1 === '&') { |
| 355 | advance(L) |
| 356 | advance(L) |
| 357 | return { type: 'OP', value: ';&', start, end: L.b } |
| 358 | } |
| 359 | if (c === '>' && c1 === '>') { |
no test coverage detected