@informalsystems/quint
Version:
Core tool for the Quint specification language
128 lines • 6.96 kB
JavaScript
"use strict";
/* ----------------------------------------------------------------------------------
* Copyright 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.flattenInstances = void 0;
/**
* Flattening for instances, creating a new module for each instance and updating names.
*
* @author Gabriela Moreira
*
* @module
*/
const lodash_1 = require("lodash");
const IRTransformer_1 = require("../ir/IRTransformer");
const namespacer_1 = require("../ir/namespacer");
const quintIr_1 = require("../ir/quintIr");
const base_1 = require("../names/base");
const flattener_1 = require("./flattener");
const idRefresher_1 = require("../ir/idRefresher");
/**
* Flattens instances from a module, creating a new module for each instance, updating names, and replacing the instance
* declaration with an import. Assumes that the module doesn't have any imports and exports (that is, those were already
* flattened).
*
* @param quintModule The module with instances to flatten
* @param modulesByName The referenced modules, by name
* @param lookupTable The lookup table for the module and its dependencies
* @param idGenerator The id generator to be used for generating fresh ids for the new modules
* @param sourceMap The source map for the module and its dependencies
* @param analysisOutput The analysis output for the module and its dependencies
*
* @returns The new modules, including the given module (with modifications) and the new modules for the instances
*/
function flattenInstances(quintModule, modulesByName, lookupTable, idGenerator, sourceMap, analysisOutput) {
const flattener = new InstanceFlattener(modulesByName, lookupTable, idGenerator, sourceMap, analysisOutput);
const moduleCopy = { ...quintModule, declarations: [...quintModule.declarations] };
const newModule = (0, IRTransformer_1.transformModule)(flattener, moduleCopy);
return [...flattener.newModules, newModule];
}
exports.flattenInstances = flattenInstances;
class InstanceFlattener {
constructor(modulesByName, lookupTable, idGenerator, sourceMap, analysisOutput) {
// The new modules created by the flattener for each instance.
this.newModules = [];
this.modulesByName = modulesByName;
this.lookupTable = lookupTable;
this.idGenerator = idGenerator;
this.sourceMap = sourceMap;
this.analysisOutput = analysisOutput;
}
enterModule(quintModule) {
this.currentModuleName = quintModule.name;
return quintModule;
}
enterName(expr) {
const def = this.lookupTable.get(expr.id);
if (def?.importedFrom?.kind !== 'instance') {
// Don't change anything from names not related to instances
return expr;
}
const namespace = (0, flattener_1.getNamespaceForDef)(def);
return { ...expr, name: (0, lodash_1.compact)([namespace, def.name]).join('::') };
}
enterApp(expr) {
const def = this.lookupTable.get(expr.id);
if (def?.importedFrom?.kind !== 'instance') {
// Don't change anything from names not related to instances
return expr;
}
const namespace = (0, flattener_1.getNamespaceForDef)(def);
return { ...expr, opcode: (0, lodash_1.compact)([namespace, def.name]).join('::') };
}
exitDecl(decl) {
if (decl.kind !== 'instance') {
// We just want to transform instances. However, it is not possible to use `exitInstance` since that requires us
// to return an instance, and in this case we want to return an import. So we use `exitDecl` and ignore
// non-instances.
return decl;
}
const protoModule = this.modulesByName.get(decl.protoName);
// We are going to construct a new module.
const newModuleDecls = [];
// A unique name for the new module. To ensure uniqueness, we add the source of the instance (the current module) to
// the name, as well as the qualifier (or the proto name itself, if no qualifier is present)
// If the current module is named "myModule":
// - The module for `import A.*` will be named `myModule::A`
// - The module for `import A as B.*` will be named `myModule::B`
const newName = [this.currentModuleName, decl.qualifiedName ?? protoModule.name].join('::');
decl.overrides.forEach(([param, expr]) => {
// Find dependencies of `expr` and add those to the new module
newModuleDecls.push(...(0, flattener_1.dependentDefinitions)(expr, this.lookupTable));
// Add a definition for the parameter, which replaces the constant in the proto module.
newModuleDecls.push({
kind: 'def',
qualifier: 'pureval',
expr: expr,
id: param.id,
name: [newName, param.name].join('::'),
typeAnnotation: this.lookupTable.get(param.id)?.typeAnnotation,
});
});
// We will copy everything but the constants, since we already introduce definitions to replace the constants
const protoDeclarations = protoModule.declarations.filter(d => d.kind !== 'const');
const transformedDecls = protoDeclarations.map(decl => {
if (!(0, quintIr_1.isDef)(decl)) {
// Keep imports/exports/instances as is
// TODO: further test and explore scenarios where the proto module has imports/exports/instances
return decl;
}
// `flow` is used to compose functions, so that we can apply multiple transformations to the definition.
return (0, lodash_1.flow)(
// Add the new module's name as the namespace, to make sure the defs are uniquely identified and won't conflict
// with other defs in the module (i.e. from other instance). Those will be imported without a qualifier.
d => (0, namespacer_1.addNamespaceToDefinition)(d, newName, new Set(base_1.builtinNames)),
// Generate new ids for the definition and its subcomponents, since the same definition in different instances
// might evaluate to different values.
d => (0, idRefresher_1.generateFreshIds)(d, this.idGenerator, this.sourceMap, this.analysisOutput))(decl);
});
newModuleDecls.push(...transformedDecls);
this.newModules.push({ ...protoModule, declarations: newModuleDecls, name: newName });
// Return the import statement for the new module, which replaces the instance declaration.
return { kind: 'import', id: decl.id, protoName: newName, defName: '*' };
}
}
//# sourceMappingURL=instanceFlattener.js.map