@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
380 lines • 15 kB
JavaScript
;
/**
* The decoration module is tasked with taking an R-ast given by a {@link RNode} and
*
* 1. assigning a unique id to each node (see {@link IdGenerator})
* 2. transforming the AST into a doubly linked tree using the ids (so it stays serializable)
*
* The main entry point is {@link decorateAst}.
*
* @module
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.deterministicCountingIdGenerator = deterministicCountingIdGenerator;
exports.deterministicPrefixIdGenerator = deterministicPrefixIdGenerator;
exports.sourcedDeterministicCountingIdGenerator = sourcedDeterministicCountingIdGenerator;
exports.nodeToLocationId = nodeToLocationId;
exports.deterministicLocationIdGenerator = deterministicLocationIdGenerator;
exports.decorateAst = decorateAst;
const assert_1 = require("../../../../../util/assert");
const bimap_1 = require("../../../../../util/collections/bimap");
const type_1 = require("../type");
const stateful_fold_1 = require("./stateful-fold");
const r_function_call_1 = require("../nodes/r-function-call");
/**
* The simplest id generator which just increments a number on each call.
*/
function deterministicCountingIdGenerator(id = 0) {
return () => id++;
}
function deterministicPrefixIdGenerator(prefix, id = 0) {
return () => `${prefix}-${id++}`;
}
function sourcedDeterministicCountingIdGenerator(path, location, start = 0) {
let id = start;
return () => `${path}-${loc2Id(location)}-${id++}`;
}
function loc2Id([sl, sc, el, ec]) {
return `${sl}:${sc}-${el}:${ec}`;
}
/**
* Generates the location id, used by {@link deterministicLocationIdGenerator}.
*
* @param data - the node to generate an id for, must have location information
*/
function nodeToLocationId(data) {
const loc = data.location;
(0, assert_1.guard)(loc !== undefined, 'location must be defined to generate a location id');
return loc2Id(loc);
}
/**
* Generates unique ids based on the locations of the node (see {@link nodeToLocationId}).
* If a node has no location information, it will be assigned a unique counter-value.
*
* @param start - the start value for the counter, in case nodes do not have location information
*/
function deterministicLocationIdGenerator(start = 0) {
let id = start;
return (data) => data.location !== undefined ? nodeToLocationId(data) : `${id++}`;
}
const defaultParentContext = {
role: "root" /* RoleInParent.Root */,
index: 0
};
const nestForElement = new Set([
type_1.RType.FunctionDefinition, type_1.RType.ForLoop, type_1.RType.WhileLoop, type_1.RType.RepeatLoop, type_1.RType.IfThenElse,
]);
/**
* Covert the given AST into a doubly linked tree while assigning ids (so it stays serializable).
*
* @param ast - The root of the AST to convert
* @param getId - The id generator: must generate a unique id für each passed node
* @param file - the path to the file this AST was extracted from will be added to the nodes
*
* @typeParam OtherInfo - The original decoration of the ast nodes (probably is nothing as the id decoration is most likely the first step to be performed after extraction)
*
* @returns A decorated AST based on the input and the id provider.
*/
function decorateAst(ast, { getId = deterministicCountingIdGenerator(0), file }) {
const idMap = new bimap_1.BiMap();
const info = { idMap, getId, file };
/* Please note, that all fold processors do not re-create copies in higher-folding steps so that the idMap stays intact. */
const foldLeaf = createFoldForLeaf(info);
const foldBinaryOp = createFoldForBinaryOp(info);
const unaryOp = createFoldForUnaryOp(info);
/* we pass down the nesting depth */
const decoratedAst = (0, stateful_fold_1.foldAstStateful)(ast, 0, {
down: (n, nesting) => {
if (nestForElement.has(n.type)) {
return nesting + 1;
}
else {
return nesting;
}
},
foldNumber: foldLeaf,
foldString: foldLeaf,
foldLogical: foldLeaf,
foldSymbol: foldLeaf,
foldAccess: createFoldForAccess(info),
foldBinaryOp: foldBinaryOp,
foldPipe: foldBinaryOp,
foldUnaryOp: unaryOp,
other: {
foldComment: foldLeaf,
foldLineDirective: foldLeaf
},
loop: {
foldFor: createFoldForForLoop(info),
foldRepeat: createFoldForRepeatLoop(info),
foldWhile: createFoldForWhileLoop(info),
foldBreak: foldLeaf,
foldNext: foldLeaf
},
foldIfThenElse: createFoldForIfThenElse(info),
foldExprList: createFoldForExprList(info),
functions: {
foldFunctionDefinition: createFoldForFunctionDefinition(info),
foldFunctionCall: createFoldForFunctionCall(info),
foldArgument: createFoldForFunctionArgument(info),
foldParameter: createFoldForFunctionParameter(info)
}
});
decoratedAst.info.role = "root" /* RoleInParent.Root */;
decoratedAst.info.index = 0;
return {
ast: decoratedAst,
idMap
};
}
function createFoldForLeaf(info) {
return (data, nesting) => {
const id = info.getId(data);
const decorated = {
...data,
info: {
...data.info,
id,
parent: undefined,
...defaultParentContext,
nesting
}
};
decorated.info.file = info.file;
info.idMap.set(id, decorated);
return decorated;
};
}
function createFoldForBinaryOp(info) {
return (data, lhs, rhs, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, lhs, rhs };
info.idMap.set(id, decorated);
const lhsInfo = lhs.info;
lhsInfo.parent = id;
const rhsInfo = rhs.info;
rhsInfo.parent = id;
rhsInfo.index = 1;
if (data.type === type_1.RType.Pipe) {
lhsInfo.role = "pipe-lhs" /* RoleInParent.PipeLhs */;
rhsInfo.role = "pipe-rhs" /* RoleInParent.PipeRhs */;
}
else {
lhsInfo.role = "binop-lhs" /* RoleInParent.BinaryOperationLhs */;
rhsInfo.role = "binop-rhs" /* RoleInParent.BinaryOperationRhs */;
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForUnaryOp(info) {
return (data, operand, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, operand };
info.idMap.set(id, decorated);
const opInfo = operand.info;
opInfo.parent = id;
opInfo.role = "unary-operand" /* RoleInParent.UnaryOperand */;
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForAccess(info) {
return (data, accessed, access, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, accessed, access };
info.idMap.set(id, decorated);
const accessedInfo = accessed.info;
accessedInfo.parent = id;
accessedInfo.role = "accessed" /* RoleInParent.Accessed */;
if (typeof access !== 'string') {
let idx = 0; // the first oe will be skipped in the first iter
for (const acc of access) {
idx++;
if (acc !== r_function_call_1.EmptyArgument) {
const curInfo = acc.info;
curInfo.parent = id;
curInfo.index = idx;
curInfo.role = "index-access" /* RoleInParent.IndexAccess */;
}
}
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForForLoop(info) {
return (data, variable, vector, body, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, variable, vector, body };
info.idMap.set(id, decorated);
const varInfo = variable.info;
varInfo.parent = id;
varInfo.role = "for-variable" /* RoleInParent.ForVariable */;
const vecInfo = vector.info;
vecInfo.parent = id;
vecInfo.index = 1;
vecInfo.role = "for-vector" /* RoleInParent.ForVector */;
const bodyInfo = body.info;
bodyInfo.parent = id;
bodyInfo.index = 2;
bodyInfo.role = "for-body" /* RoleInParent.ForBody */;
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForRepeatLoop(info) {
return (data, body, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, body };
info.idMap.set(id, decorated);
const bodyInfo = body.info;
bodyInfo.parent = id;
bodyInfo.role = "repeat-body" /* RoleInParent.RepeatBody */;
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForWhileLoop(info) {
return (data, condition, body, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, condition, body };
info.idMap.set(id, decorated);
const condInfo = condition.info;
condInfo.parent = id;
condInfo.role = "while-cond" /* RoleInParent.WhileCondition */;
const bodyInfo = body.info;
bodyInfo.parent = id;
bodyInfo.index = 1;
bodyInfo.role = "while-body" /* RoleInParent.WhileBody */;
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForIfThenElse(info) {
return (data, condition, then, otherwise, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, condition, then, otherwise };
info.idMap.set(id, decorated);
const condInfo = condition.info;
condInfo.parent = id;
condInfo.role = "if-cond" /* RoleInParent.IfCondition */;
const thenInfo = then.info;
thenInfo.parent = id;
thenInfo.index = 1;
thenInfo.role = "if-then" /* RoleInParent.IfThen */;
if (otherwise) {
const otherwiseInfo = otherwise.info;
otherwiseInfo.parent = id;
otherwiseInfo.index = 2;
otherwiseInfo.role = "if-otherwise" /* RoleInParent.IfOtherwise */;
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForExprList(info) {
return (data, grouping, children, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, grouping, children };
info.idMap.set(id, decorated);
let i = 0;
for (const child of children) {
const childInfo = child.info;
childInfo.parent = id;
childInfo.index = i++;
childInfo.role = "expr-list-child" /* RoleInParent.ExpressionListChild */;
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForFunctionCall(info) {
return (data, functionName, args, nesting) => {
const id = info.getId(data);
let decorated;
if (data.named) {
decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, functionName, arguments: args };
}
else {
decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, calledFunction: functionName, arguments: args };
}
info.idMap.set(id, decorated);
const funcInfo = functionName.info;
funcInfo.parent = id;
funcInfo.role = "call-name" /* RoleInParent.FunctionCallName */;
let idx = 0;
for (const arg of args) {
idx++;
if (arg !== r_function_call_1.EmptyArgument) {
const argInfo = arg.info;
argInfo.parent = id;
argInfo.index = idx;
argInfo.role = "call-argument" /* RoleInParent.FunctionCallArgument */;
}
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForFunctionDefinition(info) {
return (data, params, body, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, parameters: params, body };
info.idMap.set(id, decorated);
let idx = 0;
for (const param of params) {
const paramInfo = param.info;
paramInfo.parent = id;
paramInfo.index = idx++;
paramInfo.role = "function-def-param" /* RoleInParent.FunctionDefinitionParameter */;
}
const bodyInfo = body.info;
bodyInfo.parent = id;
bodyInfo.index = idx;
bodyInfo.role = "function-def-body" /* RoleInParent.FunctionDefinitionBody */;
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForFunctionParameter(info) {
return (data, name, defaultValue, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, name, defaultValue };
info.idMap.set(id, decorated);
const nameInfo = name.info;
nameInfo.parent = id;
nameInfo.role = "param-name" /* RoleInParent.ParameterName */;
if (defaultValue) {
const defaultInfo = defaultValue.info;
defaultInfo.parent = id;
defaultInfo.index = 1;
defaultInfo.role = "param-value" /* RoleInParent.ParameterDefaultValue */;
}
decorated.info.file = info.file;
return decorated;
};
}
function createFoldForFunctionArgument(info) {
return (data, name, value, nesting) => {
const id = info.getId(data);
const decorated = { ...data, info: { ...data.info, id, parent: undefined, nesting }, name, value };
info.idMap.set(id, decorated);
let idx = 0;
if (name) {
const nameInfo = name.info;
nameInfo.parent = id;
nameInfo.role = "arg-name" /* RoleInParent.ArgumentName */;
idx++; // adaptive, 0 for the value if there is no name!
}
if (value) {
const valueInfo = value.info;
valueInfo.parent = id;
valueInfo.index = idx;
valueInfo.role = "arg-value" /* RoleInParent.ArgumentValue */;
}
decorated.info.file = info.file;
return decorated;
};
}
//# sourceMappingURL=decorate.js.map