UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

217 lines 7.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseRLicense = parseRLicense; exports.stringifyRLicense = stringifyRLicense; const assert_1 = require("./assert"); const objects_1 = require("./objects"); const r_version_1 = require("./r-version"); /** * Parses an R license string into its structured representation. */ function parseRLicense(licenseString) { return parseLicenseInfo(skipWhitespace({ position: 0, remInput: licenseString })).element; } /** * Stringifies an R license element back into its string representation. */ function stringifyRLicense(license) { const t = license.type; switch (t) { case 'no-license': return 'NO LICENSE'; case 'license': return license.versionConstraint ? `${license.license} (${license.versionConstraint.raw})` : license.license; case 'exception': return `with ${license.exception}`; case 'combination': { const left = stringifyRLicense(license.elements[0]); const right = stringifyRLicense(license.elements[1]); const leftStr = license.elements[0].type === 'combination' ? `(${left})` : left; const rightStr = license.elements[1].type === 'combination' ? `(${right})` : right; return `${leftStr} ${license.combination} ${rightStr}`; } default: (0, assert_1.assertUnreachable)(t); } } function parseLicenseInfo(info) { info = skipWhitespace(info); if (info.remInput.length === 0) { return { ...info, element: { type: 'no-license' } }; } else if (info.remInput.startsWith('(')) { return parseParenthesizedExpression(info); } else { return parseBinaryOperator(info); } } function parseParenthesizedExpression(info) { const openParen = consumeString(info, '('); (0, assert_1.guard)(openParen.element, `Expected (, but found ${info.remInput[0]} at position ${info.position}`); const innerInfo = parseLicenseInfo(openParen); const closeParen = consumeString(innerInfo, ')'); (0, assert_1.guard)(closeParen.element, `Expected ), but found ${innerInfo.remInput[0]} at position ${innerInfo.position}`); return { ...closeParen, element: innerInfo.element }; } function skipWhitespace({ remInput, position }) { let idx = remInput.search(/\S/); if (idx === -1) { idx = remInput.length; } position += idx; remInput = remInput.slice(idx); return { position, remInput }; } function consumeLicenseName(info) { info = skipWhitespace(info); let licenseName = ''; let isAtWhitespace = false; for (let i = 0; i < info.remInput.length; i++) { const char = info.remInput[i]; if (/[()<>~!=,&|+]/.test(char)) { break; } else { isAtWhitespace = /\s/.test(char); } if (isAtWhitespace) { if (/^\s*(with|and|or)\s+/i.test(info.remInput.slice(i))) { break; } } licenseName += char; } const newInfo = { position: info.position + licenseName.length, remInput: info.remInput.slice(licenseName.length) }; // file licenses are a special case! if (licenseName && /^file[\s-]+license$/i.test(licenseName.trim())) { return { ...newInfo, element: 'file LICENSE' }; } return { ...newInfo, element: licenseName?.trim() ?? '' }; } function makeRange(rangeStr) { try { return (0, r_version_1.parseRRange)(rangeStr); } catch { return undefined; } } function parseLicenseElement(info) { const licenseName = consumeLicenseName(info); info = skipWhitespace(licenseName); // may be followed by version constraint in parentheses or directly let versionConstraint; if (/^\(\s*?[\d<>=!~]/.test(info.remInput)) { // version constraint if (info.remInput.startsWith('(')) { const openParen = consumeString(info, '('); (0, assert_1.guard)(openParen.element, `Expected (, but found ${info.remInput[0]} at position ${info.position}`); info = skipWhitespace(openParen); // consume until closing parenthesis const versionStr = consumeUntilString(info, ')'); versionConstraint = makeRange(versionStr.element); const closeParen = consumeString(versionStr, ')'); (0, assert_1.guard)(closeParen.element, `Expected ), but found ${versionStr.remInput[0]} at position ${versionStr.position}`); info = skipWhitespace(closeParen); } else { // consume until whitespace or special character const versionStr = consumeRegexp(info, /^[\d<>=!~.\s]+/); versionConstraint = versionStr.element ? makeRange(versionStr.element) : undefined; info = skipWhitespace(versionStr); } } const licenseInfo = (0, objects_1.compactRecord)({ type: 'license', license: licenseName.element, versionConstraint }); return { ...info, element: licenseInfo }; } const operators = { 'and': ['and', '+', '&'], 'or': ['or', '|'], 'with': ['with'] }; function parseBinaryOperator(info) { const license = parseLicenseElement(info); info = skipWhitespace(license); const operator = tryConsumeOperator(info); if (operator.element) { info = skipWhitespace(operator); let rightLicense = parseLicenseInfo(info); if (operator.element === 'with' && rightLicense.element.type === 'license') { rightLicense = { ...rightLicense, element: { type: 'exception', exception: rightLicense.element.license } }; } const combination = { type: 'combination', combination: operator.element, elements: [license.element, rightLicense.element] }; return { ...rightLicense, element: combination }; } return { ...info, element: license.element }; } function tryConsumeOperator(info) { for (const [opName, opSymbols] of Object.entries(operators)) { for (const symbol of opSymbols) { if (info.remInput.toLowerCase().startsWith(symbol)) { const newInfo = { position: info.position + symbol.length, remInput: info.remInput.slice(symbol.length) }; return { ...newInfo, element: opName }; } } } return { ...info, element: undefined }; } function consumeString(info, str) { info = skipWhitespace(info); if (info.remInput.startsWith(str)) { const newInfo = { position: info.position + str.length, remInput: info.remInput.slice(str.length) }; return { ...newInfo, element: true }; } else { return { ...info, element: false }; } } function consumeUntilString(info, str) { let idx = info.remInput.indexOf(str); if (idx === -1) { idx = info.remInput.length; } const consumed = info.remInput.slice(0, idx); const newInfo = { position: info.position + idx, remInput: info.remInput.slice(idx) }; return { ...newInfo, element: consumed }; } function consumeRegexp(info, regex) { info = skipWhitespace(info); const match = info.remInput.match(regex); if (match && match.index === 0) { const matchedStr = match[0]; const newInfo = { position: info.position + matchedStr.length, remInput: info.remInput.slice(matchedStr.length) }; return { ...newInfo, element: matchedStr }; } return { ...info, element: null }; } //# sourceMappingURL=r-license.js.map