UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

119 lines 5.27 kB
"use strict"; /* ---------------------------------------------------------------------------------- * Copyright 2025 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.evalNondet = void 0; /** * Evaluates a nondeterministic let-in expression, with retries for `oneOf` calls. * * @author Gabriela Moreira * * @module */ const either_1 = require("@sweet-monads/either"); const runtimeValue_1 = require("./runtimeValue"); const lodash_1 = require("lodash"); const evaluator_1 = require("./evaluator"); /** * Evaluates a nondet + oneOf() + body expression, which picks a value from a * set and evaluates the body with that value. If the body evaluates to false, * it retries with the next position in the set until it finds a true value or * exhausts all positions. * * - If the set is empty, it returns false. * - If the set is infinite, it picks a random value from the range * [-2^255, 2^255) for each position. * - If the set is to big (bigger than QUINT_RETRY_NONDET_SMALLER_THAN), it * does not retry and returns the first value picked from the set. * * @param name - The name of the nondeterministic expression. * @param cache - A cached value to store the picked value. * @param setEval - The evaluation function for the set from which to pick a value. * @param bodyEval - The evaluation function for the body of the let-in. * @returns An evaluation function for the nondet let-in expression. */ function evalNondet(name, cache, setEval, bodyEval) { return ctx => setEval(ctx).chain(set => { if (set.cardinality().isRight() && set.cardinality().unwrap() === 0n) { // The whole let expression is false if there are no elements to be picked. return (0, either_1.right)(runtimeValue_1.rv.mkBool(false)); } const bounds = set.bounds(); const originalPositions = bounds.map((b) => { if (b.isJust()) { return ctx.rand(b.value); } else { // An infinite set, pick an integer from the range [-2^255, 2^255). // Note that pick on Nat uses the absolute value of the passed integer. // TODO: make it a configurable parameter: // https://github.com/informalsystems/quint/issues/279 return -(2n ** 255n) + ctx.rand(2n ** 256n); } }); let result; let shouldRetry; // First attempt is the original positions. // Keep it's value to detect when we are back to the original positions, when retrying. let newPositions = [...originalPositions]; do { const pickedValue = set.pick(newPositions.values()); // Eval the let body expression with the picked value (by saving it in the cache). cache.value = pickedValue; result = bodyEval(ctx); // If the result is false and the bounds are small enough, we retry with the next position. shouldRetry = (0, evaluator_1.isFalse)(result) && shouldRetryNondet(bounds); if (shouldRetry) { increment(newPositions, bounds.map(b => b.unwrap())); } else { // Save the value to `nondetPicks` pickedValue.map(value => ctx.varStorage.nondetPicks.set(name, value)); } // Retry if condition is satisfied and we haven't exhausted all possible positions. } while (shouldRetry && !(0, lodash_1.isEqual)(newPositions, originalPositions)); // Reset the cache cache.value = undefined; return result; }); } exports.evalNondet = evalNondet; function increment(newPositions, bounds) { for (let i = newPositions.length - 1; i >= 0; i--) { if (newPositions[i] < bounds[i] - 1n) { newPositions[i]++; return; } else { newPositions[i] = 0n; } } return; } /* The maximum size of set bounds for which we retry nondet expressions. If the * set is too big, it is better to be greedy and let the execution fail if we * pick a "wrong" value. As retrying large sets can take a long time, and there * might be better chances to find an interesting run if we start from scratch. * For example, see gradualPonzi.qnt. * * There's also the case where we have multiple "nested" nondet expressions, so * we set a pretty small value as default to avoid slowdowns even in those. If * there are several nested expression with big sets, this can still be slow, * but those are not common. The user can always change the value of the * environment variable `QUINT_RETRY_NONDET_SMALLER_THAN`. */ const QUINT_RETRY_NONDET_SMALLER_THAN = BigInt(process.env.QUINT_RETRY_NONDET_SMALLER_THAN ?? '100'); function shouldRetryNondet(bounds) { return bounds.every(b => { if (b.isJust()) { return b.value < QUINT_RETRY_NONDET_SMALLER_THAN; } else { return false; } }); } //# sourceMappingURL=nondet.js.map