UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

214 lines 9.27 kB
"use strict"; /* * Support for the Informal Trace Format (ITF): * https://apalache-mc.org/docs/adr/015adr-trace.html * * Igor Konnov, Shon Feder, Informal Systems, 2023 * * Copyright 2021 Informal Systems * Licensed under the Apache License, Version 2.0. * See LICENSE in the project root for license information. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ofItfNormalized = exports.ofItf = exports.toItf = exports.NONDET_PICKS = exports.ACTION_TAKEN = void 0; const either_1 = require("@sweet-monads/either"); const lodash_1 = require("lodash"); const runtimeValue_1 = __importDefault(require("./runtime/impl/runtimeValue")); exports.ACTION_TAKEN = 'mbt::actionTaken'; exports.NONDET_PICKS = 'mbt::nondetPicks'; // Type predicates to help with type narrowing function isBigint(v) { return v['#bigint'] !== undefined; } function isTup(v) { return v['#tup'] !== undefined; } function isSet(v) { return v['#set'] !== undefined; } function isMap(v) { return v['#map'] !== undefined; } function isVariant(v) { return v['tag'] !== undefined; } function isUnserializable(v) { return v['#unserializable'] !== undefined; } /** * Convert a list of Quint expressions into an object that matches the JSON * representation of the ITF trace. This function does not add metadata * to the trace. This should be done by the caller. * * @param vars variable names * @param states an array of expressions that represent the states * @returns an object that represent the trace in the ITF format */ function toItf(vars, states, mbtMetadata = false) { const exprToItf = (ex) => { switch (ex.kind) { case 'int': // convert to a special structure, when saving to JSON return (0, either_1.right)({ '#bigint': `${ex.value}` }); case 'str': case 'bool': return (0, either_1.right)(ex.value); case 'app': switch (ex.opcode) { case 'List': return (0, either_1.merge)(ex.args.map(exprToItf)); case 'Set': return (0, either_1.merge)(ex.args.map(exprToItf)).mapRight(es => { return { '#set': es }; }); case 'Tup': return (0, either_1.merge)(ex.args.map(exprToItf)).mapRight(es => { return { '#tup': es }; }); case 'Rec': { if (ex.args.length % 2 !== 0) { return (0, either_1.left)('record: expected an even number of arguments, found:' + ex.args.length); } return (0, either_1.merge)(ex.args.map(exprToItf)).mapRight(kvs => { let obj = {}; (0, lodash_1.chunk)(kvs, 2).forEach(([k, v]) => { if (typeof k === 'string') { obj[k] = v; } else { (0, either_1.left)(`Invalid record field: ${ex}`); } }); return obj; }); } case 'Map': return (0, either_1.merge)( // Convert all the entries of the map ex.args.map(exprToItf)).chain(pairs => (0, either_1.merge)( // Quint represents map entries as tuples, but in ITF they are 2 element arrays, // so we unpack all the ITF tuples into arrays pairs.map(p => (isTup(p) ? (0, either_1.right)(p['#tup']) : (0, either_1.left)(`Invalid value in quint Map ${p}`)))).map(entries => // Finally, we can form the ITF representation of a map ({ '#map': entries, }))); case 'variant': return (0, either_1.merge)(ex.args.map(exprToItf)).map(([label, value]) => ({ tag: label, value: value })); default: return (0, either_1.left)(`Unexpected operator type: ${ex.opcode}`); } default: return (0, either_1.left)(`Unexpected expression kind: ${ex.kind}`); } }; return (0, either_1.merge)(states.map((e, i) => exprToItf(e).chain(obj => typeof obj === 'object' ? (0, either_1.right)({ '#meta': { index: i }, ...obj }) : (0, either_1.left)(`Expected a valid ITF state, but found ${obj}`)))).mapRight(s => { if (mbtMetadata) { vars = [...vars, exports.ACTION_TAKEN, exports.NONDET_PICKS]; } return { vars: vars, states: s, }; }); } exports.toItf = toItf; function ofItf(itf) { // Benign state to synthesize ids for the Quint expressions let nextId = 0n; // Produce the next ID in sequence const getId = () => { const id = nextId; nextId = nextId + 1n; return id; }; const ofItfValue = (value) => { const id = getId(); if (typeof value === 'boolean') { return { id, kind: 'bool', value }; } else if (typeof value === 'string') { return { id, kind: 'str', value }; } else if (isBigint(value)) { // this is the standard way of encoding an integer in ITF. return { id, kind: 'int', value: BigInt(value['#bigint']) }; } else if (typeof value === 'number') { // We never encode an integer as a JS number, // but we consume it for backwards compatibility with older ITF traces. // See: https://apalache-mc.org/docs/adr/015adr-trace.html return { id, kind: 'int', value: BigInt(value) }; } else if (Array.isArray(value)) { return { id, kind: 'app', opcode: 'List', args: value.map(ofItfValue) }; } else if (isTup(value)) { return { id, kind: 'app', opcode: 'Tup', args: value['#tup'].map(ofItfValue) }; } else if (isSet(value)) { return { id, kind: 'app', opcode: 'Set', args: value['#set'].map(ofItfValue) }; } else if (isUnserializable(value)) { return { id, kind: 'name', name: value['#unserializable'] }; } else if (isMap(value)) { const args = value['#map'].map(([key, value]) => { const k = ofItfValue(key); const v = ofItfValue(value); return { id: getId(), kind: 'app', opcode: 'Tup', args: [k, v] }; }); return { id, kind: 'app', opcode: 'Map', args, }; } else if (isVariant(value)) { const l = ofItfValue(value.tag); if (l.kind === 'str' && l.value === 'UNIT') { // Apalache converts empty tuples to its unit value, { tag: "UNIT" }. // We need to convert it back to Quint's unit value, the empty tuple. return { id, kind: 'app', opcode: 'Tup', args: [] }; } const v = ofItfValue(value.value); return { id, kind: 'app', opcode: 'variant', args: [l, v] }; } else if (typeof value === 'object') { // Any other object must represent a record // For each key/value pair in the object, form the quint expressions representing // the record field and value const args = Object.keys(value) .filter(key => key !== '#meta' && !key.startsWith('__')) // Must be removed from top-level objects representing states .map(f => [{ id: getId(), kind: 'str', value: f }, ofItfValue(value[f])]) .flat(); // flatten the converted pairs of fields into a single array return { id, kind: 'app', opcode: 'Rec', args }; } else { // This should be impossible, but TypeScript can't tell we've handled all cases throw new Error(`internal error: unhandled ITF value ${value}`); } }; return itf.states.map(ofItfValue); } exports.ofItf = ofItf; /** * Normalizes the given ItfTrace by converting its state to runtime values and * back to quint expressions. Note that this round trip results in normalized * expression where, for example, maps and sets are sorted alphabetically. * Usefull for pretty printing traces produced by Apalache. * * @param itf - The ItfTrace to normalize * @returns An array of its states as normalized quint expressions */ function ofItfNormalized(itf) { return ofItf(itf).map(runtimeValue_1.default.fromQuintEx).map(runtimeValue_1.default.toQuintEx); } exports.ofItfNormalized = ofItfNormalized; //# sourceMappingURL=itf.js.map