UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

205 lines 9.75 kB
"use strict"; 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