UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

83 lines 3.78 kB
"use strict"; /** * This code implements stable topological sort in a graph. We are basically * following the Kahn's algorithm, see: * ${@link https://en.wikipedia.org/wiki/Topological_sorting"}. * * This code is a port of the Scala code from Apalache: * * ${@link * https://github.com/apalache-mc/apalache/blob/dd5fff8dbfe707fb450afd2319cf50ebeb568e18/tlair/src/main/scala/at/forsyte/apalache/tla/lir/transformations/impl/StableTopologicalSort.scala * } * * @author Igor Konnov, Informal Systems, 2023 * * Copyright 2022-2023 Informal Systems * Licensed under the Apache License, Version 2.0. * See LICENSE in the project root for license information. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.toposort = void 0; const immutable_1 = require("immutable"); const immutable_2 = require("immutable"); /** * Sort the elements of the list `unsorted` according to the dependencies that * are stored in the incoming edges. The semantics of an edge `u -> v` is that * `u` depends on `v` (or `u` calls `v`). That, in a topologically sorted list, * `v` should appear before `u`. * * @param inEdges * a map from a node `u` to the set of the nodes used by `u` * * @param unsorted a list of nodes * * @returns either `Right(sorted)` that contains the correctly sorted nodes, * or `Left(nodes)` that contains a subgraph with a cycle inside. */ function toposort(inEdges, unsorted) { // map sorted ids to nodes const idToNode = unsorted.reduce((map, node) => map.set(node.id, node), (0, immutable_1.Map)()); // Use Kahn's algorithm to sort the declarations in a topological order: // https://en.wikipedia.org/wiki/Topological_sorting // // In this version, we are introducing declarations layer by layer, starting with layer 0 that contains // the nodes that have no incoming edges, then the declarations of layer 1 that have incoming edges // only from layer 1, etc. Within each layer, we maintain the original order of declarations. // the list of the ids of sorted nodes let sorted = []; // The edges that have not been closed in the graph yet. // We extend `inEdges` in two ways: // 1. Every id that appears in the right-hand of an edge is a key in the map. // 2. For every element of unsorted, its id is a key in the map. const unsortedIds = (0, immutable_2.Set)(unsorted.map(n => n.id)); const allIds = inEdges.reduce((s, ids) => ids.union(s), unsortedIds); let edges = allIds.reduce((map, id) => (map.has(id) ? map : map.set(id, (0, immutable_2.Set)())), inEdges); // the list of nodes that do not have incoming edges let sinks = []; function updateSinksAndEdges() { const [otherEdges, sinkEdges] = edges.partition(incoming => incoming.isEmpty()); sinks = [...sinkEdges.keys()]; // the other edges still have to be sorted edges = otherEdges; } // initialize sinks with the nodes that have no incoming edges updateSinksAndEdges(); while (sinks.length > 0) { // append the syncs that belong to unsorted const newLayer = sinks.filter(id => unsortedIds.has(id)); // sort the definitions inside the layer to compensate for non-determinism of maps newLayer.sort(); sorted = sorted.concat(newLayer); const toRemove = (0, immutable_2.Set)(sinks); // remove all incoming edges that contain one of the sinks as a source edges = edges.map(uses => uses.subtract(toRemove)); // recompute the sinks and edges updateSinksAndEdges(); } return { sorted: sorted.map(id => idToNode.get(id)), cycles: (0, immutable_2.Set)(edges.keys()), }; } exports.toposort = toposort; //# sourceMappingURL=toposort.js.map