UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

786 lines 38.3 kB
"use strict"; /* ---------------------------------------------------------------------------------- * 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.builtinLambda = exports.lazyBuiltinLambda = exports.lazyOps = exports.builtinValue = void 0; /** * Definitions on how to evaluate Quint builtin operators and values. * * The definitions are heavily based on the original `compilerImpl.ts` file written by Igor Konnov. * * @author Igor Konnov, Gabriela Moreira * * @module */ const either_1 = require("@sweet-monads/either"); const immutable_1 = require("immutable"); const evaluator_1 = require("./evaluator"); const runtimeValue_1 = require("./runtimeValue"); const lodash_1 = require("lodash"); const IRprinting_1 = require("../../ir/IRprinting"); const idGenerator_1 = require("../../idGenerator"); const graphics_1 = require("../../graphics"); const prettierimp_1 = require("../../prettierimp"); /** * Evaluates the given Quint builtin value by its name. * * This function is responsible for handling the evaluation of builtin values * (operators that do not take parameters). It returns an `EvalFunction` * which, when executed, provides the corresponding `RuntimeValue` or an error. * * The supported builtin values are: * - 'Bool': Returns a set containing boolean values `true` and `false`. * - 'Int': Returns an infinite set representing all integers. * - 'Nat': Returns an infinite set representing all natural numbers. * - 'q::lastTrace': Returns the list of the last trace from the context. * * If the provided name does not match any of the supported builtin values, * it returns an error indicating the unknown builtin. * * @param name - The name of the builtin value to evaluate. * @returns An `EvalFunction` that evaluates to the corresponding `RuntimeValue` or an error. */ function builtinValue(name) { switch (name) { case 'Bool': return _ => (0, either_1.right)(runtimeValue_1.rv.mkSet([runtimeValue_1.rv.mkBool(false), runtimeValue_1.rv.mkBool(true)])); case 'Int': return _ => (0, either_1.right)(runtimeValue_1.rv.mkInfSet('Int')); case 'Nat': return _ => (0, either_1.right)(runtimeValue_1.rv.mkInfSet('Nat')); case 'q::lastTrace': return ctx => (0, either_1.right)(runtimeValue_1.rv.mkList(ctx.trace.get())); default: return _ => (0, either_1.left)({ code: 'QNT404', message: `Unknown builtin ${name}` }); } } exports.builtinValue = builtinValue; /** * A list of operators that must be evaluated lazily. * These operators cannot have their arguments evaluated before their own evaluation for various reasons: * - Short-circuit operators (e.g., `and`, `or`, `implies`) where evaluation stops as soon as the result is determined. * - Conditional operators (e.g., `ite`, `matchVariant`) where only certain arguments are evaluated based on conditions. * - Repetition operators (e.g., `reps`) where the number of repetitions is unknown before evaluation. * - Operators that interact with state variables in a special way (e.g., `assign`, `next`). * - Operators where we can save resources (e.g., using `#pick()` in `oneOf` instead of enumerating the set). */ exports.lazyOps = [ 'assign', 'actionAny', 'actionAll', 'ite', 'matchVariant', 'oneOf', 'and', 'or', 'next', 'implies', 'then', 'reps', 'expect', ]; /** * Evaluates the given lazy builtin operator by its name. * * This function handles the evaluation of lazy builtin operators, * which require special handling as described in the `lazyOps` documentation. * It returns a function that takes a context and a list of evaluation functions, * and returns the result of evaluating the operator. * * If the provided operator does not match any of the supported lazy operators, * it returns an error indicating the unknown operator. * * @param op - The name of the lazy builtin operator to evaluate. * @returns A function that evaluates the operator with the given context and arguments. */ function lazyBuiltinLambda(op) { switch (op) { case 'and': // Short-circuit logical AND return (ctx, args) => { for (const arg of args) { const result = arg(ctx); if (!(0, evaluator_1.isTrue)(result)) { return result; } } return (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); }; case 'or': // Short-circuit logical OR return (ctx, args) => { for (const arg of args) { const result = arg(ctx); if (!(0, evaluator_1.isFalse)(result)) { return result; } } return (0, either_1.right)(runtimeValue_1.rv.mkBool(false)); }; case 'implies': // Short-circuit logical implication return (ctx, args) => { return args[0](ctx).chain(l => { if (!l.toBool()) { return (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); } return args[1](ctx); }); }; case 'actionAny': { // Executes the first enabled action from a randomized list of actions. // Returns false if no enabled actions are found. const app = { id: 0n, kind: 'app', opcode: 'actionAny', args: [] }; return (ctx, args) => { const nextVarsSnapshot = ctx.varStorage.snapshot(); // Create array of indices and shuffle them const indices = Array.from(args.keys()); // Fisher-Yates shuffle algorithm for (let i = indices.length - 1; i > 0; i--) { const j = Number(ctx.rand(BigInt(i + 1))); [indices[i], indices[j]] = [indices[j], indices[i]]; } // Try actions in shuffled order until we find one that's enabled for (const i of indices) { // Reset state for each attempt ctx.varStorage.actionTaken = undefined; ctx.varStorage.nondetPicks.forEach((_, key) => { ctx.varStorage.nondetPicks.set(key, undefined); }); ctx.recorder.onAnyOptionCall(app, i); const result = args[i](ctx); ctx.recorder.onAnyOptionReturn(app, i); if (result.isLeft()) { return result; } if (result.value.toBool()) { // Found an enabled action - record it and return true const successor = ctx.varStorage.snapshot(); ctx.recorder.onAnyReturn(args.length, i); ctx.varStorage.recoverSnapshot(successor); return (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); } // Reset state before trying next action ctx.varStorage.recoverSnapshot(nextVarsSnapshot); } // No enabled actions found ctx.recorder.onAnyReturn(args.length, -1); ctx.varStorage.recoverSnapshot(nextVarsSnapshot); return (0, either_1.right)(runtimeValue_1.rv.mkBool(false)); }; } case 'actionAll': // Executes all of the given actions, or none of them if any of them results in false. return (ctx, args) => { const nextVarsSnapshot = ctx.varStorage.snapshot(); for (const action of args) { const result = action(ctx); if (result.isLeft()) { return result; } if ((0, evaluator_1.isFalse)(result)) { ctx.varStorage.recoverSnapshot(nextVarsSnapshot); return (0, either_1.right)(runtimeValue_1.rv.mkBool(false)); } } return (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); }; case 'ite': // if-then-else return (ctx, args) => { return args[0](ctx).chain(condition => { return condition.toBool() ? args[1](ctx) : args[2](ctx); }); }; case 'matchVariant': // Pattern matching on variants return (ctx, args) => { const matchedEx = args[0]; return matchedEx(ctx).chain(expr => { const [label, value] = expr.toVariant(); const cases = args.slice(1); const caseForVariant = (0, lodash_1.chunk)(cases, 2).find(([caseLabel, _caseElim]) => { const l = caseLabel(ctx).unwrap().toStr(); return l === '_' || l === label; }); if (!caseForVariant) { return (0, either_1.left)({ code: 'QNT505', message: `No match for variant ${label}` }); } const [_caseLabel, caseElim] = caseForVariant; return caseElim(ctx).chain(elim => elim.toArrow()(ctx, [value])); }); }; case 'then': // Compose two actions, executing the second one only if the first one results in true. return (ctx, args) => { const oldState = ctx.varStorage.asRecord(); return args[0](ctx).chain(firstResult => { if (!firstResult.toBool()) { return (0, either_1.left)({ code: 'QNT513', message: 'Cannot continue in `then` because the highlighted expression evaluated to false', }); } ctx.shift(); const newState = ctx.varStorage.asRecord(); ctx.recorder.onNextState(oldState, newState); return args[1](ctx); }); }; case 'reps': // Repeats the given action n times, stopping if the action evaluates to false. return (ctx, args) => { return args[0](ctx).chain(n => { let result = (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); for (let i = 0; i < Number(n.toInt()); i++) { result = args[1](ctx).chain(value => value.toArrow()(ctx, [runtimeValue_1.rv.mkInt(i)])); if (result.isLeft()) { return result; } if ((0, evaluator_1.isFalse)(result)) { return (0, either_1.left)({ code: 'QNT513', message: `Reps loop could not continue after iteration #${i + 1} evaluated to false`, }); } // Don't shift after the last one if (i < Number(n.toInt()) - 1) { ctx.shift(); } } return result; }); }; case 'expect': // Translate A.expect(P): // - Evaluate A. // - When A's result is 'false', emit a runtime error. // - When A's result is 'true': // - Commit the variable updates: Shift the primed variables to unprimed. // - Evaluate `P`. // - If `P` evaluates to `false`, emit a runtime error (similar to `assert`). // - If `P` evaluates to `true`, rollback to the previous state and return `true`. return (ctx, args) => { const result = args[0](ctx).chain(action => { if (!action.toBool()) { return (0, either_1.left)({ code: 'QNT508', message: 'Cannot continue to "expect"' }); } const nextVarsSnapshot = ctx.varStorage.snapshot(); ctx.shift(); return args[1](ctx).chain(expectation => { ctx.varStorage.recoverSnapshot(nextVarsSnapshot); ctx.trace.dropLast(); if (!expectation.toBool()) { return (0, either_1.left)({ code: 'QNT508', message: 'Expect condition does not hold true' }); } return (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); }); }); return result; }; default: return () => (0, either_1.left)({ code: 'QNT000', message: 'Unknown stateful op' }); } } exports.lazyBuiltinLambda = lazyBuiltinLambda; /** * Evaluates the given builtin operator by its name. * * This function handles the evaluation of builtin operators, * which require the arguments to be pre-evaluated. It returns * a function that takes a context and a list of runtime values, * and returns the result of evaluating the operator. * * If the provided operator does not match any of the supported operators, * it returns an error indicating the unknown operator. * * @param op - The name of the builtin operator to evaluate. * @returns A function that evaluates the operator with the given context and arguments. */ function builtinLambda(op) { switch (op) { case 'Set': // Constructs a set from the given arguments. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args)); case 'Rec': // Constructs a record from the given arguments. Arguments are lists like [key1, value1, key2, value2, ...] return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkRecord((0, immutable_1.Map)((0, lodash_1.chunk)(args, 2).map(([k, v]) => [k.toStr(), v])))); case 'List': // Constructs a list from the given arguments. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkList((0, immutable_1.List)(args))); case 'Tup': // Constructs a tuple from the given arguments. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkTuple((0, immutable_1.List)(args))); case 'Map': // Constructs a map from the given arguments. Arguments are lists like [[key1, value1], [key2, value2], ...] return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkMap(args.map(kv => kv.toTuple2()))); case 'variant': // Constructs a variant from the given arguments. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkVariant(args[0].toStr(), args[1])); case 'not': // Logical negation return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(!args[0].toBool())); case 'iff': // Logical equivalence/bi-implication return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].toBool() === args[1].toBool())); case 'eq': // Equality return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].equals(args[1]))); case 'neq': // Inequality return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(!args[0].equals(args[1]))); case 'iadd': // Integer addition return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toInt() + args[1].toInt())); case 'isub': // Integer subtraction return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toInt() - args[1].toInt())); case 'imul': // Integer multiplication return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toInt() * args[1].toInt())); case 'idiv': // Integer division return (_, args) => { const divisor = args[1].toInt(); if (divisor === 0n) { return (0, either_1.left)({ code: 'QNT503', message: `Division by zero` }); } return (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toInt() / divisor)); }; case 'imod': // Integer modulus return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toInt() % args[1].toInt())); case 'ipow': // Integer exponentiation return (_, args) => { const base = args[0].toInt(); const exp = args[1].toInt(); if (base === 0n && exp === 0n) { return (0, either_1.left)({ code: 'QNT503', message: `0^0 is undefined` }); } if (exp < 0n) { return (0, either_1.left)({ code: 'QNT503', message: 'i^j is undefined for j < 0' }); } return (0, either_1.right)(runtimeValue_1.rv.mkInt(base ** exp)); }; case 'iuminus': // Integer unary minus return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(-args[0].toInt())); case 'ilt': // Integer less than return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].toInt() < args[1].toInt())); case 'ilte': // Integer less than or equal to return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].toInt() <= args[1].toInt())); case 'igt': // Integer greater than return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].toInt() > args[1].toInt())); case 'igte': // Integer greater than or equal to return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].toInt() >= args[1].toInt())); case 'item': // Access a tuple: tuples are 1-indexed, that is, _1, _2, etc. return (_, args) => { return getListElem(args[0].toList(), Number(args[1].toInt()) - 1); }; case 'tuples': // A set of all possible tuples from the elements of the respective given sets. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkCrossProd(args)); case 'range': // Constructs a list of integers from start to end. return (_, args) => { const start = Number(args[0].toInt()); const end = Number(args[1].toInt()); return (0, either_1.right)(runtimeValue_1.rv.mkList((0, immutable_1.List)((0, immutable_1.Range)(start, end).map(runtimeValue_1.rv.mkInt)))); }; case 'nth': // List access return (_, args) => getListElem(args[0].toList(), Number(args[1].toInt())); case 'replaceAt': // Replace an element at a given index in a list. return (_, args) => { const list = args[0].toList(); const idx = Number(args[1].toInt()); if (idx < 0 || idx >= list.size) { return (0, either_1.left)({ code: 'QNT510', message: `Out of bounds, replaceAt(${idx})` }); } return (0, either_1.right)(runtimeValue_1.rv.mkList(list.set(idx, args[2]))); }; case 'head': // Get the first element of a list. Not allowed in empty lists. return (_, args) => { const list = args[0].toList(); if (list.size === 0) { return (0, either_1.left)({ code: 'QNT505', message: `Called 'head' on an empty list` }); } return (0, either_1.right)(list.first()); }; case 'tail': // Get the tail (all elements but the head) of a list. Not allowed in empty lists. return (_, args) => { const list = args[0].toList(); if (list.size === 0) { return (0, either_1.left)({ code: 'QNT505', message: `Called 'tail' on an empty list` }); } return (0, either_1.right)(runtimeValue_1.rv.mkList(list.rest())); }; case 'slice': // Get a sublist of a list from start to end. return (_, args) => { const list = args[0].toList(); const start = Number(args[1].toInt()); const end = Number(args[2].toInt()); if (start < 0 || start > end || end > list.size) { return (0, either_1.left)({ code: 'QNT506', message: `slice(..., ${start}, ${end}) applied to a list of size ${list.size}`, }); } return (0, either_1.right)(runtimeValue_1.rv.mkList(list.slice(start, end))); }; case 'length': // The length of a list. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInt(args[0].toList().size)); case 'append': // Append an element to a list. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkList(args[0].toList().push(args[1]))); case 'concat': // Concatenate two lists. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkList(args[0].toList().concat(args[1].toList()))); case 'indices': // A set with the indices of a list. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInterval(0n, args[0].toList().size - 1)); case 'field': // Access a field in a record. return (_, args) => { const field = args[1].toStr(); const result = args[0].toOrderedMap().get(field); return result ? (0, either_1.right)(result) : (0, either_1.left)({ code: 'QNT501', message: `Accessing a missing record field ${field}` }); }; case 'fieldNames': // A set with the field names of a record. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args[0].toOrderedMap().keySeq().map(runtimeValue_1.rv.mkStr))); case 'with': // Replace a field value in a record. return (_, args) => { const record = args[0].toOrderedMap(); const field = args[1].toStr(); const value = args[2]; if (!record.has(field)) { return (0, either_1.left)({ code: 'QNT501', message: `Called 'with' with a non-existent field ${field}` }); } return (0, either_1.right)(runtimeValue_1.rv.mkRecord(record.set(field, value))); }; case 'powerset': // The powerset of a set. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkPowerset(args[0])); case 'contains': // Check if a set contains an element. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].contains(args[1]))); case 'in': // Check if an element is in a set. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[1].contains(args[0]))); case 'subseteq': // Check if a set is a subset of another set. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(args[0].isSubset(args[1]))); case 'exclude': // Set difference. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args[0].toSet().subtract(args[1].toSet()))); case 'union': // Set union. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args[0].toSet().union(args[1].toSet()))); case 'intersect': // Set intersection. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args[0].toSet().intersect(args[1].toSet()))); case 'size': // The size of a set. return (_, args) => args[0].cardinality().map(runtimeValue_1.rv.mkInt); case 'isFinite': // at the moment, we support only finite sets, so just return true return _args => (0, either_1.right)(runtimeValue_1.rv.mkBool(true)); case 'to': // Construct a set of integers from a to b. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkInterval(args[0].toInt(), args[1].toInt())); case 'fold': // Fold a set return (ctx, args) => applyFold('fwd', args[0].toSet(), args[1], arg => args[2].toArrow()(ctx, arg)); case 'foldl': // Fold a list from left to right. return (ctx, args) => applyFold('fwd', args[0].toList(), args[1], arg => args[2].toArrow()(ctx, arg)); case 'foldr': // Fold a list from right to left. return (ctx, args) => applyFold('rev', args[0].toList(), args[1], arg => args[2].toArrow()(ctx, arg)); case 'flatten': // Flatten a set of sets. return (_, args) => { const s = args[0].toSet().map(s => s.toSet()); return (0, either_1.right)(runtimeValue_1.rv.mkSet(s.flatten(1))); }; case 'get': // Get a value from a map. return (_, args) => { const map = args[0].toMap(); const key = args[1].normalForm(); const value = map.get(key); if (value) { return (0, either_1.right)(value); } // Else, the key does not exist. Construct an informative error message. const requestedKey = (0, IRprinting_1.expressionToString)(key.toQuintEx(idGenerator_1.zerog)); const existingKeys = map .toMap() .keySeq() .map(k => (0, IRprinting_1.expressionToString)(k.toQuintEx(idGenerator_1.zerog))) .join(', '); return (0, either_1.left)({ code: 'QNT507', message: `Called 'get' with a non-existing key. Key is ${requestedKey}. Map has keys: ${existingKeys}`, }); }; case 'set': // Set a value for an existing key in a map. return (_, args) => { const map = args[0].toMap(); const key = args[1].normalForm(); if (!map.has(key)) { return (0, either_1.left)({ code: 'QNT507', message: "Called 'set' with a non-existing key" }); } const value = args[2]; return (0, either_1.right)(runtimeValue_1.rv.fromMap(map.set(key, value))); }; case 'put': // Set a value for any key in a map. return (_, args) => { const map = args[0].toMap(); const key = args[1].normalForm(); const value = args[2]; return (0, either_1.right)(runtimeValue_1.rv.fromMap(map.set(key, value))); }; case 'setBy': // Set a value for an existing key in a map using a lambda over the current value. return (ctx, args) => { const map = args[0].toMap(); const key = args[1].normalForm(); if (!map.has(key)) { return (0, either_1.left)({ code: 'QNT507', message: `Called 'setBy' with a non- existing key ${key}` }); } const value = map.get(key); const lam = args[2].toArrow(); return lam(ctx, [value]).map(v => runtimeValue_1.rv.fromMap(map.set(key, v))); }; case 'keys': // A set with the keys of a map. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkSet(args[0].toMap().keys())); case 'exists': // Check if a predicate holds for some element in a set. return (ctx, args) => applyLambdaToSet(ctx, args[1], args[0]).map(values => runtimeValue_1.rv.mkBool(values.some(v => v.toBool()) === true)); case 'forall': // Check if a predicate holds for all elements in a set. return (ctx, args) => applyLambdaToSet(ctx, args[1], args[0]).map(values => runtimeValue_1.rv.mkBool(values.every(v => v.toBool()) === true)); case 'map': // Map a lambda over a set. return (ctx, args) => { return applyLambdaToSet(ctx, args[1], args[0]).map(values => runtimeValue_1.rv.mkSet(values)); }; case 'filter': // Filter a set using a lambda. return (ctx, args) => { const set = args[0].toSet(); const lam = args[1].toArrow(); return filterElementsWithLambda(ctx, set, lam).map(result => runtimeValue_1.rv.mkSet(result)); }; case 'select': // Filter a list using a lambda return (ctx, args) => { const list = args[0].toList(); const lam = args[1].toArrow(); return filterElementsWithLambda(ctx, list, lam).map(result => runtimeValue_1.rv.mkList(result)); }; case 'mapBy': // Construct a map by applying a lambda to the values of a set. return (ctx, args) => { const lambda = args[1].toArrow(); const keys = args[0].toSet(); const results = []; for (const key of keys) { const value = lambda(ctx, [key]); if (value.isLeft()) { return value; } results.push([key.normalForm(), value.value]); } return (0, either_1.right)(runtimeValue_1.rv.fromMap((0, immutable_1.Map)(results))); }; case 'setToMap': // Convert a set of key-value tuples to a map. return (_, args) => { const set = args[0].toSet(); return (0, either_1.right)(runtimeValue_1.rv.mkMap((0, immutable_1.Map)(set.map(s => s.toTuple2())))); }; case 'setOfMaps': // A set of all possible maps with keys and values from the given sets. return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkMapSet(args[0], args[1])); case 'fail': // Expect a value to be false return (_, args) => (0, either_1.right)(runtimeValue_1.rv.mkBool(!args[0].toBool())); case 'assert': // Expect a value to be true, returning a runtime error if it is not return (_, args) => (args[0].toBool() ? (0, either_1.right)(args[0]) : (0, either_1.left)({ code: 'QNT508', message: `Assertion failed` })); case 'allListsUpTo': // Generate all lists of length up to the given number, from a set return (_, args) => { const set = args[0].toSet(); let lists = (0, immutable_1.Set)([[]]); let last_lists = (0, immutable_1.Set)([[]]); (0, lodash_1.times)(Number(args[1].toInt())).forEach(_length => { // Generate all lists of length `length` from the set const new_lists = set.toSet().flatMap(value => { // for each value in the set, append it to all lists of length `length - 1` return last_lists.map(list => list.concat(value)); }); lists = lists.merge(new_lists); last_lists = new_lists; }); return (0, either_1.right)(runtimeValue_1.rv.mkSet(lists.map(list => runtimeValue_1.rv.mkList(list)).toOrderedSet())); }; case 'getOnlyElement': // Get the only element of a set, or an error if the set is empty or has more than one element. return (_, args) => { const set = args[0].toSet(); if (set.size !== 1) { return (0, either_1.left)({ code: 'QNT505', message: `Called 'getOnlyElement' on a set with ${set.size} elements. Make sure the set has exactly one element.`, }); } return (0, either_1.right)(set.first()); }; case 'q::debug': // Print a value to the console, and return it return (_, args) => { let columns = (0, graphics_1.terminalWidth)(); let valuePretty = (0, prettierimp_1.format)(columns, 0, (0, graphics_1.prettyQuintEx)(args[1].toQuintEx(idGenerator_1.zerog))); console.log('>', args[0].toStr(), valuePretty.toString()); return (0, either_1.right)(args[1]); }; // standard unary operators that are not handled by REPL case 'allLists': case 'chooseSome': case 'always': case 'eventually': case 'enabled': return _ => (0, either_1.left)({ code: 'QNT501', message: `Runtime does not support the built -in operator '${op}'` }); // builtin operators that are not handled by REPL case 'orKeep': case 'mustChange': case 'weakFair': case 'strongFair': return _ => (0, either_1.left)({ code: 'QNT501', message: `Runtime does not support the built -in operator '${op}'` }); default: return () => (0, either_1.left)({ code: 'QNT000', message: `Unknown builtin ${op}` }); } } exports.builtinLambda = builtinLambda; /** * Applies a lambda function to each element in a set. * * If the lambda function returns an error for any element, the function * will return that error immediately. * * @param ctx - The context in which to evaluate the lambda function. * @param lambda - The lambda function to apply to each element in the set. * @param set - The set of elements to which the lambda function will be applied. * @returns A set of the results of applying the lambda function to each element in the set, * or an error if the lambda function returns an error for any element. */ function applyLambdaToSet(ctx, lambda, set) { const f = lambda.toArrow(); const elements = set.toSet(); const results = []; // Apply using a for so we exit early if we get a left for (const element of elements) { const result = f(ctx, [element]); if (result.isLeft()) { return (0, either_1.left)(result.value); } results.push(result.value); } return (0, either_1.right)((0, immutable_1.Set)(results)); } /** * Filters elements of an iterable using a lambda function. * * If the lambda function returns an error for any element, the function * will return that error immediately. * * @param ctx - The context in which to evaluate the lambda function. * @param elements - The iterable of elements to be filtered. * @param lam - The lambda function to apply to each element in the iterable. * @returns An array of elements for which the lambda function returns true, * or an error if the lambda function returns an error for any element. */ function filterElementsWithLambda(ctx, elements, lam) { const result = []; for (const element of elements) { const value = lam(ctx, [element]); if (value.isLeft()) { return (0, either_1.left)(value.value); } if (value.value.toBool()) { result.push(element); } } return (0, either_1.right)(result); } /** * Applies a fold (reduce) operation to an iterable using a lambda function. * * If the lambda function returns an error for any pair of elements, the function * will return that error immediately. * * @param order - The order in which to apply the fold ('fwd' for forward, 'rev' for reverse). * @param iterable - The iterable of elements to be folded. * @param initial - The initial value for the fold operation. * @param lambda - The lambda function to apply to each pair of elements. * @returns The accumulated result of applying the lambda function to the elements of the iterable, * or an error if the lambda function returns an error for any pair of elements. */ function applyFold(order, iterable, initial, lambda) { const reducer = (acc, val) => { return acc.chain(accValue => { if (order === 'fwd') { return lambda([accValue, val]); } else { return lambda([val, accValue]); } }); }; const array = Array.from(iterable); if (order === 'fwd') { return array.reduce(reducer, (0, either_1.right)(initial)); } else { return array.reduceRight(reducer, (0, either_1.right)(initial)); } } /** * Accesses an element in a list by its index. * * @param list - The list of elements. * @param idx - The index of the element to access. * @returns The element at the specified index, or an error if the index is out of bounds. */ function getListElem(list, idx) { if (idx >= 0n && idx < list.size) { const elem = list.get(Number(idx)); if (elem) { return (0, either_1.right)(elem); } } return (0, either_1.left)({ code: 'QNT510', message: `Out of bounds, nth(${idx})` }); } //# sourceMappingURL=builtins.js.map