UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

1,376 lines 47.4 kB
"use strict"; /* * Runtime values that are internally produced by the simulator. * * This is an internal implementation module. Everything in it may change in * the future versions. The discussion below is intended for the developers of * the simulator, not for its end users. * * In case of Boolean and integer values, runtime values are simply wrappers * around boolean and bigint. The difference becomes noticeable when we are * dealing with data structures such as: sets, intervals, powersets, and sets * of maps. At the surface level, they all behave as iterable collections. * However, all combinations of these data structures are admitted, e.g., * `set(1, 2, 3) == 1.to(3)` is a legal Quint expression. Although we could * always expand all set operators into sets represented via an immutable set * (provided by immutable.Set), this may lead to extremely inefficient * computations. For example, `1.to(2^64).contains(25)` should be trivial to * compute, since we know that 25 lies within the interval `[1, 2^64]`. A naive * simulator would first expand `1.to(2^64)` to the set of 2^64 elements, which * would simply run out of memory. * * To this end, we introduce different classes for various set representations: * * - RuntimeValueSet is the "normal-form" representation via enumeration. It * is implemented with Set of immutable-js, which is a perfect fit for this * task. * * - RuntimeValueInterval is the interval representation via a pair of * integers [first, last]. This class behaves as a set wherever possible. It * has optimized implementations of `contains`, `isSubset`, and `equals` * (technically, `equals` is implemented in RuntimeValueBase). * * - RuntimeValuePowerset: to compactly represent powersets. * * - RuntimeValueMapSet: to compactly represent a set of functions. * * Importantly, it should be always possible to convert other set * representations to enumerative sets (backed with `RuntimeValueSet`): Many * set operators have meaningful semantics only in enumerative sets. For * example, consider `1.to(3).union(10.to(15))` can be only defined by * enumeration, not in terms of intervals. Hence, every runtime value has the * method `toSet()` that produces an immutable.Set. The algebraic set * operations can be easily performed via the corresponding methods of * `immutable.Set`. * * Moreover, some Quint expressions force us to fall back on the enumerative set * representation. For example, set(set(1, 2, 3), 1.to(3)) is equivalent to the * set `set(set(1, 2, 3))`. If we did not normalize `1.to(3)` to `set(1, 2, * 3)`, we would produce a set that would not be considered equal to * `set(set(1, 2, 3))`. This is because implementations of sets utilize hashes, * which would be different in the enumerative and interval representations. * * The operator `contains` may seem to be too specialized for introducing the * whole new layer of abstraction. However, it is deeply rooted in the * semantics of Quint, which, similar to TLA+, heavily utilizes set operators. * * Igor Konnov, Gabriela Moreira 2022-2024 * * Copyright 2022-2024 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.RuntimeValueLambda = exports.RuntimeValueVariant = exports.fromQuintEx = exports.rv = void 0; const immutable_1 = require("immutable"); const maybe_1 = require("@sweet-monads/maybe"); const assert_1 = require("assert"); const idGenerator_1 = require("../../idGenerator"); const IRprinting_1 = require("../../ir/IRprinting"); const quintError_1 = require("../../quintError"); const either_1 = require("@sweet-monads/either"); /** * A factory of runtime values that should be used to instantiate new values. */ exports.rv = { /** * Make a runtime value that represents a Boolean. * * @param value a Boolean value * @return a new runtime value that carries the Boolean value */ mkBool: (value) => { return new RuntimeValueBool(value); }, /** * Make a runtime value that represents an integer. * * @param value an integer value * @return a new runtime value that carries the integer value */ mkInt: (value) => { return new RuntimeValueInt(BigInt(value)); }, /** * Make a runtime value that represents a string. * * @param value a string * @return a new runtime value that carries the string */ mkStr: (value) => { return new RuntimeValueStr(value); }, /** * Make a runtime value that represents a tuple. * * @param value an iterable collection of runtime values * @return a new runtime value that carries the tuple */ mkTuple: (elems) => { return new RuntimeValueTupleOrList('Tup', (0, immutable_1.List)(elems)); }, /** * Make a runtime value that represents a list. * * @param value an iterable collection of runtime values * @return a new runtime value that carries the list */ mkList: (elems) => { return new RuntimeValueTupleOrList('List', (0, immutable_1.List)(elems)); }, /** * Make a runtime value that represents a record. * * @param value an iterable collection of pairs of strings and runtime values * @return a new runtime value that carries the record */ mkRecord: (elems) => { return new RuntimeValueRecord((0, immutable_1.OrderedMap)(elems).sortBy((_v, k) => k)); }, /** * Make a runtime value that represents a variant value of a sum type. * * @param label a string reperenting the variant's label * @param value the value held by the variant * @return a new runtime value that represents the variant */ mkVariant: (label, value) => { return new RuntimeValueVariant(label, value); }, /** * Make a runtime value that represents a map. * * @param value an iterable collection of pairs of runtime values * @return a new runtime value that carries the map */ mkMap: (elems) => { // convert the keys to the normal form, as they are hashed const arr = Array.from(elems).map(([k, v]) => [k.normalForm(), v]); return new RuntimeValueMap((0, immutable_1.Map)(arr)); }, /** * Make a runtime value that represents a map, using a Map. * * @param value an iterable collection of pairs of runtime values * @return a new runtime value that carries the map */ fromMap: (map) => { // convert the keys to the normal form, as they are hashed return new RuntimeValueMap(map); }, /** * Make a runtime value that represents a set via an immutable set. * * @param elems an iterable collection of runtime values * @return a new runtime value that represents * the immutable set of normalized elements */ mkSet: (elems) => { // Normalize the elements, before adding them to the set. // Otherwise, it may not behave as a set. // For example, set(set(1, 2), 1.to(2)) should be treated as set(set(1, 2)). let set = immutable_1.Set.of(); for (const e of elems) { set = set.add(e.normalForm()); } return new RuntimeValueSet(set); }, /** * Make a runtime value that represents either Nat or Int. * * @param set kind (Nat or Int) * @return a new runtime value that carries the infinite set */ mkInfSet: (kind) => { return new RuntimeValueInfSet(kind); }, /** * Make a runtime value that represents an integer interval as a pair * of big integers. This interval may be converted to an immutable set * via `this#toSet()`. * * @param first the minimal point of the interval (inclusive) * @param last the maximal poitn of the interval (inclusive) * @return a new runtime value that the interval */ mkInterval: (first, last) => { return new RuntimeValueInterval(BigInt(first), BigInt(last)); }, /** * Make a runtime value that represents a cross product of sets. * * @param value an iterable collection of runtime values * @return a new runtime value that carries the tuple */ mkCrossProd: (sets) => { return new RuntimeValueCrossProd(sets); }, /** * Make a runtime value that represents a set of maps. * * @param domainSet the set that stores the map domain * @param rangeSet the set that stores the map range * @return a new runtime value that carries the set of maps */ mkMapSet: (domainSet, rangeSet) => { return new RuntimeValueMapSet(domainSet, rangeSet); }, /** * Make a runtime value that represents a powerset. * * @param the baseset * @return a new runtime value that represents the powerset of the baseset */ mkPowerset: (baseSet) => { return new RuntimeValuePowerset(baseSet); }, /** * Make a runtime value that represents a lambda. * * @param params the lambda parameters * @param body the lambda body expression * @returns a runtime value of lambda */ mkLambda: (params, body, paramRegistry) => { const registers = params.map(param => { const register = paramRegistry.get(param.id); if (!register) { const reg = { value: (0, either_1.left)({ code: 'QNT501', message: `Parameter ${param.name} not set` }) }; paramRegistry.set(param.id, reg); return reg; } return register; }); return new RuntimeValueLambda(body, registers); }, /** * Make a runtime value from a quint expression. * @param ex - the Quint expression * @returns a runtime value for the expression */ fromQuintEx: (ex) => { const v = fromQuintEx(ex); if (v.isJust()) { return v.value; } else { throw new Error(`Cannot convert ${(0, IRprinting_1.expressionToString)(ex)} to a runtime value`); } }, /** * Convert a runtime value to a Quint expression. * @param value - the runtime value to convert * @returns a Quint expression for the runtime value */ toQuintEx: (value) => { return value.toQuintEx(idGenerator_1.zerog); }, }; /** The default entry point of this module */ exports.default = exports.rv; /** * Get a ground expression, that is, an expression * that contains only literals and constructors, and * convert it to a runtime value. * * @param ex the expression to convert * @returns the runtime value that encodes the expression */ function fromQuintEx(ex) { switch (ex.kind) { case 'bool': return (0, maybe_1.just)(exports.rv.mkBool(ex.value)); case 'int': return (0, maybe_1.just)(exports.rv.mkInt(ex.value)); case 'str': return (0, maybe_1.just)(exports.rv.mkStr(ex.value)); case 'app': switch (ex.opcode) { case 'Set': return (0, maybe_1.merge)(ex.args.map(fromQuintEx)).map(exports.rv.mkSet); case 'Map': { const pairs = (0, maybe_1.merge)(ex.args.map(arg => { (0, assert_1.strict)(arg.kind === 'app', `Expected Tup(...), found: ${arg.kind}`); (0, assert_1.strict)(arg.opcode === 'Tup', `Expected Tup(...), found: ${arg.opcode}`); (0, assert_1.strict)(arg.args.length === 2, `Expected a 2-element Tup(...), found: ${arg.args.length} elements`); return (0, maybe_1.merge)([fromQuintEx(arg.args[0]), fromQuintEx(arg.args[1])]); })); return pairs.map(exports.rv.mkMap); } case 'Tup': return (0, maybe_1.merge)(ex.args.map(fromQuintEx)).map(exports.rv.mkTuple); case 'List': return (0, maybe_1.merge)(ex.args.map(fromQuintEx)).map(exports.rv.mkList); case 'Rec': { const pairs = []; for (let i = 0; i < ex.args.length / 2; i++) { const keyEx = ex.args[2 * i]; const key = keyEx.kind === 'str' ? keyEx.value : ''; const v = fromQuintEx(ex.args[2 * i + 1]); if (v.isJust()) { pairs.push([key, v.value]); } else { return (0, maybe_1.none)(); } } return (0, maybe_1.just)(exports.rv.mkRecord(pairs)); } case 'variant': { const label = ex.args[0].value; return fromQuintEx(ex.args[1]).map(v => exports.rv.mkVariant(label, v)); } default: // no other case should be possible return (0, maybe_1.none)(); } case 'lambda': // We don't have enough information to convert lambdas directly return (0, maybe_1.none)(); default: // no other case should be possible return (0, maybe_1.none)(); } } exports.fromQuintEx = fromQuintEx; /** * The default implementation of the common methods. * This implementation is internal to the module. */ class RuntimeValueBase { constructor(isSetLike) { this.isSetLike = isSetLike; } [Symbol.iterator]() { // produce an empty iterator by default return { next() { return { done: true, value: undefined }; }, }; } normalForm() { // tuples override this method if (!this.isSetLike) { // Booleans and integers are in the normal form return this; } else { return new RuntimeValueSet(this.toSet()); } } toSet() { if (this.isSetLike) { // the default transformation to a set is done via iteration let set = immutable_1.Set.of(); for (const e of this) { set = set.add(e.normalForm()); } return set; } else { throw new Error('Expected a set-like value'); } } toList() { if (this instanceof RuntimeValueTupleOrList) { return this.list; } else { throw new Error('Expected a list value'); } } toOrderedMap() { if (this instanceof RuntimeValueRecord) { return this.map; } else { throw new Error(`Expected a record value but got ${(0, IRprinting_1.expressionToString)(this.toQuintEx(idGenerator_1.zerog))}`); } } toMap() { if (this instanceof RuntimeValueMap) { return this.map; } else { throw new Error('Expected a map value'); } } toBool() { if (this instanceof RuntimeValueBool) { return this.value; } else { throw new Error('Expected a Boolean value'); } } toInt() { if (this instanceof RuntimeValueInt) { return this.value; } else { throw new Error(`Expected an integer value, got ${(0, IRprinting_1.expressionToString)(this.toQuintEx(idGenerator_1.zerog))}`); } } toStr() { if (this instanceof RuntimeValueStr) { return this.value; } else { throw new Error('Expected a string value'); } } toTuple2() { // This is specific for tuples of size 2, as they are expected in many builtins. if (this instanceof RuntimeValueTupleOrList) { const list = this.list; if (list.size === 2) { return [list.get(0), list.get(1)]; } } throw new Error('Expected a 2-tuple'); } toArrow() { if (!(this instanceof RuntimeValueLambda)) { throw new Error('Expected a lambda value'); } const lam = this; return (ctx, args) => { if (lam.registers.length !== args.length) { return (0, either_1.left)({ code: 'QNT506', message: `Lambda expects ${lam.registers.length} arguments, but got ${args.length}`, }); } lam.registers.forEach((reg, i) => { reg.value = (0, either_1.right)(args[i]); }); return lam.body(ctx); }; } toVariant() { if (this instanceof RuntimeValueVariant) { return [this.label, this.value]; } else { throw new Error('Expected a variant value'); } } contains(elem) { // the default search is done via iteration, which is the worst case let found = false; // the element may be in a special form (e.g., an interval), normalize const elemNorm = elem.normalForm(); for (const other of this) { if (other.equals(elemNorm)) { found = true; } } return found; } isSubset(superset) { // Do O(m * n) tests, where m and n are the cardinalities of lhs and rhs. // Maybe we should use a cardinality test, when it's possible. for (const e of this) { if (!superset.contains(e)) { return false; } } return true; } equals(other) { if (typeof other !== 'object' || other === null) { return false; } if (!(other instanceof RuntimeValueBase)) { return false; } if (this instanceof RuntimeValueBool && other instanceof RuntimeValueBool) { return this.value === other.value; } if (this instanceof RuntimeValueInt && other instanceof RuntimeValueInt) { return this.value === other.value; } if (this instanceof RuntimeValueStr && other instanceof RuntimeValueStr) { return this.value === other.value; } if (this instanceof RuntimeValueTupleOrList && other instanceof RuntimeValueTupleOrList) { return this.kind === other.kind && this.list.equals(other.list); } if (this instanceof RuntimeValueRecord && other instanceof RuntimeValueRecord) { return this.map.equals(other.map); } if (this instanceof RuntimeValueVariant && other instanceof RuntimeValueVariant) { return this.label === other.label && this.value.equals(other.value); } if (this instanceof RuntimeValueSet && other instanceof RuntimeValueSet) { return (0, immutable_1.is)(this.set, other.set); } if (this instanceof RuntimeValueMap && other instanceof RuntimeValueMap) { return (0, immutable_1.is)(this.map, other.map); } if (this instanceof RuntimeValueInterval && other instanceof RuntimeValueInterval) { return this.first === other.first && this.last === other.last; } if (this instanceof RuntimeValueCrossProd && other instanceof RuntimeValueCrossProd) { const size = this.sets.length; if (size !== other.sets.length) { return false; } else { for (let i = 0; i < size; i++) { if (!this.sets[i].equals(other.sets[i])) { return false; } } return true; } } if (this instanceof RuntimeValuePowerset && other instanceof RuntimeValuePowerset) { return this.baseSet.equals(other.baseSet); } if (this instanceof RuntimeValueMapSet && other instanceof RuntimeValueMapSet) { return this.domainSet.equals(other.domainSet) && this.rangeSet.equals(other.rangeSet); } if (this instanceof RuntimeValueInfSet) { return other instanceof RuntimeValueInfSet ? this.kind === other.kind : false; } if (other instanceof RuntimeValueInfSet) { return this instanceof RuntimeValueInfSet ? this.kind === other.kind : false; } if (this.isSetLike && other.isSetLike) { // for instance, an interval and an explicit set return (0, immutable_1.is)(this.toSet(), other.toSet()); } return false; } hashCode() { // The default implementation, // to make it compatible with RuntimeValue and ValueObject. return 0; } pick(_positions) { return (0, either_1.left)({ code: 'QNT501', message: '.pick() not implemented' }); } bounds() { return []; } cardinality() { return (0, either_1.right)(0n); } toQuintEx(gen) { // the default implementation, to make it compatible with RuntimeValue return { id: gen.nextId(), kind: 'bool', value: false, }; } } /** * A Boolean runtime value. This is an internal class. */ class RuntimeValueBool extends RuntimeValueBase { constructor(value) { super(false); this.value = value; } hashCode() { return this.value ? 1 : 0; } toQuintEx(gen) { return { id: gen.nextId(), kind: 'bool', value: this.value, }; } } /** * An integer (bigint) runtime value. This is an internal class. */ class RuntimeValueInt extends RuntimeValueBase { constructor(value) { super(false); this.value = value; } hashCode() { // wrap to a 32-bit unsigned integer and convert to a number return Number(BigInt.asUintN(32, this.value)); } toQuintEx(gen) { return { id: gen.nextId(), kind: 'int', value: this.value, }; } } /** * An immutable string runtime value. This is an internal class. */ class RuntimeValueStr extends RuntimeValueBase { constructor(value) { super(false); this.value = value; } hashCode() { return (0, immutable_1.hash)(this.value); } toQuintEx(gen) { return { id: gen.nextId(), kind: 'str', value: this.value, }; } } /** * A set of runtime values represented via an immutable List. * This is an internal class. */ class RuntimeValueTupleOrList extends RuntimeValueBase { constructor(kind, values) { super(true); this.kind = kind; this.list = values; } [Symbol.iterator]() { return this.list[Symbol.iterator](); } normalForm() { const normalizedValues = []; for (const e of this.list) { normalizedValues.push(e.normalForm()); } return new RuntimeValueTupleOrList(this.kind, (0, immutable_1.List)(normalizedValues)); } hashCode() { return this.list.hashCode(); } toQuintEx(gen) { // simply enumerate the values const elems = []; for (const e of this.list) { elems.push(e.toQuintEx(gen)); } // return the expression tup(...elems) return { id: gen.nextId(), kind: 'app', opcode: this.kind, args: elems, }; } } /** * A set of runtime values represented via an immutable ordered Map. * This is an internal class. */ class RuntimeValueRecord extends RuntimeValueBase { constructor(values) { super(true); this.map = values; } normalForm() { const normalizedMap = this.map.map((v, _k) => v.normalForm()); return new RuntimeValueRecord(normalizedMap); } hashCode() { return this.map.hashCode(); } toQuintEx(gen) { // simply enumerate the values const elems = []; for (const [key, value] of this.map) { elems.push({ id: gen.nextId(), kind: 'str', value: key }); elems.push(value.toQuintEx(gen)); } // return the expression Rec(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Rec', args: elems, }; } } class RuntimeValueVariant extends RuntimeValueBase { constructor(label, value) { super(false); // Not a "set-like" value this.label = label; this.value = value; } hashCode() { return (0, immutable_1.hash)(this.value) + this.value.hashCode(); } toQuintEx(gen) { return { id: gen.nextId(), kind: 'app', opcode: 'variant', args: [{ id: gen.nextId(), kind: 'str', value: this.label }, this.value.toQuintEx(gen)], }; } } exports.RuntimeValueVariant = RuntimeValueVariant; /** * A set of runtime values represented via an immutable Map. * This is an internal class. */ class RuntimeValueMap extends RuntimeValueBase { constructor(keyValues) { super(true); this.map = keyValues; } normalForm() { const normalizedMap = this.map.mapEntries(([k, v]) => [ k.normalForm(), v.normalForm(), ]); return new RuntimeValueMap(normalizedMap); } hashCode() { return this.map.hashCode(); } toQuintEx(gen) { // convert to a set of pairs and use its normal form const pairs = this.map .toArray() .map(([k, v]) => new RuntimeValueTupleOrList('Tup', (0, immutable_1.List)([k, v]))); const set = new RuntimeValueSet((0, immutable_1.Set)(pairs)).toQuintEx(gen); if (set.kind === 'app') { // return the expression Map(pairs) return { id: gen.nextId(), kind: 'app', opcode: 'Map', args: set.args, }; } else { throw new Error('Expected a set, found: ' + set.kind); } } } /** * A set of runtime values represented via an immutable set. * This is an internal class. */ class RuntimeValueSet extends RuntimeValueBase { constructor(set) { super(true); this.set = set; } [Symbol.iterator]() { return this.set[Symbol.iterator](); } hashCode() { return this.set.hashCode(); } isSubset(superset) { if (superset instanceof RuntimeValueSet) { // do a (hopefully) less expensive test return this.set.isSubset(superset.set); } else { // do the default test via iteration return super.isSubset(superset); } } toSet() { return this.set; } contains(elem) { // do a (hopefully) less expensive test return this.set.includes(elem.normalForm()); } pick(positions) { const next = positions.next(); (0, assert_1.strict)(!next.done, 'Internal error: too few positions. Report a bug.'); let index = next.value; // Iterate over the set elements, // since the set is not indexed, find the first element that goes over // the index number. This is probably the most efficient way of doing it // without creating intermediate objects in memory. for (const e of this) { if (index <= 0) { return (0, either_1.right)(e); } index -= 1n; } // Not sure if this can happen return (0, either_1.left)({ code: 'QNT501', message: 'Index out of bounds' }); } bounds() { return [toMaybe(this.cardinality())]; } cardinality() { return (0, either_1.right)(BigInt(this.set.size)); } toQuintEx(gen) { // Sets are tricky, as we have to normalize them when producing QuintEx. // The most common normal form is the one that sorts sets according // to their string representation. Instead of computing the string // representation multiple times, we cache it in `__str` and then forget it. function cacheStr(e) { return { ...e, __str: (0, IRprinting_1.expressionToString)(e), }; } // Normalize the elements by sorting them const elems = this.set .map(e => e.toQuintEx(gen)) .map(cacheStr) .toArray() .sort((e1, e2) => e1.__str.localeCompare(e2.__str)); // erase the string cache elems.forEach(e => delete e.__str); // return the expression Set(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Set', args: elems, }; } } /** * A set of runtime values represented via an integer interval. * This is an internal class. */ class RuntimeValueInterval extends RuntimeValueBase { constructor(first, last) { super(true); if (last >= first) { this.first = first; this.last = last; } else { // the interval is empty, normalize it this.first = 1n; this.last = 0n; } } [Symbol.iterator]() { const start = this.first; const end = this.last; function* g() { for (let i = start; i <= end; i++) { yield new RuntimeValueInt(i); } } return g(); } hashCode() { // wrap the sum to a 32-bit unsigned integer and convert to a number return Number(BigInt.asUintN(32, this.first + this.last)); } contains(elem) { if (elem instanceof RuntimeValueInt) { return this.first <= elem.value && elem.value <= this.last; } else { return false; } } isSubset(superset) { if (superset instanceof RuntimeValueInterval) { return this.first >= superset.first && this.last <= superset.last; } else { // fall back to the general implementation return super.isSubset(superset); } } pick(positions) { const next = positions.next(); (0, assert_1.strict)(!next.done, 'Internal error: too few positions. Report a bug.'); const index = next.value; (0, assert_1.strict)(index >= 0 || index <= this.last - this.first, `Internal error: index ${index} is out of bounds [${this.first}, ${this.last}]. Report a bug.`); if (this.last < this.first) { return (0, either_1.left)({ code: 'QNT501', message: 'Index out of bounds' }); } else { return (0, either_1.right)(new RuntimeValueInt(this.first + BigInt(index))); } } bounds() { return [toMaybe(this.cardinality())]; } cardinality() { return (0, either_1.right)(BigInt(this.last - this.first) + 1n); } toQuintEx(gen) { // simply enumerate the values in the interval first..last const elems = []; for (const i of this) { elems.push(i.toQuintEx(gen)); } // return the expression Set(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Set', args: elems, }; } } /** * A set of runtime values represented via a cross-product of sets. * This is an internal class. */ class RuntimeValueCrossProd extends RuntimeValueBase { constructor(sets) { super(true); this.sets = sets; } [Symbol.iterator]() { // convert every set-like value to an array const arrays = this.sets.map(set => Array.from(set)); const existsEmptySet = arrays.some(arr => arr.length === 0); const nindices = arrays.length; function* gen() { if (existsEmptySet) { // yield nothing as an empty set produces the empty product return; } // Our iterator is an array of indices. // An ith index must be in the range [0, arrays[i].length). const indices = Array(nindices).fill(0); indices[0] = -1; let done = false; while (!done) { // try to increment one of the counters, starting with the first one done = true; for (let i = 0; i < nindices; i++) { // similar to how we do increment in binary, // try to increase a position, wrapping to 0, if overfull if (++indices[i] >= arrays[i].length) { // wrap around and continue indices[i] = 0; } else { // increment worked, there is a next element done = false; break; } } if (!done) { // yield a tuple that is produced with the counters const nextElem = []; for (let i = 0; i < nindices; i++) { nextElem.push(arrays[i][indices[i]]); } yield new RuntimeValueTupleOrList('Tup', (0, immutable_1.List)(nextElem)); } } } return gen(); } hashCode() { let hash = 0; for (const c of this.sets) { hash += c.hashCode(); } return hash; } contains(elem) { if (elem instanceof RuntimeValueTupleOrList) { if (elem.list.size !== this.sets.length) { return false; } else { let i = 0; for (const e of elem.list) { if (!this.sets[i].contains(e)) { return false; } i++; } return true; } } else { return false; } } isSubset(superset) { if (superset instanceof RuntimeValueCrossProd) { const size = this.sets.length; if (superset.sets.length !== size) { return false; } else { for (let i = 0; i < size; i++) { if (!this.sets[i].isSubset(superset.sets[i])) { return false; } } return true; } } else { // fall back to the general implementation return super.isSubset(superset); } } cardinality() { return (0, either_1.mergeInMany)(this.sets.map(s => s.cardinality())) .map(cards => cards.reduce((n, card) => n * card, 1n)) .mapLeft((errors) => { return { code: 'QNT501', message: errors.map(quintError_1.quintErrorToString).join('\n') }; }); } pick(positions) { const elems = (0, either_1.mergeInMany)(this.sets.map(elemSet => elemSet.pick(positions))).mapLeft((errors) => { return { code: 'QNT501', message: errors.map(quintError_1.quintErrorToString).join('\n') }; }); return elems.map(es => new RuntimeValueTupleOrList('Tup', immutable_1.List.of(...es))); } bounds() { return this.sets.map(elemSet => toMaybe(elemSet.cardinality())); } toQuintEx(gen) { // simply enumerate the values const elems = []; for (const i of this) { elems.push(i.toQuintEx(gen)); } // return the expression Set(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Set', args: elems, }; } } /** * A set of runtime values represented via powersets. * This is an internal class. */ class RuntimeValuePowerset extends RuntimeValueBase { constructor(baseSet) { super(true); this.baseSet = baseSet; } [Symbol.iterator]() { const nsets = this.cardinality().unwrap(); // copy fromIndex, as gen does not have access to this. const fromIndex = (i) => this.fromIndex(i); function* gen() { // Generate `nsets` sets by using number increments. // Note that 2 ** 0 == 1. for (let i = 0n; i < nsets; i++) { yield fromIndex(i); } } return gen(); } hashCode() { return this.baseSet.hashCode(); } contains(elem) { if (!elem.isSetLike) { return false; } for (const e of elem) { if (!this.baseSet.contains(e)) { return false; } } return true; } isSubset(superset) { if (superset instanceof RuntimeValuePowerset) { return this.baseSet.isSubset(superset.baseSet); } else { // fall back to the general implementation return super.isSubset(superset); } } cardinality() { return this.baseSet.cardinality().map(c => 2n ** c); } pick(positions) { const next = positions.next(); (0, assert_1.strict)(!next.done, 'Internal error: too few positions. Report a bug.'); return this.cardinality().map(_ => this.fromIndex(next.value)); } bounds() { return [toMaybe(this.cardinality())]; } toQuintEx(gen) { // simply enumerate the values const elems = []; for (const i of this) { elems.push(i.toQuintEx(gen)); } // return the expression Set(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Set', args: elems, }; } // Convert the global index i to bits, which define membership. // By interactively dividing the index by 2 and // taking its remainder. fromIndex(index) { const elems = []; let bits = index; for (const elem of this.baseSet) { const isMem = bits % 2n === 1n; bits = bits / 2n; if (isMem) { elems.push(elem); } } return exports.rv.mkSet(elems); } } /** * A set of runtime values represented via a set of maps. * This is an internal class. */ class RuntimeValueMapSet extends RuntimeValueBase { constructor(domainSet, rangeSet) { super(true); this.domainSet = domainSet; this.rangeSet = rangeSet; } [Symbol.iterator]() { // convert the domain and range to arrays const domainArr = Array.from(this.domainSet); const rangeArr = Array.from(this.rangeSet); // The below code is an adaptation of RuntimeValueCrossProd. // Can we generalize both? const nindices = domainArr.length; const nvalues = rangeArr.length; function* gen() { if (domainArr.length === 0) { // To reflect the behaviour of TLC, an empty domain needs to give Set(Map()) yield exports.rv.mkMap([]); return; } if (rangeArr.length === 0) { // To reflect the behaviour of TLC, an empty range needs to give Set() // yields nothing return; } // generate `nmaps` maps by using number increments const nmaps = nvalues ** nindices; for (let i = 0; i < nmaps; i++) { const pairs = []; // Convert the global index i to digits of a nvalues-based number. // By interactively dividing the index by the base and // taking its remainder. let index = i; for (let k = 0; k < nindices; k++) { pairs.push([domainArr[k], rangeArr[index % nvalues]]); index = Math.floor(index / nvalues); } yield exports.rv.mkMap(pairs); } } return gen(); } hashCode() { return this.domainSet.hashCode() + this.rangeSet.hashCode(); } contains(elem) { if (elem instanceof RuntimeValueMap) { return (this.domainSet.equals(exports.rv.mkSet(elem.map.keys())) && elem.map.find(v => !this.rangeSet.contains(v.normalForm())) === undefined); } else { return false; } } isSubset(superset) { if (superset instanceof RuntimeValueMapSet) { return this.domainSet.equals(superset.domainSet) && this.rangeSet.isSubset(superset.rangeSet); } else { // fall back to the general implementation return super.isSubset(superset); } } cardinality() { return (0, either_1.mergeInMany)([this.rangeSet.cardinality(), this.domainSet.cardinality()]) .map(([rc, dc]) => rc ** dc) .mapLeft((errors) => { return { code: 'QNT501', message: errors.map(quintError_1.quintErrorToString).join('\n') }; }); } pick(positions) { const domainSizeResult = this.domainSet.cardinality(); const rangeSizeResult = this.rangeSet.cardinality(); if (domainSizeResult.isLeft()) { // we cannot generate maps over infinite domains return (0, either_1.left)(domainSizeResult.value); } const domainSize = domainSizeResult.value; if (domainSize === 0n) { // To reflect the behaviour of TLC, an empty domain needs to give Set(Map()) return (0, either_1.right)(exports.rv.mkMap([])); } if (rangeSizeResult.isRight() && rangeSizeResult.value === 0n) { // the set of maps is empty, no way to pick a value return (0, either_1.left)({ code: 'QNT501', message: 'Empty set of maps' }); } const keyValues = []; for (const key of this.domainSet) { let rangeSet = this.rangeSet; if (rangeSet instanceof RuntimeValueMapSet) { // enumerate the range set to avoid issues like #1530 const elems = []; for (const i of rangeSet) { elems.push(i); } rangeSet = new RuntimeValueSet((0, immutable_1.Set)(elems)); } const valueOrNone = rangeSet.pick(positions); if (valueOrNone.isRight()) { keyValues.push([key, valueOrNone.value]); } else { return valueOrNone; } } return (0, either_1.right)(exports.rv.mkMap(keyValues)); } bounds() { const domainSizeOrNone = toMaybe(this.domainSet.cardinality()); (0, assert_1.strict)(domainSizeOrNone.isJust() && domainSizeOrNone.value <= Number.MAX_SAFE_INTEGER, `Domain size is over ${Number.MAX_SAFE_INTEGER}`); const sz = Number(domainSizeOrNone.value); return Array(sz).fill(toMaybe(this.rangeSet.cardinality())); } toQuintEx(gen) { // simply enumerate the values const elems = []; for (const i of this) { elems.push(i.toQuintEx(gen)); } // return the expression set(...elems) return { id: gen.nextId(), kind: 'app', opcode: 'Set', args: elems, }; } } /** * An infinite set such as Nat or Int. Since we cannot enumerate infinite * sets, the support for them is very limited. * This is an internal class. */ class RuntimeValueInfSet extends RuntimeValueBase { constructor(kind) { super(true); this.kind = kind; } [Symbol.iterator]() { throw new Error(`Infinite set ${this.kind} is non-enumerable`); } hashCode() { // the hash codes for Nat and Int are a bit arbitrary, so we make them huge return this.kind === 'Nat' ? Number.MAX_SAFE_INTEGER - 1 : Number.MAX_SAFE_INTEGER; } isSubset(superset) { if (superset instanceof RuntimeValueInfSet) { return this.kind !== 'Int' || superset.kind !== 'Nat'; } else { return false; } } toSet() { throw new Error(`Infinite set ${this.kind} is non-enumerable`); } contains(elem) { if (elem instanceof RuntimeValueInt) { return this.kind === 'Int' ? true : elem.value >= 0; } else { return false; } } pick(positions) { // Simply return the position. The actual range is up to the caller, // as Int and Nat do not really care about the ranges. const next = positions.next(); (0, assert_1.strict)(!next.done, 'Internal error: too few positions. Report a bug.'); if (this.kind === 'Int') { // Simply return the position. It's up to the caller to pick the position. return (0, either_1.right)(exports.rv.mkInt(next.value)); } else { // Nat: return the absolute value of the position. const p = next.value; return (0, either_1.right)(exports.rv.mkInt(p >= 0n ? p : -p)); } } bounds() { // it's an infinite set, so we return none() to indicate an infinite set return [(0, maybe_1.none)()]; } cardinality() { return (0, either_1.left)({ code: 'QNT514', message: `Cardinality of ${this.kind} is infinite` }); } toQuintEx(gen) { // return the built-in name return { id: gen.nextId(), kind: 'name', name: this.kind, }; } } /** * A lambda operator as a runtime value. Technically, it should not be a value * in Quint/TLA+. However, we have to carry lambdas when evaluating higher-order * operators. * * RuntimeValueLambda cannot be compared with other values. */ class RuntimeValueLambda extends RuntimeValueBase { constructor(body, registers) { super(false); this.body = body; this.registers = registers; } toQuintEx(gen) { // We produce a mock Quint expression. // It is not going to be used, // as the lambdas are passed only inside the simulator. return { id: gen.nextId(), kind: 'lambda', params: Array.from(this.registers.keys()).map(i => { return { id: gen.nextId(), name: `_a${i}` }; }), qualifier: 'def', expr: { kind: 'str', value: `lambda_${this.registers.length}_params`, id: gen.nextId(), }, }; } } exports.RuntimeValueLambda = RuntimeValueLambda; function toMaybe(r) { if (r.isRight()) { return (0, maybe_1.just)(r.value); } else { return (0, maybe_1.none)(); } } //# sourceMappingURL=runtimeValue.js.map