@informalsystems/quint
Version:
Core tool for the Quint specification language
551 lines • 27.3 kB
JavaScript
"use strict";
/**
* The commands for the quint CLI
*
* See the description at:
* https://github.com/informalsystems/quint/blob/main/doc/quint.md
*
* @author Igor Konnov, Gabriela Moreira, Shon Feder, Informal Systems, 2021-2025
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.docs = exports.outputResult = exports.outputCompilationTarget = exports.verifySpec = exports.compile = exports.runSimulator = exports.runTests = exports.runRepl = exports.typecheck = exports.parse = exports.load = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const process_1 = require("process");
const chalk_1 = __importDefault(require("chalk"));
const quintParserFrontend_1 = require("./parsing/quintParserFrontend");
const either_1 = require("@sweet-monads/either");
const repl_1 = require("./repl");
const errorReporter_1 = require("./errorReporter");
const docs_1 = require("./docs");
const idGenerator_1 = require("./idGenerator");
const simulation_1 = require("./simulation");
const verbosity_1 = require("./verbosity");
const sourceResolver_1 = require("./parsing/sourceResolver");
const quintVerifier_1 = require("./quintVerifier");
const fullFlattener_1 = require("./flattening/fullFlattener");
const quintAnalyzer_1 = require("./quintAnalyzer");
const trace_1 = require("./runtime/trace");
const lodash_1 = require("lodash");
const maybe_1 = require("@sweet-monads/maybe");
const compileToTlaplus_1 = require("./compileToTlaplus");
const evaluator_1 = require("./runtime/impl/evaluator");
const initToPredicate_1 = require("./ir/initToPredicate");
const quintRustWrapper_1 = require("./quintRustWrapper");
const cliReporting_1 = require("./cliReporting");
const cliHelpers_1 = require("./cliHelpers");
const assert_1 = require("assert");
const rng_1 = require("./rng");
const apalache_1 = require("./apalache");
/** Load a file into a string
*
* @param args the CLI arguments parsed by yargs */
async function load(args) {
const stage = { stage: 'loading', args };
if ((0, fs_1.existsSync)(args.input)) {
try {
const path = (0, path_1.resolve)((0, process_1.cwd)(), args.input);
const sourceCode = (0, fs_1.readFileSync)(path, 'utf8');
return (0, either_1.right)({
...stage,
args,
path,
sourceCode: new Map([[path, sourceCode]]),
warnings: [],
});
}
catch (err) {
return (0, cliReporting_1.cliErr)(`file ${args.input} could not be opened due to ${err}`, {
...stage,
errors: [],
sourceCode: new Map(),
});
}
}
else {
return (0, cliReporting_1.cliErr)(`file ${args.input} does not exist`, { ...stage, errors: [], sourceCode: new Map() });
}
}
exports.load = load;
/**
* Parse a Quint specification.
*
* @param loaded the procedure stage produced by `load`
*/
async function parse(loaded) {
const { args, sourceCode, path } = loaded;
const text = sourceCode.get(path);
const parsing = { ...loaded, stage: 'parsing' };
const idGen = (0, idGenerator_1.newIdGenerator)();
return (0, lodash_1.flow)([
() => {
const phase1Data = (0, quintParserFrontend_1.parsePhase1fromText)(idGen, text, path);
// if there is exactly one module in the original text, make it the main one
const defaultModuleName = phase1Data.modules.length === 1 ? (0, maybe_1.just)(phase1Data.modules[0].name) : (0, maybe_1.none)();
return { ...phase1Data, defaultModuleName };
},
phase1Data => {
const resolver = (0, sourceResolver_1.fileSourceResolver)(sourceCode);
const mainPath = resolver.lookupPath((0, path_1.dirname)(path), (0, path_1.basename)(path));
return (0, quintParserFrontend_1.parsePhase2sourceResolution)(idGen, resolver, mainPath, phase1Data);
},
phase2Data => {
if (args.sourceMap) {
// Write source map to the specified file
(0, cliReporting_1.writeToJson)(args.sourceMap, (0, quintParserFrontend_1.compactSourceMap)(phase2Data.sourceMap));
}
return (0, quintParserFrontend_1.parsePhase3importAndNameResolution)(phase2Data);
},
phase3Data => (0, quintParserFrontend_1.parsePhase4toposort)(phase3Data),
phase4Data => ({ ...parsing, ...phase4Data, idGen }),
result => {
if (result.errors.length > 0) {
const newErrorMessages = result.errors.map((0, cliHelpers_1.mkErrorMessage)(result.sourceMap));
const errorMessages = parsing.errors ? parsing.errors.concat(newErrorMessages) : newErrorMessages;
return (0, either_1.left)({ msg: 'parsing failed', stage: { ...result, errors: errorMessages } });
}
return (0, either_1.right)(result);
},
])();
}
exports.parse = parse;
/**
* Check types and effects of a Quint specification.
*
* @param parsed the procedure stage produced by `parse`
*/
async function typecheck(parsed) {
const { table, modules, sourceMap } = parsed;
const [errorMap, result] = (0, quintAnalyzer_1.analyzeModules)(table, modules);
const typechecking = { ...parsed, ...result, stage: 'typechecking' };
if (errorMap.length === 0) {
return (0, either_1.right)(typechecking);
}
else {
const errors = errorMap.map((0, cliHelpers_1.mkErrorMessage)(sourceMap));
return (0, cliReporting_1.cliErr)('typechecking failed', { ...typechecking, errors });
}
}
exports.typecheck = typecheck;
/**
* Run REPL.
*
* @param argv parameters as provided by yargs
*/
async function runRepl(argv) {
let filename = undefined;
let moduleName = undefined;
if (argv.require) {
// quint -r FILE.qnt or quint -r FILE.qnt::MODULE
const m = /^(.*?)(?:|::([a-zA-Z_]\w*))$/.exec(argv.require);
if (m) {
;
[filename, moduleName] = m.slice(1, 3);
}
}
const options = {
preloadFilename: filename,
importModule: moduleName,
replInput: argv.commands,
verbosity: argv.quiet ? 0 : argv.verbosity,
};
(0, repl_1.quintRepl)(process.stdin, process.stdout, options);
}
exports.runRepl = runRepl;
/**
* Run the tests. We imitate the output of mocha.
*
* @param typedStage the procedure stage produced by `typecheck`
*/
/**
* Main function to run tests.
*/
async function runTests(prev) {
const testing = { ...prev, stage: 'testing' };
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(prev.args);
const mainName = (0, cliHelpers_1.guessMainModule)(prev);
const main = (0, cliReporting_1.findMainModule)(prev, mainName);
if (!main) {
return (0, cliReporting_1.handleMainModuleError)(prev, mainName);
}
const options = {
testMatch: (n) => (0, cliHelpers_1.isMatchingTest)(prev.args.match, n),
maxSamples: prev.args.maxSamples,
rng: (0, rng_1.newRng)(prev.args.seed),
verbosity: verbosityLevel,
onTrace: (0, cliReporting_1.prepareOnTrace)(prev.args.input, prev.args.outItf, prev.args.nTraces, false),
};
const startMs = Date.now();
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
console.log(`\n ${mainName}`);
}
const testDefs = Array.from(prev.resolver.collector.definitionsByModule.get(mainName).values())
.flat()
.filter(d => d.kind === 'def' && options.testMatch(d.name));
const evaluator = new evaluator_1.Evaluator(prev.table, (0, trace_1.newTraceRecorder)(verbosityLevel, options.rng, 1), options.rng);
const results = testDefs.map((def, index) => evaluator.test(def, options.maxSamples, index, options.onTrace));
const elapsedMs = Date.now() - startMs;
(0, cliReporting_1.outputTestResults)(results, verbosityLevel, elapsedMs);
const passed = results.filter(r => r.status === 'passed');
const failed = results.filter(r => r.status === 'failed');
const ignored = results.filter(r => r.status === 'ignored');
const stage = {
...testing,
passed: passed.map(r => r.name),
failed: failed.map(r => r.name),
ignored: ignored.map(r => r.name),
errors: [],
};
if (failed.length === 0) {
return (0, either_1.right)(stage);
}
(0, cliReporting_1.outputTestErrors)(stage, verbosityLevel, failed);
return (0, cliReporting_1.cliErr)('Tests failed', stage);
}
exports.runTests = runTests;
/**
* Run the simulator.
*
* @param prev the procedure stage produced by `typecheck`
*/
async function runSimulator(prev) {
const simulator = { ...prev, stage: 'running' };
const startMs = Date.now();
// Verboity level controls how much of the output is shown
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(prev.args);
const mainName = (0, cliHelpers_1.guessMainModule)(prev);
const main = prev.modules.find(m => m.name === mainName);
if (!main) {
return (0, cliReporting_1.handleMainModuleError)(prev, mainName);
}
const rng = (0, rng_1.newRng)(prev.args.seed);
// We use:
// - 'invariantString' as the combined invariant string for the simulator to check
// - 'individualInvariants' for reporting which specific invariants were violated
const [invariantString, invariantsList] = (0, cliHelpers_1.getInvariants)(prev.args);
const individualInvariants = invariantsList.length > 0 ? invariantsList : ['true'];
const options = {
init: prev.args.init,
step: prev.args.step,
invariant: invariantString,
individualInvariants: individualInvariants,
maxSamples: prev.args.maxSamples,
maxSteps: prev.args.maxSteps,
rng,
verbosity: verbosityLevel,
storeMetadata: prev.args.mbt,
hideVars: prev.args.hide || [],
numberOfTraces: prev.args.nTraces,
onTrace: (0, cliReporting_1.prepareOnTrace)(prev.args.input, prev.args.outItf, prev.args.nTraces, prev.args.mbt),
};
const recorder = (0, trace_1.newTraceRecorder)(options.verbosity, options.rng, options.numberOfTraces);
const argsParsingResult = (0, either_1.mergeInMany)([prev.args.init, prev.args.step, invariantString, ...prev.args.witnesses].map(e => (0, cliHelpers_1.toExpr)(prev, e)));
if (argsParsingResult.isLeft()) {
return (0, cliReporting_1.cliErr)('Argument error', {
...simulator,
errors: argsParsingResult.value.map((0, cliHelpers_1.mkErrorMessage)(new Map())),
});
}
const [init, step, invariant, ...witnesses] = argsParsingResult.value;
let outcome;
if (prev.args.backend == 'rust') {
if (prev.args.mbt || prev.args.witnesses.length > 0) {
console.warn(chalk_1.default.yellow('Warning: --mbt and --witnesses are ignored when using the Rust backend (at this time).'));
console.warn(chalk_1.default.yellow('Use the typescript backend if you need that functionality.'));
}
// Parse the combined invariant for the Rust backend
const invariantExpr = (0, cliHelpers_1.toExpr)(prev, invariantString);
if (invariantExpr.isLeft()) {
return (0, cliReporting_1.cliErr)('Argument error', {
...simulator,
errors: [(0, cliHelpers_1.mkErrorMessage)(prev.sourceMap)(invariantExpr.value)],
});
}
const quintRustWrapper = new quintRustWrapper_1.QuintRustWrapper(verbosityLevel);
const nThreads = Math.min(prev.args.maxSamples, prev.args.nThreads);
outcome = await quintRustWrapper.simulate({ modules: [], table: prev.resolver.table, main: mainName, init, step, invariant: invariantExpr.value }, prev.path, witnesses, prev.args.maxSamples, prev.args.maxSteps, prev.args.nTraces ?? 1, nThreads, prev.args.seed, options.onTrace);
}
else {
// Use the typescript simulator
const evaluator = new evaluator_1.Evaluator(prev.resolver.table, recorder, options.rng, options.storeMetadata);
outcome = evaluator.simulate(init, step, invariant, witnesses, prev.args.maxSamples, prev.args.maxSteps, prev.args.nTraces ?? 1, options.onTrace);
}
const elapsedMs = Date.now() - startMs;
simulator.seed = outcome.bestTraces[0]?.seed;
const states = outcome.bestTraces[0]?.states;
const frames = recorder.bestTraces[0]?.frame?.subframes;
switch (outcome.status) {
case 'error':
return (0, cliReporting_1.cliErr)('Runtime error', {
...simulator,
status: outcome.status,
seed: prev.args.seed,
errors: outcome.errors.map((0, cliHelpers_1.mkErrorMessage)(prev.sourceMap)),
});
case 'ok':
(0, cliReporting_1.maybePrintCounterExample)(verbosityLevel, states, frames, prev.args.hide || []);
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
console.log(chalk_1.default.green('[ok]') +
' No violation found ' +
chalk_1.default.gray(`(${elapsedMs}ms at ${Math.round((1000 * outcome.samples) / elapsedMs)} traces/second).`));
if (verbosity_1.verbosity.hasHints(verbosityLevel)) {
console.log(chalk_1.default.gray((0, simulation_1.showTraceStatistics)(outcome.traceStatistics)));
console.log(chalk_1.default.gray('You may increase --max-samples and --max-steps.'));
console.log(chalk_1.default.gray('Use --verbosity to produce more (or less) output.'));
}
}
(0, cliReporting_1.maybePrintWitnesses)(verbosityLevel, outcome, prev.args.witnesses);
return (0, either_1.right)({
...simulator,
status: outcome.status,
trace: states,
});
case 'violation':
(0, cliReporting_1.maybePrintCounterExample)(verbosityLevel, states, frames, prev.args.hide || []);
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
console.log(chalk_1.default.red(`[violation]`) +
' Found an issue ' +
chalk_1.default.gray(`(${elapsedMs}ms at ${Math.round((1000 * outcome.samples) / elapsedMs)} traces/second).`));
(0, cliReporting_1.printViolatedInvariants)(states[states.length - 1], individualInvariants, prev);
}
if (verbosity_1.verbosity.hasHints(verbosityLevel)) {
console.log(chalk_1.default.gray('Use --verbosity=3 to show executions.'));
}
(0, cliReporting_1.maybePrintWitnesses)(verbosityLevel, outcome, prev.args.witnesses);
return (0, cliReporting_1.cliErr)('Invariant violated', {
...simulator,
status: outcome.status,
trace: states,
errors: [],
});
}
}
exports.runSimulator = runSimulator;
/** Compile to a flattened module, that includes the special q::* declarations
*
* @param typechecked the output of a preceding type checking stage
*/
async function compile(typechecked) {
const args = typechecked.args;
const mainName = (0, cliHelpers_1.guessMainModule)(typechecked);
const main = typechecked.modules.find(m => m.name === mainName);
if (!main) {
return (0, cliReporting_1.cliErr)(`module ${mainName} does not exist`, { ...typechecked, errors: [], sourceCode: new Map() });
}
const extraDefsAsText = [`action q::init = ${args.init}`, `action q::step = ${args.step}`];
const [invariantString, invariantsList] = (0, cliHelpers_1.getInvariants)(typechecked.args);
if (invariantsList.length > 0) {
extraDefsAsText.push(`val q::inv = and(${invariantString})`);
}
if (args.inductiveInvariant) {
extraDefsAsText.push(`val q::inductiveInv = ${args.inductiveInvariant}`);
}
if (args.temporal) {
extraDefsAsText.push(`temporal q::temporalProps = and(${args.temporal})`);
}
const extraDefs = extraDefsAsText.map(d => (0, quintParserFrontend_1.parseDefOrThrow)(d, typechecked.idGen, new Map()));
main.declarations.push(...extraDefs);
// We have to update the lookup table and analysis result with the new definitions. This is not ideal, and the problem
// is that is hard to add this definitions in the proper stage, in our current setup. We should try to tackle this
// while solving #1052.
const resolutionResult = (0, quintParserFrontend_1.parsePhase3importAndNameResolution)({ ...typechecked, errors: [] });
if (resolutionResult.errors.length > 0) {
const errors = resolutionResult.errors.map((0, cliHelpers_1.mkErrorMessage)(typechecked.sourceMap));
return (0, cliReporting_1.cliErr)('name resolution failed', { ...typechecked, errors });
}
typechecked.table = resolutionResult.table;
(0, quintAnalyzer_1.analyzeInc)(typechecked, typechecked.table, extraDefs);
// CANNOT be `if (!args.flatten)`, we need to make sure it's a boolean value
if (args.flatten === false) {
if (args.target === 'tlaplus') {
console.warn(chalk_1.default.yellow('Warning: flattening is required for TLA+ output, ignoring --flatten=false option.'));
}
else {
// Early return with the original (unflattened) module and its fields
return (0, either_1.right)({
...typechecked,
mainModule: main,
main: mainName,
stage: 'compiling',
});
}
}
// Flatten modules, replacing instances, imports and exports with their definitions
const { flattenedModules, flattenedTable, flattenedAnalysis } = (0, fullFlattener_1.flattenModules)(typechecked.modules, typechecked.table, typechecked.idGen, typechecked.sourceMap, typechecked);
// Pick the main module
const flatMain = flattenedModules.find(m => m.name === mainName);
return (0, either_1.right)({
...typechecked,
...flattenedAnalysis,
mainModule: flatMain,
table: flattenedTable,
main: mainName,
stage: 'compiling',
});
}
exports.compile = compile;
/**
* Verify a spec via Apalache.
*
* @param prev the procedure stage produced by `typecheck`
*/
async function verifySpec(prev) {
const verifying = { ...prev, stage: 'verifying' };
const args = verifying.args;
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(prev.args);
const itfFile = prev.args.outItf;
if (itfFile) {
if (itfFile.includes(cliHelpers_1.PLACEHOLDERS.test) || itfFile.includes(cliHelpers_1.PLACEHOLDERS.seq)) {
console.log(`${chalk_1.default.yellow('[warning]')} the output file contains ${chalk_1.default.grey(cliHelpers_1.PLACEHOLDERS.test)} or ${chalk_1.default.grey(cliHelpers_1.PLACEHOLDERS.seq)}, but this has no effect since at most a single trace will be produced.`);
}
}
const loadedConfig = (0, cliHelpers_1.loadApalacheConfig)(verifying, args.apalacheConfig);
const veryfiyingFlat = { ...prev, modules: [prev.mainModule] };
const parsedSpec = (0, cliReporting_1.outputJson)(veryfiyingFlat);
const [invariantsString, invariantsList] = (0, cliHelpers_1.getInvariants)(prev.args);
if (args.inductiveInvariant) {
const hasOrdinaryInvariant = invariantsList.length > 0;
const nPhases = hasOrdinaryInvariant ? 3 : 2;
const initConfig = (0, apalache_1.createConfig)(loadedConfig, parsedSpec, { ...args, maxSteps: 0 }, ['q::inductiveInv']);
// Checking whether the inductive invariant holds in the initial state(s)
(0, cliReporting_1.printInductiveInvariantProgress)(verbosityLevel, args, 1, nPhases);
const startMs = Date.now();
return (0, quintVerifier_1.verify)(args.serverEndpoint, args.apalacheVersion, initConfig, verbosityLevel).then(res => {
if (res.isLeft()) {
return (0, cliReporting_1.processVerifyResult)(res, startMs, verbosityLevel, verifying, [args.inductiveInvariant]);
}
// Checking whether the inductive invariant is preserved by the step
(0, cliReporting_1.printInductiveInvariantProgress)(verbosityLevel, args, 2, nPhases);
const stepConfig = (0, apalache_1.createConfig)(loadedConfig, parsedSpec, { ...args, maxSteps: 1 }, ['q::inductiveInv'], 'q::inductiveInv');
return (0, quintVerifier_1.verify)(args.serverEndpoint, args.apalacheVersion, stepConfig, verbosityLevel).then(res => {
if (res.isLeft() || !hasOrdinaryInvariant) {
return (0, cliReporting_1.processVerifyResult)(res, startMs, verbosityLevel, verifying, [args.inductiveInvariant]);
}
// Checking whether the inductive invariant implies the ordinary invariant
(0, cliReporting_1.printInductiveInvariantProgress)(verbosityLevel, args, 3, nPhases, invariantsString);
const propConfig = (0, apalache_1.createConfig)(loadedConfig, parsedSpec, { ...args, maxSteps: 0 }, ['q::inv'], 'q::inductiveInv');
return (0, quintVerifier_1.verify)(args.serverEndpoint, args.apalacheVersion, propConfig, verbosityLevel).then(res => {
return (0, cliReporting_1.processVerifyResult)(res, startMs, verbosityLevel, verifying, invariantsList);
});
});
});
}
// We need to insert the data form CLI args into their appropriate locations
// in the Apalache config
const config = (0, apalache_1.createConfig)(loadedConfig, parsedSpec, args, invariantsList.length > 0 ? ['q::inv'] : []);
const startMs = Date.now();
return (0, quintVerifier_1.verify)(args.serverEndpoint, args.apalacheVersion, config, verbosityLevel).then(res => {
return (0, cliReporting_1.processVerifyResult)(res, startMs, verbosityLevel, verifying, invariantsList);
});
}
exports.verifySpec = verifySpec;
/** output a compiled spec in the format specified in the `compiled.args.target` to stdout
*
* @param compiled The result of a preceding compile stage
*/
async function outputCompilationTarget(compiled) {
const stage = 'outputting target';
const args = compiled.args;
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(args);
const target = compiled.args.target.toLowerCase();
const removeRuns = (module) => {
return { ...module, declarations: module.declarations.filter(d => d.kind !== 'def' || d.qualifier !== 'run') };
};
const main = target == 'tlaplus'
? (0, initToPredicate_1.convertInit)(removeRuns(compiled.mainModule), compiled.table, compiled.modes)
: (0, either_1.right)(compiled.mainModule);
if (main.isLeft()) {
return (0, cliReporting_1.cliErr)('Failed to convert init to predicate', {
...compiled,
errors: main.value.map((0, cliHelpers_1.mkErrorMessage)(compiled.sourceMap)),
});
}
const parsedSpecJson = (0, cliReporting_1.outputJson)({ ...compiled, modules: [main.value], table: compiled.table });
switch (target) {
case 'json':
process.stdout.write(parsedSpecJson);
return (0, either_1.right)(compiled);
case 'tlaplus': {
const toTlaResult = await (0, compileToTlaplus_1.compileToTlaplus)(args.serverEndpoint, args.apalacheVersion, parsedSpecJson, verbosityLevel);
return toTlaResult
.mapRight(tla => {
process.stdout.write(tla); // Write out, since all went right
return compiled;
})
.mapLeft(err => {
return {
msg: err.explanation,
stage: { ...compiled, stage, status: 'error', errors: err.errors },
};
});
}
default:
// This is validated in the arg parsing
(0, assert_1.fail)(`Invalid option for --target`);
}
}
exports.outputCompilationTarget = outputCompilationTarget;
/** Write the OutputStage of the procedureStage as JSON, if --out is set
* Otherwise, report any stage errors to STDOUT
*/
function outputResult(result) {
result
.map(stage => {
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(stage.args);
if (stage.args.out) {
(0, cliReporting_1.writeOutputToJson)(stage.args.out, stage);
}
else if (!stage.args.outItf && stage.seed && verbosity_1.verbosity.hasResults(verbosityLevel)) {
const backend = stage.args.backend ?? 'typescript';
console.log(chalk_1.default.gray(`Use --seed=0x${stage.seed.toString(16)} --backend=${backend} to reproduce.`));
}
process.exit(0);
})
.mapLeft(({ msg, stage }) => {
const { args, errors, sourceCode } = stage;
const verbosityLevel = (0, cliHelpers_1.deriveVerbosity)(args);
if (args.out) {
(0, cliReporting_1.writeOutputToJson)(args.out, stage);
}
else {
const finders = (0, errorReporter_1.createFinders)(sourceCode);
(0, lodash_1.uniqWith)(errors, lodash_1.isEqual).forEach(err => console.error((0, errorReporter_1.formatError)(sourceCode, finders, err)));
if (!stage.args.outItf && stage.seed && verbosity_1.verbosity.hasResults(verbosityLevel)) {
const backend = stage.args.backend ?? 'typescript';
console.log(chalk_1.default.gray(`Use --seed=0x${stage.seed.toString(16)} --backend=${backend} to reproduce.`));
}
console.error(`error: ${msg}`);
}
process.exit(1);
});
}
exports.outputResult = outputResult;
/**
* Produces documentation from docstrings in a Quint specification.
*
* @param loaded the procedure stage produced by `load`
*/
async function docs(loaded) {
const { sourceCode, path } = loaded;
const text = sourceCode.get(path);
const parsing = { ...loaded, stage: 'documentation' };
const phase1Data = (0, quintParserFrontend_1.parsePhase1fromText)((0, idGenerator_1.newIdGenerator)(), text, path);
const allEntries = phase1Data.modules.map(module => {
const documentationEntries = (0, docs_1.produceDocs)(module);
const title = `# Documentation for ${module.name}\n\n`;
const markdown = title + [...documentationEntries.values()].map(docs_1.toMarkdown).join('\n\n');
console.log(markdown);
return [module.name, documentationEntries];
});
if (phase1Data.errors.length > 0) {
const newErrorMessages = phase1Data.errors.map((0, cliHelpers_1.mkErrorMessage)(phase1Data.sourceMap));
const errorMessages = parsing.errors ? parsing.errors.concat(newErrorMessages) : newErrorMessages;
return (0, either_1.left)({ msg: 'parsing failed', stage: { ...parsing, errors: errorMessages } });
}
return (0, either_1.right)({ ...parsing, documentation: new Map(allEntries) });
}
exports.docs = docs;
//# sourceMappingURL=cliCommands.js.map