482 lines
18 KiB
JavaScript
482 lines
18 KiB
JavaScript
// 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);
|
|
case 'rust': return curataRust(text);
|
|
case 'swift': return curataSwift(text);
|
|
case 'kotlin': return curataKotlin(text);
|
|
default: return { code: text, siruri: [] };
|
|
}
|
|
}
|
|
|
|
// Sfarsitul unui comentariu bloc IMBRICAT (/* a /* b */ c */) care incepe la i; n daca nu se inchide. Comun pentru Rust, Swift si
|
|
// Kotlin: toate trei imbrica; un lexer C ar fi inchis comentariul la primul */ si ar fi lasat restul drept cod.
|
|
function sfarsitComentariuImbricat(text, i, n) {
|
|
let j = i + 2;
|
|
let ad = 1;
|
|
while (j < n && ad > 0) {
|
|
if (text[j] === '/' && text[j + 1] === '*') { ad++; j += 2; continue; }
|
|
if (text[j] === '*' && text[j + 1] === '/') { ad--; j += 2; continue; }
|
|
j++;
|
|
}
|
|
return j;
|
|
}
|
|
|
|
// Swift (0.4.0, 2026-10-18): comentariile bloc se imbrica; sirurile sunt "..." (un singur rand), """...""" (multi-rand), si formele
|
|
// brute #"..."# / #"""..."""# cu oricate diezuri; INTERPOLAREA \( expr ) (in sirurile brute \#( expr )) poate contine la randul ei
|
|
// siruri si paranteze, deci sfarsitul sirului se cauta cu o stiva de cadre, nu cu "pana la urmatorul ghilimea". Nu exista literale
|
|
// char. Scanarea e ITERATIVA (stiva explicita): un fisier facut anume din `"\(` repetat ar fi 85.000 de niveluri de recursie.
|
|
// Continutul sirului (interpolare inclusa) ramane un singur interval: un nume de API scris in interpolare nu e o folosire masurata.
|
|
function sfarsitSirSwift(text, start, n) {
|
|
// cadre: { fel: 'sir', diezi, multi } sau { fel: 'interp', ad } (ad = paranteze deschise in interpolare)
|
|
const stiva = [];
|
|
const deschide = (j) => {
|
|
let d = 0;
|
|
while (text[j] === '#') { d++; j++; }
|
|
if (text[j] !== '"') return -1;
|
|
const multi = text.startsWith('"""', j);
|
|
stiva.push({ fel: 'sir', diezi: d, multi });
|
|
return j + (multi ? 3 : 1);
|
|
};
|
|
let j = deschide(start);
|
|
if (j < 0) return -1;
|
|
while (j < n && stiva.length) {
|
|
const cadru = stiva[stiva.length - 1];
|
|
const c = text[j];
|
|
if (cadru.fel === 'sir') {
|
|
if (c === '\\') {
|
|
// escape-ul unui sir brut e \ urmat de exact `diezi` diezi; un \ singur in sirul brut e un caracter obisnuit
|
|
let k = j + 1;
|
|
let d = 0;
|
|
while (d < cadru.diezi && text[k] === '#') { d++; k++; }
|
|
if (d < cadru.diezi) { j++; continue; }
|
|
if (text[k] === '(') { stiva.push({ fel: 'interp', ad: 1 }); j = k + 1; continue; }
|
|
j = k + 1; continue;
|
|
}
|
|
if (c === '"') {
|
|
const inchidere = (cadru.multi ? '"""' : '"') + '#'.repeat(cadru.diezi);
|
|
if (text.startsWith(inchidere, j)) { stiva.pop(); j += inchidere.length; continue; }
|
|
j++; continue;
|
|
}
|
|
if (c === '\n' && !cadru.multi) { stiva.pop(); j++; continue; } // sir neterminat pe rand: se inchide la capatul randului
|
|
j++; continue;
|
|
}
|
|
// in interpolare: paranteze echilibrate, siruri imbricate
|
|
if (c === '(') { cadru.ad++; j++; continue; }
|
|
if (c === ')') { if (--cadru.ad === 0) stiva.pop(); j++; continue; }
|
|
if (c === '"' || (c === '#' && /^#+"/.test(text.slice(j, j + 8)))) {
|
|
const k = deschide(j);
|
|
if (k >= 0) { j = k; continue; }
|
|
}
|
|
j++;
|
|
}
|
|
return Math.min(j, n);
|
|
}
|
|
|
|
function curataSwift(text) {
|
|
const n = text.length;
|
|
const out = text.split('');
|
|
const goleste = golitor(out);
|
|
const siruri = [];
|
|
let i = 0;
|
|
while (i < n) {
|
|
const c = text[i];
|
|
const d = text[i + 1];
|
|
if (c === '/' && d === c) { // (scris altfel decat in lexerul C si in cel Rust: controlul negativ isi ancoreaza cioturile pe acelea)
|
|
let j = i;
|
|
while (j < n && text[j] !== '\n') j++;
|
|
goleste(i, j); i = j; continue;
|
|
}
|
|
if (c === '/' && d === '*') {
|
|
const jSw = sfarsitComentariuImbricat(text, i, n);
|
|
goleste(i, jSw); i = jSw; continue;
|
|
}
|
|
if (c === '"' || (c === '#' && (d === '#' || d === '"'))) {
|
|
const sf = sfarsitSirSwift(text, i, n);
|
|
if (sf > i) { siruri.push([i, sf]); i = sf; continue; }
|
|
}
|
|
if (/[A-Za-z_]/.test(c)) {
|
|
let j = i + 1;
|
|
while (j < n && /\w/.test(text[j])) j++;
|
|
i = j; continue;
|
|
}
|
|
i++;
|
|
}
|
|
return { code: out.join(''), siruri };
|
|
}
|
|
|
|
// Kotlin (0.4.0, 2026-10-18): comentariile bloc se imbrica; sirurile sunt "..." (un singur rand, cu escape-uri) si """...""" (brute,
|
|
// fara escape-uri); amandoua au sabloane $nume si ${ expr }, iar expr poate contine siruri si acolade, deci sfarsitul se cauta cu o
|
|
// stiva de cadre, iterativ. 'c' e un literal char ('a', '\n', 'A', '\''), nu deschide un sir. Continutul sirului ramane un singur
|
|
// interval: un nume de API scris intr-un sablon nu e o folosire masurata.
|
|
function sfarsitSirKotlin(text, start, n) {
|
|
const stiva = []; // { fel: 'sir', brut } | { fel: 'sablon', ad }
|
|
const deschide = (j) => {
|
|
if (text[j] !== '"') return -1;
|
|
const brut = text.startsWith('"""', j);
|
|
stiva.push({ fel: 'sir', brut });
|
|
return j + (brut ? 3 : 1);
|
|
};
|
|
let j = deschide(start);
|
|
if (j < 0) return -1;
|
|
while (j < n && stiva.length) {
|
|
const cadru = stiva[stiva.length - 1];
|
|
const c = text[j];
|
|
if (cadru.fel === 'sir') {
|
|
if (c === '\\' && !cadru.brut) { j += 2; continue; }
|
|
if (c === '$' && text[j + 1] === '{') { stiva.push({ fel: 'sablon', ad: 1 }); j += 2; continue; }
|
|
if (c === '"') {
|
|
if (cadru.brut) {
|
|
if (text.startsWith('"""', j)) { j += 3; while (text[j] === '"') j++; stiva.pop(); continue; } // """"" inchide si pastreaza ghilimelele in plus
|
|
j++; continue;
|
|
}
|
|
stiva.pop(); j++; continue;
|
|
}
|
|
if (c === '\n' && !cadru.brut) { stiva.pop(); j++; continue; }
|
|
j++; continue;
|
|
}
|
|
if (c === '{') { cadru.ad++; j++; continue; }
|
|
if (c === '}') { if (--cadru.ad === 0) stiva.pop(); j++; continue; }
|
|
if (c === '"') { const k = deschide(j); if (k >= 0) { j = k; continue; } }
|
|
if (c === "'") { const k = sfarsitCharKotlin(text, j, n); if (k > 0) { j = k; continue; } }
|
|
j++;
|
|
}
|
|
return Math.min(j, n);
|
|
}
|
|
|
|
// literal char Kotlin la i: 'x', '\n', '\'', 'A', un caracter astral; -1 daca la i nu incepe un literal char
|
|
function sfarsitCharKotlin(text, i, n) {
|
|
const d = text[i + 1];
|
|
if (d === undefined || d === '\n' || d === "'") return -1;
|
|
if (d === '\\') {
|
|
let j = i + 2;
|
|
while (j < n && text[j] !== "'" && text[j] !== '\n' && j - i < 8) j++;
|
|
return text[j] === "'" ? j + 1 : -1;
|
|
}
|
|
if (text[i + 2] === "'") return i + 3;
|
|
const cod = d.charCodeAt(0);
|
|
if (cod >= 0xd800 && cod <= 0xdbff && text[i + 3] === "'") return i + 4;
|
|
return -1;
|
|
}
|
|
|
|
function curataKotlin(text) {
|
|
const n = text.length;
|
|
const out = text.split('');
|
|
const goleste = golitor(out);
|
|
const siruri = [];
|
|
let i = 0;
|
|
while (i < n) {
|
|
const c = text[i];
|
|
const d = text[i + 1];
|
|
if (c === d && d === '/') { // (scris altfel decat in lexerele C, Rust si Swift: fiecare ciot al controlului negativ are ancora lui)
|
|
let j = i;
|
|
while (j < n && text[j] !== '\n') j++;
|
|
goleste(i, j); i = j; continue;
|
|
}
|
|
if (c === '/' && d === '*') {
|
|
const jKt = sfarsitComentariuImbricat(text, i, n);
|
|
goleste(i, jKt); i = jKt; continue;
|
|
}
|
|
if (c === '"') {
|
|
const sf = sfarsitSirKotlin(text, i, n);
|
|
if (sf > i) { siruri.push([i, sf]); i = sf; continue; }
|
|
}
|
|
if (c === "'") {
|
|
const sf = sfarsitCharKotlin(text, i, n);
|
|
if (sf > 0) { siruri.push([i, sf]); i = sf; continue; }
|
|
i++; continue;
|
|
}
|
|
if (/[A-Za-z_]/.test(c)) {
|
|
let j = i + 1;
|
|
while (j < n && /\w/.test(text[j])) j++;
|
|
i = j; continue;
|
|
}
|
|
i++;
|
|
}
|
|
return { code: out.join(''), 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 };
|
|
}
|
|
|
|
// Rust (0.3.0): comentariile bloc se IMBRICA; sirurile pot trece peste randuri si au forme brute (r"...", r#"..."#, br"...",
|
|
// cr#"..."#) si de octeti (b"..."); apostroful NU deschide un sir decat ca literal char ('a', '\n', '\u{1F600}', b'x'): altfel e
|
|
// o durata de viata ('a, 'static) sau o eticheta ('outer:), si un lexer C ar fi deschis acolo un "sir" pana la urmatorul apostrof.
|
|
function curataRust(text) {
|
|
const n = text.length;
|
|
const out = text.split('');
|
|
const goleste = golitor(out);
|
|
const siruri = [];
|
|
let i = 0;
|
|
while (i < n) {
|
|
const c = text[i];
|
|
const d = text[i + 1];
|
|
if (d === '/' && c === '/') { // (scris invers fata de lexerul C: controlul negativ isi ancoreaza ciotul pe acela)
|
|
let j = i;
|
|
while (j < n && text[j] !== '\n') j++;
|
|
goleste(i, j); i = j; continue;
|
|
}
|
|
if (c === '/' && d === '*') {
|
|
const j = sfarsitComentariuImbricat(text, i, n); // (0.4.0: aceeasi functie pentru Rust, Swift si Kotlin)
|
|
goleste(i, j); i = j; continue;
|
|
}
|
|
const dupaCuvant = i > 0 && /\w/.test(text[i - 1]);
|
|
if (!dupaCuvant && (c === 'r' || ((c === 'b' || c === 'c') && d === 'r'))) {
|
|
let j = c === 'r' ? i + 1 : i + 2;
|
|
let diezi = 0;
|
|
while (text[j] === '#') { diezi++; j++; }
|
|
if (text[j] === '"') {
|
|
const inchidere = '"' + '#'.repeat(diezi);
|
|
const k = text.indexOf(inchidere, j + 1);
|
|
const sf = k < 0 ? n : k + inchidere.length;
|
|
siruri.push([i, sf]); i = sf; continue;
|
|
}
|
|
}
|
|
if (c === '"' || (!dupaCuvant && (c === 'b' || c === 'c') && d === '"')) {
|
|
const start = i;
|
|
let j = c === '"' ? i + 1 : i + 2;
|
|
while (j < n && text[j] !== '"') { if (text[j] === '\\') j++; j++; }
|
|
j = Math.min(n, j + 1);
|
|
siruri.push([start, j]); i = j; continue;
|
|
}
|
|
if (c === "'") {
|
|
if (d === '\\') {
|
|
let j = i + 2;
|
|
while (j < n && text[j] !== "'" && text[j] !== '\n' && j - i < 16) j++;
|
|
if (text[j] === "'") { siruri.push([i, j + 1]); i = j + 1; continue; }
|
|
i++; continue;
|
|
}
|
|
if (d !== undefined && d !== '\n' && text[i + 2] === "'") { siruri.push([i, i + 3]); i += 3; continue; }
|
|
if (d !== undefined && d.charCodeAt(0) >= 0xd800 && d.charCodeAt(0) <= 0xdbff && text[i + 3] === "'") { siruri.push([i, i + 4]); i += 4; continue; }
|
|
i++; continue;
|
|
}
|
|
if (/[A-Za-z_]/.test(c)) {
|
|
let j = i + 1;
|
|
while (j < n && /\w/.test(text[j])) j++;
|
|
i = j; continue;
|
|
}
|
|
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 };
|
|
}
|