UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

283 lines 16.1 kB
"use strict"; /* * A collection of CLI functions that implement color output and pseudo-graphics. * * Igor Konnov, 2023 * * Copyright 2023 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.printTrace = exports.printExecutionFrameRec = exports.prettyQuintType = exports.prettyTypeScheme = exports.prettyQuintDeclaration = exports.prettyQuintEx = exports.terminalWidth = void 0; const assert_1 = require("assert"); const chalk_1 = __importDefault(require("chalk")); const prettierimp_1 = require("./prettierimp"); const quintIr_1 = require("./ir/quintIr"); const idGenerator_1 = require("./idGenerator"); const quintTypes_1 = require("./ir/quintTypes"); const printing_1 = require("./types/printing"); const IRprinting_1 = require("./ir/IRprinting"); const simplification_1 = require("./types/simplification"); /** * Find out the terminal width for text formatting. * Since this number may change while running, it is a function. * Also, when the output is redirected, the number of columns is undefined. */ const terminalWidth = () => { const cols = process.stdout.columns; return cols !== undefined && cols > 0 ? cols : 80; }; exports.terminalWidth = terminalWidth; // convert a Quint expression to a colored pretty-printed doc, tuned for REPL function prettyQuintEx(ex) { switch (ex.kind) { case 'bool': return (0, prettierimp_1.richtext)(chalk_1.default.yellow, ex.value.toString()); case 'int': return (0, prettierimp_1.richtext)(chalk_1.default.yellow, ex.value.toString()); case 'str': return (0, prettierimp_1.richtext)(chalk_1.default.green, `"${ex.value}"`); case 'app': switch (ex.opcode) { case 'Set': return (0, prettierimp_1.group)([(0, prettierimp_1.richtext)(chalk_1.default.green, 'Set'), nary((0, prettierimp_1.text)('('), ex.args.map(prettyQuintEx), (0, prettierimp_1.text)(')'))]); case 'Map': { const ps = ex.args.map(tup => { if (tup.kind === 'app' && tup.opcode === 'Tup' && tup.args.length === 2) { const [k, v] = tup.args; return (0, prettierimp_1.group)([prettyQuintEx(k), (0, prettierimp_1.richtext)(chalk_1.default.gray, ' ->'), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), prettyQuintEx(v)])]); } else { return (0, prettierimp_1.text)('<expected-pair>'); } }); return (0, prettierimp_1.group)([(0, prettierimp_1.richtext)(chalk_1.default.green, 'Map'), nary((0, prettierimp_1.text)('('), ps, (0, prettierimp_1.text)(')'))]); } case 'Tup': return nary((0, prettierimp_1.text)('('), ex.args.map(prettyQuintEx), (0, prettierimp_1.text)(')')); case 'List': { return nary((0, prettierimp_1.text)('['), ex.args.map(prettyQuintEx), (0, prettierimp_1.text)(']')); } case 'Rec': { const kvs = []; for (let i = 0; i < ex.args.length / 2; i++) { const key = ex.args[2 * i]; if (key && key.kind === 'str') { const value = prettyQuintEx(ex.args[2 * i + 1]); kvs.push((0, prettierimp_1.group)([(0, prettierimp_1.text)(key.value), (0, prettierimp_1.richtext)(chalk_1.default.gray, ':'), (0, prettierimp_1.nest)(' ', [(0, prettierimp_1.line)(), value])])); } } return nary((0, prettierimp_1.text)('{'), kvs, (0, prettierimp_1.text)('}'), (0, prettierimp_1.line)()); } case 'variant': { const labelExpr = ex.args[0]; (0, assert_1.strict)(labelExpr.kind === 'str', 'malformed variant operator'); const label = (0, prettierimp_1.richtext)(chalk_1.default.green, labelExpr.value); const valueExpr = ex.args[1]; const value = valueExpr.kind === 'app' && valueExpr.opcode === 'Tup' && valueExpr.args.length === 0 ? [] // A payload with the empty tuple is shown as a bare label : [(0, prettierimp_1.text)('('), prettyQuintEx(valueExpr), (0, prettierimp_1.text)(')')]; return (0, prettierimp_1.group)([label, ...value]); } default: // instead of throwing, show it in red return (0, prettierimp_1.richtext)(chalk_1.default.red, `unsupported operator: ${ex.opcode}(...)`); } case 'lambda': { const params = (0, prettierimp_1.parens)((0, prettierimp_1.docJoin)([(0, prettierimp_1.text)(','), (0, prettierimp_1.line)()], ex.params.map(p => (0, prettierimp_1.text)(p.name)))); return (0, prettierimp_1.braces)((0, prettierimp_1.group)((0, prettierimp_1.textify)([params, (0, prettierimp_1.line)(), '=>', (0, prettierimp_1.line)(), '...']))); } default: return (0, prettierimp_1.richtext)(chalk_1.default.red, `unsupported operator: ${ex.kind}`); } } exports.prettyQuintEx = prettyQuintEx; function prettyQuintDeclaration(decl, includeBody = true, type) { const typeAnnotation = (0, quintIr_1.isAnnotatedDef)(decl) ? [(0, prettierimp_1.text)(': '), prettyQuintType(decl.typeAnnotation)] : type // If annotation is not present, but type is, use the type ? [(0, prettierimp_1.text)(': '), prettyTypeScheme(type)] : []; switch (decl.kind) { case 'def': { const header = (0, prettierimp_1.group)([ (0, prettierimp_1.richtext)(chalk_1.default.magenta, (0, IRprinting_1.qualifierToString)(decl.qualifier)), (0, prettierimp_1.text)(' '), (0, prettierimp_1.richtext)(chalk_1.default.blue, decl.name), ...typeAnnotation, ]); return includeBody ? (0, prettierimp_1.group)([header, (0, prettierimp_1.text)(' = '), prettyQuintEx(decl.expr)]) : header; } case 'typedef': { const header = (0, prettierimp_1.group)([(0, prettierimp_1.text)('type '), (0, prettierimp_1.richtext)(chalk_1.default.blue, decl.name)]); if (decl.type) { return (0, prettierimp_1.group)([header, (0, prettierimp_1.text)(' = '), prettyQuintType(decl.type)]); } return header; } default: // TODO, not used for now return (0, prettierimp_1.text)((0, IRprinting_1.declarationToString)(decl)); } } exports.prettyQuintDeclaration = prettyQuintDeclaration; function prettyTypeScheme(scheme) { const [vars, type] = (0, printing_1.canonicalTypeScheme)(scheme); const varsDoc = vars.length > 0 ? (0, prettierimp_1.group)([(0, prettierimp_1.text)('∀ '), (0, prettierimp_1.docJoin)([(0, prettierimp_1.text)(','), (0, prettierimp_1.line)()], vars.map(prettierimp_1.text)), (0, prettierimp_1.text)(' . ')]) : (0, prettierimp_1.text)(''); return (0, prettierimp_1.group)([varsDoc, prettyQuintType(type)]); } exports.prettyTypeScheme = prettyTypeScheme; function prettyQuintType(type) { switch (type.kind) { case 'bool': case 'int': case 'str': return (0, prettierimp_1.richtext)(chalk_1.default.yellow, type.kind); case 'const': case 'var': return (0, prettierimp_1.richtext)(chalk_1.default.blue, type.name); case 'set': return (0, prettierimp_1.group)([(0, prettierimp_1.richtext)(chalk_1.default.green, 'Set'), (0, prettierimp_1.text)('['), prettyQuintType(type.elem), (0, prettierimp_1.text)(']')]); case 'list': return (0, prettierimp_1.group)([(0, prettierimp_1.richtext)(chalk_1.default.green, 'List'), (0, prettierimp_1.text)('['), prettyQuintType(type.elem), (0, prettierimp_1.text)(']')]); case 'fun': return (0, prettierimp_1.group)([prettyQuintType(type.arg), (0, prettierimp_1.richtext)(chalk_1.default.gray, ' -> '), prettyQuintType(type.res)]); case 'oper': { const args = type.args.map(prettyQuintType); return (0, prettierimp_1.group)([nary((0, prettierimp_1.text)('('), args, (0, prettierimp_1.text)(')')), (0, prettierimp_1.text)(' => '), prettyQuintType(type.res)]); } case 'tup': return (0, prettierimp_1.group)([(0, prettierimp_1.text)('('), prettyRow(type.fields, false), (0, prettierimp_1.text)(')')]); case 'rec': { if ((0, IRprinting_1.rowToString)(type.fields) === '{}') { return (0, prettierimp_1.text)('{}'); } return (0, prettierimp_1.group)([(0, prettierimp_1.text)('{ '), prettyRow(type.fields), (0, prettierimp_1.text)('}')]); } case 'sum': { return prettySumRow(type.fields); } case 'app': { const args = type.args.map(prettyQuintType); return (0, prettierimp_1.group)([prettyQuintType(type), (0, prettierimp_1.text)('['), ...args, (0, prettierimp_1.text)(']')]); } } } exports.prettyQuintType = prettyQuintType; function prettyRow(r, showFieldName = true) { const row = (0, simplification_1.simplifyRow)(r); const fields = row.kind === 'row' ? row.fields : []; const other = row.kind === 'row' ? row.other : undefined; const fieldsDocs = fields.map(f => { const prefix = showFieldName ? `${f.fieldName}: ` : ''; return (0, prettierimp_1.group)([(0, prettierimp_1.text)(prefix), prettyQuintType(f.fieldType)]); }); const otherDoc = other?.kind === 'var' ? [(0, prettierimp_1.text)(` | ${other.name}`)] : []; return (0, prettierimp_1.group)([(0, prettierimp_1.nest)(' ', [prettierimp_1.linebreak, (0, prettierimp_1.docJoin)([(0, prettierimp_1.text)(','), (0, prettierimp_1.line)()], fieldsDocs)]), ...otherDoc, prettierimp_1.linebreak]); } function prettySumRow(r) { const row = (0, simplification_1.simplifyRow)(r); const fields = row.kind === 'row' ? row.fields : []; const other = row.kind === 'row' ? row.other : undefined; const fieldsDocs = fields.map(f => { if (other?.kind === 'empty') { return (0, prettierimp_1.group)((0, prettierimp_1.text)(f.fieldName)); } else if ((0, quintTypes_1.isUnitType)(f.fieldType)) { // Print the variant `Foo({})` return (0, prettierimp_1.group)([(0, prettierimp_1.text)(`${f.fieldName}`)]); } else { return (0, prettierimp_1.group)([(0, prettierimp_1.text)(`${f.fieldName}(`), prettyQuintType(f.fieldType), (0, prettierimp_1.text)(')')]); } }); return (0, prettierimp_1.group)([(0, prettierimp_1.nest)(' ', [prettierimp_1.linebreak, (0, prettierimp_1.docJoin)([(0, prettierimp_1.text)('|'), (0, prettierimp_1.line)()], fieldsDocs)]), prettierimp_1.linebreak]); } /** * Print an execution frame and its children recursively. * Since this function is printing a tree, we need precise text alignment. * Using a tree here with an optional line break would produce incorrect output. * * @param box the box to print in * @param frame the frame to print * @param isLast the array of booleans, one per ancestor, that indicates whether * an ancestor does not have siblings to the right, the last index * corresponds to the direct parent. */ function printExecutionFrameRec(box, frame, isLast) { // convert the arguments and the result to strings const args = (0, prettierimp_1.docJoin)([(0, prettierimp_1.text)(','), (0, prettierimp_1.line)()], frame.args.map(a => prettyQuintEx(a.toQuintEx(idGenerator_1.zerog)))); const r = frame.result.isLeft() ? (0, prettierimp_1.text)('none') : prettyQuintEx(frame.result.value.toQuintEx(idGenerator_1.zerog)); const depth = isLast.length; // generate the tree ASCII graphics for this frame let treeArt = isLast .map((il, i) => i < depth - 1 ? // continue the ancestor's branch, unless it's the last one il ? ' ' : '│ ' : // close or close & continue the leaf branch, // depending on whether this frame is the last one il ? '└─ ' : '├─ ') .join(''); box.out(treeArt); // format the call with its arguments and place it right after the tree art let argsDoc = frame.args.length === 0 ? (0, prettierimp_1.text)('') : (0, prettierimp_1.group)((0, prettierimp_1.textify)(['(', (0, prettierimp_1.nest)(' ', [prettierimp_1.linebreak, (0, prettierimp_1.group)(args)]), prettierimp_1.linebreak, ')'])); // draw proper branches in the indentation const indentTreeArt = isLast.map(il => (il ? ' ' : '│ ')).join(''); // pretty print the arguments and the result const callDoc = (0, prettierimp_1.group)((0, prettierimp_1.nest)(indentTreeArt, [(0, prettierimp_1.text)(frame.app.opcode), (0, prettierimp_1.group)([argsDoc, (0, prettierimp_1.nest)(' ', (0, prettierimp_1.group)([(0, prettierimp_1.text)(' =>'), (0, prettierimp_1.line)(), r]))])])); box.out((0, prettierimp_1.format)(box.width, treeArt.length, callDoc)); box.out('\n'); const n = frame.subframes.length; // visualize the children frame.subframes.forEach((f, i) => printExecutionFrameRec(box, f, isLast.concat([i === n - 1]))); } exports.printExecutionFrameRec = printExecutionFrameRec; /** * Print a trace with chalk. */ function printTrace(console, states, frames, hideVars = []) { const b = chalk_1.default.bold; states.forEach((state, index) => { (0, assert_1.strict)(state.kind === 'app' && state.opcode === 'Rec' && state.args.length % 2 === 0); if (index < frames.length) { // be lenient to broken input console.out(`[${b('Frame ' + index)}]\n`); printExecutionFrameRec(console, frames[index], []); console.out('\n'); } // Filter out hidden variables from the state display let filteredState = state; if (hideVars.length > 0 && state.kind === 'app' && state.opcode === 'Rec') { // A record is represented as [key1, value1, key2, value2, ...] const filteredArgs = []; for (let i = 0; i < state.args.length; i += 2) { const key = state.args[i]; const value = state.args[i + 1]; // Only include this key-value pair if the key is not in hideVars if (key.kind === 'str' && !hideVars.includes(key.value)) { filteredArgs.push(key); filteredArgs.push(value); } } filteredState = { ...state, args: filteredArgs }; } const stateDoc = [(0, prettierimp_1.group)([(0, prettierimp_1.brackets)((0, prettierimp_1.richtext)(b, `State ${index}`)), (0, prettierimp_1.line)()]), prettyQuintEx(filteredState)]; console.out((0, prettierimp_1.format)(console.width, 0, stateDoc)); console.out('\n\n'); }); } exports.printTrace = printTrace; // a helper function to produce specific indentation function nary(left, args, right, padding = prettierimp_1.linebreak) { const as = (0, prettierimp_1.group)([(0, prettierimp_1.nest)(' ', [padding, (0, prettierimp_1.docJoin)([(0, prettierimp_1.text)(','), (0, prettierimp_1.line)()], args)]), padding]); return (0, prettierimp_1.group)([left, as, right]); } //# sourceMappingURL=graphics.js.map