UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

306 lines 15.4 kB
"use strict"; /* ---------------------------------------------------------------------------------- * 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) }, // the Apalache!Gen extension { name: 'apalache::generate', effect: (0, exports.standardPropagation)(1) }, // 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