// Lexer minimal pe limbaje: comentariile devin spatii (liniile si coloanele raman pe loc), // sirurile raman in text, dar li se tin minte intervalele, ca un detector sa poata cere // "numele API-ului e COD, nu text dintr-un sir". // // Nu e un parser: e cat trebuie ca un tipar sa nu se potriveasca in comentarii, in // docstring-uri Python sau in textul unui sir. const KW_REGEX = new Set(['return', 'typeof', 'instanceof', 'in', 'of', 'new', 'delete', 'void', 'throw', 'case', 'do', 'else', 'yield', 'await']); const PUNCT_REGEX = '(,=:[!&|?{};+-*%<>~^'; export function curata(text, limbaj) { switch (limbaj) { case 'javascript': return curataC(text, { template: true, regex: true }); case 'java': return curataC(text, { textBlock: true }); case 'go': return curataC(text, { raw: true }); case 'python': return curataPython(text); default: return { code: text, siruri: [] }; } } function golitor(out) { return (a, b) => { for (let k = a; k < b; k++) if (out[k] !== '\n' && out[k] !== '\r') out[k] = ' '; }; } function curataC(text, o) { const n = text.length; const out = text.split(''); const goleste = golitor(out); const siruri = []; const stiva = []; let acolade = 0; let prevSig = ''; let prevWord = ''; let i = 0; let esecRegex = null; // bit 1: din (pozitie, in afara clasei) nu se ajunge la sfarsitul literalului; bit 2: la fel, in clasa // scaneaza un segment de template literal pornind de la j; intoarce pozitia si daca s-a deschis ${ const scanTpl = (inceput, j) => { while (j < n) { const ch = text[j]; if (ch === '\\') { j += 2; continue; } if (ch === '`') { siruri.push([inceput, j + 1]); return { pos: j + 1, deschis: false }; } if (ch === '$' && text[j + 1] === '{') { siruri.push([inceput, j + 2]); return { pos: j + 2, deschis: true }; } j++; } siruri.push([inceput, n]); return { pos: n, deschis: false }; }; while (i < n) { const c = text[i]; const d = text[i + 1]; if (c === '/' && d === '/') { let j = i; while (j < n && text[j] !== '\n') j++; goleste(i, j); i = j; continue; } if (c === '/' && d === '*') { let j = text.indexOf('*/', i + 2); j = j < 0 ? n : j + 2; goleste(i, j); i = j; continue; } if (o.textBlock && c === '"' && text.startsWith('"""', i)) { let j = text.indexOf('"""', i + 3); j = j < 0 ? n : j + 3; siruri.push([i, j]); i = j; prevSig = '"'; continue; } if (c === '"' || c === "'") { let j = i + 1; while (j < n && text[j] !== c && text[j] !== '\n') { if (text[j] === '\\') j++; j++; } j = Math.min(n, j + 1); siruri.push([i, j]); i = j; prevSig = c; continue; } if (c === '`' && o.raw) { let j = text.indexOf('`', i + 1); j = j < 0 ? n : j + 1; siruri.push([i, j]); i = j; prevSig = '`'; continue; } if (c === '`' && o.template) { const r = scanTpl(i, i + 1); i = r.pos; if (r.deschis) { stiva.push(acolade); acolade++; } prevSig = '`'; continue; } if (c === '/' && o.regex) { const permis = prevSig === '' || PUNCT_REGEX.includes(prevSig) || (prevSig === 'a' && KW_REGEX.has(prevWord)); if (permis) { // Cautarea sfarsitului unui literal regex e determinista din (pozitie, "in clasa"), deci o stare din care s-a ajuns o data // la capatul randului fara sfarsit duce acolo si a doua oara. Starile acestea se tin minte (2026-09-29, revizuirea // adversariala): fara ele un rand lung cu multe `/` nedeschise (de ex. `=/[` repetat) costa patratic, minute pe 1 MB. let j = i + 1; let inClasa = false; let bun = false; const vizitate = []; while (j < n && text[j] !== '\n') { if (esecRegex && esecRegex[j] & (inClasa ? 2 : 1)) break; vizitate.push(inClasa ? -j - 1 : j); const ch = text[j]; if (ch === '\\') { j += 2; continue; } if (ch === '[') inClasa = true; else if (ch === ']') inClasa = false; else if (ch === '/' && !inClasa) { bun = true; break; } j++; } if (!bun) { if (!esecRegex) esecRegex = new Uint8Array(n); for (const v of vizitate) { if (v < 0) esecRegex[-v - 1] |= 2; else esecRegex[v] |= 1; } } if (bun) { j++; while (j < n && /[a-z]/i.test(text[j])) j++; siruri.push([i, j]); i = j; prevSig = '/'; continue; } } prevSig = '/'; i++; continue; } if (c === '{') { acolade++; prevSig = '{'; i++; continue; } if (c === '}') { acolade--; if (stiva.length && acolade === stiva[stiva.length - 1]) { stiva.pop(); const r = scanTpl(i, i + 1); i = r.pos; if (r.deschis) { stiva.push(acolade); acolade++; } prevSig = '`'; continue; } prevSig = '}'; i++; continue; } if (/[A-Za-z_$]/.test(c)) { let j = i + 1; while (j < n && /[\w$]/.test(text[j])) j++; prevWord = text.slice(i, j); prevSig = 'a'; i = j; continue; } if (/[0-9]/.test(c)) { let j = i + 1; while (j < n && /[\w.]/.test(text[j])) j++; prevWord = ''; prevSig = '0'; i = j; continue; } if (!/\s/.test(c)) prevSig = c; i++; } return { code: out.join(''), siruri }; } function curataPython(text) { const n = text.length; const out = text.split(''); const goleste = golitor(out); const siruri = []; let i = 0; // primul caracter care nu e spatiu pe randul curent (-1 = niciunul inca). Tinut din mers (2026-09-29, revizuirea adversariala): // forma veche cauta inapoi inceputul randului la FIECARE sir triplu, deci un rand lung cu multe siruri triple costa patratic. let primulPeRand = -1; while (i < n) { const c = text[i]; if (c === '\n') { primulPeRand = -1; i++; continue; } if (c === '#') { let j = i; while (j < n && text[j] !== '\n') j++; goleste(i, j); i = j; continue; } if (c === '"' || c === "'") { // prefixul (r, b, f, u, rb, ...) face parte din sir let start = i; while (start > 0 && /[rRbBuUfF]/.test(text[start - 1]) && i - start < 2) start--; if (start < i && start > 0 && /[\w]/.test(text[start - 1])) start = i; const triplu = text.startsWith(c.repeat(3), i); let j; // inainte de sir, pe randul lui, sunt numai spatii (prefixul r/b/f/u face parte din sir)? const singurPeRand = primulPeRand < 0 || primulPeRand >= start; let ultimaLinieNoua = -1; if (triplu) { j = i + 3; while (j < n && !text.startsWith(c.repeat(3), j)) { if (text[j] === '\\') { if (text[j + 1] === '\n') ultimaLinieNoua = j + 1; j++; } else if (text[j] === '\n') ultimaLinieNoua = j; j++; } j = Math.min(n, j + 3); } else { j = i + 1; while (j < n && text[j] !== c && text[j] !== '\n') { if (text[j] === '\\') { if (text[j + 1] === '\n') ultimaLinieNoua = j + 1; j++; } j++; } if (j < n && text[j] === '\n') ultimaLinieNoua = j; // sir neterminat: intervalul lui se opreste DUPA acest rand nou j = Math.min(n, j + 1); } // dupa sir: daca sirul a trecut peste un rand nou, randul curent incepe in el (coada lui nu e spatiu), afara de cazul in // care sirul se termina chiar cu randul nou; altfel randul curent are acum cel putin sirul if (ultimaLinieNoua >= 0) primulPeRand = ultimaLinieNoua === j - 1 ? -1 : ultimaLinieNoua + 1; else if (primulPeRand < 0) primulPeRand = start; // docstring: sirul triplu e singur pe randul lui, ca instructiune; se trateaza ca un comentariu if (triplu && singurPeRand) { goleste(start, j); i = j; continue; } siruri.push([start, j]); i = j; continue; } if (primulPeRand < 0 && c !== ' ' && c !== '\t') primulPeRand = i; i++; } return { code: out.join(''), siruri }; } // cautare binara: pozitia e inauntrul unui sir? export function inSir(siruri, pos) { let lo = 0; let hi = siruri.length - 1; while (lo <= hi) { const mid = (lo + hi) >> 1; const [s, e] = siruri[mid]; if (pos < s) hi = mid - 1; else if (pos >= e) lo = mid + 1; else return true; } return false; } export function inceputuriDeRand(text) { const r = [0]; for (let k = 0; k < text.length; k++) if (text.charCodeAt(k) === 10) r.push(k + 1); return r; } export function randul(inceputuri, pos) { let lo = 0; let hi = inceputuri.length - 1; while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (inceputuri[mid] <= pos) lo = mid; else hi = mid - 1; } return { line: lo + 1, column: pos - inceputuri[lo] + 1 }; }