UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

422 lines 15.5 kB
#!/usr/bin/env node "use strict"; /** * Command-line interface for quint. * * See the description at: * https://github.com/informalsystems/quint/blob/main/doc/quint.md * * @author Igor Konnov, Gabriela Moreira, Shon Feder, Informal Systems, 2021-2023 * @author Igor Konnov, konnov.phd, 2024 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const os_1 = __importDefault(require("os")); const yargs_1 = __importDefault(require("yargs/yargs")); const cliCommands_1 = require("./cliCommands"); const verbosity_1 = require("./verbosity"); const version_1 = require("./version"); const apalache_1 = require("./apalache"); const defaultOpts = (yargs) => yargs.option('out', { desc: 'output file (suppresses all console output)', type: 'string', }); // Arguments used by routines that pass thru the `compile` stage const compileOpts = (yargs) => defaultOpts(yargs) .option('main', { desc: 'name of the main module (by default, computed from filename)', type: 'string', }) .option('init', { desc: 'name of the initializer action', type: 'string', default: 'init', }) .option('step', { desc: 'name of the step action', type: 'string', default: 'step', }) .option('invariant', { desc: 'the invariants to check, separated by commas', type: 'string', }) .option('temporal', { desc: 'the temporal properties to check, separated by commas', type: 'string', }); // Chain async CLIProcedures // // This saves us having to manually thread the result argument like // // `prevCmd.then(prevResult => prevResult.asyncChain(nextCmd))` // // Instead writing: // // `prevCmd.then(chainCmd(nextCmd))` function chainCmd(nextCmd) { return (prevResult) => prevResult.asyncChain(nextCmd); } // construct parsing commands with yargs const parseCmd = { command: 'parse <input>', desc: 'parse a Quint specification', builder: (yargs) => defaultOpts(yargs).option('source-map', { desc: 'name of the source map', type: 'string', }), handler: (args) => (0, cliCommands_1.load)(args).then(chainCmd(cliCommands_1.parse)).then(cliCommands_1.outputResult), }; // construct typecheck commands with yargs const typecheckCmd = { command: 'typecheck <input>', desc: 'check types and effects of a Quint specification', builder: defaultOpts, handler: (args) => (0, cliCommands_1.load)(args).then(chainCmd(cliCommands_1.parse)).then(chainCmd(cliCommands_1.typecheck)).then(cliCommands_1.outputResult), }; // construct the compile subcommand const compileCmd = { command: 'compile <input>', desc: 'compile a Quint specification into the target, the output is written to stdout', builder: (yargs) => compileOpts(yargs) .option('target', { desc: `the compilation target.`, type: 'string', choices: ['tlaplus', 'json'], default: 'json', }) .option('flatten', { desc: 'Whether or not to flatten the modules into one. Use --flatten=false to disable', type: 'boolean', default: true, }) .option('verbosity', { desc: 'control how much output is produced (0 to 5)', type: 'number', default: verbosity_1.verbosity.defaultLevel, }) .option('apalache-version', { desc: 'The version of Apalache to use, if no running server is found (using this option may result in incompatibility)', type: 'string', default: apalache_1.DEFAULT_APALACHE_VERSION_TAG, }) .option('server-endpoint', { desc: 'Apalache server endpoint hostname:port', type: 'string', default: 'localhost:8822', }) .coerce('server-endpoint', (arg) => { const errorOrEndpoint = (0, apalache_1.parseServerEndpoint)(arg); if (errorOrEndpoint.isLeft()) { throw new Error(errorOrEndpoint.value); } else { return errorOrEndpoint.value; } }), handler: (args) => (0, cliCommands_1.load)(args) .then(chainCmd(cliCommands_1.parse)) .then(chainCmd(cliCommands_1.typecheck)) .then(chainCmd(cliCommands_1.compile)) .then(chainCmd(cliCommands_1.outputCompilationTarget)) .then(cliCommands_1.outputResult), }; // construct repl commands with yargs const replCmd = { command: ['repl [commands..]', '*'], desc: 'Run an interactive Read-Evaluate-Print-Loop. Optionally, takes one or more commands to execute upon entering the REPL.', builder: (yargs) => yargs .option('require', { desc: 'filename[::module]. Preload the file and, optionally, import the module', alias: 'r', type: 'string', }) .option('quiet', { desc: 'Disable banners and prompts, to simplify scripting (alias for --verbosity=0)', alias: 'q', type: 'boolean', default: false, }) .option('verbosity', { desc: 'control how much output is produced (0 to 5)', type: 'number', default: verbosity_1.verbosity.defaultLevel, }), handler: cliCommands_1.runRepl, }; // construct test commands with yargs const testCmd = { command: 'test <input>', desc: 'Run tests against a Quint specification', builder: (yargs) => defaultOpts(yargs) .option('main', { desc: 'name of the main module (by default, computed from filename)', type: 'string', }) .option('out-itf', { desc: 'write a trace for every test, e.g., out_{test}_{seq}.itf.json where {test} is the name of a test, and {seq} is the test sequence number', type: 'string', }) // Hidden alias for `--out-itf` .option('output', { type: 'string', }) .hide('output') .option('max-samples', { desc: 'the maximum number of successful runs to try for every randomized test', type: 'number', default: 10000, }) .option('seed', { desc: 'random seed to use for non-deterministic choice', type: 'string', }) .coerce('seed', coerceSeed) .option('verbosity', { desc: 'control how much output is produced (0 to 5)', type: 'number', default: verbosity_1.verbosity.defaultLevel, }) // Timeouts are postponed for: // https://github.com/informalsystems/quint/issues/633 // // .option('timeout', { // desc: 'timeout in seconds', // type: 'number', // }) .option('match', { desc: 'a string or regex that selects names to use as tests', type: 'string', }), handler: (args) => { if (args.output != null) { args.outItf = args['out-itf'] = args.output; delete args.output; } (0, cliCommands_1.load)(args).then(chainCmd(cliCommands_1.parse)).then(chainCmd(cliCommands_1.typecheck)).then(chainCmd(cliCommands_1.runTests)).then(cliCommands_1.outputResult); }, }; // construct run commands with yargs const runCmd = { command: 'run <input>', desc: 'Simulate a Quint specification and (optionally) check invariants', builder: (yargs) => defaultOpts(yargs) .option('main', { desc: 'name of the main module (by default, computed from filename)', type: 'string', }) .option('out-itf', { desc: 'output the trace in the Informal Trace Format to file, e.g., out_{seq}.itf.json where {seq} is the trace sequence number', type: 'string', }) .option('max-samples', { desc: 'the maximum number of runs to attempt before giving up', type: 'number', default: 10000, }) .option('n-traces', { desc: 'how many traces to generate (only affects output to out-itf)', type: 'number', default: 1, }) .option('max-steps', { desc: 'the maximum on the number of steps in every trace', type: 'number', default: 20, }) .option('n-threads', { desc: 'the number of threads to use when running simulations with the `rust` backend', type: 'number', default: os_1.default.cpus().length, }) .option('init', { desc: 'name of the initializer action', type: 'string', default: 'init', }) .option('step', { desc: 'name of the step action', type: 'string', default: 'step', }) .option('invariants', { desc: 'space separated list of invariants to check (definition names). When specified, all invariants are combined with AND and checked together, with detailed reporting of which ones were violated', type: 'array', default: [], }) .option('invariant', { desc: 'invariant to check: a definition name or an expression. Can be used together with --invariants', type: 'string', default: 'true', }) .option('witnesses', { desc: 'space separated list of witnesses to report on (counting for how many traces the witness is true)', type: 'array', default: [], }) .option('hide', { desc: 'space separated list of variable names to hide from the terminal output (does not affect ITF output)', type: 'array', default: [], }) .option('seed', { desc: 'random seed to use for non-deterministic choice', type: 'string', }) .coerce('seed', coerceSeed) .option('verbosity', { desc: 'control how much output is produced (0 to 5)', type: 'number', default: verbosity_1.verbosity.defaultLevel, }) .option('mbt', { desc: '(experimental) whether to produce metadata to be used by model-based testing', type: 'boolean', default: false, }) .option('backend', { desc: 'the backend to use for simulation', type: 'string', choices: ['typescript', 'rust'], default: 'typescript', }), // Timeouts are postponed for: // https://github.com/informalsystems/quint/issues/633 // // .option('timeout', { // desc: 'timeout in seconds', // type: 'number', // }) handler: (args) => (0, cliCommands_1.load)(args).then(chainCmd(cliCommands_1.parse)).then(chainCmd(cliCommands_1.typecheck)).then(chainCmd(cliCommands_1.runSimulator)).then(cliCommands_1.outputResult), }; // construct verify commands with yargs const verifyCmd = { command: 'verify <input>', desc: `Verify a Quint specification via Apalache`, builder: (yargs) => compileOpts(yargs) .option('invariants', { desc: 'space separated list of invariants to check (definition names). When specified, all invariants are combined with AND and checked together, with detailed reporting of which ones were violated', type: 'array', default: [], }) .option('inductive-invariant', { desc: 'inductive invariant to check. Can be used together with ordinary invariants.', type: 'string', }) .option('out-itf', { desc: 'output the trace in the Informal Trace Format to file, e.g., out.itf.json (suppresses all console output)', type: 'string', }) .option('max-steps', { desc: 'the maximum number of steps in every trace', type: 'number', default: 10, }) .option('random-transitions', { desc: 'choose transitions at random (= use symbolic simulation)', type: 'boolean', default: false, }) .option('apalache-config', { desc: 'path to an additional Apalache configuration file (in JSON)', type: 'string', }) .option('verbosity', { desc: 'control how much output is produced (0 to 5)', type: 'number', default: verbosity_1.verbosity.defaultLevel, }) .option('apalache-version', { desc: 'The version of Apalache to use, if no running server is found (using this option may result in incompatibility)', type: 'string', default: apalache_1.DEFAULT_APALACHE_VERSION_TAG, }) .option('server-endpoint', { desc: 'Apalache server endpoint hostname:port', type: 'string', default: 'localhost:8822', }) .coerce('server-endpoint', (arg) => { const errorOrEndpoint = (0, apalache_1.parseServerEndpoint)(arg); if (errorOrEndpoint.isLeft()) { throw new Error(errorOrEndpoint.value); } else { return errorOrEndpoint.value; } }), // Timeouts are postponed for: // https://github.com/informalsystems/quint/issues/633 // // .option('timeout', { // desc: 'timeout in seconds', // type: 'number', // }) handler: (args) => (0, cliCommands_1.load)(args) .then(chainCmd(cliCommands_1.parse)) .then(chainCmd(cliCommands_1.typecheck)) .then(chainCmd(cliCommands_1.compile)) .then(chainCmd(cliCommands_1.verifySpec)) .then(cliCommands_1.outputResult), }; // construct documenting commands with yargs const docsCmd = { command: 'docs <input>', desc: 'produces documentation from docstrings in a Quint specification', builder: defaultOpts, handler: (args) => (0, cliCommands_1.load)(args).then(chainCmd(cliCommands_1.docs)).then(cliCommands_1.outputResult), }; // Perform parser input validation. const validate = (argv, opts) => { // Validate that only array options can be specified more than once. for (const key in argv) { if (key == 'commands' && (argv['_'][0] || 'repl') === 'repl') { // Skip checking repl's positional arguments for both `quint ...` and // `quint repl ...` commands. Note that `opts` don't have enough information // on positional arguments, thus making this special case necessary. continue; } if (key !== '_' && !opts.array.includes(key) && Array.isArray(argv[key])) { throw new Error(`--${key} can not be specified more than once`); } } // Validate that --n-traces is not greater than --max-samples. if (argv['n-traces'] !== undefined && argv['max-samples'] !== undefined) { if (argv['n-traces'] > argv['max-samples']) { throw new Error(`--n-traces (${argv['n-traces']}) cannot be greater than --max-samples (${argv['max-samples']}).`); } } return true; }; function coerceSeed(seedText) { // since yargs does not has a type for big integers, // we do it with a fallback try { return BigInt(seedText); } catch (SyntaxError) { throw new Error(`--seed must be a big integer, found: ${seedText}`); } } async function main() { // parse the command-line arguments and execute the handlers await (0, yargs_1.default)(process.argv.slice(2)) .showHelpOnFail(false) .command(parseCmd) .command(typecheckCmd) .command(compileCmd) .command(replCmd) .command(runCmd) .command(testCmd) .command(verifyCmd) .command(docsCmd) .demandCommand(1) .check(validate) .version(version_1.version) .strict() .parse(); } main(); //# sourceMappingURL=cli.js.map