UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

398 lines 13.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.diffRuntimeValueDoc = void 0; const chalk_1 = __importDefault(require("chalk")); const prettierimp_1 = require("../../prettierimp"); const json_bigint_1 = __importDefault(require("json-bigint")); // Color constants const GRAY = chalk_1.default.gray; const RED = chalk_1.default.red; const GREEN = chalk_1.default.green; // Helper functions for colored rendering const tint = (s, c) => (0, prettierimp_1.richtext)(c, s); const grayRV = (v, config) => prettyRVWith(v, GRAY, config); const greenRV = (v, config) => prettyRVWith(v, GREEN, config); const redRV = (v, config) => prettyRVWith(v, RED, config); /** * Check if a runtime value should be collapsed based on its size */ function shouldCollapseValue(value, config) { if (!config || config.collapseThreshold <= 0) { return false; } // Try different value types to check their size const checkers = [ () => value.toOrderedMap().size, () => value.toMap().size, () => value.toList().size, () => (value.isSetLike ? value.toSet().size : 0), // Sets ]; for (const checker of checkers) { try { const size = checker(); if (size > config.collapseThreshold) { return true; } } catch { // This type check failed, continue to next continue; } } return false; } /** * Check if a runtime value is a primitive (bool, int, string) */ function tryRenderPrimitive(value, color) { const primitiveCheckers = [ () => tint(String(value.toBool()), color), () => tint(value.toInt().toString(), color), () => (0, prettierimp_1.group)([tint('"', color), tint(value.toStr(), color), tint('"', color)]), ]; for (const checker of primitiveCheckers) { try { return checker(); } catch { // This primitive type check failed, continue to next continue; } } return undefined; } /** * Main public function to create a diff between two runtime values */ function diffRuntimeValueDoc(a, b, config) { return diffRuntimeValueDocInternal(a, b, config, false); } exports.diffRuntimeValueDoc = diffRuntimeValueDoc; /** * Internal diff function that tracks nesting context */ function diffRuntimeValueDocInternal(a, b, config, isNested = false) { // Equal values - render in gray, collapse if nested and large if (a.equals(b)) { if (isNested && shouldCollapseValue(a, config)) { return tint('...', GRAY); } return grayRV(a, config); } // Try to render as primitive values const atomOld = tryRenderPrimitive(a, RED); const atomNew = tryRenderPrimitive(b, GREEN); if (atomOld && atomNew) { return (0, prettierimp_1.group)([atomOld, (0, prettierimp_1.text)(' => '), atomNew]); } // Try different composite types return (tryDiffRecords(a, b, config) || tryDiffMaps(a, b, config) || tryDiffLists(a, b, config) || tryDiffVariants(a, b, config) || tryDiffSets(a, b, config) || // Fallback: show as old => new (0, prettierimp_1.group)([prettyRVWith(a, RED, config), (0, prettierimp_1.text)(' => '), prettyRVWith(b, GREEN, config)])); } /** * Try to diff two values as records */ function tryDiffRecords(a, b, config) { try { const ra = a.toOrderedMap(); const rb = b.toOrderedMap(); const keys = new Set([...ra.keySeq().toArray(), ...rb.keySeq().toArray()]); const fields = []; Array.from(keys) .sort() .forEach(k => { const inA = ra.has(k); const inB = rb.has(k); if (inA && inB) { // Field exists in both - diff the values fields.push((0, prettierimp_1.group)([ tint(k, GRAY), tint(':', GRAY), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), diffRuntimeValueDocInternal(ra.get(k), rb.get(k), config, true)]), ])); } else if (inA) { // Field only in A (removed) fields.push((0, prettierimp_1.group)([tint(k, RED), tint(':', RED), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), redRV(ra.get(k), config)])])); } else { // Field only in B (added) fields.push((0, prettierimp_1.group)([tint(k, GREEN), tint(':', GREEN), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), greenRV(rb.get(k), config)])])); } }); return nary(GRAY, tint('{', GRAY), fields, tint('}', GRAY), (0, prettierimp_1.line)()); } catch { return undefined; } } /** * Try to diff two values as maps */ function tryDiffMaps(a, b, config) { try { const ma = a.toMap(); const mb = b.toMap(); const aKeys = ma.keySeq().toArray(); const bKeys = mb.keySeq().toArray(); const consumed = new Set(); const pairs = []; // Match keys from A with keys in B aKeys.forEach(ka => { const idx = matchKeyIdx(ka, bKeys); if (idx >= 0) { consumed.add(idx); pairs.push((0, prettierimp_1.group)([ diffRuntimeValueDocInternal(ka, bKeys[idx], config, true), tint(' -> ', GRAY), diffRuntimeValueDocInternal(ma.get(ka), mb.get(bKeys[idx]), config, true), ])); } else { pairs.push((0, prettierimp_1.group)([redRV(ka, config), tint(' -> ', RED), redRV(ma.get(ka), config)])); } }); // Add unmatched keys from B bKeys.forEach((kb, i) => { if (!consumed.has(i)) { pairs.push((0, prettierimp_1.group)([greenRV(kb, config), tint(' -> ', GREEN), greenRV(mb.get(kb), config)])); } }); return (0, prettierimp_1.group)([tint('Map', GRAY), nary(GRAY, tint('(', GRAY), pairs, tint(')', GRAY))]); } catch { return undefined; } } /** * Try to diff two values as lists/tuples */ function tryDiffLists(a, b, config) { try { const la = a.toList().toArray(); const lb = b.toList().toArray(); if (la.length === lb.length) { // Same length - diff element by element const items = la.map((x, i) => diffRuntimeValueDocInternal(x, lb[i], config, true)); return nary(GRAY, tint('[', GRAY), items, tint(']', GRAY)); } else { // Different length - show as old => new return (0, prettierimp_1.group)([ nary(RED, tint('[', RED), la.map(x => redRV(x, config)), tint(']', RED)), (0, prettierimp_1.text)(' => '), nary(GREEN, tint('[', GREEN), lb.map(x => greenRV(x, config)), tint(']', GREEN)), ]); } } catch { return undefined; } } /** * Try to diff two values as variants */ function tryDiffVariants(a, b, config) { try { const [la, va] = a.toVariant(); const [lb, vb] = b.toVariant(); const payloadIsEmptyTuple = (rv) => { try { return rv.toList().toArray().length === 0; } catch { return false; } }; const renderVariant = (label, payload, color) => { const labelDoc = tint(label, color); return payloadIsEmptyTuple(payload) ? labelDoc : (0, prettierimp_1.group)([labelDoc, (0, prettierimp_1.text)('('), prettyRVWith(payload, color, config), (0, prettierimp_1.text)(')')]); }; if (la === lb) { // Same label - diff the payload const labelDoc = tint(la, GRAY); if (payloadIsEmptyTuple(va) && payloadIsEmptyTuple(vb)) { return labelDoc; } else { return (0, prettierimp_1.group)([labelDoc, (0, prettierimp_1.text)('('), diffRuntimeValueDocInternal(va, vb, config, true), (0, prettierimp_1.text)(')')]); } } else { // Different labels - show old => new const oldDoc = renderVariant(la, va, RED); const newDoc = renderVariant(lb, vb, GREEN); return (0, prettierimp_1.group)([oldDoc, (0, prettierimp_1.text)(' => '), newDoc]); } } catch { return undefined; } } /** * Try to diff two values as sets */ function tryDiffSets(a, b, config) { if (!a.isSetLike && !b.isSetLike) { return undefined; } try { const sa = a.isSetLike ? Array.from(a.toSet()) : []; const sb = b.isSetLike ? Array.from(b.toSet()) : []; const matchedB = new Array(sb.length).fill(false); const items = []; // Find matching elements for (const ea of sa) { let matched = false; for (let i = 0; i < sb.length; i++) { if (!matchedB[i] && ea.equals(sb[i])) { matchedB[i] = true; matched = true; items.push(grayRV(ea, config)); break; } } if (!matched) { items.push(redRV(ea, config)); } } // Add unmatched elements from B for (let i = 0; i < sb.length; i++) { if (!matchedB[i]) { items.push(greenRV(sb[i], config)); } } return (0, prettierimp_1.group)([tint('Set', GRAY), nary(GRAY, tint('(', GRAY), items, tint(')', GRAY))]); } catch { return undefined; } } /** * Helper function for n-ary layouts (records, lists, etc.) * Same indentation behavior as graphics.ts */ function nary(c, left, args, right, padding = prettierimp_1.linebreak) { const as = (0, prettierimp_1.group)([(0, prettierimp_1.nest)(' ', [padding, (0, prettierimp_1.docJoin)([tint(',', c), (0, prettierimp_1.line)()], args)]), padding]); return (0, prettierimp_1.group)([left, as, right]); } /** * A neutral pretty-printer for RuntimeValue that applies a single color. * It deliberately avoids the per-token color scheme in prettyQuintEx, since diff colors * carry the semantics here. */ function prettyRVWith(v, c, config) { // Try primitive types first const primitive = tryRenderPrimitive(v, c); if (primitive) { return primitive; } // Try composite types return (tryPrettyRecord(v, c, config) || tryPrettyMap(v, c, config) || tryPrettyList(v, c, config) || tryPrettyVariant(v, c, config) || tryPrettySet(v, c, config) || // Fallback to JSON tryPrettyFallback(v, c)); } function tryPrettyRecord(v, c, config) { try { const om = v.toOrderedMap(); const kvs = []; om.forEach((val, key) => { kvs.push((0, prettierimp_1.group)([tint(key, c), tint(':', c), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), prettyRVWith(val, c, config)])])); }); return nary(c, tint('{', c), kvs, tint('}', c), (0, prettierimp_1.line)()); } catch { return undefined; } } function tryPrettyMap(v, c, config) { try { const m = v.toMap(); const ps = []; m.forEach((vv, kk) => { ps.push((0, prettierimp_1.group)([prettyRVWith(kk, c, config), tint(' -> ', c), prettyRVWith(vv, c, config)])); }); return (0, prettierimp_1.group)([tint('Map', c), nary(c, tint('(', c), ps, tint(')', c))]); } catch { return undefined; } } function tryPrettyList(v, c, config) { try { const list = v.toList(); const items = list.toArray().map(x => prettyRVWith(x, c, config)); return nary(c, tint('[', c), items, tint(']', c)); } catch { return undefined; } } function tryPrettyVariant(v, c, config) { try { const [label, value] = v.toVariant(); // Check if variant has empty tuple payload let isEmptyTuple = false; try { isEmptyTuple = value.toList().toArray().length === 0; } catch { // Not a tuple or conversion failed } const labelDoc = tint(label, c); return isEmptyTuple ? labelDoc : (0, prettierimp_1.group)([labelDoc, nary(c, tint('(', c), [prettyRVWith(value, c, config)], tint(')', c))]); } catch { return undefined; } } function tryPrettySet(v, c, config) { if (!v.isSetLike) { return undefined; } try { const set = v.toSet(); const elems = Array.from(set).map(x => prettyRVWith(x, c, config)); return (0, prettierimp_1.group)([tint('Set', c), nary(c, tint('(', c), elems, tint(')', c))]); } catch { // Non-enumerable (infinite) set - let it fall through return undefined; } } function tryPrettyFallback(v, c) { try { return tint(json_bigint_1.default.stringify(v), c); } catch { return tint('<value>', c); } } /** * Helper function to find the index of a matching key in an array */ function matchKeyIdx(key, keys) { for (let i = 0; i < keys.length; i++) { if (key.equals(keys[i])) { return i; } } return -1; } //# sourceMappingURL=runtimeValueDiff.js.map