/** * Shell tokenizer slice (diagnostics / trace); planner and executor use {@link ./shell-lex.js}. */ import { lexShellLine } from './shell-lex.js' export function tokenizeBareShellLineForDiagnostics(line) { const toks = tokenizeBareShellLineDetailed(line) return toks.map((t) => ({ type: 'word', value: t.value })) } /** * Structured token stream for diagnostics and grammar development. * @param {string} line * @returns {{ type: 'word', value: string, mode: 'normal'|'single'|'double'|'arith'|'heredoc'|'op', start: number, end: number }[]} */ export function tokenizeBareShellLineDetailed(line) { const raw = lexShellLine(String(line || '')) /** @type {{ type: 'word', value: string, mode: 'normal'|'single'|'double'|'arith'|'heredoc'|'op', start: number, end: number }[]} */ const out = [] for (const t of raw) { const st = t.start ?? 0 const en = t.end ?? st if (t.type === 'op') { const v = t.value let mode = /** @type {'normal'|'heredoc'} */ ('normal') if (v === '<<' || v === '<<-' || v === '<<<') mode = 'heredoc' out.push({ type: 'word', value: v, mode, start: st, end: en }) continue } const parts = t.parts || [] const joined = parts.map((p) => p.t).join('') let mode = /** @type {'normal'|'single'|'double'|'arith'} */ ('normal') if (parts.length === 1 && parts[0].q === 's') mode = 'single' else if (parts.length === 1 && parts[0].q === 'd') mode = 'double' if (/\$\(\(/.test(joined)) mode = 'arith' out.push({ type: 'word', value: t.value, mode, start: st, end: en }) } return out }