@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
129 lines • 5.84 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyAndBuildPipeline = verifyAndBuildPipeline;
const invalid_pipeline_error_1 = require("./invalid-pipeline-error");
const json_1 = require("../../../util/json");
const arrays_1 = require("../../../util/arrays");
/**
* Given a set of {@link IPipelineStep|steps} with their dependencies, this function verifies all requirements of {@link createPipeline}.
*/
function verifyAndBuildPipeline(steps) {
if (steps.length === 0) {
throw new invalid_pipeline_error_1.InvalidPipelineError('0) Pipeline is empty');
}
const [perFileSteps, perRequestSteps] = (0, arrays_1.partitionArray)(steps, s => s.executed === 0 /* PipelineStepStage.OncePerFile */);
// we construct a map linking each name to its respective step
const perFileStepMap = new Map();
const initsPerFile = [];
const visited = new Set();
// we start by working on the per-file steps
initializeSteps(perFileSteps, perFileStepMap, initsPerFile, visited);
// first, we sort the per-file steps
const sortedPerFile = topologicalSort(initsPerFile, perFileStepMap, visited);
validateStepOutput(sortedPerFile, perFileStepMap, steps);
const perRequestStepMap = new Map(perFileStepMap);
// we track all elements without dependencies, i.e., those that start the pipeline
const initsPerRequest = [];
// now, we do the same for the per-request steps, keeping the per-file steps known
initializeSteps(perRequestSteps, perRequestStepMap, initsPerRequest, visited);
const sortedPerRequest = topologicalSort(initsPerRequest, perRequestStepMap, visited);
const sorted = [...sortedPerFile, ...sortedPerRequest];
validateStepOutput(sorted, perRequestStepMap, steps);
return {
steps: perRequestStepMap,
order: sorted,
firstStepPerRequest: sortedPerFile.length
};
}
function validateStepOutput(sorted, stepMap, steps) {
if (sorted.length !== stepMap.size) {
// check if any of the dependencies in the map are invalid
checkForInvalidDependency(steps, stepMap);
// otherwise, we assume a cycle
throw new invalid_pipeline_error_1.InvalidPipelineError(`3) Pipeline contains at least one cycle; sorted: ${JSON.stringify(sorted)}, steps: ${JSON.stringify([...stepMap.keys()])}`);
}
}
function allDependenciesAreVisited(step, visited) {
return step.dependencies.every(d => visited.has(d));
}
function handleStep(step, init, visited, sorted, elem, decoratorsOfLastOthers, inits) {
if (step.decorates === init) {
if (allDependenciesAreVisited(step, visited)) {
sorted.push(elem);
visited.add(elem);
}
else {
decoratorsOfLastOthers.add(elem);
}
}
else if (step.decorates === undefined && allDependenciesAreVisited(step, visited)) {
inits.push(elem);
}
}
function topologicalSort(inits, stepMap, visited) {
const sorted = [];
while (inits.length > 0) {
const init = inits.pop();
sorted.push(init);
visited.add(init);
// these decorators still have dependencies open; we have to check if they can be satisfied by the other steps to add
const decoratorsOfLastOthers = new Set();
for (const [elem, step] of stepMap.entries()) {
if (visited.has(elem)) {
continue;
}
handleStep(step, init, visited, sorted, elem, decoratorsOfLastOthers, inits);
}
// for the other decorators we have to cycle until we find a solution, or know, that no solution exists
topologicallyInsertDecoratorElements(decoratorsOfLastOthers, stepMap, visited, sorted);
}
return sorted;
}
function topologicallyInsertDecoratorElements(decoratorsOfLastOthers, stepMap, visited, sorted) {
if (decoratorsOfLastOthers.size === 0) {
return;
}
let changed = true;
while (changed) {
changed = false;
for (const elem of [...decoratorsOfLastOthers]) {
const step = stepMap.get(elem);
if (allDependenciesAreVisited(step, visited)) {
decoratorsOfLastOthers.delete(elem);
sorted.push(elem);
visited.add(elem);
changed = true;
}
}
}
if (decoratorsOfLastOthers.size > 0) {
throw new invalid_pipeline_error_1.InvalidPipelineError(`5) Pipeline contains at least one decoration cycle: ${JSON.stringify(decoratorsOfLastOthers, json_1.jsonReplacer)}`);
}
}
function checkForInvalidDependency(steps, stepMap) {
for (const step of steps) {
for (const dep of step.dependencies) {
if (!stepMap.has(dep)) {
throw new invalid_pipeline_error_1.InvalidPipelineError(`2) Step "${step.name}" depends on step "${dep}" which does not exist`);
}
}
if (step.decorates && !stepMap.has(step.decorates)) {
throw new invalid_pipeline_error_1.InvalidPipelineError(`4) Step "${step.name}" decorates step "${step.decorates}" which does not exist`);
}
}
}
function initializeSteps(steps, stepMap, inits, visited) {
for (const step of steps) {
const name = step.name;
// if the name is already in the map, we have a duplicate
if (stepMap.has(name)) {
throw new invalid_pipeline_error_1.InvalidPipelineError(`1) Step name "${name}" is not unique in the pipeline`);
}
stepMap.set(name, step);
// only steps that have no dependencies and do not decorate others can be initial steps
if (allDependenciesAreVisited(step, visited) && (step.decorates === undefined || visited.has(step.decorates))) {
inits.push(name);
}
}
}
//# sourceMappingURL=create-pipeline.js.map