UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

163 lines 7.11 kB
"use strict"; /* ---------------------------------------------------------------------------------- * 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