@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
149 lines • 5.07 kB
JavaScript
;
/**
* Just to avoid another library for splitting arguments, we use this module to provide what we need.
* @module
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.splitAtEscapeSensitive = splitAtEscapeSensitive;
exports.splitOnNestingSensitive = splitOnNestingSensitive;
/**
* This splits an input string on the given split string (e.g., ` `), but checks if the string is quoted or escaped.
*
* Given an input string like `a "b c" d`, with a space character as split, and escapeQuote set to true,
* this splits the arguments similar to common shell interpreters (i.e., `a`, `b c`, and `d`).
*
* When escapeQuote is set to false instead, we keep quotation marks in the result (i.e., `a`, `"b c"`, and `d`.).
* @param inputString - The string to split
* @param escapeQuote - Keep quotes in args
* @param split - The character or character sequence to split on (can not be backslash or quote!)
*/
function splitAtEscapeSensitive(inputString, escapeQuote = true, split = ' ') {
const args = [];
let current = '';
let inQuotes = false;
let escaped = false;
for (let i = 0; i < inputString.length; i++) {
const c = inputString[i];
const sub = inputString.slice(i);
if (escaped) {
escaped = false;
switch (c) {
case 'n':
current += '\n';
break;
case 't':
current += '\t';
break;
case 'r':
current += '\r';
break;
case 'v':
current += '\v';
break;
case 'f':
current += '\f';
break;
case 'b':
current += '\b';
break;
default: current += c;
}
}
else if (!inQuotes
&& current !== ''
&& (split instanceof RegExp ? split.test(sub) : inputString.slice(i, i + split.length) === split)) {
args.push(current);
current = '';
}
else if (c === '"' || c === "'") {
if (!inQuotes) {
inQuotes = c;
if (escapeQuote) {
continue;
}
}
else if (inQuotes === c) {
inQuotes = false;
if (escapeQuote) {
continue;
}
}
current += c;
}
else if (c === '\\' && escapeQuote) {
escaped = true;
}
else {
current += c;
}
}
if (current !== '') {
args.push(current);
}
return args;
}
const MatchingClose = {
'<': '>',
'[': ']',
'(': ')',
'"': '"',
"'": "'"
};
/**
* Splits the given string on 'and', but only if not nested inside `<>`, `[]`, `()`, or quotes.
* This also handles escaped quotes.
* @param str - The string to split
* @param splitOn - The string to split on (default: 'and')
* @param closes - The matching of closing characters for nesting, structures with different open and close characters only can be nested
* of each other, while those with the same open and close character (like quotes) can not be nested inside themselves.
*/
function splitOnNestingSensitive(str, splitOn = 'and', closes = MatchingClose) {
const result = [];
const openCloseSame = new Set(Object.entries(closes).filter(([open, close]) => open === close).map(([open]) => open));
let current = '';
const nestStack = [];
for (let i = 0; i < str.length; i++) {
const c = str[i];
if (c === '\\' && i + 1 < str.length) {
// skip escaped characters
current += c + str[i + 1];
i++;
}
else if (nestStack.length > 0) {
if (!openCloseSame.has(c) && c in closes) {
// opening a new nest
nestStack.push(c);
}
else {
const top = nestStack[nestStack.length - 1];
if (c === closes[top]) {
nestStack.pop();
}
}
current += c;
}
else {
if (c in closes) {
nestStack.push(c);
current += c;
continue;
}
// check for 'and' split
if (str.slice(i, i + splitOn.length) === splitOn &&
(i === 0 || /\s/.test(str[i - 1])) &&
(i + splitOn.length >= str.length || /\s/.test(str[i + splitOn.length]))) {
// split here
result.push(current.trim());
current = '';
i += splitOn.length - 1;
continue;
}
current += c;
}
}
if (current.trim().length > 0) {
result.push(current.trim());
}
return result;
}
//# sourceMappingURL=args.js.map