@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
217 lines • 7.84 kB
JavaScript
;
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