@informalsystems/quint
Version:
Core tool for the Quint specification language
422 lines • 15.5 kB
JavaScript
#!/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