@informalsystems/quint
Version:
Core tool for the Quint specification language
163 lines • 7.11 kB
JavaScript
;
/* ----------------------------------------------------------------------------------
* Copyright 2023 Informal Systems
* Licensed under the Apache License, Version 2.0.
* See LICENSE in the project root for license information.
* --------------------------------------------------------------------------------- */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNamespaceForDef = exports.dependentDefinitions = exports.flattenModule = void 0;
/**
* Flattening for modules, replacing imports and exports with definitions referred by the module.
*
* @author Gabriela Moreira
*
* @module
*/
const base_1 = require("../names/base");
const quintIr_1 = require("../ir/quintIr");
const IRVisitor_1 = require("../ir/IRVisitor");
const namespacer_1 = require("../ir/namespacer");
const assert_1 = __importDefault(require("assert"));
const lodash_1 = require("lodash");
/**
* Flatten a module, replacing instances, imports and exports with definitions referred by the module.
*
* @param quintModule - The module to be flattened
* @param modulesByName - A map of referred modules by name
* @param lookupTable - The lookup table for the module and all its references
*
* @returns The flattened module
*/
function flattenModule(quintModule, modulesByName, lookupTable) {
const moduleCopy = { ...quintModule, declarations: [...quintModule.declarations] };
const flattener = new Flattener(modulesByName, lookupTable);
(0, IRVisitor_1.walkModule)(flattener, moduleCopy);
return moduleCopy;
}
exports.flattenModule = flattenModule;
/**
* Find definitions used by a given expression, by flattening that expression.
*
* @param expr - The expression for which to find definitions
* @param lookupTable - The lookup table with all related references
*
* @returns The definitions used by the expression and their dependencies
*/
function dependentDefinitions(expr, lookupTable) {
const flattener = new Flattener(new Map(), lookupTable);
(0, IRVisitor_1.walkExpression)(flattener, expr);
// Walking an expression should never add a non-definition declaration. The following assertion is just to make sure.
(0, assert_1.default)(flattener.newDeclarations.every(d => (0, quintIr_1.isDef)(d)));
return [...flattener.newDeclarations.values()];
}
exports.dependentDefinitions = dependentDefinitions;
class Flattener {
constructor(modulesByName, lookupTable) {
// Buffer of declarations, topologically sorted
this.newDeclarations = [];
this.modulesByName = modulesByName;
this.lookupTable = lookupTable;
}
enterModule(_quintModule) {
// Reset `newDeclarations`
this.newDeclarations = [];
}
exitModule(quintModule) {
// Get rid of imports and exports, and add the definitions we collected
quintModule.declarations = (0, lodash_1.uniqBy)(this.newDeclarations.filter(d => d.kind !== 'import' && d.kind !== 'export'), d => ((0, quintIr_1.isDef)(d) ? d.name : d.id));
}
exitDecl(decl) {
// Add declarations to the buffer as they are visited. This way, new declarations are addded in between, allowing us
// to keep the topological order.
this.newDeclarations.push(decl);
}
enterName(name) {
this.flattenName(name.id);
}
enterApp(app) {
this.flattenName(app.id);
}
enterExport(decl) {
// Find all top-level definitions in the exported module that are used somewhere
// (not necessarily in the module itself)
const ids = this.modulesByName.get(decl.protoName).declarations.map(d => d.id);
const definitions = [...this.lookupTable.values()].filter(d => ids.includes(d.id));
definitions.forEach(def => {
if (def.kind === 'param') {
throw new Error(`Impossible: intersection of top-level declarations with lookup table entries should never be a param. Found ${def}`);
}
const namespace = this.namespaceForNested ?? (0, quintIr_1.qualifier)(decl);
const newDef = namespace && !def.name.startsWith(namespace)
? (0, namespacer_1.addNamespaceToDefinition)(def, namespace, new Set(base_1.builtinNames))
: def;
if (this.newDeclarations.some(d => (0, quintIr_1.isDef)(d) && d.name === newDef.name)) {
// Already added
return;
}
// Typescript still keeps `LookupDefinition` extra fields even if we say `newDef` is a `QuintDef`. We need to
// remove them manually, so when this is collected back into a `LookupTable`, this leftover values don't get
// propagated.
const newDefWithoutMetadata = { ...newDef, hidden: false, namespaces: [], importedFrom: undefined };
this.walkNested(namespace, newDef);
this.newDeclarations.push(newDefWithoutMetadata);
});
}
enterInstance(decl) {
if (decl.qualifiedName) {
this.modulesByName.set(decl.qualifiedName, this.modulesByName.get(decl.protoName));
}
}
enterImport(decl) {
if (decl.qualifiedName) {
this.modulesByName.set(decl.qualifiedName, this.modulesByName.get(decl.protoName));
}
}
flattenName(id) {
const def = this.lookupTable.get(id);
if (!def) {
// skip bulit-in names
return;
}
if (def.kind === 'def' && def.depth && def.depth > 0) {
// skip non-top level definitions
return;
}
if (def.kind === 'param') {
// skip lambda parameters
return;
}
if (def.importedFrom?.kind === 'instance') {
// Don't do anything for definitions that come from instances. Those have to be instantiated first.
return;
}
const namespace = this.namespaceForNested ?? getNamespaceForDef(def);
const newDef = namespace && !def.name.startsWith(namespace)
? (0, namespacer_1.addNamespaceToDefinition)(def, namespace, new Set(base_1.builtinNames))
: def;
this.walkNested(namespace, newDef);
if (this.newDeclarations.some(d => (0, quintIr_1.isDef)(d) && d.name === newDef.name)) {
// Already added
return;
}
this.newDeclarations.push(newDef);
}
walkNested(namespace, def) {
// Save previous value to be restored
const nestedBefore = this.namespaceForNested;
this.namespaceForNested = namespace;
(0, IRVisitor_1.walkDefinition)(this, def);
// Restore previous value
this.namespaceForNested = nestedBefore;
}
}
function getNamespaceForDef(def) {
if (!def.namespaces || def.namespaces.length === 0) {
return;
}
return def.namespaces.reduce((a, b) => `${b}::${a}`);
}
exports.getNamespaceForDef = getNamespaceForDef;
//# sourceMappingURL=flattener.js.map