@informalsystems/quint
Version:
Core tool for the Quint specification language
304 lines • 15.3 kB
JavaScript
;
/* ----------------------------------------------------------------------------------
* Copyright 2022 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.integerOperators = exports.listOperators = exports.tupleOperators = exports.recordOperators = exports.mapOperators = exports.setOperators = exports.booleanOperators = exports.standardPropagation = exports.getSignatures = void 0;
/**
* Effect signatures for built-in operators
*
* @author Gabriela Moreira
*
* @module
*/
const base_1 = require("./base");
const parser_1 = require("./parser");
const lodash_1 = require("lodash");
function getSignatures() {
return new Map(fixedAritySignatures.concat(multipleAritySignatures));
}
exports.getSignatures = getSignatures;
function parseAndQuantify(effectString) {
const e = (0, parser_1.parseEffectOrThrow)(effectString);
return { effect: e, ...(0, base_1.effectNames)(e) };
}
/** Generate a name for an entity variable of certain component kind:
* r0, r1, r2, ... for read components
* t0, t1, t2, ... for temporal components
* u0, u1, u2, ... for update components
*/
function nameForVariable(kind, i) {
const prefixByComponentKind = new Map([
['read', 'r'],
['temporal', 't'],
['update', 'u'],
]);
return `${prefixByComponentKind.get(kind)}${i}`;
}
/**
* Builds a signature that propagates components of the given kinds
*
* Example: propagateComponents(['read', 'temporal'])(2) results in
* (Read[r1] & Temporal[t1], Read[r2] & Temporal[t2]) => Read[r1, r2] & Temporal[t1, t2]
*
* @param kinds the kinds of components to propagate
* @returns an arrow function taking arity and returning the effect for that arity
*/
function propagateComponents(kinds) {
return (arity) => {
const params = (0, lodash_1.times)(arity, i => {
const components = kinds.map(kind => {
return { kind: kind, entity: { kind: 'variable', name: nameForVariable(kind, i + 1) } };
});
return { kind: 'concrete', components: components };
});
const resultComponents = kinds.map(kind => {
const names = (0, lodash_1.times)(arity, i => nameForVariable(kind, i + 1));
const entities = names.map(name => ({ kind: 'variable', name }));
return { kind: kind, entity: { kind: 'union', entities } };
});
const result = { kind: 'concrete', components: resultComponents };
const effect = { kind: 'arrow', params: params, result: result };
return { effect, ...(0, base_1.effectNames)(effect) };
};
}
/**
* Builds a signature that propagates components of the given kinds, including a lambda parameter
* for the last argument
*
* Used in operators like `map`, `filter` and `fold`
*
* Example: propagationWithLambda(['read', 'temporal'])(2) results in
* (Read[r1] & Temporal[t1], (Read[r1] & Temporal[t1]) => Read[r2] & Temporal[t2]) => Read[r1, r2] & Temporal[t1, t2]
*
* @param kinds the kinds of components to propagate
* @returns an arrow function taking arity and returning the effect for that arity
*/
function propagationWithLambda(kinds) {
return (arity) => {
const params = (0, lodash_1.times)(arity - 1, i => {
const components = kinds.map(kind => {
return { kind: kind, entity: { kind: 'variable', name: nameForVariable(kind, i + 1) } };
});
return { kind: 'concrete', components: components };
});
const lambdaResult = {
kind: 'concrete',
components: kinds.map(kind => {
return { kind: kind, entity: { kind: 'variable', name: nameForVariable(kind, arity) } };
}),
};
const lambda = { kind: 'arrow', params, result: lambdaResult };
const resultComponents = kinds.map(kind => {
const names = (0, lodash_1.times)(arity, i => nameForVariable(kind, i + 1));
const entities = names.map(name => ({ kind: 'variable', name }));
return { kind: kind, entity: { kind: 'union', entities } };
});
const result = { kind: 'concrete', components: resultComponents };
const effect = { kind: 'arrow', params: params.concat(lambda), result };
return { effect, ...(0, base_1.effectNames)(effect) };
};
}
exports.standardPropagation = propagateComponents(['read', 'temporal']);
const literals = ['Nat', 'Int', 'Bool'].map(name => ({ name, effect: (0, base_1.toScheme)({ kind: 'concrete', components: [] }) }));
exports.booleanOperators = [
{ name: 'eq', effect: (0, exports.standardPropagation)(2) },
{ name: 'neq', effect: (0, exports.standardPropagation)(2) },
{ name: 'not', effect: (0, exports.standardPropagation)(1) },
{ name: 'iff', effect: (0, exports.standardPropagation)(2) },
{ name: 'implies', effect: (0, exports.standardPropagation)(2) },
];
exports.setOperators = [
{ name: 'exists', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'forall', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'in', effect: (0, exports.standardPropagation)(2) },
{ name: 'contains', effect: (0, exports.standardPropagation)(2) },
{ name: 'union', effect: (0, exports.standardPropagation)(2) },
{ name: 'intersect', effect: (0, exports.standardPropagation)(2) },
{ name: 'exclude', effect: (0, exports.standardPropagation)(2) },
{ name: 'subseteq', effect: (0, exports.standardPropagation)(2) },
{ name: 'filter', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'map', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'fold', effect: propagationWithLambda(['read', 'temporal'])(3) },
{ name: 'powerset', effect: (0, exports.standardPropagation)(1) },
{ name: 'flatten', effect: (0, exports.standardPropagation)(1) },
{ name: 'allLists', effect: (0, exports.standardPropagation)(1) },
{ name: 'allListsUpTo', effect: (0, exports.standardPropagation)(2) },
{ name: 'getOnlyElement', effect: (0, exports.standardPropagation)(1) },
{ name: 'chooseSome', effect: (0, exports.standardPropagation)(1) },
{ name: 'oneOf', effect: (0, exports.standardPropagation)(1) },
{ name: 'isFinite', effect: (0, exports.standardPropagation)(1) },
{ name: 'size', effect: (0, exports.standardPropagation)(1) },
];
exports.mapOperators = [
{ name: 'get', effect: (0, exports.standardPropagation)(2) },
{ name: 'keys', effect: (0, exports.standardPropagation)(1) },
{ name: 'mapBy', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'setToMap', effect: (0, exports.standardPropagation)(1) },
{ name: 'setOfMaps', effect: (0, exports.standardPropagation)(2) },
{ name: 'set', effect: (0, exports.standardPropagation)(3) },
{ name: 'setBy', effect: parseAndQuantify('(Read[r1], Read[r2], (Read[r1]) => Read[r3]) => Read[r1, r2, r3]') },
{ name: 'put', effect: (0, exports.standardPropagation)(3) },
];
exports.recordOperators = [
// Keys should be pure, as we don't allow dynamic key access.
{ name: 'field', effect: parseAndQuantify('(Read[r], Pure) => Read[r]') },
{ name: 'fieldNames', effect: parseAndQuantify('(Read[r]) => Read[r]') },
{ name: 'with', effect: parseAndQuantify('(Read[r1], Pure, Read[r2]) => Read[r1, r2]') },
];
exports.tupleOperators = [
// Indexes for tuples should be pure, as we don't allow dynamic tuple access.
{ name: 'item', effect: parseAndQuantify('(Read[r1], Pure) => Read[r1]') },
];
exports.listOperators = [
{ name: 'append', effect: (0, exports.standardPropagation)(2) },
{ name: 'concat', effect: (0, exports.standardPropagation)(2) },
{ name: 'head', effect: (0, exports.standardPropagation)(1) },
{ name: 'tail', effect: (0, exports.standardPropagation)(1) },
{ name: 'length', effect: (0, exports.standardPropagation)(1) },
{ name: 'nth', effect: (0, exports.standardPropagation)(2) },
{ name: 'indices', effect: (0, exports.standardPropagation)(1) },
{ name: 'replaceAt', effect: (0, exports.standardPropagation)(3) },
{ name: 'slice', effect: (0, exports.standardPropagation)(3) },
{ name: 'range', effect: (0, exports.standardPropagation)(2) },
{ name: 'select', effect: propagationWithLambda(['read', 'temporal'])(2) },
{ name: 'foldl', effect: propagationWithLambda(['read', 'temporal'])(3) },
{ name: 'foldr', effect: propagationWithLambda(['read', 'temporal'])(3) },
];
exports.integerOperators = [
{ name: 'iadd', effect: (0, exports.standardPropagation)(2) },
{ name: 'isub', effect: (0, exports.standardPropagation)(2) },
{ name: 'iuminus', effect: (0, exports.standardPropagation)(1) },
{ name: 'imul', effect: (0, exports.standardPropagation)(2) },
{ name: 'idiv', effect: (0, exports.standardPropagation)(2) },
{ name: 'imod', effect: (0, exports.standardPropagation)(2) },
{ name: 'ipow', effect: (0, exports.standardPropagation)(2) },
{ name: 'ilt', effect: (0, exports.standardPropagation)(2) },
{ name: 'igt', effect: (0, exports.standardPropagation)(2) },
{ name: 'ilte', effect: (0, exports.standardPropagation)(2) },
{ name: 'igte', effect: (0, exports.standardPropagation)(2) },
{ name: 'to', effect: (0, exports.standardPropagation)(2) },
];
const temporalOperators = [
{ name: 'always', effect: parseAndQuantify('(Read[r] & Temporal[t]) => Temporal[r, t]') },
{ name: 'eventually', effect: parseAndQuantify('(Read[r] & Temporal[t]) => Temporal[r, t]') },
{ name: 'next', effect: parseAndQuantify('(Read[r]) => Temporal[r]') },
{ name: 'orKeep', effect: parseAndQuantify('(Read[r] & Update[u], Read[v]) => Temporal[r, u, v]') },
{ name: 'mustChange', effect: parseAndQuantify('(Read[r] & Update[u], Read[v]) => Temporal[r, u, v]') },
// Enabled: Should we do this? https://github.com/informalsystems/quint/discussions/109
// Or should the result be temporal?
{ name: 'enabled', effect: parseAndQuantify('(Read[r1] & Update[u1]) => Read[r1]') },
{ name: 'weakFair', effect: parseAndQuantify('(Read[r] & Update[u], Read[v]) => Temporal[r, u, v]') },
{ name: 'strongFair', effect: parseAndQuantify('(Read[r] & Update[u], Read[v]) => Temporal[r, u, v]') },
];
const otherOperators = [
{ name: 'assign', effect: parseAndQuantify('(Read[r1], Read[r2]) => Read[r2] & Update[r1]') },
{ name: 'then', effect: parseAndQuantify('(Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r] & Update[u]') },
{ name: 'expect', effect: parseAndQuantify('(Read[r1] & Update[u], Read[r2]) => Read[r1] & Update[u]') },
{ name: 'reps', effect: parseAndQuantify('(Pure, (Read[r1]) => Read[r2] & Update[u]) => Read[r1, r2] & Update[u]') },
{ name: 'fail', effect: propagateComponents(['read', 'update'])(1) },
{ name: 'assert', effect: propagateComponents(['read'])(1) },
{ name: 'q::debug', effect: propagateComponents(['read'])(2) },
// FIXME: The following should produce run mode
{ name: 'q::lastTrace', effect: parseAndQuantify('Pure') },
{
name: 'q::test',
effect: parseAndQuantify('(Pure, Pure, Pure, Update[u1], Read[r2] & Update[u2], Read[r3]) => Read[r2, r3] & Update[u2]'),
},
{
name: 'q::testOnce',
effect: parseAndQuantify('(Pure, Pure, Update[u1], Read[r2] & Update[u2], Read[r3]) => Read[r2, r3] & Update[u2]'),
},
{
name: 'ite',
effect: parseAndQuantify('(Read[r1], Read[r2] & Update[u], Read[r3] & Update[u]) => Read[r1, r2, r3] & Update[u]'),
},
{
name: 'variant',
effect: parseAndQuantify('(Pure, Read[r] & Update[u]) => Read[r] & Update[u]'),
},
];
const multipleAritySignatures = [
['List', exports.standardPropagation],
['Set', exports.standardPropagation],
['Map', exports.standardPropagation],
['Rec', exports.standardPropagation],
['Tup', exports.standardPropagation],
['tuples', exports.standardPropagation],
['and', exports.standardPropagation],
['or', exports.standardPropagation],
[
// A match operator that looks like
//
// matchVariant(expr: s_i, label0: string, elim0: (s_0) => t, ..., labeln: string, elimn: (s_n) => t))
// : t
// {where 0 <= i <= n}
//
// has an effect signature matching the scheme
//
// (a, Pure, (a) => Read[r0] & Update[u], ..., (a) => Read[rn] & Update[u])
// => Read[r0,...,rn] & Update[u]
//
// Because:
//
// - Assuming `expr` has effect `a`, each eliminator must take a parameter with effect `a`.
// - Each label is a string literal, which must be `Pure`.
// - The result of applying the operator may have the effect of the body of any of the eliminators:
// the union of effect variables here corresponding to the disjunctive structure of the sum-type eliminator.
// - All eliminators must have the same update effect.
'matchVariant',
(arity) => {
// We need indexes for each eliminator (i.e., lambdas), so that we can number
// the effect variables corresponding to body of each respective eliminator.
const eliminatorIdxs = (0, lodash_1.range)((arity - 1) / 2);
const readVars = eliminatorIdxs.map(i => `r${i}`);
const matchedExprEffect = 'a';
const eliminationCaseEffects = readVars.map(r => `Pure, (a) => Read[${r}] & Update[u]`);
const argumentEffects = [matchedExprEffect, ...eliminationCaseEffects].join(', ');
const resultEffect = `Read[${readVars.join(',')}] & Update[u]`;
return parseAndQuantify(`(${argumentEffects}) => ${resultEffect}`);
},
],
[
'actionAll',
(arity) => {
const indexes = (0, lodash_1.range)(arity);
const args = indexes.map(i => `Read[r${i}] & Update[u${i}]`);
const readVars = indexes.map(i => `r${i}`).join(', ');
const updateVars = indexes.map(i => `u${i}`).join(', ');
return parseAndQuantify(`(${args.join(', ')}) => Read[${readVars}] & Update[${updateVars}]`);
},
],
['actionAll', propagateComponents(['read', 'update'])],
[
'actionAny',
(arity) => {
const indexes = (0, lodash_1.range)(arity);
const args = indexes.map(i => `Read[r${i}] & Update[u]`);
const readVars = indexes.map(i => `r${i}`).join(', ');
return parseAndQuantify(`(${args.join(', ')}) => Read[${readVars}] & Update[u]`);
},
],
];
const fixedAritySignatures = [
literals,
exports.booleanOperators,
exports.setOperators,
exports.mapOperators,
exports.recordOperators,
exports.tupleOperators,
exports.listOperators,
exports.integerOperators,
temporalOperators,
otherOperators,
]
.flat()
.map(sig => [
sig.name,
(_) => {
return sig.effect;
},
]);
//# sourceMappingURL=builtinSignatures.js.map