@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
205 lines • 9.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.escapeRegExp = escapeRegExp;
exports.filterValidNames = filterValidNames;
exports.getArgumentValue = getArgumentValue;
exports.getEffectiveArgs = getEffectiveArgs;
exports.getFunctionArgument = getFunctionArgument;
exports.getFunctionArguments = getFunctionArguments;
exports.getUnresolvedSymbolsInExpression = getUnresolvedSymbolsInExpression;
exports.hasCriticalArgument = hasCriticalArgument;
exports.isDataFrameArgument = isDataFrameArgument;
exports.isNamedArgument = isNamedArgument;
exports.isRNull = isRNull;
exports.isValidColName = isValidColName;
exports.parseRequestContent = parseRequestContent;
const vertex_1 = require("../../../dataflow/graph/vertex");
const make_argument_1 = require("../../../dataflow/internal/process/functions/call/argument/make-argument");
const model_1 = require("../../../r-bridge/lang-4.x/ast/model/model");
const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const convert_values_1 = require("../../../r-bridge/lang-4.x/convert-values");
const assert_1 = require("../../../util/assert");
const files_1 = require("../../../util/files");
const resolve_args_1 = require("../resolve-args");
const identifier_1 = require("../../../dataflow/environments/identifier");
/** Regular expression representing valid columns names, e.g. for `data.frame` */
const ColNamesRegex = /^[A-Za-z.][A-Za-z0-9_.]*$/;
/** Regular expression representing line terminations (LF, CRLF, CR) */
const LineTerminationRegex = /\r\n|\r|\n/;
/**
* Escapes a regular expression given as string by escaping all special regular expression characters.
* @param text - The text to escape
* @param allowTokens - Whether to allow and keep unescaped tokens like `\s`, `\t`, or `\n`
* @returns The escaped text
*/
function escapeRegExp(text, allowTokens = false) {
if (allowTokens) { // only allow and keep the tokens `\s`, `\t`, and `\n` in the text
return text.replace(/[.*+?^${}()|[\]]|\\[^stn]/g, '\\$&');
}
else {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}
/**
* Maps all invalid, duplicate, or empty column names to top depending on the provided arguments.
* @param colnames - The columns names to filter
* @param checkNames - Whether to map all invalid column names to top (`undefined`)
* @param noDupNames - Whether to map all duplicate column names to top (`undefined`)
* @param noEmptyNames - Whether to map all empty column names to top (`undefined`)
* @param collapseDups - Whether duplicate columns should be collapsed to single occurrences afterward (excluding `undefined` values)
* @returns The filtered column names
*/
function filterValidNames(colnames, checkNames, noDupNames, noEmptyNames, collapseDups = false) {
if (checkNames) { // map all invalid column names to top
colnames = colnames?.map(entry => isValidColName(entry) ? entry : undefined);
}
if (noDupNames) { // map all duplicate column names to top
colnames = colnames?.map((entry, _, list) => entry !== undefined && list.filter(other => other === entry).length === 1 ? entry : undefined);
}
if (noEmptyNames) { // map all empty column names to top
colnames = colnames?.map(entry => entry?.length === 0 ? undefined : entry);
}
if (collapseDups) {
colnames = colnames?.filter((value, index, array) => value === undefined || array.indexOf(value) === index);
}
return colnames;
}
/**
* Gets the value of an argument that specified as {@link FunctionParameterLocation}.
* @param args - The arguments to get the requested argument from
* @param argument - The specification of the argument to get the value for
* @param info - Argument resolve information
* @returns The resolved value of the argument or `undefined`
*/
function getArgumentValue(args, argument, info) {
const arg = getFunctionArgument(args, argument, info);
const defaultValue = typeof argument !== 'string' ? argument.default : undefined;
return arg !== undefined ? (0, resolve_args_1.resolveIdToArgValue)(arg, info) : defaultValue;
}
/**
* Gets all effective argument from a list of arguments by removing all arguments whose names should be excluded.
* @param args - The list of arguments to filter
* @param excluded - The names of the arguments to exclude
* @returns The filtered list of arguments
*/
function getEffectiveArgs(args, excluded) {
return args.filter(arg => arg === r_function_call_1.EmptyArgument || arg.name === undefined || !excluded.includes((0, resolve_args_1.unquoteArgument)(arg.name.content)));
}
/**
* Gets an argument specified as {@link FunctionParameterLocation} from a list of arguments.
* @param args - The arguments to get the requested argument from
* @param argument - The specification of the argument to get
* @param info - Argument resolve information
* @returns An argument matching the specified `argument` or `undefined`
*/
function getFunctionArgument(args, argument, info) {
const pos = typeof argument !== 'string' ? argument.pos : -1;
const name = typeof argument !== 'string' ? argument.name : argument;
let arg = undefined;
if (name !== undefined) {
arg = args.find(arg => (0, resolve_args_1.resolveIdToArgName)(arg, info) === name);
}
const hasArgPos = arg === undefined && pos >= 0 && pos < args.length && args[pos] !== r_function_call_1.EmptyArgument && args[pos].name === undefined;
if (hasArgPos) {
arg = args[pos];
}
return arg;
}
/**
* Get all function arguments of a function call node in the data flow graph.
* @param node - The function call node to get the arguments for
* @param dfg - The data flow graph for retrieving the arguments
* @returns The arguments of the function call in the data flow graph
*/
function getFunctionArguments(node, dfg) {
const vertex = dfg.getVertex(node.info.id);
if (vertex?.tag === vertex_1.VertexType.FunctionCall && dfg.idMap !== undefined) {
const idMap = dfg.idMap;
return vertex.args
.map(arg => arg === r_function_call_1.EmptyArgument ? arg : idMap.get(arg.nodeId))
.map(arg => arg === r_function_call_1.EmptyArgument || arg?.type === type_1.RType.Argument ? arg : (0, make_argument_1.toUnnamedArgument)(arg, idMap));
}
return node.arguments;
}
/**
* Gets all nested symbols in an expression that have no outgoing edges in the data flow graph.
* @param expression - The expression to get the symbols from
* @param dfg - The data flow graph for checking the outgoing edges
* @returns The name of all unresolved symbols in the expression
*/
function getUnresolvedSymbolsInExpression(expression, dfg) {
if (expression === undefined || expression === r_function_call_1.EmptyArgument || dfg === undefined) {
return [];
}
const unresolvedSymbols = [];
model_1.RNode.visitAst(expression, node => {
if (node.type === type_1.RType.Symbol) {
const vertex = dfg.get(node.info.id);
if ((0, vertex_1.isUseVertex)(vertex?.[0]) && vertex[1].size === 0) {
unresolvedSymbols.push(identifier_1.Identifier.mapName(node.content, resolve_args_1.unquoteArgument));
}
}
});
return unresolvedSymbols;
}
/**
* Checks whether a list of arguments contains any critical argument.
* @param args - The list of arguments to check
* @param critical - The critical arguments to search for (as string or {@link FunctionParameterLocation}s)
* @param info - Argument resolve information
* @returns Whether the arguments contain any critical argument
*/
function hasCriticalArgument(args, critical, info) {
for (const param of critical ?? []) {
const arg = getFunctionArgument(args, param, info);
if (arg === undefined) {
continue;
}
else if (typeof param !== 'string' && param.default !== undefined) {
const value = (0, resolve_args_1.resolveIdToArgValue)(arg, info);
if (value !== undefined && value === param.default) {
continue;
}
}
return true;
}
return false;
}
function isDataFrameArgument(arg, inference) {
return arg !== r_function_call_1.EmptyArgument && inference.getAbstractValue(arg) !== undefined;
}
/**
* Checks whether a function argument is a names argument.
*/
function isNamedArgument(arg) {
return arg !== r_function_call_1.EmptyArgument && arg?.name !== undefined;
}
function isRNull(node) {
if (node === r_function_call_1.EmptyArgument || node?.type === type_1.RType.Argument) {
return node !== r_function_call_1.EmptyArgument && isRNull(node.value);
}
return node?.type === type_1.RType.Symbol && node.content === convert_values_1.RNull;
}
/**
* Checks whether a string is a valid columns name according to the flag `check.names` in R.
*/
function isValidColName(colname) {
return colname !== undefined && ColNamesRegex.test(colname);
}
/**
* Parses a text of file parse request using the provided parser function.
*/
function parseRequestContent(request, parser, maxLines) {
const requestType = request.request;
switch (requestType) {
case 'text':
(0, resolve_args_1.unescapeSpecialChars)(request.content).split(LineTerminationRegex).forEach(parser);
return true;
case 'file':
return (0, files_1.readLineByLineSync)(request.content, parser, maxLines);
default:
(0, assert_1.assertUnreachable)(requestType);
}
}
//# sourceMappingURL=arguments.js.map