UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

185 lines 6.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RootId = void 0; exports.prepareParsedData = prepareParsedData; exports.convertPreparedParsedData = convertPreparedParsedData; const retriever_1 = require("../../../../retriever"); const assert_1 = require("../../../../../util/assert"); const type_1 = require("../../model/type"); exports.RootId = 0; /** * R's `getParseData` escapes the lexeme `text` with `encodeString`, whose escaping is locale-dependent: under a * non-UTF-8 locale (a common CI setup) non-ASCII bytes come out as octal (`\303\264`) or hex (`\xc3`) escapes that * are not valid JSON. Valid JSON escapes (`\n`, `\"`, `\uXXXX`, ...) are left untouched. */ function jsonSafeRParseData(data) { let out = ''; for (let i = 0; i < data.length;) { if (data[i] !== '\\') { out += data[i++]; continue; } const next = data[i + 1]; if (next !== undefined && (next === '\\' || next === '"' || next === '/' || next === 'b' || next === 'f' || next === 'n' || next === 'r' || next === 't' || next === 'u')) { out += data[i] + next; i += 2; continue; } if (next !== undefined && next >= '0' && next <= '7') { const run = readByteRun(data, i, collectOctal); out += bytesToUtf8Json(run.bytes); i = run.next; continue; } if (next === 'x') { const run = readByteRun(data, i, collectHex); out += bytesToUtf8Json(run.bytes); i = run.next; continue; } if (next === undefined) { out += data[i]; i += 1; } else { out += `\\u${next.charCodeAt(0).toString(16).padStart(4, '0')}`; i += 2; } } return out; } /** Reads a run of consecutive `\NNN` octal (or `\xNN` hex) escapes starting at `start` (which points at a `\`). */ function readByteRun(data, start, collect) { const bytes = []; let pos = start; for (;;) { const read = collect(data, pos); if (read === undefined) { break; } bytes.push(read.value & 0xff); pos = read.next; } return { bytes, next: pos }; } function collectOctal(data, pos) { if (data[pos] !== '\\' || !(data[pos + 1] >= '0' && data[pos + 1] <= '7')) { return undefined; } let oct = '', j = pos + 1; while (j < data.length && oct.length < 3 && data[j] >= '0' && data[j] <= '7') { oct += data[j++]; } return { value: parseInt(oct, 8), next: j }; } function collectHex(data, pos) { if (data[pos] !== '\\' || data[pos + 1] !== 'x' || !/[0-9a-fA-F]/.test(data[pos + 2] ?? '')) { return undefined; } let hex = '', j = pos + 2; while (j < data.length && hex.length < 2 && /[0-9a-fA-F]/.test(data[j])) { hex += data[j++]; } return { value: parseInt(hex, 16), next: j }; } /** Decodes raw bytes as UTF-8 and re-escapes the result for a JSON string context. */ function bytesToUtf8Json(bytes) { const decoded = Buffer.from(bytes).toString('utf-8'); // JSON.stringify gives a valid JSON string literal; strip its surrounding quotes to splice back inline return JSON.stringify(decoded).slice(1, -1); } /** * Takes the raw {@link RShell} output and extracts the csv information contained */ function prepareParsedData(data) { let json; try { json = JSON.parse(`[${data.trim()}]`); } catch { // the fast path failed, most likely because a non-UTF-8 R locale produced octal/hex string escapes try { json = JSON.parse(`[${jsonSafeRParseData(data.trim())}]`); } catch (e) { throw new Error(`Failed to parse data [${data}]: ${e?.message}`, { cause: e }); } } (0, assert_1.guard)(Array.isArray(json), () => `Expected ${data} to be an array but was not`); const ret = new Map(json.map(([line1, col1, line2, col2, id, parent, token, terminal, text]) => { return [id, { line1, col1, line2, col2, id, parent, token: (0, retriever_1.removeRQuotes)(token), terminal, text }]; })); const roots = []; // iterate a second time to set parent-child relations (since they may be out of order in the csv) for (const entry of ret.values()) { if (entry.parent != exports.RootId) { /** it turns out that comments may return a negative id pair to their parent */ const parent = ret.get(Math.abs(entry.parent)); if (parent) { parent.children ??= []; parent.children.push(entry); } } else { roots.push(entry); } } return roots; } /** * Takes the CSV-Entries and maps them to the old json format for compatibility. */ function convertPreparedParsedData(roots) { const partialEntry = { token: type_1.RawRType.ExpressionList, text: '', id: exports.RootId, parent: exports.RootId }; // if we don't have children, this is simple if (roots.length <= 0) { return { ...partialEntry, line1: 1, col1: 1, line2: 1, col2: 1, children: [] }; } // Locate start, end of a source file (order children in advance). const rootEntries = roots.slice().sort(orderOf); const start = rootEntries[0]; const end = rootEntries[rootEntries.length - 1]; // Construct CsvEntry for the root, handling empty input. const csvParent = { ...partialEntry, line1: start?.line1 ?? 1, col1: start?.col1 ?? 1, line2: end?.line2 ?? 1, col2: end?.col2 ?? 1, children: rootEntries, terminal: false }; // Return actual value. return convertEntry(csvParent); } function convertEntry(csvEntry) { return { ...csvEntry, // check and recursively iterate children children: csvEntry.children?.sort(orderOf).map(convertEntry) ?? [] }; } /** * we sort children the same way xmlparsedata does (by line, by column, by inverse end line, by inverse end column, by terminal state, by combined "start" tiebreaker value) * (https://github.com/r-lib/xmlparsedata/blob/v1.0.5/R/package.R#L120) */ function orderOf(c1, c2) { return c1.line1 - c2.line1 || c1.col1 - c2.col1 || c2.line2 - c1.line2 || c2.col2 - c1.col2 || Number(c1.terminal) - Number(c2.terminal) || sortTiebreak(c1) - sortTiebreak(c2); } function sortTiebreak({ line1, col1, col2 }) { // see https://github.com/r-lib/xmlparsedata/blob/v1.0.5/R/package.R#L86 return line1 * (Math.max(col1, col2) + 1) + col1; } //# sourceMappingURL=format.js.map