diff --git a/CHANGELOG.md b/CHANGELOG.md index da33eb1ee..ace7c4cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## UNRELEASED ### Added + +- Calling `q::test`, `q::testOnce` and `q::lastTrace` on the REPL now works properly (#1495) + ### Changed + +- Performance of the REPL was drastically improved (#1495) +- Error reporting was improved for many runtime errors (#1495) + ### Deprecated ### Removed ### Fixed + +- Sending SIGINT (hitting Ctrl+C) to the run and test commands now actually stops the execution (#1495) + ### Security ## v0.21.2 -- 2024-09-09 diff --git a/quint/io-cli-tests.md b/quint/io-cli-tests.md index 3f5e8a317..27b0cb256 100644 --- a/quint/io-cli-tests.md +++ b/quint/io-cli-tests.md @@ -466,7 +466,7 @@ exit $exit_code ``` An example execution: -[State 0] { action_taken: "q::init", n: 1, nondet_picks: { } } +[State 0] { action_taken: "init", n: 1, nondet_picks: { } } [State 1] { action_taken: "OnPositive", n: 2, nondet_picks: { } } @@ -616,8 +616,7 @@ An example execution: [Frame 0] q::initAndInvariant => true -├─ q::init => true -│ └─ init => true +├─ init => true └─ isUInt(0) => true [State 0] @@ -629,18 +628,17 @@ q::initAndInvariant => true [Frame 1] q::stepAndInvariant => true -├─ q::step => true -│ └─ step => true -│ └─ mint( -│ "alice", -│ "eve", -│ 33944027745092921485394061592130395256199599638916782090017603421409072478812 -│ ) => true -│ ├─ require(true) => true -│ └─ require(true) => true -│ └─ isUInt( -│ 33944027745092921485394061592130395256199599638916782090017603421409072478812 -│ ) => true +├─ step => true +│ └─ mint( +│ "alice", +│ "eve", +│ 33944027745092921485394061592130395256199599638916782090017603421409072478812 +│ ) => true +│ ├─ require(true) => true +│ └─ require(true) => true +│ └─ isUInt( +│ 33944027745092921485394061592130395256199599638916782090017603421409072478812 +│ ) => true └─ isUInt( 33944027745092921485394061592130395256199599638916782090017603421409072478812 ) => true @@ -661,18 +659,17 @@ q::stepAndInvariant => true [Frame 2] q::stepAndInvariant => true -├─ q::step => true -│ └─ step => true -│ └─ mint( -│ "alice", -│ "eve", -│ 37478542505835205046968520025158070945751003972871720238447843997511300995974 -│ ) => true -│ ├─ require(true) => true -│ └─ require(true) => true -│ └─ isUInt( -│ 71422570250928126532362581617288466201950603611788502328465447418920373474786 -│ ) => true +├─ step => true +│ └─ mint( +│ "alice", +│ "eve", +│ 37478542505835205046968520025158070945751003972871720238447843997511300995974 +│ ) => true +│ ├─ require(true) => true +│ └─ require(true) => true +│ └─ isUInt( +│ 71422570250928126532362581617288466201950603611788502328465447418920373474786 +│ ) => true └─ isUInt( 71422570250928126532362581617288466201950603611788502328465447418920373474786 ) => true @@ -692,19 +689,18 @@ q::stepAndInvariant => true } [Frame 3] -q::stepAndInvariant => true -├─ q::step => true -│ └─ step => true -│ └─ mint( -│ "alice", -│ "null", -│ 109067983118832076063755963802104322727953985633488183463930115464609414175363 -│ ) => true -│ ├─ require(true) => true -│ └─ require(true) => true -│ └─ isUInt( -│ 109067983118832076063755963802104322727953985633488183463930115464609414175363 -│ ) => true +q::stepAndInvariant => false +├─ step => true +│ └─ mint( +│ "alice", +│ "null", +│ 109067983118832076063755963802104322727953985633488183463930115464609414175363 +│ ) => true +│ ├─ require(true) => true +│ └─ require(true) => true +│ └─ isUInt( +│ 109067983118832076063755963802104322727953985633488183463930115464609414175363 +│ ) => true └─ isUInt( 180490553369760202596118545419392788929904589245276685792395562883529787650149 ) => false @@ -811,24 +807,6 @@ rm out-itf-mbt-example.itf.json "#bigint": "49617995555028370892926474303042238797407019137772330780016167115018841762373" } }, - "eveToBob": { - "tag": "None", - "value": { - "#tup": [] - } - }, - "mintBob": { - "tag": "None", - "value": { - "#tup": [] - } - }, - "mintEve": { - "tag": "None", - "value": { - "#tup": [] - } - }, "receiver": { "tag": "Some", "value": "charlie" @@ -895,15 +873,13 @@ rm out-itf-example*.itf.json ### Test outputs ITF -TODO: output states after fix: https://github.com/informalsystems/quint/issues/288 - ``` output=$(quint test --out-itf='coin_{seq}_{test}.itf.json' \ ../examples/tutorials/coin.qnt) exit_code=$? echo "$output" | sed -e 's/([0-9]*ms)/(duration)/g' -e 's#^.*coin.qnt# HOME/coin.qnt#g' -cat coin_0_sendWithoutMintTest.itf.json | jq '.states' +cat coin_0_sendWithoutMintTest.itf.json | jq '.states[0]."balances"."#map"' rm coin_0_sendWithoutMintTest.itf.json cat coin_1_mintSendTest.itf.json | jq '.states[0]."balances"."#map"' rm coin_1_mintSendTest.itf.json @@ -918,7 +894,38 @@ exit $exit_code ok mintSendTest passed 10000 test(s) 2 passing (duration) -[] +[ + [ + "alice", + { + "#bigint": "0" + } + ], + [ + "bob", + { + "#bigint": "0" + } + ], + [ + "charlie", + { + "#bigint": "0" + } + ], + [ + "eve", + { + "#bigint": "0" + } + ], + [ + "null", + { + "#bigint": "0" + } + ] +] [ [ "alice", @@ -1001,7 +1008,7 @@ cd - > /dev/null ``` -output=$(quint test --seed=0x1cce8452305113 --match=mintTwiceThenSendError \ +output=$(quint test --seed=0x1286bf2e1dacb3 --match=mintTwiceThenSendError \ --verbosity=3 ../examples/tutorials/coin.qnt) exit_code=$? echo "$output" | sed -e 's/([0-9]*ms)/(duration)/g' -e 's#^.*coin.qnt# HOME/coin.qnt#g' @@ -1012,7 +1019,7 @@ exit $exit_code ``` coin - 1) mintTwiceThenSendError failed after 2 test(s) + 1) mintTwiceThenSendError failed after 1 test(s) 1 failed @@ -1025,45 +1032,45 @@ init => true [Frame 1] mint( - "alice", + "bob", "eve", - 62471107147077426559451191183102889181018012614560866566772535482230624081662 + 74252675173190743514494160784973331842148624838292266741626378055869698233769 ) => true ├─ require(true) => true └─ require(true) => true └─ isUInt( - 62471107147077426559451191183102889181018012614560866566772535482230624081662 + 74252675173190743514494160784973331842148624838292266741626378055869698233769 ) => true [Frame 2] mint( - "alice", "bob", - 108068598360285515924422306643202051157703255799754639660642704769543958308579 + "bob", + 97700478479458321253548605902971263977055085704583752584562220159652816914987 ) => true ├─ require(true) => true └─ require(true) => true └─ isUInt( - 108068598360285515924422306643202051157703255799754639660642704769543958308579 + 97700478479458321253548605902971263977055085704583752584562220159652816914987 ) => true [Frame 3] send( "eve", "bob", - 17111225533527540742175456584955554462321462289172248790585577326828420782641 + 47769583726968424739901588588333904197787985995488944788698867328177315688645 ) => false ├─ require(true) => true ├─ require(true) => true │ └─ isUInt( -│ 45359881613549885817275734598147334718696550325388617776186958155402203299021 +│ 26483091446222318774592572196639427644360638842803321952927510727692382545124 │ ) => true └─ require(false) => false └─ isUInt( - 125179823893813056666597763228157605620024718088926888451228282096372379091220 + 145470062206426745993450194491305168174843071700072697373261087487830132603632 ) => false - Use --seed=0x1cce845230512f --match=mintTwiceThenSendError to repeat. + Use --seed=0x1286bf2e1dacb3 --match=mintTwiceThenSendError to repeat. ``` ### test fails on invalid seed @@ -1129,9 +1136,9 @@ echo -e "A1::f(1)\nA2::f(1)" | quint -r ../examples/language-features/instances. ``` -output=$(quint test testFixture/_1040compileError.qnt 2>&1) +output=$(quint test testFixture/_1040compileError.qnt --seed=1 2>&1) exit_code=$? -echo "$output" | sed -e 's#^.*_1040compileError.qnt# HOME/_1040compileError.qnt#g' +echo "$output" | sed -E 's#(/[^ ]*/)_1040compileError.qnt#HOME/_1040compileError.qnt#g' exit $exit_code ``` @@ -1140,15 +1147,19 @@ exit $exit_code ``` _1040compileError - HOME/_1040compileError.qnt:2:3 - error: [QNT500] Uninitialized const n. Use: import (n=).* -2: const n: int - ^^^^^^^^^^^^ + 1) myTest failed after 1 test(s) + + 1 failed + + 1) myTest: + HOME/_1040compileError.qnt:5:12 - error: [QNT500] Uninitialized const n. Use: import (n=).* + 5: assert(n > 0) + Use --seed=0x1 --match=myTest to repeat. - HOME/_1040compileError.qnt:5:12 - error: [QNT502] Name n not found -5: assert(n > 0) - ^ -error: Tests could not be run due to an error during compilation + Use --verbosity=3 to show executions. + Further debug with: quint test --verbosity=3 testFixture/_1040compileError.qnt +error: Tests failed ``` ### Fail on run with uninitialized constants @@ -1165,11 +1176,7 @@ exit $exit_code ``` -HOME/_1041compileConst.qnt:2:3 - error: [QNT500] Uninitialized const N. Use: import (N=).* -2: const N: int - ^^^^^^^^^^^^ - -HOME/_1041compileConst.qnt:5:24 - error: [QNT502] Name N not found +HOME/_1041compileConst.qnt:5:24 - error: [QNT500] Uninitialized const N. Use: import (N=).* 5: action init = { x' = N } ^ @@ -1214,6 +1221,81 @@ echo 'q::debug("value:", { foo: 42, bar: "Hello, World!" })' | quint | tail -n + >>> ``` +### REPL continues to work after missing name errors + + + +``` +echo -e 'inexisting_name\n1 + 1' | quint -q +``` + + +``` +static analysis error: error: [QNT404] Name 'inexisting_name' not found +inexisting_name +^^^^^^^^^^^^^^^ + +2 + +``` + +### REPL continues to work after conflicting definitions + +Regression for https://github.com/informalsystems/quint/issues/434 + + +``` +echo -e 'def farenheit(celsius) = celsius * 9 / 5 + 32\ndef farenheit(celsius) = celsius * 9 / 5 + 32\nfarenheit(1)' | quint -q +``` + + +``` + +static analysis error: error: [QNT101] Conflicting definitions found for name 'farenheit' in module '__repl__' +def farenheit(celsius) = celsius * 9 / 5 + 32 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +static analysis error: error: [QNT101] Conflicting definitions found for name 'farenheit' in module '__repl__' +def farenheit(celsius) = celsius * 9 / 5 + 32 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +33 + +``` + +### REPL continues to work after type errors + + +``` +echo -e 'def foo = 1 + "a"\nfoo\n1 + "a"\n1 + 1' | quint -q +``` + + +``` +static analysis error: error: [QNT000] Couldn't unify int and str +Trying to unify int and str +Trying to unify (int, int) => int and (int, str) => _t0 + +def foo = 1 + "a" + ^^^^^^^ + +static analysis error: error: [QNT404] Name 'foo' not found +foo +^^^ + +static analysis error: error: [QNT000] Couldn't unify int and str +Trying to unify int and str +Trying to unify (int, int) => int and (int, str) => _t0 + +1 + "a" +^^^^^^^ + +2 + +``` + + ### Errors are reported in the right file @@ -1245,7 +1327,8 @@ quint run --main=invalid ./testFixture/_1050diffName.qnt ``` -error: Main module invalid not found +error: [QNT405] Main module invalid not found +error: Argument error ``` ### test fails on invalid module @@ -1259,7 +1342,7 @@ quint test --main=invalid ./testFixture/_1050diffName.qnt ``` error: [QNT405] Main module invalid not found -error: Tests could not be run due to an error during compilation +error: Argument error ``` ### Multiple tests output different json diff --git a/quint/src/cli.ts b/quint/src/cli.ts index 210361b15..af27fe333 100755 --- a/quint/src/cli.ts +++ b/quint/src/cli.ts @@ -266,7 +266,7 @@ const runCmd = { .option('invariant', { desc: 'invariant to check: a definition name or an expression', type: 'string', - default: ['true'], + default: 'true', }) .option('seed', { desc: 'random seed to use for non-deterministic choice', diff --git a/quint/src/cliCommands.ts b/quint/src/cliCommands.ts index 3fda4e0a3..b4daaf930 100644 --- a/quint/src/cliCommands.ts +++ b/quint/src/cliCommands.ts @@ -17,6 +17,7 @@ import { SourceMap, compactSourceMap, parseDefOrThrow, + parseExpressionOrDeclaration, parsePhase1fromText, parsePhase2sourceResolution, parsePhase3importAndNameResolution, @@ -24,19 +25,19 @@ import { } from './parsing/quintParserFrontend' import { ErrorMessage } from './ErrorMessage' -import { Either, left, right } from '@sweet-monads/either' -import { fail } from 'assert' +import { Either, left, mergeInMany, right } from '@sweet-monads/either' +import assert, { fail } from 'assert' import { EffectScheme } from './effects/base' -import { LookupTable, UnusedDefinitions } from './names/base' +import { LookupTable } from './names/base' import { ReplOptions, quintRepl } from './repl' -import { FlatModule, OpQualifier, QuintEx, QuintModule, QuintOpDef, qualifier } from './ir/quintIr' +import { FlatModule, OpQualifier, QuintBool, QuintEx, QuintModule } from './ir/quintIr' import { TypeScheme } from './types/base' import { createFinders, formatError } from './errorReporter' import { DocumentationEntry, produceDocs, toMarkdown } from './docs' import { QuintError, quintErrorToString } from './quintError' -import { TestOptions, TestResult, compileAndTest } from './runtime/testing' -import { IdGenerator, newIdGenerator } from './idGenerator' -import { SimulatorOptions, compileAndRun } from './simulation' +import { TestOptions, TestResult } from './runtime/testing' +import { IdGenerator, newIdGenerator, zerog } from './idGenerator' +import { Outcome, SimulatorOptions } from './simulation' import { ofItf, toItf } from './itf' import { printExecutionFrameRec, printTrace, terminalWidth } from './graphics' import { verbosity } from './verbosity' @@ -45,10 +46,13 @@ import { fileSourceResolver } from './parsing/sourceResolver' import { verify } from './quintVerifier' import { flattenModules } from './flattening/fullFlattener' import { AnalysisOutput, analyzeInc, analyzeModules } from './quintAnalyzer' -import { ExecutionFrame } from './runtime/trace' +import { ExecutionFrame, newTraceRecorder } from './runtime/trace' import { flow, isEqual, uniqWith } from 'lodash' import { Maybe, just, none } from '@sweet-monads/maybe' import { compileToTlaplus } from './compileToTlaplus' +import { Evaluator } from './runtime/impl/evaluator' +import { NameResolver } from './names/resolver' +import { walkExpression } from './ir/IRVisitor' export type stage = | 'loading' @@ -66,7 +70,6 @@ interface OutputStage { // the modules and the lookup table produced by 'parse' modules?: QuintModule[] table?: LookupTable - unusedDefinitions?: UnusedDefinitions // the tables produced by 'typecheck' types?: Map effects?: Map @@ -92,7 +95,6 @@ const pickOutputStage = ({ warnings, modules, table, - unusedDefinitions, types, effects, errors, @@ -108,7 +110,6 @@ const pickOutputStage = ({ warnings, modules, table, - unusedDefinitions, types, effects, errors, @@ -136,7 +137,7 @@ interface ParsedStage extends LoadedStage { defaultModuleName: Maybe sourceMap: SourceMap table: LookupTable - unusedDefinitions: UnusedDefinitions + resolver: NameResolver idGen: IdGenerator } @@ -313,6 +314,11 @@ export async function runTests(prev: TypecheckedStage): Promise m.name === mainName) + if (!main) { + const error: QuintError = { code: 'QNT405', message: `Main module ${mainName} not found` } + return cliErr('Argument error', { ...testing, errors: [mkErrorMessage(prev.sourceMap)(error)] }) + } const rngOrError = mkRng(prev.args.seed) if (rngOrError.isLeft()) { @@ -321,12 +327,13 @@ export async function runTests(prev: TypecheckedStage): Promise isMatchingTest(testing.args.match, n) + const maxSamples = testing.args.maxSamples const options: TestOptions = { testMatch: matchFun, maxSamples: testing.args.maxSamples, rng, verbosity: verbosityLevel, - onTrace: (index: number, name: string, status: string, vars: string[], states: QuintEx[]) => { + onTrace: (index: number) => (name: string, status: string, vars: string[], states: QuintEx[]) => { if (outputTemplate) { const filename = expandNamedOutputTemplate(outputTemplate, name, index, { autoAppend: prev.args.nTraces > 1 }) const trace = toItf(vars, states) @@ -351,69 +358,17 @@ export async function runTests(prev: TypecheckedStage): Promise d.kind === 'def' && options.testMatch(d.name) - ) - // Define name expressions referencing each test that is not referenced elsewhere, adding the references to the lookup - // table - const args: QuintEx[] = unusedTests.map(defToAdd => { - const id = testing.idGen.nextId() - testing.table.set(id, defToAdd) - const namespace = defToAdd.importedFrom ? qualifier(defToAdd.importedFrom) : undefined - const name = namespace ? [namespace, defToAdd.name].join('::') : defToAdd.name - - return { kind: 'name', id, name } - }) - // Wrap all expressions in a single declaration - const testDeclaration: QuintOpDef = { - id: testing.idGen.nextId(), - name: 'q::unitTests', - kind: 'def', - qualifier: 'run', - expr: { kind: 'app', opcode: 'actionAll', args, id: testing.idGen.nextId() }, - } - // Add the declaration to the main module - const main = testing.modules.find(m => m.name === mainName) - main?.declarations.push(testDeclaration) - - // TODO The following block should all be moved into compileAndTest - const analysisOutput = { types: testing.types, effects: testing.effects, modes: testing.modes } - const { flattenedModules, flattenedTable, flattenedAnalysis } = flattenModules( - testing.modules, - testing.table, - testing.idGen, - testing.sourceMap, - analysisOutput - ) - const compilationState = { - originalModules: testing.modules, - modules: flattenedModules, - sourceMap: testing.sourceMap, - analysisOutput: flattenedAnalysis, - idGen: testing.idGen, - sourceCode: testing.sourceCode, - } + const testDefs = Array.from(prev.resolver.collector.definitionsByModule.get(mainName)!.values()) + .flat() + .filter(d => d.kind === 'def' && options.testMatch(d.name)) - const testResult = compileAndTest(compilationState, mainName, flattenedTable, options) - - // We have a compilation failure, so early exit without reporting test results - if (testResult.isLeft()) { - return cliErr('Tests could not be run due to an error during compilation', { - ...testing, - errors: testResult.value.map(mkErrorMessage(testing.sourceMap)), - }) - } + const evaluator = new Evaluator(testing.table, newTraceRecorder(verbosityLevel, rng, 1), rng) + const results = testDefs.map((def, index) => { + return evaluator.test(def, maxSamples, options.onTrace(index)) + }) // We're finished running the tests const elapsedMs = Date.now() - startMs - // So we can analyze the results now - const results = testResult.value // output the status for every test let nFailures = 1 @@ -458,7 +413,7 @@ export async function runTests(prev: TypecheckedStage): Promise r.name), failed: failed.map(r => r.name), ignored: ignored.map(r => r.name), - errors: namedErrors.map(([_, e]) => e), + errors: [], } // Nothing failed, so we are OK, and can exit early @@ -504,7 +459,7 @@ export async function runTests(prev: TypecheckedStage): Promise> { const simulator = { ...prev, stage: 'running' as stage } + const startMs = Date.now() // Force disable output if `--out-itf` is set const verbosityLevel = prev.args.outItf ? 0 : deriveVerbosity(prev.args) const mainName = guessMainModule(prev) + const main = prev.modules.find(m => m.name === mainName) + if (!main) { + const error: QuintError = { code: 'QNT405', message: `Main module ${mainName} not found` } + return cliErr('Argument error', { ...prev, errors: [mkErrorMessage(prev.sourceMap)(error)] }) + } const rngOrError = mkRng(prev.args.seed) if (rngOrError.isLeft()) { @@ -564,35 +525,82 @@ export async function runSimulator(prev: TypecheckedStage): Promise m.name === mainName) - if (mainModule === undefined) { - return cliErr(`Main module ${mainName} not found`, { ...simulator, errors: [] }) + function toExpr(input: string): Either { + const parseResult = parseExpressionOrDeclaration(input, '', prev.idGen, prev.sourceMap) + if (parseResult.kind !== 'expr') { + return left({ code: 'QNT501', message: `Expected ${input} to be a valid expression` }) + } + + prev.resolver.switchToModule(mainName) + walkExpression(prev.resolver, parseResult.expr) + if (prev.resolver.errors.length > 0) { + return left(prev.resolver.errors[0]) + } + + return right(parseResult.expr) } - const mainId = mainModule.id - const mainStart = prev.sourceMap.get(mainId)!.start.index - const mainEnd = prev.sourceMap.get(mainId)!.end!.index - const result = compileAndRun(newIdGenerator(), mainText, mainStart, mainEnd, mainName, mainPath, options) + + const argsParsingResult = mergeInMany([prev.args.init, prev.args.step, prev.args.invariant].map(toExpr)) + if (argsParsingResult.isLeft()) { + return cliErr('Argument error', { + ...simulator, + errors: argsParsingResult.value.map(mkErrorMessage(new Map())), + }) + } + const [init, step, invariant] = argsParsingResult.value + + const evaluator = new Evaluator(prev.resolver.table, recorder, options.rng, options.storeMetadata) + const evalResult = evaluator.simulate( + init, + step, + invariant, + prev.args.maxSamples, + prev.args.maxSteps, + prev.args.nTraces ?? 1 + ) const elapsedMs = Date.now() - startMs - switch (result.outcome.status) { + const outcome: Outcome = evalResult.isRight() + ? { status: (evalResult.value as QuintBool).value ? 'ok' : 'violation' } + : { status: 'error', errors: [evalResult.value] } + + recorder.bestTraces.forEach((trace, index) => { + const maybeEvalResult = trace.frame.result + if (maybeEvalResult.isLeft()) { + return cliErr('Runtime error', { + ...simulator, + errors: [mkErrorMessage(simulator.sourceMap)(maybeEvalResult.value)], + }) + } + const quintExResult = maybeEvalResult.value.toQuintEx(prev.idGen) + assert(quintExResult.kind === 'bool', 'invalid simulation produced non-boolean value ') + const simulationSucceeded = quintExResult.value + const status = simulationSucceeded ? 'ok' : 'violation' + const states = trace.frame.args.map(e => e.toQuintEx(prev.idGen)) + + options.onTrace(index, status, evaluator.varNames(), states) + }) + + const states = recorder.bestTraces[0]?.frame?.args?.map(e => e.toQuintEx(zerog)) + const frames = recorder.bestTraces[0]?.frame?.subframes + const seed = recorder.bestTraces[0]?.seed + switch (outcome.status) { case 'error': return cliErr('Runtime error', { ...simulator, - status: result.outcome.status, - trace: result.states, - errors: result.outcome.errors.map(mkErrorMessage(prev.sourceMap)), + status: outcome.status, + trace: states, + errors: outcome.errors.map(mkErrorMessage(prev.sourceMap)), }) case 'ok': - maybePrintCounterExample(verbosityLevel, result.states, result.frames) + maybePrintCounterExample(verbosityLevel, states, frames) if (verbosity.hasResults(verbosityLevel)) { console.log(chalk.green('[ok]') + ' No violation found ' + chalk.gray(`(${elapsedMs}ms).`)) - console.log(chalk.gray(`Use --seed=0x${result.seed.toString(16)} to reproduce.`)) + console.log(chalk.gray(`Use --seed=0x${seed.toString(16)} to reproduce.`)) if (verbosity.hasHints(verbosityLevel)) { console.log(chalk.gray('You may increase --max-samples and --max-steps.')) console.log(chalk.gray('Use --verbosity to produce more (or less) output.')) @@ -601,15 +609,15 @@ export async function runSimulator(prev: TypecheckedStage): Promise Read[r2, r3] & Update[u2]' + ), + }, + { + name: 'q::testOnce', + effect: parseAndQuantify('(Pure, Pure, Update[u1], Read[r2] & Update[u2], Read[r3]) => Read[r2, r3] & Update[u2]'), + }, { name: 'ite', effect: parseAndQuantify('(Read[r1], Read[r2] & Update[u], Read[r3] & Update[u]) => Read[r1, r2, r3] & Update[u]'), diff --git a/quint/src/graphics.ts b/quint/src/graphics.ts index 2fa8080f4..629775ff0 100644 --- a/quint/src/graphics.ts +++ b/quint/src/graphics.ts @@ -260,7 +260,7 @@ export function printExecutionFrameRec(box: ConsoleBox, frame: ExecutionFrame, i [text(','), line()], frame.args.map(a => prettyQuintEx(a.toQuintEx(zerog))) ) - const r = frame.result.isNone() ? text('none') : prettyQuintEx(frame.result.value.toQuintEx(zerog)) + const r = frame.result.isLeft() ? text('none') : prettyQuintEx(frame.result.value.toQuintEx(zerog)) const depth = isLast.length // generate the tree ASCII graphics for this frame let treeArt = isLast diff --git a/quint/src/names/base.ts b/quint/src/names/base.ts index f625be68b..3d30739b8 100644 --- a/quint/src/names/base.ts +++ b/quint/src/names/base.ts @@ -16,6 +16,7 @@ import { cloneDeep, compact } from 'lodash' import { QuintDef, QuintExport, QuintImport, QuintInstance, QuintLambdaParameter } from '../ir/quintIr' import { QuintType } from '../ir/quintTypes' import { QuintError } from '../quintError' +import { NameResolver } from './resolver' /** * Possible kinds for definitions @@ -84,6 +85,7 @@ export type NameResolutionResult = { table: LookupTable unusedDefinitions: UnusedDefinitions errors: QuintError[] + resolver: NameResolver } export function getTopLevelDef(defs: DefinitionsByName, name: string): LookupDefinition | undefined { @@ -250,4 +252,6 @@ export const builtinNames = [ 'variant', 'q::debug', 'q::lastTrace', + 'q::test', + 'q::testOnce', ] diff --git a/quint/src/names/collector.ts b/quint/src/names/collector.ts index 393353459..392bb880d 100644 --- a/quint/src/names/collector.ts +++ b/quint/src/names/collector.ts @@ -63,6 +63,11 @@ export class NameCollector implements IRVisitor { private currentModuleName: string = '' + switchToModule(moduleName: string): void { + this.currentModuleName = moduleName + this.definitionsByName = this.definitionsByModule.get(moduleName) ?? new Map() + } + enterModule(module: QuintModule): void { this.currentModuleName = module.name this.definitionsByName = new Map() @@ -129,7 +134,7 @@ export class NameCollector implements IRVisitor { // For each override, check if the name exists in the instantiated module and is a constant. // If so, update the value definition to point to the expression being overriden - decl.overrides.forEach(([param, ex]) => { + decl.overrides.forEach(([param, _ex]) => { // Constants are always top-level const constDef = getTopLevelDef(instanceTable, param.name) @@ -143,8 +148,6 @@ export class NameCollector implements IRVisitor { return } - // Update the definition to point to the expression being overriden - constDef.id = ex.id constDef.hidden = false }) diff --git a/quint/src/names/resolver.ts b/quint/src/names/resolver.ts index e9adb4c42..71c549b4f 100644 --- a/quint/src/names/resolver.ts +++ b/quint/src/names/resolver.ts @@ -32,14 +32,19 @@ export function resolveNames(quintModules: QuintModule[]): NameResolutionResult quintModules.forEach(module => { walkModule(visitor, module) }) - return { table: visitor.table, unusedDefinitions: visitor.unusedDefinitions, errors: visitor.errors } + return { + table: visitor.table, + unusedDefinitions: visitor.unusedDefinitions, + errors: visitor.errors, + resolver: visitor, + } } /** * `NameResolver` uses `NameCollector` to collect top-level definitions. Scoped * definitions are collected inside of `NameResolver` as it navigates the IR. */ -class NameResolver implements IRVisitor { +export class NameResolver implements IRVisitor { collector: NameCollector errors: QuintError[] = [] table: LookupTable = new Map() @@ -64,6 +69,10 @@ class NameResolver implements IRVisitor { return new Set(difference(definitions, usedDefinitions)) } + switchToModule(moduleName: string): void { + this.collector.switchToModule(moduleName) + } + enterModule(module: QuintModule): void { // First thing to do in resolving names for a module is to collect all // top-level definitions for that module. This has to be done in a separate diff --git a/quint/src/parsing/quintParserFrontend.ts b/quint/src/parsing/quintParserFrontend.ts index 513b232ff..da53fa659 100644 --- a/quint/src/parsing/quintParserFrontend.ts +++ b/quint/src/parsing/quintParserFrontend.ts @@ -20,7 +20,7 @@ import { QuintDeclaration, QuintDef, QuintEx, QuintModule, isDef } from '../ir/q import { IdGenerator, newIdGenerator } from '../idGenerator' import { ToIrListener } from './ToIrListener' import { LookupTable, UnusedDefinitions } from '../names/base' -import { resolveNames } from '../names/resolver' +import { NameResolver, resolveNames } from '../names/resolver' import { QuintError } from '../quintError' import { SourceLookupPath, SourceResolver, fileSourceResolver } from './sourceResolver' import { CallGraphVisitor, mkCallGraphContext } from '../static/callgraph' @@ -60,6 +60,7 @@ export interface ParserPhase2 extends ParserPhase1 {} export interface ParserPhase3 extends ParserPhase2 { table: LookupTable unusedDefinitions: UnusedDefinitions + resolver: NameResolver } /** diff --git a/quint/src/repl.ts b/quint/src/repl.ts index 968c2b9aa..6d7af2651 100644 --- a/quint/src/repl.ts +++ b/quint/src/repl.ts @@ -11,38 +11,29 @@ import * as readline from 'readline' import { Readable, Writable } from 'stream' import { readFileSync, writeFileSync } from 'fs' -import { Maybe, just, none } from '@sweet-monads/maybe' -import { Either, right } from '@sweet-monads/either' +import { none } from '@sweet-monads/maybe' import chalk from 'chalk' import { format } from './prettierimp' -import { FlatModule, QuintDef, QuintEx } from './ir/quintIr' -import { - CompilationContext, - CompilationState, - compileDecls, - compileExpr, - compileFromCode, - contextNameLookup, - inputDefName, - newCompilationState, -} from './runtime/compile' +import { FlatModule, QuintModule, isDef } from './ir/quintIr' import { createFinders, formatError } from './errorReporter' -import { Register } from './runtime/runtime' import { TraceRecorder, newTraceRecorder } from './runtime/trace' -import { parseDefOrThrow, parseExpressionOrDeclaration } from './parsing/quintParserFrontend' +import { SourceMap, parse, parseExpressionOrDeclaration } from './parsing/quintParserFrontend' import { prettyQuintEx, printExecutionFrameRec, terminalWidth } from './graphics' import { verbosity } from './verbosity' import { Rng, newRng } from './rng' import { version } from './version' import { fileSourceResolver } from './parsing/sourceResolver' import { cwd } from 'process' -import { newIdGenerator } from './idGenerator' +import { IdGenerator, newIdGenerator } from './idGenerator' import { moduleToString } from './ir/IRprinting' import { mkErrorMessage } from './cliCommands' import { QuintError } from './quintError' import { ErrorMessage } from './ErrorMessage' -import { EvaluationState, newEvaluationState } from './runtime/impl/base' +import { Evaluator } from './runtime/impl/evaluator' +import { walkDeclaration, walkExpression } from './ir/IRVisitor' +import { AnalysisOutput, analyzeInc, analyzeModules } from './quintAnalyzer' +import { NameResolver } from './names/resolver' // tunable settings export const settings = { @@ -52,6 +43,39 @@ export const settings = { type writer = (_text: string) => void +/** + * A data structure that holds the state of the compilation process. + */ +export interface CompilationState { + // The ID generator used during compilation. + idGen: IdGenerator + // File content loaded for each source, used to report errors + sourceCode: Map + // A list of modules in context + modules: QuintModule[] + // The name of the main module. + mainName?: string + // The source map for the compiled code. + sourceMap: SourceMap + // The output of the Quint analyzer. + analysisOutput: AnalysisOutput +} + +/* An empty initial compilation state */ +export function newCompilationState(): CompilationState { + return { + idGen: newIdGenerator(), + sourceCode: new Map(), + modules: [], + sourceMap: new Map(), + analysisOutput: { + types: new Map(), + effects: new Map(), + modes: new Map(), + }, + } +} + /** * The internal state of the REPL. */ @@ -64,15 +88,19 @@ class ReplState { lastLoadedFileAndModule: [string?, string?] // The state of pre-compilation phases compilationState: CompilationState - // The state of the compiler visitor - evaluationState: EvaluationState + // The evaluator to be used + evaluator: Evaluator + // The name resolver to be used + nameResolver: NameResolver constructor(verbosityLevel: number, rng: Rng) { + const recorder = newTraceRecorder(verbosityLevel, rng) this.moduleHist = '' this.exprHist = [] this.lastLoadedFileAndModule = [undefined, undefined] this.compilationState = newCompilationState() - this.evaluationState = newEvaluationState(newTraceRecorder(verbosityLevel, rng)) + this.evaluator = new Evaluator(new Map(), recorder, rng) + this.nameResolver = new NameResolver() } clone() { @@ -85,23 +113,26 @@ class ReplState { } addReplModule() { - const replModule: FlatModule = { name: '__repl__', declarations: simulatorBuiltins(this.compilationState), id: 0n } + const replModule: FlatModule = { name: '__repl__', declarations: [], id: 0n } this.compilationState.modules.push(replModule) - this.compilationState.originalModules.push(replModule) this.compilationState.mainName = '__repl__' this.moduleHist += moduleToString(replModule) } clear() { + const rng = newRng(this.rng.getState()) + const recorder = newTraceRecorder(this.verbosity, rng) + this.moduleHist = '' this.exprHist = [] this.compilationState = newCompilationState() - this.evaluationState = newEvaluationState(newTraceRecorder(this.verbosity, this.rng)) + this.evaluator = new Evaluator(new Map(), recorder, rng) + this.nameResolver = new NameResolver() } get recorder(): TraceRecorder { // ReplState always passes TraceRecorder in the evaluation state - return this.evaluationState.listener as TraceRecorder + return this.evaluator.recorder } get rng(): Rng { @@ -241,26 +272,28 @@ export function quintRepl( const newState = loadFromFile(out, state, filename) if (!newState) { - return state + return } state.lastLoadedFileAndModule[0] = filename - const moduleNameToLoad = moduleName ?? getMainModuleAnnotation(newState.moduleHist) ?? '__repl__' - if (moduleNameToLoad === '__repl__') { + const moduleNameToLoad = moduleName ?? getMainModuleAnnotation(newState.moduleHist) + if (!moduleNameToLoad) { // No module to load, introduce the __repl__ module newState.addReplModule() } - if (tryEvalModule(out, newState, moduleNameToLoad)) { + if (tryEvalModule(out, newState, moduleNameToLoad ?? '__repl__')) { state.lastLoadedFileAndModule[1] = moduleNameToLoad } else { out(chalk.yellow('Pick the right module name and import it (the file has been loaded)\n')) - return newState + return } if (newState.exprHist) { - newState.exprHist.forEach(expr => { + const expressionsToEvaluate = newState.exprHist + newState.exprHist = [] + expressionsToEvaluate.forEach(expr => { tryEvalAndClearRecorder(out, newState, expr) }) } @@ -268,7 +301,8 @@ export function quintRepl( state.moduleHist = newState.moduleHist state.exprHist = newState.exprHist state.compilationState = newState.compilationState - state.evaluationState = newState.evaluationState + state.evaluator = newState.evaluator + state.nameResolver = newState.nameResolver } // the read-eval-print loop @@ -409,13 +443,14 @@ export function quintRepl( function saveToFile(out: writer, state: ReplState, filename: string) { // 1. Write the previously loaded modules. - // 2. Write the definitions in the special module called __repl__. + // 2. Write the definitions in the loaded module (or in __repl__ if no module was loaded). // 3. Wrap expressions into special comments. try { - const text = - `// @mainModule ${state.lastLoadedFileAndModule[1]}\n` + - `${state.moduleHist}` + - state.exprHist.map(s => `/*! ${s} !*/\n`).join('\n') + const mainModuleAnnotation = state.moduleHist.startsWith('// @mainModule') + ? '' + : `// @mainModule ${state.lastLoadedFileAndModule[1] ?? '__repl__'}\n` + + const text = mainModuleAnnotation + `${state.moduleHist}` + state.exprHist.map(s => `/*! ${s} !*/\n`).join('\n') writeFileSync(filename, text) out(`Session saved to: ${filename}\n`) @@ -445,89 +480,36 @@ function loadFromFile(out: writer, state: ReplState, filename: string): ReplStat } } -/** - * Moves the nextvars register values into the vars, and clears the nextvars. - * Returns an array of variable names that were not updated. - * @param vars An array of Register objects representing the current state of the variables. - * @param nextvars An array of Register objects representing the next state of the variables. - * @returns An array of variable names that were not updated, or none if all variables were updated. - */ -function saveVars(vars: Register[], nextvars: Register[]): Maybe { - let isAction = false - - const nonUpdated = vars.reduce((acc, varRegister) => { - const nextVarRegister = nextvars.find(v => v.name === varRegister.name) - if (nextVarRegister && nextVarRegister.registerValue.isJust()) { - varRegister.registerValue = nextVarRegister.registerValue - nextVarRegister.registerValue = none() - isAction = true - } else { - // No nextvar for this variable, so it was not updated - acc.push(varRegister.name) - } - - return acc - }, [] as string[]) - - if (isAction) { - // return the names of the variables that have not been updated - return just(nonUpdated) - } else { - return none() - } -} - -// Declarations that are overloaded by the simulator. -// In the future, we will declare them in a separate module. -function simulatorBuiltins(st: CompilationState): QuintDef[] { - return [ - parseDefOrThrow( - `def q::test = (q::nruns, q::nsteps, q::ntraces, q::init, q::next, q::inv) => false`, - st.idGen, - st.sourceMap - ), - parseDefOrThrow( - `def q::testOnce = (q::nsteps, q::ntraces, q::init, q::next, q::inv) => false`, - st.idGen, - st.sourceMap - ), - ] -} - function tryEvalModule(out: writer, state: ReplState, mainName: string): boolean { const modulesText = state.moduleHist const mainPath = fileSourceResolver(state.compilationState.sourceCode).lookupPath(cwd(), 'repl.ts') state.compilationState.sourceCode.set(mainPath.toSourceName(), modulesText) - const context = compileFromCode( - newIdGenerator(), - modulesText, - mainName, + // FIXME(#1052): We should build a proper sourceCode map from the files we previously loaded + const sourceCode: Map = new Map() + const idGen = newIdGenerator() + const { modules, table, resolver, sourceMap, errors } = parse( + idGen, + mainPath.toSourceName(), mainPath, - state.evaluationState.listener, - state.rng.next, - false + modulesText, + sourceCode ) - if ( - context.evaluationState?.context.size === 0 || - context.compileErrors.length > 0 || - context.syntaxErrors.length > 0 - ) { - const tempState = state.clone() - // The compilation state has updated source code maps, to be used in error reporting - tempState.compilationState = context.compilationState - printErrors(out, tempState, context) + // On errors, we'll produce the computational context up to this point + const [analysisErrors, analysisOutput] = analyzeModules(table, modules) + + state.compilationState = { idGen, sourceCode, modules, sourceMap, analysisOutput } + + if (errors.length > 0 || analysisErrors.length > 0) { + printErrorMessages(out, state, 'syntax error', modulesText, errors) + printErrorMessages(out, state, 'static analysis error', modulesText, analysisErrors) return false } - if (context.analysisErrors.length > 0) { - printErrors(out, state, context) - // provisionally, continue on type & effects errors - } + resolver.switchToModule(mainName) + state.nameResolver = resolver - // Save compilation state - state.compilationState = context.compilationState - state.evaluationState = context.evaluationState + state.evaluator.updateTable(table) return true } @@ -542,6 +524,8 @@ function tryEvalAndClearRecorder(out: writer, state: ReplState, newInput: string // try to evaluate the expression in a string and print it, if successful function tryEval(out: writer, state: ReplState, newInput: string): boolean { + const columns = terminalWidth() + if (state.compilationState.modules.length === 0) { state.addReplModule() tryEvalModule(out, state, '__repl__') @@ -564,66 +548,115 @@ function tryEval(out: writer, state: ReplState, newInput: string): boolean { } // evaluate the input, depending on its type if (parseResult.kind === 'expr') { - const context = compileExpr(state.compilationState, state.evaluationState, state.rng, false, parseResult.expr) - - if (context.syntaxErrors.length > 0 || context.compileErrors.length > 0 || context.analysisErrors.length > 0) { - printErrors(out, state, context, newInput) - if (context.syntaxErrors.length > 0 || context.compileErrors.length > 0) { - return false - } // else: provisionally, continue on type & effects errors + walkExpression(state.nameResolver, parseResult.expr) + if (state.nameResolver.errors.length > 0) { + printErrorMessages(out, state, 'static analysis error', newInput, state.nameResolver.errors) + state.nameResolver.errors = [] + return false + } + state.evaluator.updateTable(state.nameResolver.table) + + const [analysisErrors, _analysisOutput] = analyzeInc( + state.compilationState.analysisOutput, + state.nameResolver.table, + [ + { + kind: 'def', + qualifier: 'action', + name: 'q::input', + expr: parseResult.expr, + id: state.compilationState.idGen.nextId(), + }, + ] + ) + + if (analysisErrors.length > 0) { + printErrorMessages(out, state, 'static analysis error', newInput, analysisErrors) + return false } state.exprHist.push(newInput.trim()) - // Save the evaluation state only, as state vars changes should persist - state.evaluationState = context.evaluationState - - return evalExpr(state, out) - .mapLeft(msg => { - // when #618 is implemented, we should remove this - printErrorMessages(out, state, 'runtime error', newInput, context.getRuntimeErrors()) - // print the error message produced by the lookup - out(chalk.red(msg)) - out('\n') // be nice to external programs - }) - .isRight() + const evalResult = state.evaluator.evaluate(parseResult.expr) + + evalResult.map(ex => { + out(format(columns, 0, prettyQuintEx(ex))) + out('\n') + + if (ex.kind === 'bool' && ex.value) { + // A Boolean expression may be an action or a run. + // Save the state, if there were any updates to variables. + const missing = state.evaluator.shiftAndCheck() + if (missing.length > 0) { + out(chalk.yellow('[warning] some variables are undefined: ' + missing.join(', ') + '\n')) + } + } + return ex + }) + + if (verbosity.hasUserOpTracking(state.verbosity)) { + const trace = state.recorder.currentFrame + if (trace.subframes.length > 0) { + out('\n') + trace.subframes.forEach((f, i) => { + out(`[Frame ${i}]\n`) + printExecutionFrameRec({ width: columns, out }, f, []) + out('\n') + }) + } + } + + if (evalResult.isLeft()) { + printErrorMessages(out, state, 'runtime error', newInput, [evalResult.value]) + return false + } + + return true } if (parseResult.kind === 'declaration') { - // compile the module and add it to history if everything worked - const context = compileDecls(state.compilationState, state.evaluationState, state.rng, false, parseResult.decls) - - if ( - context.evaluationState.context.size === 0 || - context.compileErrors.length > 0 || - context.syntaxErrors.length > 0 - ) { - printErrors(out, state, context, newInput) + parseResult.decls.forEach(decl => { + walkDeclaration(state.nameResolver.collector, decl) + walkDeclaration(state.nameResolver, decl) + }) + if (state.nameResolver.errors.length > 0) { + printErrorMessages(out, state, 'static analysis error', newInput, state.nameResolver.errors) + out('\n') + + parseResult.decls.forEach(decl => { + if (isDef(decl)) { + state.nameResolver.collector.deleteDefinition(decl.name) + } + }) + + state.nameResolver.errors = [] return false } - if (context.analysisErrors.length > 0) { - printErrors(out, state, context, newInput) - // provisionally, continue on type & effects errors + const [analysisErrors, analysisOutput] = analyzeInc( + state.compilationState.analysisOutput, + state.nameResolver.table, + parseResult.decls + ) + + if (analysisErrors.length > 0) { + printErrorMessages(out, state, 'static analysis error', newInput, analysisErrors) + parseResult.decls.forEach(decl => { + if (isDef(decl)) { + state.nameResolver.collector.deleteDefinition(decl.name) + } + }) + + return false } - out('\n') // be nice to external programs + state.compilationState.analysisOutput = analysisOutput state.moduleHist = state.moduleHist.slice(0, state.moduleHist.length - 1) + newInput + '\n}' // update the history - // Save compilation state - state.compilationState = context.compilationState - state.evaluationState = context.evaluationState + out('\n') } return true } -// output errors to the console in red -function printErrors(out: writer, state: ReplState, context: CompilationContext, newInput: string = '') { - printErrorMessages(out, state, 'syntax error', newInput, context.syntaxErrors) - printErrorMessages(out, state, 'static analysis error', newInput, context.analysisErrors, chalk.yellow) - printErrorMessages(out, state, 'compile error', newInput, context.compileErrors) - out('\n') // be nice to external programs -} - // print error messages with proper colors function printErrorMessages( out: writer, @@ -714,47 +747,6 @@ function countBraces(str: string): [number, number, number] { return [nOpenBraces, nOpenParen, nOpenComments] } -function evalExpr(state: ReplState, out: writer): Either { - const computable = contextNameLookup(state.evaluationState.context, inputDefName, 'callable') - const columns = terminalWidth() - const result = computable.chain(comp => { - return comp - .eval() - .chain(value => { - const ex = value.toQuintEx(state.compilationState.idGen) - out(format(columns, 0, prettyQuintEx(ex))) - out('\n') - - if (ex.kind === 'bool' && ex.value) { - // A Boolean expression may be an action or a run. - // Save the state, if there were any updates to variables. - saveVars(state.evaluationState.vars, state.evaluationState.nextVars).map(missing => { - if (missing.length > 0) { - out(chalk.yellow('[warning] some variables are undefined: ' + missing.join(', ') + '\n')) - } - }) - } - return right(ex) - }) - .mapLeft(e => state.evaluationState.errorTracker.addRuntimeError(e.reference, e)) - .mapLeft(_ => '') - }) - - if (verbosity.hasUserOpTracking(state.verbosity)) { - const trace = state.recorder.currentFrame - if (trace.subframes.length > 0) { - out('\n') - trace.subframes.forEach((f, i) => { - out(`[Frame ${i}]\n`) - printExecutionFrameRec({ width: columns, out }, f, []) - out('\n') - }) - } - } - - return result -} - function getMainModuleAnnotation(moduleHist: string): string | undefined { const moduleName = moduleHist.match(/^\/\/ @mainModule\s+(\w+)\n/) return moduleName?.at(1) diff --git a/quint/src/runtime/compile.ts b/quint/src/runtime/compile.ts deleted file mode 100644 index e37060722..000000000 --- a/quint/src/runtime/compile.ts +++ /dev/null @@ -1,338 +0,0 @@ -/* - * A compiler to the runtime environment. - * - * Igor Konnov, Informal Systems, 2022-2023 - * - * Copyright 2022-2023 Informal Systems - * Licensed under the Apache License, Version 2.0. - * See LICENSE in the project root for license information. - */ - -import { Either, left, right } from '@sweet-monads/either' -import { SourceMap, parse, parsePhase3importAndNameResolution } from '../parsing/quintParserFrontend' -import { Computable, ComputableKind, kindName } from './runtime' -import { ExecutionListener } from './trace' -import { FlatModule, QuintDeclaration, QuintDef, QuintEx, QuintModule } from '../ir/quintIr' -import { CompilerVisitor } from './impl/compilerImpl' -import { walkDefinition } from '../ir/IRVisitor' -import { LookupTable } from '../names/base' -import { AnalysisOutput, analyzeInc, analyzeModules } from '../quintAnalyzer' -import { IdGenerator, newIdGenerator } from '../idGenerator' -import { SourceLookupPath } from '../parsing/sourceResolver' -import { Rng } from '../rng' -import { flattenModules } from '../flattening/fullFlattener' -import { QuintError } from '../quintError' -import { EvaluationState, newEvaluationState } from './impl/base' - -/** - * The name of the builtin name that returns the last found trace. - */ -export const lastTraceName = 'q::lastTrace' - -/** - * The name of a definition that wraps the user input, e.g., in REPL. - */ -export const inputDefName: string = 'q::input' - -/** - * A compilation context returned by 'compile'. - */ -export interface CompilationContext { - // the lookup table to query for values and definitions - lookupTable: LookupTable - // messages that are produced during parsing - syntaxErrors: QuintError[] - // messages that are produced by static analysis - analysisErrors: QuintError[] - // messages that are produced during compilation - compileErrors: QuintError[] - // messages that get populated as the compiled code is executed - getRuntimeErrors: () => QuintError[] - // The state of pre-compilation phases. - compilationState: CompilationState - // The state of the compiler visitor. - evaluationState: EvaluationState -} - -/** - * A data structure that holds the state of the compilation process. - */ -export interface CompilationState { - // The ID generator used during compilation. - idGen: IdGenerator - // File content loaded for each source, used to report errors - sourceCode: Map - // A list of modules as they are constructed, without flattening. This is - // needed to derive correct name resolution during incremental compilation in - // a flattened context. - originalModules: QuintModule[] - // A list of flattened modules. - modules: FlatModule[] - // The name of the main module. - mainName?: string - // The source map for the compiled code. - sourceMap: SourceMap - // The output of the Quint analyzer. - analysisOutput: AnalysisOutput -} - -/* An empty initial compilation state */ -export function newCompilationState(): CompilationState { - return { - idGen: newIdGenerator(), - sourceCode: new Map(), - originalModules: [], - modules: [], - sourceMap: new Map(), - analysisOutput: { - types: new Map(), - effects: new Map(), - modes: new Map(), - }, - } -} - -export function errorContextFromMessage( - listener: ExecutionListener -): (_: { errors: QuintError[]; sourceMap: SourceMap }) => CompilationContext { - const evaluationState = newEvaluationState(listener) - return ({ errors, sourceMap }) => { - return { - lookupTable: new Map(), - syntaxErrors: errors, - analysisErrors: [], - compileErrors: [], - getRuntimeErrors: () => [], - compilationState: { ...newCompilationState(), sourceMap }, - evaluationState, - } - } -} - -export function contextNameLookup( - context: Map, - defName: string, - kind: ComputableKind -): Either { - const value = context.get(kindName(kind, defName)) - if (!value) { - console.log(`key = ${kindName(kind, defName)}`) - return left(`No value for definition ${defName}`) - } else { - return right(value) - } -} - -/** - * Compile Quint defs to JS runtime objects from the parsed and type-checked - * data structures. This is a user-facing function. In case of an error, the - * error messages are passed to an error handler and the function returns - * undefined. - * - * @param compilationState the state of the compilation process - * @param evaluationState the state of the compiler visitor - * @param lookupTable lookup table as produced by the parser - * @param rand the random number generator - * @param storeMetadata whether to store metadata in the trace states - * @param defs the definitions to compile - * @returns the compilation context - */ -export function compile( - compilationState: CompilationState, - evaluationState: EvaluationState, - lookupTable: LookupTable, - rand: (bound: bigint) => bigint, - storeMetadata: boolean, - defs: QuintDef[] -): CompilationContext { - const visitor = new CompilerVisitor(lookupTable, rand, evaluationState, storeMetadata) - - defs.forEach(def => walkDefinition(visitor, def)) - - return { - lookupTable, - syntaxErrors: [], - analysisErrors: [], - compileErrors: visitor.getCompileErrors(), - getRuntimeErrors: () => { - return visitor.getRuntimeErrors().splice(0) - }, - compilationState, - evaluationState: visitor.getEvaluationState(), - } -} - -/** - * Compile a single Quint expression, given a non-empty compilation and - * evaluation state. That is, those states should have the results of the - * compilation of at least one module. - * - * @param state - The current compilation state - * @param evaluationState - The current evaluation state - * @param rng - The random number generator - * @param storeMetadata - whether to store metadata in the trace states - * @param expr - The Quint exporession to be compiled - * - * @returns A compilation context with the compiled expression or its errors - */ -export function compileExpr( - state: CompilationState, - evaluationState: EvaluationState, - rng: Rng, - storeMetadata: boolean, - expr: QuintEx -): CompilationContext { - // Create a definition to encapsulate the parsed expression. - // Note that the expression may contain nested definitions. - // Hence, we have to compile it via an auxilliary definition. - const def: QuintDef = { kind: 'def', qualifier: 'action', name: inputDefName, expr, id: state.idGen.nextId() } - - return compileDecls(state, evaluationState, rng, storeMetadata, [def]) -} - -/** - * Compile a single Quint definition, given a non-empty compilation and - * evaluation state. That is, those states should have the results of the - * compilation of at least one module. - * - * @param state - The current compilation state - * @param evaluationState - The current evaluation state - * @param rng - The random number generator - * @param storeMetadata - whether to store metadata in the trace states - * @param decls - The Quint declarations to be compiled - * - * @returns A compilation context with the compiled definition or its errors - */ -export function compileDecls( - state: CompilationState, - evaluationState: EvaluationState, - rng: Rng, - storeMetadata: boolean, - decls: QuintDeclaration[] -): CompilationContext { - if (state.originalModules.length === 0 || state.modules.length === 0) { - throw new Error('No modules in state') - } - - // Define a new module list with the new definition in the main module, - // ensuring the original object is not modified - const originalModules = state.originalModules.map(m => { - if (m.name === state.mainName) { - return { ...m, declarations: [...m.declarations, ...decls] } - } - return m - }) - - const mainModule = state.modules.find(m => m.name === state.mainName)! - - // We need to resolve names for this new definition. Incremental name - // resolution is not our focus now, so just resolve everything again. - const { table, errors } = parsePhase3importAndNameResolution({ - modules: originalModules, - sourceMap: state.sourceMap, - errors: [], - }) - - if (errors.length > 0) { - // For now, don't try to run analysis and flattening if there are errors - return errorContextFromMessage(evaluationState.listener)({ errors, sourceMap: state.sourceMap }) - } - - const [analysisErrors, analysisOutput] = analyzeInc(state.analysisOutput, table, decls) - - const { - flattenedModules: flatModules, - flattenedTable, - flattenedAnalysis, - } = flattenModules(originalModules, table, state.idGen, state.sourceMap, analysisOutput) - - const newState = { - ...state, - analysisOutput: flattenedAnalysis, - modules: flatModules, - originalModules: originalModules, - } - - const flatDefinitions = flatModules.find(m => m.name === state.mainName)!.declarations - - // Filter definitions that were not compiled yet - const defsToCompile = flatDefinitions.filter(d => !mainModule.declarations.some(d2 => d2.id === d.id)) - - const ctx = compile(newState, evaluationState, flattenedTable, rng.next, storeMetadata, defsToCompile) - - return { ...ctx, analysisErrors } -} - -/** - * Parse a string that contains Quint modules and compile it to executable - * objects. This is a user-facing function. In case of an error, the error - * messages are passed to an error handler and the function returns undefined. - * - * @param code text that stores one or several Quint modules, - * which should be parseable without any context - * @param mainName the name of the module that may contain state varibles - * @param execListener execution listener - * @param rand the random number generator - * @param storeMetadata whether to store metadata in the trace states - * @returns the compilation context - */ -export function compileFromCode( - idGen: IdGenerator, - code: string, - mainName: string, - mainPath: SourceLookupPath, - execListener: ExecutionListener, - rand: (bound: bigint) => bigint, - storeMetadata: boolean -): CompilationContext { - // parse the module text - // FIXME(#1052): We should build a proper sourceCode map from the files we previously loaded - const sourceCode: Map = new Map() - const { modules, table, sourceMap, errors } = parse(idGen, mainPath.toSourceName(), mainPath, code, sourceCode) - // On errors, we'll produce the computational context up to this point - const [analysisErrors, analysisOutput] = analyzeModules(table, modules) - - const { flattenedModules, flattenedTable, flattenedAnalysis } = flattenModules( - modules, - table, - idGen, - sourceMap, - analysisOutput - ) - const compilationState: CompilationState = { - originalModules: modules, - modules: flattenedModules, - mainName, - sourceMap, - analysisOutput: flattenedAnalysis, - idGen, - sourceCode, - } - - const main = flattenedModules.find(m => m.name === mainName) - // when the main module is not found, we will report an error - const mainNotFoundError: QuintError[] = main - ? [] - : [ - { - code: 'QNT405', - message: `Main module ${mainName} not found`, - }, - ] - const defsToCompile = main ? main.declarations : [] - const ctx = compile( - compilationState, - newEvaluationState(execListener), - flattenedTable, - rand, - storeMetadata, - defsToCompile - ) - - return { - ...ctx, - compileErrors: ctx.compileErrors.concat(mainNotFoundError), - analysisErrors, - syntaxErrors: errors, - } -} diff --git a/quint/src/runtime/impl/Context.ts b/quint/src/runtime/impl/Context.ts new file mode 100644 index 000000000..9066eb19a --- /dev/null +++ b/quint/src/runtime/impl/Context.ts @@ -0,0 +1,83 @@ +/* ---------------------------------------------------------------------------------- + * Copyright 2022-2024 Informal Systems + * Licensed under the Apache License, Version 2.0. + * See LICENSE in the project root for license information. + * --------------------------------------------------------------------------------- */ + +/** + * The evaluation context for the Quint evaluator. + * + * @author Gabriela Moreira + * + * @module + */ + +import { Either } from '@sweet-monads/either' +import { QuintError } from '../../quintError' +import { RuntimeValue } from './runtimeValue' +import { TraceRecorder } from '../trace' +import { VarStorage } from './VarStorage' +import { Trace } from './trace' + +/** + * A pointer to a value, so we can use the same reference in multiple places, and just update the value. + */ +export interface Register { + /** + * The value stored in the register, which can either be a runtime value or an error. + */ + value: Either +} + +/** + * A pointer to an optional value, so we can use the same reference in multiple places, and just update the value. + */ +export interface CachedValue { + /** + * The cached value, which can either be a runtime value, an error, or undefined if not cached yet. + */ + value: Either | undefined +} + +export class Context { + /** + * Function to generate a random bigint up to a specified maximum value. + */ + public rand: (n: bigint) => bigint + + /** + * The recorder for tracing the evaluation process. + */ + public recorder: TraceRecorder + + /** + * The trace object for recording variable values at each state in an execution. + */ + public trace: Trace = new Trace() + + /** + * Storage for variables at current and next state. + */ + public varStorage: VarStorage + + /** + * Constructs a new evaluation context. + * + * @param recorder - The trace recorder to use. + * @param rand - Function to generate random bigints. + * @param varStorage - The variable storage to use (should be the same as the builder's) + */ + constructor(recorder: TraceRecorder, rand: (n: bigint) => bigint, varStorage: VarStorage) { + this.recorder = recorder + this.rand = rand + this.varStorage = varStorage + } + + /** + * Shifts the variables in storage and extends the trace with the current variable state. + */ + shift() { + this.varStorage.shiftVars() + this.trace.extend(this.varStorage.asRecord()) + } +} diff --git a/quint/src/runtime/impl/VarStorage.ts b/quint/src/runtime/impl/VarStorage.ts new file mode 100644 index 000000000..d3b16c71d --- /dev/null +++ b/quint/src/runtime/impl/VarStorage.ts @@ -0,0 +1,178 @@ +/* ---------------------------------------------------------------------------------- + * Copyright 2022-2024 Informal Systems + * Licensed under the Apache License, Version 2.0. + * See LICENSE in the project root for license information. + * --------------------------------------------------------------------------------- */ + +/** + * A storage to keep track of Quint state variables in the current and next state. + * + * @author Gabriela Moreira + * + * @module + */ + +import { Either, left } from '@sweet-monads/either' +import { QuintError } from '../../quintError' +import { RuntimeValue, rv } from './runtimeValue' +import { Map as ImmutableMap } from 'immutable' +import { CachedValue, Register } from './Context' + +/** + * A named pointer to a value, so we can use the same reference in multiple places, and just update the value. + * The name is used for error messages. + */ +export interface NamedRegister extends Register { + name: string +} + +/** + * A snapshot of the VarStorage at a given point in time. Stores only information that is needed to backtrack. + */ +interface Snapshot { + nextVars: ImmutableMap + nondetPicks: Map + actionTaken: string | undefined +} + +/** + * Initializes the register value for a given variable name. + * + * @param name - The name of the variable to initialize, to be used in error messages + * @returns a QuintError indicating the variable is not set + */ +export function initialRegisterValue(name: string): Either { + return left({ code: 'QNT502', message: `Variable ${name} not set` }) +} + +/** + * A storage to keep track of Quint state variables in the current and next state. + */ +export class VarStorage { + /** + * An immutable map with registers for the current state variables. + */ + public vars: ImmutableMap = ImmutableMap() + + /** + * An immutable map with registers for the next state variables. + */ + public nextVars: ImmutableMap = ImmutableMap() + + /** + * Non-deterministic picks and their values for the current step. + */ + public nondetPicks: Map = new Map() + + /** + * The action taken in the current step. + */ + public actionTaken: string | undefined + + /** + * Cached values that need to be cleared when shifting. + */ + public cachesToClear: CachedValue[] = [] + + /** + * Indicates whether to store metadata. + */ + private storeMetadata: boolean + + /** + * Constructs a new VarStorage instance. + * + * @param storeMetadata - Indicates whether to store metadata. + * @param nondetPicks - Non-deterministic picks and their values for the current step. Should be + the one constructed in the builder. + */ + constructor(storeMetadata: boolean, nondetPicks: Map) { + this.storeMetadata = storeMetadata + this.nondetPicks = nondetPicks + } + + /** + * Shifts the current state variables to the next state variables. + * This method updates the current state variable registers with the values + * from the next state variable registers, initializes the next state variable + * registers, and clears cached values. + */ + shiftVars() { + this.vars.forEach((reg, key) => { + reg.value = this.nextVars.get(key)?.value ?? initialRegisterValue(reg.name) + }) + + this.nextVars.forEach(reg => (reg.value = initialRegisterValue(reg.name))) + this.clearCaches() + } + + /** + * Converts the current state variables and metadata into a RuntimeValue record. + * + * @returns A RuntimeValue representing the current state variables and metadata. + */ + asRecord(): RuntimeValue { + const map: [string, RuntimeValue][] = this.vars + .valueSeq() + .toArray() + .filter(r => r.value.isRight()) + .map(r => [r.name, r.value.unwrap()]) + + if (this.storeMetadata) { + const nondetPicksRecord = rv.mkRecord( + [...this.nondetPicks.entries()].map(([name, value]) => { + const valueVariant = value ? rv.mkVariant('Some', value) : rv.mkVariant('None', rv.mkTuple([])) + return [name, valueVariant] + }) + ) + map.push(['nondet_picks', nondetPicksRecord]) + map.push(['action_taken', rv.mkStr(this.actionTaken ?? '')]) + } + + return rv.mkRecord(map) + } + + /** + * Resets the current and next state variable registers to their initial values. + * This method sets the value of each register in both the current and next state + * variable maps to an undefined (error) value. + */ + reset() { + this.vars.forEach(reg => (reg.value = initialRegisterValue(reg.name))) + this.nextVars.forEach(reg => (reg.value = initialRegisterValue(reg.name))) + } + + /** + * Creates a snapshot of the current state of the VarStorage, with the relevant information to backtrack. + * @returns A snapshot of the current state of the VarStorage. + */ + snapshot(): Snapshot { + return { + nextVars: this.nextVars.map(reg => ({ ...reg })), + nondetPicks: new Map(this.nondetPicks), + actionTaken: this.actionTaken, + } + } + + /** + * Recovers the state of the VarStorage from a snapshot. + * + * @param snapshot - the snapshot to recover the state from + */ + recoverSnapshot(snapshot: Snapshot) { + this.nextVars.forEach((reg, key) => { + const snapshotReg = snapshot.nextVars.get(key) + if (snapshotReg) { + reg.value = snapshotReg.value + } + }) + this.nondetPicks = snapshot.nondetPicks + this.actionTaken = snapshot.actionTaken + } + + private clearCaches() { + this.cachesToClear.forEach(cachedValue => { + cachedValue.value = undefined + }) + } +} diff --git a/quint/src/runtime/impl/base.ts b/quint/src/runtime/impl/base.ts deleted file mode 100644 index aea0e74df..000000000 --- a/quint/src/runtime/impl/base.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* ---------------------------------------------------------------------------------- - * Copyright 2022-2024 Informal Systems - * Licensed under the Apache License, Version 2.0. - * See LICENSE in the project root for license information. - * --------------------------------------------------------------------------------- */ - -/** - * Base types and functions for the runtime implementation. - * - * @author Igor Konnov, Gabriela Moreira - * - * @module - */ - -import { Maybe, just, none } from '@sweet-monads/maybe' -import { ErrorCode, QuintError } from '../../quintError' -import { Computable, EvaluationResult, Register, kindName, mkCallable } from '../runtime' -import { ExecutionListener } from '../trace' -import { Trace } from './trace' -import { Either, right } from '@sweet-monads/either' -import { RuntimeValue, rv } from './runtimeValue' - -// Internal names in the compiler, which have special treatment. -// For some reason, if we replace 'q::input' with inputDefName, everything breaks. -// What kind of JS magic is that? -export const specialNames = ['q::input', 'q::runResult', 'q::nruns', 'q::nsteps', 'q::init', 'q::next', 'q::inv'] - -/** - * Returns a Map containing the built-in Computable objects for the Quint language. - * These include the callable objects for Bool, Int, and Nat. - * - * @returns a Map containing the built-in Computable objects. - */ -export function builtinContext() { - return new Map([ - [kindName('callable', 'Bool'), mkCallable([], mkConstComputable(rv.mkSet([rv.mkBool(false), rv.mkBool(true)])))], - [kindName('callable', 'Int'), mkCallable([], mkConstComputable(rv.mkInfSet('Int')))], - [kindName('callable', 'Nat'), mkCallable([], mkConstComputable(rv.mkInfSet('Nat')))], - ]) -} - -/** - * Represents the state of evaluation of Quint code. - * All the fields are mutated by CompilerVisitor, either directly, or via calls. - */ -export interface EvaluationState { - // The context of the evaluation, containing the Computable objects. - context: Map - // The list of variables in the current state. - vars: Register[] - // The list of variables in the next state. - nextVars: Register[] - // The current trace of states - trace: Trace - // The error tracker for the evaluation to store errors on callbacks. - errorTracker: CompilerErrorTracker - // The execution listener that the compiled code uses to report execution info. - listener: ExecutionListener -} - -/** - * Creates a new EvaluationState object. - * - * @returns a new EvaluationState object. - */ -export class CompilerErrorTracker { - // messages that are produced during compilation - compileErrors: QuintError[] = [] - // messages that get populated as the compiled code is executed - runtimeErrors: QuintError[] = [] - - addCompileError(reference: bigint, code: ErrorCode, message: string) { - this.compileErrors.push({ code, message, reference }) - } - - addRuntimeError(reference: bigint | undefined, error: QuintError) { - this.runtimeErrors.push({ ...error, reference }) - } -} - -/** - * Creates a new EvaluationState object with the initial state of the evaluation. - * - * @returns a new EvaluationState object - */ -export function newEvaluationState(listener: ExecutionListener): EvaluationState { - const state: EvaluationState = { - context: builtinContext(), - vars: [], - nextVars: [], - trace: new Trace(), - errorTracker: new CompilerErrorTracker(), - listener: listener, - } - - return state -} - -export function toMaybe(r: Either): Maybe { - if (r.isRight()) { - return just(r.value) - } else { - return none() - } -} - -// make a `Computable` that always returns a given runtime value -export function mkConstComputable(value: RuntimeValue) { - return { - eval: () => { - return right(value) - }, - } -} - -// make a `Computable` that always returns a given runtime value -export function mkFunComputable(fun: () => EvaluationResult) { - return { - eval: () => { - return fun() - }, - } -} diff --git a/quint/src/runtime/impl/builder.ts b/quint/src/runtime/impl/builder.ts new file mode 100644 index 000000000..b51839663 --- /dev/null +++ b/quint/src/runtime/impl/builder.ts @@ -0,0 +1,561 @@ +/* ---------------------------------------------------------------------------------- + * Copyright 2022-2024 Informal Systems + * Licensed under the Apache License, Version 2.0. + * See LICENSE in the project root for license information. + * --------------------------------------------------------------------------------- */ + +/** + * A builder to build arrow functions used to evaluate Quint expressions. + * + * Caching and var storage are heavily based on the original `compilerImpl.ts` file written by Igor Konnov. + * The performance of evaluation relies on a lot of memoization done mainly with closures in this file. + * We define registers and cached value data structures that work as pointers, to avoid the most lookups and + * memory usage as possible. This adds a lot of complexity to the code, but it is necessary to achieve feasible + * performance, as the functions built here will be called thousands of times by the simulator. + * + * @author Igor Konnov, Gabriela Moreira + * + * @module + */ + +import { Either, left, right } from '@sweet-monads/either' +import { QuintError } from '../../quintError' +import { RuntimeValue, rv } from './runtimeValue' +import { builtinLambda, builtinValue, lazyBuiltinLambda, lazyOps } from './builtins' +import { CachedValue, Context, Register } from './Context' +import { QuintApp, QuintEx, QuintVar } from '../../ir/quintIr' +import { LookupDefinition, LookupTable } from '../../names/base' +import { NamedRegister, VarStorage, initialRegisterValue } from './VarStorage' +import { List } from 'immutable' + +/** + * The type returned by the builder in its methods, which can be called to get the + * evaluation result under a given context. + */ +export type EvalFunction = (ctx: Context) => Either + +/** + * A builder to build arrow functions used to evaluate Quint expressions. + * It can be understood as a Quint compiler that compiles Quint expressions into + * typescript arrow functions. It is called a builder instead of compiler because + * the compiler term is overloaded. + */ +export class Builder { + table: LookupTable + paramRegistry: Map = new Map() + constRegistry: Map = new Map() + scopedCachedValues: Map = new Map() + initialNondetPicks: Map = new Map() + memo: Map = new Map() + memoByInstance: Map> = new Map() + namespaces: List = List() + varStorage: VarStorage + + /** + * Constructs a new Builder instance. + * + * @param table - The lookup table containing definitions. + * @param storeMetadata - A flag indicating whether to store metadata (`actionTaken` and `nondetPicks`). + */ + constructor(table: LookupTable, storeMetadata: boolean) { + this.table = table + this.varStorage = new VarStorage(storeMetadata, this.initialNondetPicks) + } + + /** + * Adds a variable to the var storage if it is not there yet. + * + * @param id + * @param name + */ + discoverVar(id: bigint, name: string) { + // Keep the key as simple as possible + const key = [id, ...this.namespaces].join('#') + if (this.varStorage.vars.has(key)) { + return + } + + const varName = nameWithNamespaces(name, this.namespaces) + const register: NamedRegister = { name: varName, value: initialRegisterValue(varName) } + const nextRegister: NamedRegister = { name: varName, value: initialRegisterValue(varName) } + this.varStorage.vars = this.varStorage.vars.set(key, register) + this.varStorage.nextVars = this.varStorage.nextVars.set(key, nextRegister) + } + + /** + * Gets the register for a variable by its id and the namespaces in scope (tracked by this builder). + * + * @param def - The variable to get the register for. + * + * @returns the register for the variable + */ + getVar(def: QuintVar): NamedRegister { + const key = [def.id, ...this.namespaces].join('#') + const result = this.varStorage.vars.get(key) + if (!result) { + this.discoverVar(def.id, def.name) + return this.varStorage.vars.get(key)! + } + + return result + } + + /** + * Gets the register for the next state of a variable by its id and the namespaces in scope (tracked by this builder). + * + * @param id - The identifier of the variable. + * + * @returns the register for the next state of the variable + */ + getNextVar(id: bigint): NamedRegister { + const key = [id, ...this.namespaces].join('#') + const result = this.varStorage.nextVars.get(key) + if (!result) { + throw new Error(`Variable not found: ${key}`) + } + + return result + } + + /** + * Gets the register for a constant by its id and the instances in scope (tracked by this builder). + * + * @param id - The identifier of the constant. + * @param name - The constant name to be used in error messages. + * + * @returns the register for the constant + */ + registerForConst(id: bigint, name: string): Register { + let register = this.constRegistry.get(id) + if (!register) { + const message = `Uninitialized const ${name}. Use: import (${name}=).*` + register = { value: left({ code: 'QNT500', message }) } + this.constRegistry.set(id, register) + return register + } + return register + } +} + +/* Bulding functionality is given by functions that take a builder instead of Builder methods. + * This should help separating responsability and splitting this into multiple files if ever needed */ + +/** + * Builds an evaluation function for a given Quint expression. + * + * This function first checks if the expression has already been memoized. If it has, + * it returns the memoized evaluation function. If not, it builds the core evaluation + * function for the expression and wraps it to handle errors and memoization. + * + * @param builder - The Builder instance used to construct the evaluation function. + * @param expr - The Quint expression to evaluate. + * + * @returns An evaluation function that takes a context and returns either a QuintError or a RuntimeValue. + */ +export function buildExpr(builder: Builder, expr: QuintEx): EvalFunction { + if (builder.memo.has(expr.id)) { + return builder.memo.get(expr.id)! + } + const exprEval = buildExprCore(builder, expr) + const wrappedEval: EvalFunction = ctx => { + try { + // This is where we add the reference to the error, if it is not already there. + // This way, we don't need to worry about references anywhere else :) + return exprEval(ctx).mapLeft(err => (err.reference === undefined ? { ...err, reference: expr.id } : err)) + } catch (error) { + const message = error instanceof Error ? error.message : 'unknown error' + return left({ code: 'QNT501', message: message, reference: expr.id }) + } + } + builder.memo.set(expr.id, wrappedEval) + return wrappedEval +} + +/** + * Builds an evaluation function for a given definition. + * + * This function first checks if the definition has already been memoized. If it has, + * it returns the memoized evaluation function. If the definition is imported from an instance, + * it builds the evaluation function under the context of the instance. Otherwise, it builds the + * core evaluation function for the definition and wraps it to handle errors and memoization. + * + * @param builder - The Builder instance used to construct the evaluation function. + * @param def - The LookupDefinition to evaluate. + * + * @returns An evaluation function that takes a context and returns either a QuintError or a RuntimeValue. + */ +export function buildDef(builder: Builder, def: LookupDefinition): EvalFunction { + if (!def.importedFrom || def.importedFrom.kind !== 'instance') { + return buildDefWithMemo(builder, def) + } + + return buildUnderDefContext(builder, def, () => buildDefWithMemo(builder, def)) +} + +/** + * Given an arrow that builds something, wrap it in modifications over the builder so it has the proper context. + * Specifically, this includes instance overrides in context so that the build function use the right registers + * for the instance if it originated from an instance. + * + * @param builder - The builder instance. + * @param def - The definition for which the context is being built. + * @param buildFunction - The function that builds the EvalFunction. + * + * @returns the result of buildFunction, evaluated under the right context. + */ +function buildUnderDefContext( + builder: Builder, + def: LookupDefinition, + buildFunction: () => EvalFunction +): EvalFunction { + if (!def.importedFrom || def.importedFrom.kind !== 'instance') { + // Nothing to worry about if there are no instances involved + return buildFunction() + } + + // This originates from an instance, so we need to handle overrides + const instance = def.importedFrom + + // Save how the builder was before so we can restore it after + const memoBefore = builder.memo + const namespacesBefore = builder.namespaces + + // We need separate memos for each instance. + // For example, if N is a constant, the expression N + 1 can have different values for different instances. + // We re-use the same memo for the same instance. So, let's check if there is an existing memo, + // or create and save a new one + if (builder.memoByInstance.has(instance.id)) { + builder.memo = builder.memoByInstance.get(instance.id)! + } else { + builder.memo = new Map() + builder.memoByInstance.set(instance.id, builder.memo) + } + + // We also need to update the namespaces to include the instance's namespaces. + // So, if variable x is updated, we update the instance's x, i.e. my_instance::my_module::x + builder.namespaces = List(def.namespaces) + + // Pre-compute as much as possible for the overrides: find the registers and find the expressions to evaluate + // so we don't need to look that up in runtime + const overrides: [Register, EvalFunction][] = instance.overrides.map(([param, expr]) => { + const id = builder.table.get(param.id)!.id + const register = builder.registerForConst(id, param.name) + + // Build the expr as a pure val def so it gets properly cached + const purevalEval = buildDef(builder, { kind: 'def', qualifier: 'pureval', expr, name: param.name, id: param.id }) + return [register, purevalEval] + }) + + // Here, we have the right context to build the function. That is, all constants are pointing to the right registers, + // and all namespaces are set for unambiguous variable access and update. + const result = buildFunction() + + // Restore the builder to its previous state + builder.namespaces = namespacesBefore + builder.memo = memoBefore + + // And then, in runtime, we only need to evaluate the override expressions, update the respective registers + // and then call the function that was built + return ctx => { + overrides.forEach(([register, evaluate]) => (register.value = evaluate(ctx))) + return result(ctx) + } +} + +/** + * Given a lookup definition, build the evaluation function for it, without worring about memoization or error handling. + * + * @param builder - The builder instance. + * @param def - The definition for which the evaluation function is being built. + * + * @returns the evaluation function for the given definition. + */ +function buildDefCore(builder: Builder, def: LookupDefinition): EvalFunction { + switch (def.kind) { + case 'def': { + if (def.qualifier === 'action') { + // Create an app to be recorded + const app: QuintApp = { id: def.id, kind: 'app', opcode: def.name, args: [] } + + const body = buildExpr(builder, def.expr) + return (ctx: Context) => { + if (def.expr.kind !== 'lambda') { + // Lambdas are recorded when they are called, no need to record them here + ctx.recorder.onUserOperatorCall(app) + } + + if (ctx.varStorage.actionTaken === undefined) { + ctx.varStorage.actionTaken = def.name + } + + const result = body(ctx) + + if (def.expr.kind !== 'lambda') { + ctx.recorder.onUserOperatorReturn(app, [], result) + } + return result + } + } + + if (def.expr.kind === 'lambda' || def.depth === undefined || def.depth === 0) { + // We need to avoid scoped caching in lambdas or top-level expressions + // We still have memoization. This caching is special for scoped defs (let-ins) + return buildExpr(builder, def.expr) + } + + // Else, we are dealing with a scoped value. + // We need to cache it, so every time we access it, it has the same value. + const cachedValue = builder.scopedCachedValues.get(def.id)! + + const bodyEval = buildExpr(builder, def.expr) + if (def.qualifier === 'nondet') { + // Create an entry in the map for this nondet pick, + // as we want the resulting record to be the same at every state. + // Value is optional, and starts with undefined + builder.initialNondetPicks.set(def.name, undefined) + } + + return ctx => { + if (cachedValue.value === undefined) { + cachedValue.value = bodyEval(ctx) + if (def.qualifier === 'nondet') { + cachedValue.value + .map(value => ctx.varStorage.nondetPicks.set(def.name, value)) + .mapLeft(_ => ctx.varStorage.nondetPicks.set(def.name, undefined)) + } + } + return cachedValue.value + } + } + case 'param': { + // Every parameter has a single register, and we just change this register's value before evaluating the body + // So, a reference to a parameter simply evaluates to the value of the register. + const register = builder.paramRegistry.get(def.id) + if (!register) { + const reg: Register = { value: left({ code: 'QNT501', message: `Parameter ${def.name} not set` }) } + builder.paramRegistry.set(def.id, reg) + return _ => reg.value + } + return _ => register.value + } + + case 'var': { + // Every variable has a single register, and we just change this register's value at each state + // So, a reference to a variable simply evaluates to the value of the register. + const register = builder.getVar(def) + return _ => { + return register.value + } + } + case 'const': { + // Every constant has a single register, and we just change this register's value when overrides are present + // So, a reference to a constant simply evaluates to the value of the register. + const register = builder.registerForConst(def.id, def.name) + return _ => register.value + } + default: + return _ => left({ code: 'QNT000', message: `Not implemented for def kind ${def.kind}` }) + } +} + +/** + * Builds a definition with memoization and caching. + * - Memoization: use the same built function for the same definition. + * - Caching: for top-level value definitions, cache the resulting value being aware of variable changes. + * + * @param builder - The builder instance. + * @param def - The definition for which the evaluation function is being built. + * + * @returns the evaluation function for the given definition. + */ +function buildDefWithMemo(builder: Builder, def: LookupDefinition): EvalFunction { + if (builder.memo.has(def.id)) { + return builder.memo.get(def.id)! + } + + const defEval = buildDefCore(builder, def) + + // For top-level value definitions, we can cache the resulting value, as long as we are careful with state changes. + const statefulCachingCondition = + def.kind === 'def' && + (def.qualifier === 'pureval' || def.qualifier === 'val') && + (def.depth === undefined || def.depth === 0) + + if (!statefulCachingCondition) { + // Only use memo, no runtime caching + builder.memo.set(def.id, defEval) + return defEval + } + + // PS: Since we memoize things separately per instance, we can store even values that depend on constants + + // Construct a cached value object (a register with optional value) + const cachedValue: CachedValue = { value: undefined } + if (def.qualifier === 'val') { + // This definition may use variables, so we need to clear the cache when they change + builder.varStorage.cachesToClear.push(cachedValue) + } + // Wrap the evaluation function with caching + const wrappedEval: EvalFunction = ctx => { + if (cachedValue.value === undefined) { + cachedValue.value = defEval(ctx) + } + return cachedValue.value + } + builder.memo.set(def.id, wrappedEval) + return wrappedEval +} + +/** + * Given an expression, build the evaluation function for it, without worring about memoization or error handling. + * + * @param builder - The builder instance. + * @param expr - The expression for which the evaluation function is being built. + * + * @returns the evaluation function for the given expression. + */ +function buildExprCore(builder: Builder, expr: QuintEx): EvalFunction { + switch (expr.kind) { + case 'int': + case 'bool': + case 'str': { + // These are already values, just return them + const value = right(rv.fromQuintEx(expr)) + return _ => value + } + case 'lambda': { + // Lambda is also like a value, but we should construct it with the context + const body = buildExpr(builder, expr.expr) + const lambda = rv.mkLambda(expr.params, body, builder.paramRegistry) + return _ => right(lambda) + } + case 'name': { + const def = builder.table.get(expr.id) + if (!def) { + // FIXME: If this refers to a builtin operator, we need to return it as an arrow (see #1332) + return builtinValue(expr.name) + } + return buildDef(builder, def) + } + case 'app': { + if (expr.opcode === 'assign') { + // Assign is too special, so we handle it separately. + // We need to build things under the context of the variable being assigned, as it may come from an instance, + // and that changed everything + const varDef = builder.table.get(expr.args[0].id)! + return buildUnderDefContext(builder, varDef, () => { + builder.discoverVar(varDef.id, varDef.name) + const register = builder.getNextVar(varDef.id) + const exprEval = buildExpr(builder, expr.args[1]) + + return ctx => { + return exprEval(ctx).map(value => { + register.value = right(value) + return rv.mkBool(true) + }) + } + }) + } + + const args = expr.args.map(arg => buildExpr(builder, arg)) + + // If the operator is a lazy operator, we can't evaluate the arguments before evaluating application + if (lazyOps.includes(expr.opcode)) { + const op = lazyBuiltinLambda(expr.opcode) + return ctx => op(ctx, args) + } + + // Otherwise, this is either a normal (eager) builtin, or an user-defined operator. + // For both, we first evaluate the arguments and then apply the operator. + + const operatorFunction = buildApp(builder, expr) + const userDefined = builder.table.has(expr.id) + + return ctx => { + if (userDefined) { + ctx.recorder.onUserOperatorCall(expr) + } + const argValues = [] + for (const arg of args) { + const argValue = arg(ctx) + if (argValue.isLeft()) { + return argValue + } + argValues.push(argValue.unwrap()) + } + + const result = operatorFunction(ctx, argValues) + if (userDefined) { + ctx.recorder.onUserOperatorReturn(expr, argValues, result) + } + return result + } + } + case 'let': { + // First, we create a cached value (a register with optional value) for the definition in this let expression + let cachedValue = builder.scopedCachedValues.get(expr.opdef.id) + if (!cachedValue) { + // TODO: check if either this is always the case or never the case. + cachedValue = { value: undefined } + builder.scopedCachedValues.set(expr.opdef.id, cachedValue) + } + // Then, we build the expression for the let body. It will use the lookup table and, every time it needs the value + // for the definition under the let, it will use the cached value (or eval a new value and store it). + const bodyEval = buildExpr(builder, expr.expr) + return ctx => { + const result = bodyEval(ctx) + // After evaluating the whole let expression, we clear the cached value, as it is no longer in scope. + // The next time the whole let expression is evaluated, the definition will be re-evaluated. + cachedValue!.value = undefined + return result + } + } + } +} + +/** + * Builds the application function for a given Quint application. + * + * This function first checks if the application corresponds to a user-defined operator. + * If it does, it retrieves the corresponding evaluation function. If the operator is a built-in, + * it retrieves the built-in lambda function. The resulting function evaluates the operator + * with the given context and arguments. + * + * @param builder - The Builder instance + * @param app - The Quint application expression to evaluate. + * + * @returns A function that takes a context and arguments, and returns either a QuintError or a RuntimeValue. + */ +function buildApp( + builder: Builder, + app: QuintApp +): (ctx: Context, args: RuntimeValue[]) => Either { + const def = builder.table.get(app.id)! + if (!def) { + // If it is not in the lookup table, it must be a builtin operator + return builtinLambda(app.opcode) + } + + const defEval = buildDef(builder, def) + return (ctx, args) => { + return defEval(ctx).chain(lambda => { + const arrow = lambda.toArrow() + return arrow(ctx, args) + }) + } +} + +/** + * Constructs a fully qualified name by combining the given name with the namespaces. + * + * The namespaces are reversed and joined with the name using the "::" delimiter. + * + * @param name - The name to be qualified. + * @param namespaces - A list of namespaces to be included in the fully qualified name. + * + * @returns The fully qualified name as a string. + */ +export function nameWithNamespaces(name: string, namespaces: List): string { + const revertedNamespaces = namespaces.reverse() + return revertedNamespaces.push(name).join('::') +} diff --git a/quint/src/runtime/impl/builtins.ts b/quint/src/runtime/impl/builtins.ts new file mode 100644 index 000000000..ca8ebbfd2 --- /dev/null +++ b/quint/src/runtime/impl/builtins.ts @@ -0,0 +1,901 @@ +/* ---------------------------------------------------------------------------------- + * Copyright 2022-2024 Informal Systems + * Licensed under the Apache License, Version 2.0. + * See LICENSE in the project root for license information. + * --------------------------------------------------------------------------------- */ + +/** + * Definitions on how to evaluate Quint builtin operators and values. + * + * The definitions are heavily based on the original `compilerImpl.ts` file written by Igor Konnov. + * + * @author Igor Konnov, Gabriela Moreira + * + * @module + */ + +import { Either, left, mergeInMany, right } from '@sweet-monads/either' +import { QuintError } from '../../quintError' +import { List, Map, Range, Set } from 'immutable' +import { isFalse, isTrue } from './evaluator' +import { Context } from './Context' +import { RuntimeValue, rv } from './runtimeValue' +import { chunk, times } from 'lodash' +import { expressionToString } from '../../ir/IRprinting' +import { zerog } from '../../idGenerator' +import { QuintApp } from '../../ir/quintIr' +import { prettyQuintEx, terminalWidth } from '../../graphics' +import { format } from '../../prettierimp' +import { EvalFunction } from './builder' + +/** + * Evaluates the given Quint builtin value by its name. + * + * This function is responsible for handling the evaluation of builtin values + * (operators that do not take parameters). It returns an `EvalFunction` + * which, when executed, provides the corresponding `RuntimeValue` or an error. + * + * The supported builtin values are: + * - 'Bool': Returns a set containing boolean values `true` and `false`. + * - 'Int': Returns an infinite set representing all integers. + * - 'Nat': Returns an infinite set representing all natural numbers. + * - 'q::lastTrace': Returns the list of the last trace from the context. + * + * If the provided name does not match any of the supported builtin values, + * it returns an error indicating the unknown builtin. + * + * @param name - The name of the builtin value to evaluate. + * @returns An `EvalFunction` that evaluates to the corresponding `RuntimeValue` or an error. + */ +export function builtinValue(name: string): EvalFunction { + switch (name) { + case 'Bool': + return _ => right(rv.mkSet([rv.mkBool(false), rv.mkBool(true)])) + case 'Int': + return _ => right(rv.mkInfSet('Int')) + case 'Nat': + return _ => right(rv.mkInfSet('Nat')) + case 'q::lastTrace': + return ctx => right(rv.mkList(ctx.trace.get())) + default: + return _ => left({ code: 'QNT404', message: `Unknown builtin ${name}` }) + } +} + +/** + * A list of operators that must be evaluated lazily. + * These operators cannot have their arguments evaluated before their own evaluation for various reasons: + * - Short-circuit operators (e.g., `and`, `or`, `implies`) where evaluation stops as soon as the result is determined. + * - Conditional operators (e.g., `ite`, `matchVariant`) where only certain arguments are evaluated based on conditions. + * - Repetition operators (e.g., `reps`) where the number of repetitions is unknown before evaluation. + * - Operators that interact with state variables in a special way (e.g., `assign`, `next`). + * - Operators where we can save resources (e.g., using `#pick()` in `oneOf` instead of enumerating the set). + */ +export const lazyOps = [ + 'assign', + 'actionAny', + 'actionAll', + 'ite', + 'matchVariant', + 'oneOf', + 'and', + 'or', + 'next', + 'implies', + 'then', + 'reps', + 'expect', +] + +/** + * Evaluates the given lazy builtin operator by its name. + * + * This function handles the evaluation of lazy builtin operators, + * which require special handling as described in the `lazyOps` documentation. + * It returns a function that takes a context and a list of evaluation functions, + * and returns the result of evaluating the operator. + * + * If the provided operator does not match any of the supported lazy operators, + * it returns an error indicating the unknown operator. + * + * @param op - The name of the lazy builtin operator to evaluate. + * @returns A function that evaluates the operator with the given context and arguments. + */ +export function lazyBuiltinLambda( + op: string +): (ctx: Context, args: EvalFunction[]) => Either { + switch (op) { + case 'and': + // Short-circuit logical AND + return (ctx, args) => { + for (const arg of args) { + const result = arg(ctx) + if (!isTrue(result)) { + return result + } + } + return right(rv.mkBool(true)) + } + case 'or': + // Short-circuit logical OR + return (ctx, args) => { + for (const arg of args) { + const result = arg(ctx) + if (!isFalse(result)) { + return result + } + } + return right(rv.mkBool(false)) + } + case 'implies': + // Short-circuit logical implication + return (ctx, args) => { + return args[0](ctx).chain(l => { + if (!l.toBool()) { + return right(rv.mkBool(true)) + } + + return args[1](ctx) + }) + } + + case 'actionAny': { + // Executes any of the given actions. + // First, we filter actions so that we only consider those that are enabled. + // Then, we use `rand()` to pick one of the enabled actions. + const app: QuintApp = { id: 0n, kind: 'app', opcode: 'actionAny', args: [] } + return (ctx, args) => { + const nextVarsSnapshot = ctx.varStorage.snapshot() + + const evaluationResults = args.map((arg, i) => { + // on `any`, we reset the action taken as the goal is to save the last + // action picked in an `any` call + ctx.varStorage.actionTaken = undefined + ctx.varStorage.nondetPicks.forEach((_, key) => { + ctx.varStorage.nondetPicks.set(key, undefined) + }) + + ctx.recorder.onAnyOptionCall(app, i) + const result = arg(ctx).map(result => { + // Save vars + const successor = ctx.varStorage.snapshot() + + return result.toBool() ? [{ snapshot: successor, index: i }] : [] + }) + ctx.recorder.onAnyOptionReturn(app, i) + + return result + }) + + const processedResults = mergeInMany(evaluationResults) + .map(suc => suc.flat()) + .mapLeft(errors => errors[0]) + + return processedResults.map(potentialSuccessors => { + switch (potentialSuccessors.length) { + case 0: + ctx.recorder.onAnyReturn(args.length, -1) + ctx.varStorage.recoverSnapshot(nextVarsSnapshot) + return rv.mkBool(false) + case 1: + ctx.recorder.onAnyReturn(args.length, potentialSuccessors[0].index) + ctx.varStorage.recoverSnapshot(potentialSuccessors[0].snapshot) + return rv.mkBool(true) + default: { + const choice = Number(ctx.rand(BigInt(potentialSuccessors.length))) + ctx.recorder.onAnyReturn(args.length, potentialSuccessors[choice].index) + ctx.varStorage.recoverSnapshot(potentialSuccessors[choice].snapshot) + return rv.mkBool(true) + } + } + }) + } + } + case 'actionAll': + // Executes all of the given actions, or none of them if any of them results in false. + return (ctx, args) => { + const nextVarsSnapshot = ctx.varStorage.snapshot() + for (const action of args) { + const result = action(ctx) + + if (result.isLeft()) { + return result + } + + if (isFalse(result)) { + ctx.varStorage.recoverSnapshot(nextVarsSnapshot) + return right(rv.mkBool(false)) + } + } + return right(rv.mkBool(true)) + } + case 'ite': + // if-then-else + return (ctx, args) => { + return args[0](ctx).chain(condition => { + return condition.toBool() ? args[1](ctx) : args[2](ctx) + }) + } + + case 'matchVariant': + // Pattern matching on variants + return (ctx, args) => { + const matchedEx = args[0] + return matchedEx(ctx).chain(expr => { + const [label, value] = expr.toVariant() + + const cases = args.slice(1) + + const caseForVariant = chunk(cases, 2).find(([caseLabel, _caseElim]) => { + const l = caseLabel(ctx).unwrap().toStr() + return l === '_' || l === label + }) + + if (!caseForVariant) { + return left({ code: 'QNT505', message: `No match for variant ${label}` }) + } + + const [_caseLabel, caseElim] = caseForVariant + return caseElim(ctx).chain(elim => elim.toArrow()(ctx, [value])) + }) + } + + case 'oneOf': + // Randomly selects one element of the set. + return (ctx, args) => { + return args[0](ctx).chain(set => { + const bounds = set.bounds() + const positions: Either = mergeInMany( + bounds.map((b): Either => { + if (b.isJust()) { + const sz = b.value + + if (sz === 0n) { + return left({ code: 'QNT509', message: `Applied oneOf to an empty set` }) + } + return right(ctx.rand(sz)) + } else { + // An infinite set, pick an integer from the range [-2^255, 2^255). + // Note that pick on Nat uses the absolute value of the passed integer. + // TODO: make it a configurable parameter: + // https://github.com/informalsystems/quint/issues/279 + return right(-(2n ** 255n) + ctx.rand(2n ** 256n)) + } + }) + ).mapLeft(errors => errors[0]) + + return positions.chain(ps => set.pick(ps.values())) + }) + } + case 'then': + // Compose two actions, executing the second one only if the first one results in true. + return (ctx, args) => { + const oldState = ctx.varStorage.asRecord() + return args[0](ctx).chain(firstResult => { + if (!firstResult.toBool()) { + return left({ + code: 'QNT513', + message: `Cannot continue in A.then(B), A evaluates to 'false'`, + }) + } + + ctx.shift() + const newState = ctx.varStorage.asRecord() + ctx.recorder.onNextState(oldState, newState) + + return args[1](ctx) + }) + } + case 'reps': + // Repeats the given action n times, stopping if the action evaluates to false. + return (ctx, args) => { + return args[0](ctx).chain(n => { + let result: Either = right(rv.mkBool(true)) + for (let i = 0; i < Number(n.toInt()); i++) { + result = args[1](ctx).chain(value => value.toArrow()(ctx, [rv.mkInt(i)])) + if (result.isLeft()) { + return result + } + + if (isFalse(result)) { + return left({ + code: 'QNT513', + message: `Reps loop could not continue after iteration #${i + 1} evaluated to false`, + }) + } + + // Don't shift after the last one + if (i < Number(n.toInt()) - 1) { + ctx.shift() + } + } + return result + }) + } + case 'expect': + // Translate A.expect(P): + // - Evaluate A. + // - When A's result is 'false', emit a runtime error. + // - When A's result is 'true': + // - Commit the variable updates: Shift the primed variables to unprimed. + // - Evaluate `P`. + // - If `P` evaluates to `false`, emit a runtime error (similar to `assert`). + // - If `P` evaluates to `true`, rollback to the previous state and return `true`. + return (ctx, args) => { + const result: Either = args[0](ctx).chain(action => { + if (!action.toBool()) { + return left({ code: 'QNT508', message: 'Cannot continue to "expect"' }) + } + + const nextVarsSnapshot = ctx.varStorage.snapshot() + ctx.shift() + return args[1](ctx).chain(expectation => { + ctx.varStorage.recoverSnapshot(nextVarsSnapshot) + + if (!expectation.toBool()) { + return left({ code: 'QNT508', message: 'Expect condition does not hold true' }) + } + + return right(rv.mkBool(true)) + }) + }) + return result + } + + default: + return () => left({ code: 'QNT000', message: 'Unknown stateful op' }) + } +} + +/** + * Evaluates the given builtin operator by its name. + * + * This function handles the evaluation of builtin operators, + * which require the arguments to be pre-evaluated. It returns + * a function that takes a context and a list of runtime values, + * and returns the result of evaluating the operator. + * + * If the provided operator does not match any of the supported operators, + * it returns an error indicating the unknown operator. + * + * @param op - The name of the builtin operator to evaluate. + * @returns A function that evaluates the operator with the given context and arguments. + */ +export function builtinLambda(op: string): (ctx: Context, args: RuntimeValue[]) => Either { + switch (op) { + case 'Set': + // Constructs a set from the given arguments. + return (_, args) => right(rv.mkSet(args)) + case 'Rec': + // Constructs a record from the given arguments. Arguments are lists like [key1, value1, key2, value2, ...] + return (_, args) => right(rv.mkRecord(Map(chunk(args, 2).map(([k, v]) => [k.toStr(), v])))) + case 'List': + // Constructs a list from the given arguments. + return (_, args) => right(rv.mkList(List(args))) + case 'Tup': + // Constructs a tuple from the given arguments. + return (_, args) => right(rv.mkTuple(List(args))) + case 'Map': + // Constructs a map from the given arguments. Arguments are lists like [[key1, value1], [key2, value2], ...] + return (_, args) => right(rv.mkMap(args.map(kv => kv.toTuple2()))) + case 'variant': + // Constructs a variant from the given arguments. + return (_, args) => right(rv.mkVariant(args[0].toStr(), args[1])) + case 'not': + // Logical negation + return (_, args) => right(rv.mkBool(!args[0].toBool())) + case 'iff': + // Logical equivalence/bi-implication + return (_, args) => right(rv.mkBool(args[0].toBool() === args[1].toBool())) + case 'eq': + // Equality + return (_, args) => right(rv.mkBool(args[0].equals(args[1]))) + case 'neq': + // Inequality + return (_, args) => right(rv.mkBool(!args[0].equals(args[1]))) + case 'iadd': + // Integer addition + return (_, args) => right(rv.mkInt(args[0].toInt() + args[1].toInt())) + case 'isub': + // Integer subtraction + return (_, args) => right(rv.mkInt(args[0].toInt() - args[1].toInt())) + case 'imul': + // Integer multiplication + return (_, args) => right(rv.mkInt(args[0].toInt() * args[1].toInt())) + case 'idiv': + // Integer division + return (_, args) => { + const divisor = args[1].toInt() + if (divisor === 0n) { + return left({ code: 'QNT503', message: `Division by zero` }) + } + return right(rv.mkInt(args[0].toInt() / divisor)) + } + case 'imod': + // Integer modulus + return (_, args) => right(rv.mkInt(args[0].toInt() % args[1].toInt())) + case 'ipow': + // Integer exponentiation + return (_, args) => { + const base = args[0].toInt() + const exp = args[1].toInt() + if (base === 0n && exp === 0n) { + return left({ code: 'QNT503', message: `0^0 is undefined` }) + } + if (exp < 0n) { + return left({ code: 'QNT503', message: 'i^j is undefined for j < 0' }) + } + + return right(rv.mkInt(base ** exp)) + } + case 'iuminus': + // Integer unary minus + return (_, args) => right(rv.mkInt(-args[0].toInt())) + case 'ilt': + // Integer less than + return (_, args) => right(rv.mkBool(args[0].toInt() < args[1].toInt())) + case 'ilte': + // Integer less than or equal to + return (_, args) => right(rv.mkBool(args[0].toInt() <= args[1].toInt())) + case 'igt': + // Integer greater than + return (_, args) => right(rv.mkBool(args[0].toInt() > args[1].toInt())) + case 'igte': + // Integer greater than or equal to + return (_, args) => right(rv.mkBool(args[0].toInt() >= args[1].toInt())) + + case 'item': + // Access a tuple: tuples are 1-indexed, that is, _1, _2, etc. + return (_, args) => { + return getListElem(args[0].toList(), Number(args[1].toInt()) - 1) + } + case 'tuples': + // A set of all possible tuples from the elements of the respective given sets. + return (_, args) => right(rv.mkCrossProd(args)) + + case 'range': + // Constructs a list of integers from start to end. + return (_, args) => { + const start = Number(args[0].toInt()) + const end = Number(args[1].toInt()) + return right(rv.mkList(List(Range(start, end).map(rv.mkInt)))) + } + + case 'nth': + // List access + return (_, args) => getListElem(args[0].toList(), Number(args[1].toInt())) + + case 'replaceAt': + // Replace an element at a given index in a list. + return (_, args) => { + const list = args[0].toList() + const idx = Number(args[1].toInt()) + if (idx < 0 || idx >= list.size) { + return left({ code: 'QNT510', message: `Out of bounds, replaceAt(${idx})` }) + } + + return right(rv.mkList(list.set(idx, args[2]))) + } + + case 'head': + // Get the first element of a list. Not allowed in empty lists. + return (_, args) => { + const list = args[0].toList() + if (list.size === 0) { + return left({ code: 'QNT505', message: `Called 'head' on an empty list` }) + } + return right(list.first()!) + } + + case 'tail': + // Get the tail (all elements but the head) of a list. Not allowed in empty lists. + return (_, args) => { + const list = args[0].toList() + if (list.size === 0) { + return left({ code: 'QNT505', message: `Called 'tail' on an empty list` }) + } + return right(rv.mkList(list.rest())) + } + + case 'slice': + // Get a sublist of a list from start to end. + return (_, args) => { + const list = args[0].toList() + const start = Number(args[1].toInt()) + const end = Number(args[2].toInt()) + if (start < 0 || start > end || end > list.size) { + return left({ + code: 'QNT506', + message: `slice(..., ${start}, ${end}) applied to a list of size ${list.size}`, + }) + } + + return right(rv.mkList(list.slice(start, end))) + } + + case 'length': + // The length of a list. + return (_, args) => right(rv.mkInt(args[0].toList().size)) + case 'append': + // Append an element to a list. + return (_, args) => right(rv.mkList(args[0].toList().push(args[1]))) + case 'concat': + // Concatenate two lists. + return (_, args) => right(rv.mkList(args[0].toList().concat(args[1].toList()))) + case 'indices': + // A set with the indices of a list. + return (_, args) => right(rv.mkInterval(0n, args[0].toList().size - 1)) + + case 'field': + // Access a field in a record. + return (_, args) => { + const field = args[1].toStr() + const result = args[0].toOrderedMap().get(field) + return result ? right(result) : left({ code: 'QNT501', message: `Accessing a missing record field ${field}` }) + } + + case 'fieldNames': + // A set with the field names of a record. + return (_, args) => right(rv.mkSet(args[0].toOrderedMap().keySeq().map(rv.mkStr))) + + case 'with': + // Replace a field value in a record. + return (_, args) => { + const record = args[0].toOrderedMap() + const field = args[1].toStr() + const value = args[2] + + if (!record.has(field)) { + return left({ code: 'QNT501', message: `Called 'with' with a non-existent field ${field}` }) + } + + return right(rv.mkRecord(record.set(field, value))) + } + + case 'powerset': + // The powerset of a set. + return (_, args) => right(rv.mkPowerset(args[0])) + case 'contains': + // Check if a set contains an element. + return (_, args) => right(rv.mkBool(args[0].contains(args[1]))) + case 'in': + // Check if an element is in a set. + return (_, args) => right(rv.mkBool(args[1].contains(args[0]))) + case 'subseteq': + // Check if a set is a subset of another set. + return (_, args) => right(rv.mkBool(args[0].isSubset(args[1]))) + case 'exclude': + // Set difference. + return (_, args) => right(rv.mkSet(args[0].toSet().subtract(args[1].toSet()))) + case 'union': + // Set union. + return (_, args) => right(rv.mkSet(args[0].toSet().union(args[1].toSet()))) + case 'intersect': + // Set intersection. + return (_, args) => right(rv.mkSet(args[0].toSet().intersect(args[1].toSet()))) + case 'size': + // The size of a set. + return (_, args) => args[0].cardinality().map(rv.mkInt) + case 'isFinite': + // at the moment, we support only finite sets, so just return true + return _args => right(rv.mkBool(true)) + + case 'to': + // Construct a set of integers from a to b. + return (_, args) => right(rv.mkInterval(args[0].toInt(), args[1].toInt())) + case 'fold': + // Fold a set + return (ctx, args) => applyFold('fwd', args[0].toSet(), args[1], arg => args[2].toArrow()(ctx, arg)) + case 'foldl': + // Fold a list from left to right. + return (ctx, args) => applyFold('fwd', args[0].toList(), args[1], arg => args[2].toArrow()(ctx, arg)) + case 'foldr': + // Fold a list from right to left. + return (ctx, args) => applyFold('rev', args[0].toList(), args[1], arg => args[2].toArrow()(ctx, arg)) + + case 'flatten': + // Flatten a set of sets. + return (_, args) => { + const s = args[0].toSet().map(s => s.toSet()) + return right(rv.mkSet(s.flatten(1) as Set)) + } + + case 'get': + // Get a value from a map. + return (_, args) => { + const map = args[0].toMap() + const key = args[1].normalForm() + const value = map.get(key) + if (value) { + return right(value) + } + + // Else, the key does not exist. Construct an informative error message. + const requestedKey = expressionToString(key.toQuintEx(zerog)) + const existingKeys = map + .toMap() + .keySeq() + .map(k => expressionToString(k.toQuintEx(zerog))) + .join(', ') + + return left({ + code: 'QNT507', + message: `Called 'get' with a non-existing key. Key is ${requestedKey}. Map has keys: ${existingKeys}`, + }) + } + + case 'set': + // Set a value for an existing key in a map. + return (_, args) => { + const map = args[0].toMap() + const key = args[1].normalForm() + if (!map.has(key)) { + return left({ code: 'QNT507', message: "Called 'set' with a non-existing key" }) + } + const value = args[2] + return right(rv.fromMap(map.set(key, value))) + } + + case 'put': + // Set a value for any key in a map. + return (_, args) => { + const map = args[0].toMap() + const key = args[1].normalForm() + const value = args[2] + return right(rv.fromMap(map.set(key, value))) + } + + case 'setBy': + // Set a value for an existing key in a map using a lambda over the current value. + return (ctx, args) => { + const map = args[0].toMap() + const key = args[1].normalForm() + if (!map.has(key)) { + return left({ code: 'QNT507', message: `Called 'setBy' with a non- existing key ${key}` }) + } + + const value = map.get(key)! + const lam = args[2].toArrow() + return lam(ctx, [value]).map(v => rv.fromMap(map.set(key, v))) + } + + case 'keys': + // A set with the keys of a map. + return (_, args) => right(rv.mkSet(args[0].toMap().keys())) + + case 'exists': + // Check if a predicate holds for some element in a set. + return (ctx, args) => + applyLambdaToSet(ctx, args[1], args[0]).map(values => rv.mkBool(values.some(v => v.toBool()) === true)) + + case 'forall': + // Check if a predicate holds for all elements in a set. + return (ctx, args) => + applyLambdaToSet(ctx, args[1], args[0]).map(values => rv.mkBool(values.every(v => v.toBool()) === true)) + + case 'map': + // Map a lambda over a set. + return (ctx, args) => { + return applyLambdaToSet(ctx, args[1], args[0]).map(values => rv.mkSet(values)) + } + + case 'filter': + // Filter a set using a lambda. + return (ctx, args) => { + const set = args[0].toSet() + const lam = args[1].toArrow() + + return filterElementsWithLambda(ctx, set, lam).map(result => rv.mkSet(result)) + } + + case 'select': + // Filter a list using a lambda + return (ctx, args) => { + const list = args[0].toList() + const lam = args[1].toArrow() + + return filterElementsWithLambda(ctx, list, lam).map(result => rv.mkList(result)) + } + + case 'mapBy': + // Construct a map by applying a lambda to the values of a set. + return (ctx, args) => { + const lambda = args[1].toArrow() + const keys = args[0].toSet() + const results: [RuntimeValue, RuntimeValue][] = [] + + for (const key of keys) { + const value = lambda(ctx, [key]) + if (value.isLeft()) { + return value + } + results.push([key.normalForm(), value.value]) + } + + return right(rv.fromMap(Map(results))) + } + + case 'setToMap': + // Convert a set of key-value tuples to a map. + return (_, args) => { + const set = args[0].toSet() + return right(rv.mkMap(Map(set.map(s => s.toTuple2())))) + } + + case 'setOfMaps': + // A set of all possible maps with keys and values from the given sets. + return (_, args) => right(rv.mkMapSet(args[0], args[1])) + + case 'fail': + // Expect a value to be false + return (_, args) => right(rv.mkBool(!args[0].toBool())) + case 'assert': + // Expect a value to be true, returning a runtime error if it is not + return (_, args) => (args[0].toBool() ? right(args[0]) : left({ code: 'QNT508', message: `Assertion failed` })) + + case 'allListsUpTo': + // Generate all lists of length up to the given number, from a set + return (_, args) => { + const set = args[0].toSet() + let lists: Set = Set([[]]) + let last_lists: Set = Set([[]]) + times(Number(args[1].toInt())).forEach(_length => { + // Generate all lists of length `length` from the set + const new_lists: Set = set.toSet().flatMap(value => { + // for each value in the set, append it to all lists of length `length - 1` + return last_lists.map(list => list.concat(value)) + }) + + lists = lists.merge(new_lists) + last_lists = new_lists + }) + + return right(rv.mkSet(lists.map(list => rv.mkList(list)).toOrderedSet())) + } + + case 'q::debug': + // Print a value to the console, and return it + return (_, args) => { + let columns = terminalWidth() + let valuePretty = format(columns, 0, prettyQuintEx(args[1].toQuintEx(zerog))) + console.log('>', args[0].toStr(), valuePretty.toString()) + return right(args[1]) + } + + // standard unary operators that are not handled by REPL + case 'allLists': + case 'chooseSome': + case 'always': + case 'eventually': + case 'enabled': + return _ => left({ code: 'QNT501', message: `Runtime does not support the built -in operator '${op}'` }) + + // builtin operators that are not handled by REPL + case 'orKeep': + case 'mustChange': + case 'weakFair': + case 'strongFair': + return _ => left({ code: 'QNT501', message: `Runtime does not support the built -in operator '${op}'` }) + + default: + return () => left({ code: 'QNT000', message: `Unknown builtin ${op}` }) + } +} + +/** + * Applies a lambda function to each element in a set. + * + * If the lambda function returns an error for any element, the function + * will return that error immediately. + * + * @param ctx - The context in which to evaluate the lambda function. + * @param lambda - The lambda function to apply to each element in the set. + * @param set - The set of elements to which the lambda function will be applied. + * @returns A set of the results of applying the lambda function to each element in the set, + * or an error if the lambda function returns an error for any element. + */ +function applyLambdaToSet( + ctx: Context, + lambda: RuntimeValue, + set: RuntimeValue +): Either> { + const f = lambda.toArrow() + const elements = set.toSet() + const results = [] + + // Apply using a for so we exit early if we get a left + for (const element of elements) { + const result = f(ctx, [element]) + if (result.isLeft()) { + return left(result.value) + } + results.push(result.value) + } + + return right(Set(results)) +} + +/** + * Filters elements of an iterable using a lambda function. + * + * If the lambda function returns an error for any element, the function + * will return that error immediately. + * + * @param ctx - The context in which to evaluate the lambda function. + * @param elements - The iterable of elements to be filtered. + * @param lam - The lambda function to apply to each element in the iterable. + * @returns An array of elements for which the lambda function returns true, + * or an error if the lambda function returns an error for any element. + */ +function filterElementsWithLambda( + ctx: Context, + elements: Iterable, + lam: (ctx: Context, args: RuntimeValue[]) => Either +): Either { + const result = [] + for (const element of elements) { + const value = lam(ctx, [element]) + if (value.isLeft()) { + return left(value.value) + } + if (value.value.toBool()) { + result.push(element) + } + } + return right(result) +} + +/** + * Applies a fold (reduce) operation to an iterable using a lambda function. + * + * If the lambda function returns an error for any pair of elements, the function + * will return that error immediately. + * + * @param order - The order in which to apply the fold ('fwd' for forward, 'rev' for reverse). + * @param iterable - The iterable of elements to be folded. + * @param initial - The initial value for the fold operation. + * @param lambda - The lambda function to apply to each pair of elements. + * @returns The accumulated result of applying the lambda function to the elements of the iterable, + * or an error if the lambda function returns an error for any pair of elements. + */ +function applyFold( + order: 'fwd' | 'rev', + iterable: Iterable, + initial: RuntimeValue, + lambda: (args: RuntimeValue[]) => Either +): Either { + const reducer = (acc: Either, val: RuntimeValue) => { + return acc.chain(accValue => { + if (order === 'fwd') { + return lambda([accValue, val]) + } else { + return lambda([val, accValue]) + } + }) + } + + const array = Array.from(iterable) + if (order === 'fwd') { + return array.reduce(reducer, right(initial)) + } else { + return array.reduceRight(reducer, right(initial)) + } +} + +/** + * Accesses an element in a list by its index. + * + * @param list - The list of elements. + * @param idx - The index of the element to access. + * @returns The element at the specified index, or an error if the index is out of bounds. + */ +function getListElem(list: List, idx: number): Either { + if (idx >= 0n && idx < list.size) { + const elem = list.get(Number(idx)) + if (elem) { + return right(elem) + } + } + + return left({ code: 'QNT510', message: `Out of bounds, nth(${idx})` }) +} diff --git a/quint/src/runtime/impl/compilerImpl.ts b/quint/src/runtime/impl/compilerImpl.ts deleted file mode 100644 index 7c7f6cee3..000000000 --- a/quint/src/runtime/impl/compilerImpl.ts +++ /dev/null @@ -1,1647 +0,0 @@ -/* - * Compiler of Quint expressions and definitions to Computable values - * that can be evaluated in the runtime. - * - * Igor Konnov, Gabriela Moreira, 2022-2024 - * - * Copyright 2022-2024 Informal Systems - * Licensed under the Apache License, Version 2.0. - * See LICENSE in the project root for license information. - */ - -import { strict as assert } from 'assert' -import { Maybe, just, none } from '@sweet-monads/maybe' -import { OrderedMap, Set } from 'immutable' -import { Presets, SingleBar } from 'cli-progress' - -import { LookupTable } from '../../names/base' -import { IRVisitor } from '../../ir/IRVisitor' -import { - Callable, - Computable, - ComputableKind, - EvaluationResult, - Register, - fail, - kindName, - mkCallable, - mkRegister, -} from '../runtime' - -import { ExecutionListener } from '../trace' - -import * as ir from '../../ir/quintIr' - -import { RuntimeValue, RuntimeValueLambda, RuntimeValueVariant, rv } from './runtimeValue' -import { Trace } from './trace' -import { ErrorCode, QuintError, quintErrorToString } from '../../quintError' - -import { inputDefName, lastTraceName } from '../compile' -import { prettyQuintEx, terminalWidth } from '../../graphics' -import { format } from '../../prettierimp' -import { unreachable } from '../../util' -import { zerog } from '../../idGenerator' -import { chunk, times } from 'lodash' -import { Either, left, mergeInMany, right } from '@sweet-monads/either' -import { applyBoolOp, applyFold, applyFun, getListElem, mapLambdaThenReduce } from './operatorEvaluator' -import { - CompilerErrorTracker, - EvaluationState, - mkConstComputable, - mkFunComputable, - specialNames, - toMaybe, -} from './base' -import { updateList } from './operatorEvaluator' -import { sliceList } from './operatorEvaluator' - -/** - * Compiler visitor turns Quint definitions and expressions into Computable - * objects, essentially, lazy JavaScript objects. Importantly, it does not do - * any evaluation during the translation and thus delegates the actual - * computation to the JavaScript engine. Since many of Quint operators may be - * computationally expensive, it is crucial to maintain this separation of - * compilation vs. computation. - * - * This class does not do any dynamic type checking, assuming that the type - * checker will be run before the translation in the future. As we do not have - * the type checker yet, computations may fail with weird JavaScript errors. - */ -export class CompilerVisitor implements IRVisitor { - // the lookup table to use for the module - private lookupTable: LookupTable - // the stack of computable values - private compStack: Computable[] = [] - // The map of identifiers (and sometimes, names) to their compiled values: - // - wrappers around RuntimeValue - // - an instance of Register - // - an instance of Callable. - // The keys should be constructed via `kindName`. - context: Map - - // all variables declared during compilation - private vars: Register[] - // the registers allocated for the next-state values of vars - private nextVars: Register[] - // keeps errors in a state - private errorTracker: CompilerErrorTracker - // pre-initialized random number generator - private rand - // execution listener - private execListener: ExecutionListener - // a tracker for the current execution trace - private trace: Trace - - // whether to track `actionTaken` and `nondetPicks` - private storeMetadata: boolean - // the chosen action in the last `any` evaluation - private actionTaken: Maybe = none() - // a record with nondet definition names as fields and their last chosen value as values - private nondetPicks: RuntimeValue // initialized at constructor - - // the current depth of operator definitions: top-level defs are depth 0 - // FIXME(#1279): The walk* functions update this value, but they need to be - // initialized to -1 here for that to work on all scenarios. - definitionDepth: number = -1 - - constructor( - lookupTable: LookupTable, - rand: (bound: bigint) => bigint, - evaluationState: EvaluationState, - storeMetadata: boolean - ) { - this.lookupTable = lookupTable - this.rand = rand - this.storeMetadata = storeMetadata - - this.context = evaluationState.context - this.vars = evaluationState.vars - this.nextVars = evaluationState.nextVars - this.errorTracker = evaluationState.errorTracker - this.execListener = evaluationState.listener - this.trace = evaluationState.trace - - this.nondetPicks = this.emptyNondetPicks() - } - - /** - * Get the compiler state. - */ - getEvaluationState(): EvaluationState { - return { - context: this.context, - vars: this.vars, - nextVars: this.nextVars, - errorTracker: this.errorTracker, - listener: this.execListener, - trace: this.trace, - } - } - - /** - * Get the names of the compiled variables. - */ - getVars(): string[] { - return this.vars.map(r => r.name) - } - - /** - * Get the array of compile errors, which changes as the code gets executed. - */ - getCompileErrors(): QuintError[] { - return this.errorTracker.compileErrors - } - - /** - * Get the array of runtime errors, which changes as the code gets executed. - */ - getRuntimeErrors(): QuintError[] { - return this.errorTracker.runtimeErrors - } - - exitOpDef(opdef: ir.QuintOpDef) { - // Either a runtime value, or a def, action, etc. - // All of them are compiled to callables, which may have zero parameters. - let boundValue = this.compStack.pop() as Callable - if (boundValue === undefined) { - this.errorTracker.addCompileError(opdef.id, 'QNT501', `No expression for ${opdef.name} on compStack`) - return - } - - if (opdef.qualifier === 'action' && opdef.expr.kind !== 'lambda') { - // A nullary action like `init` or `step`. - // It is not handled via applyUserDefined. - // Wrap this value with the listener calls. - // Importantly, we do not touch the original boundValue, but decorate it. - // Consider the following definitions: - // action input1 = step - // action input2 = step - // - // Both input1 and input2 wrap step, but in their individual computables. - const unwrappedValue = boundValue - const app: ir.QuintApp = { id: opdef.id, kind: 'app', opcode: opdef.name, args: [] } - const evalApp: ir.QuintApp = { id: 0n, kind: 'app', opcode: '_', args: [app] } - boundValue = { - eval: () => { - if (this.actionTaken.isNone()) { - this.actionTaken = just(rv.mkStr(opdef.name)) - } - - if (app.opcode === inputDefName) { - this.execListener.onUserOperatorCall(evalApp) - // do not call onUserOperatorReturn on '_' later, as it may span over multiple frames - } else { - this.execListener.onUserOperatorCall(app) - } - const r = unwrappedValue.eval() - this.execListener.onUserOperatorReturn(app, [], toMaybe(r)) - return r - }, - nparams: unwrappedValue.nparams, - } - } - - if (this.definitionDepth === 0 && opdef.qualifier === 'pureval') { - // a pure value may be cached, once evaluated - const originalEval = boundValue.eval - let cache: EvaluationResult | undefined = undefined - boundValue.eval = () => { - if (cache !== undefined) { - return cache - } else { - cache = originalEval() - return cache - } - } - } - - if (opdef.qualifier === 'action' && opdef.expr.kind === 'lambda') { - const unwrappedValue = boundValue - boundValue = { - eval: (args?: Either[]) => { - if (this.actionTaken.isNone()) { - this.actionTaken = just(rv.mkStr(opdef.name)) - } - - return unwrappedValue.eval(args) - }, - nparams: unwrappedValue.nparams, - } - } - - const kname = kindName('callable', opdef.id) - // bind the callable from the stack - this.context.set(kname, boundValue) - - if (specialNames.includes(opdef.name)) { - // bind the callable under its name as well - this.context.set(kindName('callable', opdef.name), boundValue) - } - } - - exitLet(letDef: ir.QuintLet) { - // When dealing with a `val` or `nondet`, freeze the callable under - // the let definition. Otherwise, forms like 'nondet x = oneOf(S); A' - // may produce multiple values for the same name 'x' - // inside a single evaluation of A. - // In case of `val`, this is simply an optimization. - const qualifier = letDef.opdef.qualifier - if (qualifier !== 'val' && qualifier !== 'nondet') { - // a non-constant value, ignore - return - } - - // get the expression that is evaluated in the context of let. - const exprUnderLet = this.compStack.slice(-1).pop() - if (exprUnderLet === undefined) { - this.errorTracker.addCompileError( - letDef.opdef.id, - 'QNT501', - `No expression for ${letDef.opdef.name} on compStack` - ) - return - } - - const kname = kindName('callable', letDef.opdef.id) - const boundValue = this.context.get(kname) ?? fail - // Override the behavior of the expression under let: - // It precomputes the bound value and uses it in the evaluation. - // Once the evaluation is done, the value is reset, so that - // a new random value may be produced later. - const undecoratedEval = exprUnderLet.eval - const boundValueEval = boundValue.eval - exprUnderLet.eval = () => { - const cachedValue = boundValueEval() - boundValue.eval = function () { - return cachedValue - } - // compute the result and immediately reset the cache - const result = undecoratedEval() - - // Store the nondet picks after evaluation as we want to collect them while we move up the IR tree, - // to make sure all nondet values in scenarios like this are collected: - // action step = any { - // someAction, - // nondet time = oneOf(times) - // timeRelatedAction(time) - // } - // - // action timeRelatedAction(time) = any { AdvanceTime(time), RevertTime(time) } - - if (result.isRight() && qualifier === 'nondet') { - // A nondet value was just defined, save it in the nondetPicks record. - const value = rv.mkVariant('Some', cachedValue.value as RuntimeValue) - this.nondetPicks = rv.mkRecord(this.nondetPicks.toOrderedMap().set(letDef.opdef.name, value)) - } - boundValue.eval = boundValueEval - return result - } - } - - exitConst(cdef: ir.QuintConst) { - // all constants should be instantiated before running the simulator - const code: ErrorCode = 'QNT500' - const msg = `Uninitialized const ${cdef.name}. Use: import (${cdef.name}=).*` - this.errorTracker.addCompileError(cdef.id, code, msg) - } - - exitVar(vardef: ir.QuintVar) { - const varName = vardef.name - - // In the process of incremental compilation, we might revisit the same var - // definition. Don't overwrite the register if that happens. In some cases - // (with instances), the variable can have a different ID, but the same - // name. In that case, we assign the register with that name for the new ID. - if (this.context.has(kindName('var', varName))) { - const register = this.context.get(kindName('var', varName))! - this.context.set(kindName('var', vardef.id), register) - - if (this.context.has(kindName('nextvar', varName))) { - const register = this.context.get(kindName('nextvar', varName))! - this.context.set(kindName('nextvar', vardef.id), register) - } - - return - } - - // simply introduce two registers: - // one for the variable, and - // one for its next-state version - const prevRegister = mkRegister('var', varName, none(), { - code: 'QNT502', - message: `Variable ${varName} is not set`, - reference: vardef.id, - }) - this.vars.push(prevRegister) - // at the moment, we have to refer to variables both via id and name - this.context.set(kindName('var', varName), prevRegister) - this.context.set(kindName('var', vardef.id), prevRegister) - const nextRegister = mkRegister('nextvar', varName, none(), { - code: 'QNT502', - message: `${varName}' is not set`, - reference: vardef.id, - }) - this.nextVars.push(nextRegister) - // at the moment, we have to refer to variables both via id and name - this.context.set(kindName('nextvar', varName), nextRegister) - this.context.set(kindName('nextvar', vardef.id), nextRegister) - } - - enterLiteral(expr: ir.QuintBool | ir.QuintInt | ir.QuintStr) { - switch (expr.kind) { - case 'bool': - this.compStack.push(mkConstComputable(rv.mkBool(expr.value))) - break - - case 'int': - this.compStack.push(mkConstComputable(rv.mkInt(expr.value))) - break - - case 'str': - this.compStack.push(mkConstComputable(rv.mkStr(expr.value))) - break - - default: - unreachable(expr) - } - } - - enterName(name: ir.QuintName) { - if (name.name === lastTraceName) { - this.compStack.push(mkConstComputable(rv.mkList(this.trace.get()))) - return - } - // The name belongs to one of the objects: - // a shadow variable, a variable, an argument, a callable. - // The order is important, as defines the name priority. - const comp = - this.contextLookup(name.id, ['arg', 'var', 'callable']) ?? - // a backup case for Nat, Int, and Bool, and special names such as q::input - this.contextGet(name.name, ['arg', 'callable']) - if (comp) { - // this name has an associated computable object already - this.compStack.push(comp) - } else { - // this should not happen, due to the name resolver - this.errorTracker.addCompileError(name.id, 'QNT502', `Name ${name.name} not found`) - this.compStack.push(fail) - } - } - - exitApp(app: ir.QuintApp) { - if (!ir.isQuintBuiltin(app)) { - this.applyUserDefined(app) - } else { - switch (app.opcode) { - case 'next': - { - const register = this.compStack.pop() - if (register) { - const name = (register as Register).name - const nextvar = this.contextGet(name, ['nextvar']) - this.compStack.push(nextvar ?? fail) - } else { - this.errorTracker.addCompileError(app.id, 'QNT502', 'Operator next(...) needs one argument') - this.compStack.push(fail) - } - } - break - - case 'assign': - this.translateAssign(app.id) - break - - case 'eq': - this.applyFun(app.id, 2, (x, y) => right(rv.mkBool(x.equals(y)))) - break - - case 'neq': - this.applyFun(app.id, 2, (x, y) => right(rv.mkBool(!x.equals(y)))) - break - - // conditional - case 'ite': - this.translateIfThenElse(app.id) - break - - // Booleans - case 'not': - this.applyFun(app.id, 1, p => right(rv.mkBool(!p.toBool()))) - break - - case 'and': - // a conjunction over expressions is lazy - this.translateBoolOp(app, rv.mkBool(true), (_, r) => (!r ? rv.mkBool(false) : undefined)) - break - - case 'actionAll': - this.translateAllOrThen(app) - break - - case 'or': - // a disjunction over expressions is lazy - this.translateBoolOp(app, rv.mkBool(false), (_, r) => (r ? rv.mkBool(true) : undefined)) - break - - case 'actionAny': - this.translateOrAction(app) - break - - case 'implies': - // an implication is lazy - this.translateBoolOp(app, rv.mkBool(false), (n, r) => (n == 0 && !r ? rv.mkBool(true) : undefined)) - break - - case 'iff': - this.applyFun(app.id, 2, (p, q) => right(rv.mkBool(p.toBool() === q.toBool()))) - break - - // integers - case 'iuminus': - this.applyFun(app.id, 1, n => right(rv.mkInt(-n.toInt()))) - break - - case 'iadd': - this.applyFun(app.id, 2, (p, q) => right(rv.mkInt(p.toInt() + q.toInt()))) - break - - case 'isub': - this.applyFun(app.id, 2, (p, q) => right(rv.mkInt(p.toInt() - q.toInt()))) - break - - case 'imul': - this.applyFun(app.id, 2, (p, q) => right(rv.mkInt(p.toInt() * q.toInt()))) - break - - case 'idiv': - this.applyFun(app.id, 2, (p, q) => { - if (q.toInt() !== 0n) { - return right(rv.mkInt(p.toInt() / q.toInt())) - } else { - return left({ code: 'QNT503', message: 'Division by zero', reference: app.id }) - } - }) - break - - case 'imod': - this.applyFun(app.id, 2, (p, q) => right(rv.mkInt(p.toInt() % q.toInt()))) - break - - case 'ipow': - this.applyFun(app.id, 2, (p, q) => { - if (q.toInt() == 0n && p.toInt() == 0n) { - return left({ code: 'QNT503', message: '0^0 is undefined', reference: app.id }) - } else if (q.toInt() < 0n) { - return left({ code: 'QNT503', message: 'i^j is undefined for j < 0', reference: app.id }) - } else { - return right(rv.mkInt(p.toInt() ** q.toInt())) - } - }) - break - - case 'igt': - this.applyFun(app.id, 2, (p, q) => right(rv.mkBool(p.toInt() > q.toInt()))) - break - - case 'ilt': - this.applyFun(app.id, 2, (p, q) => right(rv.mkBool(p.toInt() < q.toInt()))) - break - - case 'igte': - this.applyFun(app.id, 2, (p, q) => right(rv.mkBool(p.toInt() >= q.toInt()))) - break - - case 'ilte': - this.applyFun(app.id, 2, (p, q) => right(rv.mkBool(p.toInt() <= q.toInt()))) - break - - case 'Tup': - // Construct a tuple from an array of values - this.applyFun(app.id, app.args.length, (...values: RuntimeValue[]) => right(rv.mkTuple(values))) - break - - case 'item': - // Access a tuple: tuples are 1-indexed, that is, _1, _2, etc. - this.applyFun(app.id, 2, (tuple, idx) => getListElem(tuple.toList(), Number(idx.toInt()) - 1)) - break - - case 'tuples': - // Construct a cross product - this.applyFun(app.id, app.args.length, (...sets: RuntimeValue[]) => right(rv.mkCrossProd(sets))) - break - - case 'List': - // Construct a list from an array of values - this.applyFun(app.id, app.args.length, (...values: RuntimeValue[]) => right(rv.mkList(values))) - break - - case 'range': - this.applyFun(app.id, 2, (start, end) => { - const [s, e] = [Number(start.toInt()), Number(end.toInt())] - if (s <= e) { - const arr: RuntimeValue[] = [] - for (let i = s; i < e; i++) { - arr.push(rv.mkInt(BigInt(i))) - } - return right(rv.mkList(arr)) - } else { - return left({ code: 'QNT504', message: `range(${s}, ${e}) is out of bounds` }) - } - }) - break - - case 'nth': - // Access a list - this.applyFun(app.id, 2, (list, idx) => getListElem(list.toList(), Number(idx.toInt()))) - break - - case 'replaceAt': - this.applyFun(app.id, 3, (list, idx, value) => updateList(list.toList(), Number(idx.toInt()), value)) - break - - case 'head': - this.applyFun(app.id, 1, list => getListElem(list.toList(), 0)) - break - - case 'tail': - this.applyFun(app.id, 1, list => { - const l = list.toList() - if (l.size > 0) { - return sliceList(l, 1, l.size) - } else { - return left({ code: 'QNT505', message: 'Applied tail to an empty list' }) - } - }) - break - - case 'slice': - this.applyFun(app.id, 3, (list, start, end) => { - const [l, s, e] = [list.toList(), Number(start.toInt()), Number(end.toInt())] - if (s >= 0 && s <= l.size && e <= l.size && e >= s) { - return sliceList(l, s, e) - } else { - return left({ code: 'QNT506', message: `slice(..., ${s}, ${e}) applied to a list of size ${l.size}` }) - } - }) - break - - case 'length': - this.applyFun(app.id, 1, list => right(rv.mkInt(BigInt(list.toList().size)))) - break - - case 'append': - this.applyFun(app.id, 2, (list, elem) => right(rv.mkList(list.toList().push(elem)))) - break - - case 'concat': - this.applyFun(app.id, 2, (list1, list2) => right(rv.mkList(list1.toList().concat(list2.toList())))) - break - - case 'indices': - this.applyFun(app.id, 1, list => right(rv.mkInterval(0n, BigInt(list.toList().size - 1)))) - break - - case 'Rec': - // Construct a record - this.applyFun(app.id, app.args.length, (...values: RuntimeValue[]) => { - const keys = values.filter((e, i) => i % 2 === 0).map(k => k.toStr()) - const map: OrderedMap = keys.reduce((map, key, i) => { - const v = values[2 * i + 1] - return v ? map.set(key, v) : map - }, OrderedMap()) - return right(rv.mkRecord(map)) - }) - break - - case 'field': - // Access a record via the field name - this.applyFun(app.id, 2, (rec, fieldName) => { - const name = fieldName.toStr() - const fieldValue = rec.toOrderedMap().get(name) - if (fieldValue) { - return right(fieldValue) - } else { - return left({ code: 'QNT501', message: `Accessing a missing record field ${name}` }) - } - }) - break - - case 'fieldNames': - this.applyFun(app.id, 1, rec => { - const keysAsRuntimeValues = rec - .toOrderedMap() - .keySeq() - .map(key => rv.mkStr(key)) - return right(rv.mkSet(keysAsRuntimeValues)) - }) - break - - case 'with': - // update a record - this.applyFun(app.id, 3, (rec, fieldName, fieldValue) => { - const oldMap = rec.toOrderedMap() - const key = fieldName.toStr() - if (oldMap.has(key)) { - const newMap = rec.toOrderedMap().set(key, fieldValue) - return right(rv.mkRecord(newMap)) - } else { - return left({ code: 'QNT501', message: `Called 'with' with a non-existent key ${key}` }) - } - }) - break - - case 'variant': - // Construct a variant of a sum type. - this.applyFun(app.id, 2, (labelName, value) => right(rv.mkVariant(labelName.toStr(), value))) - break - - case 'matchVariant': - this.applyFun(app.id, app.args.length, (variantExpr, ...cases) => { - // Type checking ensures that this is a variant expression - assert(variantExpr instanceof RuntimeValueVariant, 'invalid value in match expression') - const label = variantExpr.label - const value = variantExpr.value - - // Find the eliminator marked with the variant's label - let result: EvaluationResult | undefined - for (const [caseLabel, caseElim] of chunk(cases, 2)) { - const caseLabelStr = caseLabel.toStr() - if (caseLabelStr === '_' || caseLabelStr === label) { - // Type checking ensures the second item of each case is a lambda - assert(caseElim instanceof RuntimeValueLambda, 'invalid eliminator in match expression') - const eliminator = caseElim as RuntimeValueLambda - result = eliminator.eval([right(value)]).map(r => r as RuntimeValue) - break - } - } - // Type checking ensures we have cases for every possible variant of a sum type. - assert(result, 'non-exhaustive match expression') - - return result - }) - break - - case 'Set': - // Construct a set from an array of values. - this.applyFun(app.id, app.args.length, (...values: RuntimeValue[]) => right(rv.mkSet(values))) - break - - case 'powerset': - this.applyFun(app.id, 1, (baseset: RuntimeValue) => right(rv.mkPowerset(baseset))) - break - - case 'contains': - this.applyFun(app.id, 2, (set, value) => right(rv.mkBool(set.contains(value)))) - break - - case 'in': - this.applyFun(app.id, 2, (value, set) => right(rv.mkBool(set.contains(value)))) - break - - case 'subseteq': - this.applyFun(app.id, 2, (l, r) => right(rv.mkBool(l.isSubset(r)))) - break - - case 'union': - this.applyFun(app.id, 2, (l, r) => right(rv.mkSet(l.toSet().union(r.toSet())))) - break - - case 'intersect': - this.applyFun(app.id, 2, (l, r) => right(rv.mkSet(l.toSet().intersect(r.toSet())))) - break - - case 'exclude': - this.applyFun(app.id, 2, (l, r) => right(rv.mkSet(l.toSet().subtract(r.toSet())))) - break - - case 'size': - this.applyFun(app.id, 1, set => set.cardinality().map(rv.mkInt)) - break - - case 'isFinite': - // at the moment, we support only finite sets, so just return true - this.applyFun(app.id, 1, _set => right(rv.mkBool(true))) - break - - case 'to': - this.applyFun(app.id, 2, (i, j) => right(rv.mkInterval(i.toInt(), j.toInt()))) - break - - case 'fold': - this.applyFold(app.id, 'fwd') - break - - case 'foldl': - this.applyFold(app.id, 'fwd') - break - - case 'foldr': - this.applyFold(app.id, 'rev') - break - - case 'flatten': - this.applyFun(app.id, 1, set => { - // unpack the sets from runtime values - const setOfSets = set.toSet().map(e => e.toSet()) - // and flatten the set of sets via immutable-js - return right(rv.mkSet(setOfSets.flatten(1) as Set)) - }) - break - - case 'get': - // Get a map value - this.applyFun(app.id, 2, (map, key) => { - const value = map.toMap().get(key.normalForm()) - if (value) { - return right(value) - } else { - // Should we print the key? It may be a complex expression. - return left({ code: 'QNT507', message: "Called 'get' with a non-existing key" }) - } - }) - break - - case 'set': - // Update a map value - this.applyFun(app.id, 3, (map, key, newValue) => { - const normalKey = key.normalForm() - const asMap = map.toMap() - if (asMap.has(normalKey)) { - const newMap = asMap.set(normalKey, newValue) - return right(rv.fromMap(newMap)) - } else { - return left({ code: 'QNT507', message: "Called 'set' with a non-existing key" }) - } - }) - break - - case 'put': - // add a value to a map - this.applyFun(app.id, 3, (map, key, newValue) => { - const normalKey = key.normalForm() - const asMap = map.toMap() - const newMap = asMap.set(normalKey, newValue) - return right(rv.fromMap(newMap)) - }) - break - - case 'setBy': { - // Update a map value via a lambda - const fun = this.compStack.pop() ?? fail - this.applyFun(app.id, 2, (map, key) => { - const normalKey = key.normalForm() - const asMap = map.toMap() - if (asMap.has(normalKey)) { - return fun.eval([right(asMap.get(normalKey))]).map(newValue => { - const newMap = asMap.set(normalKey, newValue as RuntimeValue) - return rv.fromMap(newMap) - }) - } else { - return left({ code: 'QNT507', message: "Called 'setBy' with a non-existing key" }) - } - }) - break - } - - case 'keys': - // map keys as a set - this.applyFun(app.id, 1, map => { - return right(rv.mkSet(map.toMap().keys())) - }) - break - - case 'oneOf': - this.applyOneOf(app.id) - break - - case 'exists': - this.mapLambdaThenReduce(app.id, set => rv.mkBool(set.find(([result, _]) => result.toBool()) !== undefined)) - break - - case 'forall': - this.mapLambdaThenReduce(app.id, set => rv.mkBool(set.find(([result, _]) => !result.toBool()) === undefined)) - break - - case 'map': - this.mapLambdaThenReduce(app.id, array => rv.mkSet(array.map(([result, _]) => result))) - break - - case 'filter': - this.mapLambdaThenReduce(app.id, arr => rv.mkSet(arr.filter(([r, _]) => r.toBool()).map(([_, e]) => e))) - break - - case 'select': - this.mapLambdaThenReduce(app.id, arr => rv.mkList(arr.filter(([r, _]) => r.toBool()).map(([_, e]) => e))) - break - - case 'mapBy': - this.mapLambdaThenReduce(app.id, arr => rv.mkMap(arr.map(([v, k]) => [k, v]))) - break - - case 'Map': - this.applyFun(app.id, app.args.length, (...pairs: any[]) => right(rv.mkMap(pairs))) - break - - case 'setToMap': - this.applyFun(app.id, 1, (set: RuntimeValue) => - right( - rv.mkMap( - set.toSet().map(p => { - const arr = p.toList().toArray() - return [arr[0], arr[1]] - }) - ) - ) - ) - break - - case 'setOfMaps': - this.applyFun(app.id, 2, (dom, rng) => { - return right(rv.mkMapSet(dom, rng)) - }) - break - - case 'then': - this.translateAllOrThen(app) - break - - case 'fail': - this.applyFun(app.id, 1, result => { - return right(rv.mkBool(!result.toBool())) - }) - break - - case 'expect': - this.translateExpect(app) - break - - case 'assert': - this.applyFun(app.id, 1, cond => { - if (!cond.toBool()) { - return left({ code: 'QNT508', message: 'Assertion failed', reference: app.id }) - } - return right(cond) - }) - break - - case 'reps': - this.translateReps(app) - break - - case 'q::test': - // the special operator that runs random simulation - this.test(app.id) - break - - case 'q::testOnce': - // the special operator that runs random simulation - this.testOnce(app.id) - break - - case 'q::debug': - this.applyFun(app.id, 2, (msg, value) => { - let columns = terminalWidth() - let valuePretty = format(columns, 0, prettyQuintEx(value.toQuintEx(zerog))) - console.log('>', msg.toStr(), valuePretty.toString()) - return right(value) - }) - break - - case 'allListsUpTo': - this.applyFun(app.id, 2, (set: RuntimeValue, max_length: RuntimeValue) => { - let lists: Set = Set([[]]) - let last_lists: Set = Set([[]]) - times(Number(max_length.toInt())).forEach(_length => { - // Generate all lists of length `length` from the set - const new_lists: Set = set.toSet().flatMap(value => { - // for each value in the set, append it to all lists of length `length - 1` - return last_lists.map(list => list.concat(value)) - }) - - lists = lists.merge(new_lists) - last_lists = new_lists - }) - - return right(rv.mkSet(lists.map(list => rv.mkList(list)).toOrderedSet())) - }) - break - - // standard unary operators that are not handled by REPL - case 'allLists': - case 'chooseSome': - case 'always': - case 'eventually': - case 'enabled': - this.applyFun(app.id, 1, _ => { - return left({ code: 'QNT501', message: `Runtime does not support the built-in operator '${app.opcode}'` }) - }) - break - - // builtin operators that are not handled by REPL - case 'orKeep': - case 'mustChange': - case 'weakFair': - case 'strongFair': - this.applyFun(app.id, 2, _ => { - return left({ code: 'QNT501', message: `Runtime does not support the built-in operator '${app.opcode}'` }) - }) - break - - default: - unreachable(app.opcode) - } - } - } - - private applyUserDefined(app: ir.QuintApp) { - const onError = (sourceId: bigint, msg: string): void => { - const error: EvaluationResult = left({ code: 'QNT501', message: msg, reference: sourceId }) - this.errorTracker.addCompileError(sourceId, 'QNT501', msg) - this.compStack.push({ eval: () => error }) - } - - // look up for the operator to see, whether it's just an operator, or a parameter - const lookupEntry = this.lookupTable.get(app.id) - if (lookupEntry === undefined) { - return onError(app.id, `Called unknown operator ${app.opcode}`) - } - - // this function gives us access to the compiled operator later - let callableRef: () => Either - - if (lookupEntry.kind !== 'param') { - // The common case: the operator has been defined elsewhere. - // We simply look up for the operator and return it via callableRef. - const callable = this.contextLookup(app.id, ['callable']) as Callable - if (callable === undefined || callable.nparams === undefined) { - return onError(app.id, `Called unknown operator ${app.opcode}`) - } - callableRef = () => right(callable) - } else { - // The operator is a parameter of another operator. - // We do not have access to the operator yet. - let register = this.contextLookup(app.id, ['arg']) as Register - if (register === undefined) { - return onError(app.id, `Parameter ${app.opcode} is not found`) - } - // every time we need a Callable, we retrieve it from the register - callableRef = () => { - const result = register.registerValue.map(v => v as Callable) - if (result.isJust()) { - return right(result.value) - } else { - return left({ code: 'QNT501', message: `Parameter ${app.opcode} is not set` }) - } - } - } - - const nargs = app.args.length // === operScheme.type.args.length - const nactual = this.compStack.length - if (nactual < nargs) { - return onError(app.id, `Expected ${nargs} arguments for ${app.opcode}, found: ${nactual}`) - } else { - // pop nargs elements of the compStack - const args = this.compStack.splice(-nargs, nargs) - // Produce the new computable value. - // This code is similar to applyFun, but it calls the listener before - const comp = { - eval: () => { - this.execListener.onUserOperatorCall(app) - // compute the values of the arguments at this point - const values = args.map(arg => arg.eval()) - const result = callableRef().chain(callable => { - return callable.eval(values) - }) - mergeInMany(values).map(vs => this.execListener.onUserOperatorReturn(app, vs, toMaybe(result))) - return result - }, - } - this.compStack.push(comp) - } - } - - enterLambda(lam: ir.QuintLambda) { - // introduce a register for every parameter - lam.params.forEach(p => { - const register = mkRegister('arg', p.name, none(), { - code: 'QNT501', - message: `Parameter ${p} is not set`, - reference: p.id, - }) - this.context.set(kindName('arg', p.id), register) - - if (specialNames.includes(p.name)) { - this.context.set(kindName('arg', p.name), register) - } - }) - // After this point, the body of the lambda gets compiled. - // The body of the lambda may refer to the parameter via names, - // which are stored in the registers we've just created. - } - - exitLambda(lam: ir.QuintLambda) { - // The expression on the stack is the body of the lambda. - // Transform it to Callable together with the registers. - const registers: Register[] = [] - lam.params.forEach(p => { - const id = specialNames.includes(p.name) ? p.name : p.id - - const key = kindName('arg', id) - const register = this.contextGet(id, ['arg']) as Register - - if (register && register.registerValue) { - this.context.delete(key) - registers.push(register) - } else { - this.errorTracker.addCompileError(p.id, 'QNT501', `Parameter ${p.name} not found`) - } - }) - - const lambdaBody = this.compStack.pop() - if (lambdaBody) { - this.compStack.push(mkCallable(registers, lambdaBody)) - } else { - this.errorTracker.addCompileError(lam.id, 'QNT501', 'Compilation of lambda failed') - } - } - - private translateAssign(sourceId: bigint): void { - if (this.compStack.length < 2) { - this.errorTracker.addCompileError(sourceId, 'QNT501', `Assignment '=' needs two arguments`) - return - } - const [register, rhs] = this.compStack.splice(-2) - const name = (register as Register).name - if (name === undefined) { - this.errorTracker.addCompileError(sourceId, 'QNT501', `Assignment '=' applied to a non-variable`) - this.compStack.push(fail) - return - } - - const nextvar = this.contextGet(name, ['nextvar']) as Register - if (nextvar) { - this.compStack.push(rhs) - this.applyFun(sourceId, 1, value => { - nextvar.registerValue = just(value) - return right(rv.mkBool(true)) - }) - } else { - this.errorTracker.addCompileError(sourceId, 'QNT502', `Undefined next variable in ${name} = ...`) - this.compStack.push(fail) - } - } - - /** - * A generalized application of a one-argument Callable to a set-like - * runtime value, as required by `exists`, `forall`, `map`, and `filter`. - * - * This method expects `compStack` to look like follows: - * - * - `(top)` translated lambda, as `Callable`. - * - * - `(top - 1)`: a set-like value to iterate over, as `Computable`. - * - * The method evaluates the Callable for each element of the iterable value - * and either produces `none`, if evaluation failed for one of the elements, - * or it applies `mapResultAndElems` to the pairs that consists of the Callable - * result and the original element of the iterable value. - * The final result is stored on the stack. - */ - private mapLambdaThenReduce( - sourceId: bigint, - reduceFunction: (_array: Array<[RuntimeValue, RuntimeValue]>) => RuntimeValue - ): void { - this.popArgs(2) - .map(args => mapLambdaThenReduce(sourceId, reduceFunction, args)) - .map(comp => this.compStack.push(comp)) - .mapLeft(e => this.errorTracker.addRuntimeError(sourceId, e)) - } - - /** - * Translate one of the operators: fold, foldl, and foldr. - */ - private applyFold(sourceId: bigint, order: 'fwd' | 'rev'): void { - this.popArgs(3) - .map(args => applyFold(order, args)) - .map(comp => this.compStack.push(comp)) - .mapLeft(e => this.errorTracker.addRuntimeError(sourceId, e)) - } - - // pop nargs computable values, pass them the 'fun' function, and - // push the combined computable value on the stack - private applyFun(sourceId: bigint, nargs: number, fun: (..._args: RuntimeValue[]) => EvaluationResult) { - this.popArgs(nargs) - .map(args => applyFun(sourceId, fun, args)) - .map(comp => this.compStack.push(comp)) - .mapLeft(e => this.errorTracker.addRuntimeError(sourceId, e)) - } - - private popArgs(nargs: number): Either { - if (this.compStack.length < nargs) { - return left({ code: 'QNT501', message: 'Not enough arguments' }) - } - return right(this.compStack.splice(-nargs, nargs)) - } - - // if-then-else requires special treatment, - // as it should not evaluate both arms - private translateIfThenElse(sourceId: bigint) { - if (this.compStack.length < 3) { - this.errorTracker.addCompileError(sourceId, 'QNT501', 'Not enough arguments') - } else { - // pop 3 elements of the compStack - const [cond, thenArm, elseArm] = this.compStack.splice(-3, 3) - // produce the new computable value - const comp = { - eval: () => { - // compute the values of the arguments at this point - // TODO: register errors - const v = cond.eval().map(pred => (pred.equals(rv.mkBool(true)) ? thenArm.eval() : elseArm.eval())) - return v.join() - }, - } - this.compStack.push(comp) - } - } - - /** - * Compute all { ... } or A.then(B)...then(E) for a chain of actions. - * @param actions actions as computable to execute - * @param kind is it 'all { ... }' or 'A.then(B)'? - * @param actionId given the action index, return the id that produced this action - * @returns evaluation result - */ - private chainAllOrThen( - actions: Computable[], - kind: 'all' | 'then', - actionId: (idx: number) => bigint - ): EvaluationResult { - // save the values of the next variables, as actions may update them - const savedValues = this.snapshotNextVars() - const savedTrace = this.trace.get() - - let result: EvaluationResult = right(rv.mkBool(true)) - // Evaluate arguments iteratively. - // Stop as soon as one of the arguments returns false. - // This is a form of Boolean short-circuiting. - let nactionsLeft = actions.length - for (const action of actions) { - nactionsLeft-- - result = action.eval() - const isFalse = result.isRight() && !(result.value as RuntimeValue).toBool() - if (result.isLeft() || isFalse) { - // As soon as one of the arguments does not evaluate to true, - // break out of the loop. - // Restore the values of the next variables, - // as evaluation was not successful. - this.recoverNextVars(savedValues) - this.trace.reset(savedTrace) - - if (kind === 'then' && nactionsLeft > 0 && isFalse) { - // Cannot extend a run. Emit an error message. - const actionNo = actions.length - (nactionsLeft + 1) - result = left({ - code: 'QNT513', - message: `Cannot continue in A.then(B), A evaluates to 'false'`, - reference: actionId(actionNo), - }) - return result - } else { - return result - } - } - - // switch to the next frame, when implementing A.then(B) - if (kind === 'then' && nactionsLeft > 0) { - const oldState: RuntimeValue = this.varsToRecord() - this.shiftVars() - const newState: RuntimeValue = this.varsToRecord() - this.trace.extend(newState) - this.execListener.onNextState(oldState, newState) - } - } - - return result - } - - // translate all { A, ..., C } or A.then(B) - private translateAllOrThen(app: ir.QuintApp): void { - const kind = app.opcode === 'then' ? 'then' : 'all' - - this.popArgs(app.args.length) - .map(args => { - const lazyComputable = () => this.chainAllOrThen(args, kind, idx => app.args[idx].id) - return this.compStack.push(mkFunComputable(lazyComputable)) - }) - .mapLeft(e => this.errorTracker.addRuntimeError(app.id, e)) - } - - // Translate A.expect(P): - // - Evaluate A. - // - When A's result is 'false', emit a runtime error. - // - When A's result is 'true': - // - Commit the variable updates: Shift the primed variables to unprimed. - // - Evaluate `P`. - // - If `P` evaluates to `false`, emit a runtime error (similar to `assert`). - // - If `P` evaluates to `true`, rollback to the previous state and return `true`. - private translateExpect(app: ir.QuintApp): void { - // The code below is an adaption of chainAllOrThen. - // If someone finds how to nicely combine both, please refactor. - if (this.compStack.length !== 2) { - this.errorTracker.addCompileError(app.id, 'QNT501', `Not enough arguments on stack for "${app.opcode}"`) - return - } - const [action, pred] = this.compStack.splice(-2) - const lazyCompute = (): EvaluationResult => { - const savedNextVars = this.snapshotNextVars() - const savedTrace = this.trace.get() - const actionResult = action.eval() - if (actionResult.isLeft() || !(actionResult.value as RuntimeValue).toBool()) { - // 'A' evaluates to 'false', or produces an error. - // Restore the values of the next variables. - this.recoverNextVars(savedNextVars) - this.trace.reset(savedTrace) - // expect emits an error when the run could not finish - return left({ code: 'QNT508', message: 'Cannot continue to "expect"', reference: app.args[0].id }) - } else { - const savedVarsAfterAction = this.snapshotVars() - const savedNextVarsAfterAction = this.snapshotNextVars() - const savedTraceAfterAction = this.trace.get() - // Temporarily, switch to the next frame, to make a look-ahead evaluation. - // For example, if `x == 1` and `x' == 2`, we would have `x == 2` and `x'` would be undefined. - this.shiftVars() - // evaluate P - const predResult = pred.eval() - // Recover the assignments to unprimed and primed variables. - // E.g., we recover variables to `x == 1` and `x' == 2` in the above example. - // This lets us combine the result of `expect` with other actions via `then`. - // For example: `A.expect(P).then(B)`. - this.recoverVars(savedVarsAfterAction) - this.recoverNextVars(savedNextVarsAfterAction) - this.trace.reset(savedTraceAfterAction) - if (predResult.isLeft() || !(predResult.value as RuntimeValue).toBool()) { - return left({ code: 'QNT508', message: 'Expect condition does not hold true', reference: app.args[1].id }) - } - return predResult - } - } - - this.compStack.push(mkFunComputable(lazyCompute)) - } - - // translate n.reps(A) - private translateReps(app: ir.QuintApp): void { - if (this.compStack.length < 2) { - this.errorTracker.addCompileError(app.id, 'QNT501', `Not enough arguments on stack for "${app.opcode}"`) - return - } - const [niterations, action] = this.compStack.splice(-2) - - const lazyCompute = () => { - // compute the number of iterations and repeat 'action' that many times - return niterations - .eval() - .map(num => { - const n = Number((num as RuntimeValue).toInt()) - const indices = [...Array(n).keys()] - const actions = indices.map(i => { - return { - eval: () => { - return action.eval([right(rv.mkInt(BigInt(i)))]) - }, - } - }) - // In case the case of reps, we have multiple copies of the same action. - // This is why all occurrences have the same id. - return this.chainAllOrThen(actions, 'then', _ => app.args[1].id) - }) - .join() - } - - this.compStack.push(mkFunComputable(lazyCompute)) - } - - // translate one of the Boolean operators with short-circuiting: - // - or { A, ..., C } - // - and { A, ..., C } - // - A implies B - private translateBoolOp( - app: ir.QuintApp, - defaultValue: RuntimeValue, - shortCircuit: (no: number, r: boolean) => RuntimeValue | undefined - ): void { - this.popArgs(app.args.length) - .map(args => applyBoolOp(defaultValue, shortCircuit, args)) - .map(comp => this.compStack.push(comp)) - .mapLeft(e => this.errorTracker.addRuntimeError(app.id, e)) - } - - // translate any { A, ..., C } - private translateOrAction(app: ir.QuintApp): void { - if (this.compStack.length < app.args.length) { - this.errorTracker.addCompileError(app.id, 'QNT501', 'Not enough arguments on stack for "any"') - return - } - const args = this.compStack.splice(-app.args.length) - - // According to the semantics of action-level disjunctions, - // we have to find out which branches are enabled and pick one of them - // non-deterministically. Instead of modeling non-determinism, - // we use a random number generator. This may change in the future. - const lazyCompute = () => { - // on `any`, we reset the action taken as the goal is to save the last - // action picked in an `any` call - this.actionTaken = none() - // we also reset nondet picks as they are collected when we move up the - // tree, and this is now a leaf - this.nondetPicks = this.emptyNondetPicks() - - // save the values of the next variables, as actions may update them - const valuesBefore = this.snapshotNextVars() - // we store the potential successor values in this array - const successors: Maybe[][] = [] - const successorIndices: number[] = [] - // Evaluate arguments iteratively. - args.forEach((arg, i) => { - this.recoverNextVars(valuesBefore) - // either the argument is evaluated to false, or fails - this.execListener.onAnyOptionCall(app, i) - const result = arg.eval().or(right(rv.mkBool(false))) - const boolResult = (result.unwrap() as RuntimeValue).toBool() - this.execListener.onAnyOptionReturn(app, i) - // if this arm evaluates to true, save it in the candidates - if (boolResult === true) { - successors.push(this.snapshotNextVars()) - successorIndices.push(i) - } - }) - - const ncandidates = successors.length - let choice - if (ncandidates === 0) { - // no successor: restore the state and return false - this.recoverNextVars(valuesBefore) - this.execListener.onAnyReturn(args.length, -1) - return right(rv.mkBool(false)) - } else if (ncandidates === 1) { - // There is exactly one successor, the execution is deterministic. - // No need for randomization. This may reduce the number of tests. - choice = 0 - } else { - // randomly pick a successor and return true - choice = Number(this.rand(BigInt(ncandidates))) - } - - this.recoverNextVars(successors[choice]) - this.execListener.onAnyReturn(args.length, successorIndices[choice]) - return right(rv.mkBool(true)) - } - - this.compStack.push(mkFunComputable(lazyCompute)) - } - - // Apply the operator oneOf - private applyOneOf(sourceId: bigint) { - this.applyFun(sourceId, 1, set => { - const bounds = set.bounds() - const positions: Either = mergeInMany( - bounds.map((b): Either => { - if (b.isJust()) { - const sz = b.value - - if (sz === 0n) { - return left({ code: 'QNT509', message: `Applied oneOf to an empty set` }) - } - return right(this.rand(sz)) - } else { - // An infinite set, pick an integer from the range [-2^255, 2^255). - // Note that pick on Nat uses the absolute value of the passed integer. - // TODO: make it a configurable parameter: - // https://github.com/informalsystems/quint/issues/279 - return right(-(2n ** 255n) + this.rand(2n ** 256n)) - } - }) - ).mapLeft(errors => ({ code: 'QNT501', message: errors.map(quintErrorToString).join('\n') })) - - return positions.chain(ps => set.pick(ps.values())) - }) - } - - private test(sourceId: bigint) { - if (this.compStack.length < 6) { - this.errorTracker.addCompileError(sourceId, 'QNT501', 'Not enough arguments on stack for "q::test"') - return - } - - const [nruns, nsteps, ntraces, init, next, inv] = this.compStack.splice(-6) - this.runTestSimulation(nruns, nsteps, ntraces, init, next, inv) - } - - private testOnce(sourceId: bigint) { - if (this.compStack.length < 5) { - this.errorTracker.addCompileError(sourceId, 'QNT501', 'Not enough arguments on stack for "q::testOnce"') - return - } - - const [nsteps, ntraces, init, next, inv] = this.compStack.splice(-5) - const nruns = mkConstComputable(rv.mkInt(1n)) - this.runTestSimulation(nruns, nsteps, ntraces, init, next, inv) - } - - // The simulator core: produce multiple random runs - // and check the given state invariant (state assertion). - // - // Technically, this is similar to the implementation of folds. - // However, it also restores the state and saves a trace, if there is any. - private runTestSimulation( - nrunsComp: Computable, - nstepsComp: Computable, - ntracesComp: Computable, - init: Computable, - next: Computable, - inv: Computable - ) { - const doRun = (): EvaluationResult => { - return mergeInMany([nrunsComp, nstepsComp, ntracesComp].map(c => c.eval())) - .mapLeft((errors): QuintError => { - return { code: 'QNT501', message: errors.map(quintErrorToString).join('\n') } - }) - .chain(([nrunsRes, nstepsRes, nTracesRes]) => { - const isTrue = (res: EvaluationResult) => { - return !res.isLeft() && (res.value as RuntimeValue).toBool() === true - } - // a failure flag for the case a runtime error is found - let failure = false - // counter for errors found - let errorsFound = 0 - // save the registers to recover them later - const vars = this.snapshotVars() - const nextVars = this.snapshotNextVars() - // do multiple runs, stop at the first failing run - const nruns = (nrunsRes as RuntimeValue).toInt() - const ntraces = (nTracesRes as RuntimeValue).toInt() - - const bar = new SingleBar( - { - clearOnComplete: true, - forceRedraw: true, - format: 'Running... [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} samples', - }, - Presets.rect - ) - - bar.start(Number(nruns), 0) - - for (let runNo = 0; errorsFound < ntraces && !failure && runNo < nruns; runNo++) { - bar.update(runNo) - - this.execListener.onRunCall() - this.trace.reset() - // check Init() - const initApp: ir.QuintApp = { id: 0n, kind: 'app', opcode: 'q::initAndInvariant', args: [] } - this.execListener.onUserOperatorCall(initApp) - const initResult = init.eval() - failure = initResult.isLeft() || failure - if (!isTrue(initResult)) { - this.execListener.onUserOperatorReturn(initApp, [], toMaybe(initResult)) - } else { - // The initial action evaluates to true. - // Our guess of values was good. - this.shiftVars() - this.trace.extend(this.varsToRecord()) - // check the invariant Inv - const invResult = inv.eval() - this.execListener.onUserOperatorReturn(initApp, [], toMaybe(initResult)) - failure = invResult.isLeft() || failure - if (!isTrue(invResult)) { - errorsFound++ - } else { - // check all { Next(), shift(), Inv } in a loop - const nsteps = (nstepsRes as RuntimeValue).toInt() - for (let i = 0; errorsFound < ntraces && !failure && i < nsteps; i++) { - const nextApp: ir.QuintApp = { - id: 0n, - kind: 'app', - opcode: 'q::stepAndInvariant', - args: [], - } - this.execListener.onUserOperatorCall(nextApp) - const nextResult = next.eval() - failure = nextResult.isLeft() || failure - if (isTrue(nextResult)) { - this.shiftVars() - this.trace.extend(this.varsToRecord()) - errorsFound += isTrue(inv.eval()) ? 0 : 1 - this.execListener.onUserOperatorReturn(nextApp, [], toMaybe(nextResult)) - } else { - // Otherwise, the run cannot be extended. - // In some cases, this may indicate a deadlock. - // Since we are doing random simulation, it is very likely - // that we have not generated good values for extending - // the run. Hence, do not report an error here, but simply - // drop the run. Otherwise, we would have a lot of false - // positives, which look like deadlocks but they are not. - this.execListener.onUserOperatorReturn(nextApp, [], toMaybe(nextResult)) - this.execListener.onRunReturn(just(rv.mkBool(true)), this.trace.get()) - break - } - } - } - } - const outcome = !failure ? just(rv.mkBool(errorsFound == 0)) : none() - this.execListener.onRunReturn(outcome, this.trace.get()) - // recover the state variables - this.recoverVars(vars) - this.recoverNextVars(nextVars) - } // end of a single random run - - bar.stop() - - // finally, return true, if no error was found - return !failure ? right(rv.mkBool(errorsFound == 0)) : left({ code: 'QNT501', message: 'Simulation failure' }) - }) - } - this.compStack.push(mkFunComputable(doRun)) - } - - // convert the current variable values to a record - private varsToRecord(): RuntimeValue { - const map: [string, RuntimeValue][] = this.vars - .filter(r => r.registerValue.isJust()) - .map(r => [r.name, r.registerValue.value as RuntimeValue]) - - if (this.storeMetadata) { - if (this.actionTaken.isJust()) { - map.push(['action_taken', this.actionTaken.value!]) - map.push(['nondet_picks', this.nondetPicks]) - } - } - - return rv.mkRecord(map) - } - - private shiftVars() { - this.recoverVars(this.snapshotNextVars()) - this.nextVars.forEach(r => (r.registerValue = none())) - } - - // save the values of the vars into an array - private snapshotVars(): Maybe[] { - return this.vars.map(r => r.registerValue).concat([this.actionTaken, just(this.nondetPicks)]) - } - - // save the values of the next vars into an array - private snapshotNextVars(): Maybe[] { - return this.nextVars.map(r => r.registerValue).concat([this.actionTaken, just(this.nondetPicks)]) - } - - // load the values of the variables from an array - private recoverVars(values: Maybe[]) { - this.vars.forEach((r, i) => (r.registerValue = values[i])) - this.actionTaken = values[this.vars.length] ?? none() - this.nondetPicks = values[this.vars.length + 1].unwrap() - } - - // load the values of the next variables from an array - private recoverNextVars(values: Maybe[]) { - this.nextVars.forEach((r, i) => (r.registerValue = values[i])) - this.actionTaken = values[this.vars.length] ?? none() - this.nondetPicks = values[this.vars.length + 1].unwrap() - } - - // The initial value of nondet picks should already have record fields for all - // nondet values so the type of `nondet_picks` is the same throughout the - // trace. The field values are initialized as None. - private emptyNondetPicks() { - const nondetNames = [...this.lookupTable.values()] - .filter(d => d.kind === 'def' && d.qualifier === 'nondet') - .map(d => d.name) - return rv.mkRecord(OrderedMap(nondetNames.map(n => [n, rv.mkVariant('None', rv.mkTuple([]))]))) - } - - private contextGet(name: string | bigint, kinds: ComputableKind[]) { - for (const k of kinds) { - const value = this.context.get(kindName(k, name)) - if (value) { - return value - } - } - - return undefined - } - - private contextLookup(id: bigint, kinds: ComputableKind[]) { - const vdef = this.lookupTable.get(id) - if (vdef) { - const refId = vdef.id! - for (const k of kinds) { - const value = this.context.get(kindName(k, refId)) - if (value) { - return value - } - } - } - - return undefined - } -} diff --git a/quint/src/runtime/impl/evaluator.ts b/quint/src/runtime/impl/evaluator.ts new file mode 100644 index 000000000..5001d522d --- /dev/null +++ b/quint/src/runtime/impl/evaluator.ts @@ -0,0 +1,410 @@ +/* ---------------------------------------------------------------------------------- + * Copyright 2022-2024 Informal Systems + * Licensed under the Apache License, Version 2.0. + * See LICENSE in the project root for license information. + * --------------------------------------------------------------------------------- */ + +/** + * An evaluator for Quint in Node TS runtime. + * + * Testing and simulation are heavily based on the original `compilerImpl.ts` file written by Igor Konnov. + * + * @author Igor Konnov, Gabriela Moreira + * + * @module + */ + +import { Either, left, right } from '@sweet-monads/either' +import { QuintApp, QuintEx } from '../../ir/quintIr' +import { LookupDefinition, LookupTable } from '../../names/base' +import { QuintError } from '../../quintError' +import { TraceRecorder } from '../trace' +import { Trace } from './trace' +import { RuntimeValue, rv } from './runtimeValue' +import { Context } from './Context' +import { TestResult } from '../testing' +import { Rng } from '../../rng' +import { zerog } from '../../idGenerator' +import { List } from 'immutable' +import { Builder, buildDef, buildExpr, nameWithNamespaces } from './builder' +import { Presets, SingleBar } from 'cli-progress' + +/** + * An evaluator for Quint in Node TS runtime. + */ +export class Evaluator { + public ctx: Context + public recorder: TraceRecorder + private rng: Rng + private builder: Builder + + /** + * Constructs an Evaluator that can be re-used across evaluations. + * + * @param table - The lookup table for definitions. + * @param recorder - The trace recorder to log evaluation traces. + * @param rng - The random number generator to use for evaluation. + * @param storeMetadata - Optional, whether to store `actionTaken` and `nondetPicks`. Default is false. + */ + constructor(table: LookupTable, recorder: TraceRecorder, rng: Rng, storeMetadata: boolean = false) { + this.recorder = recorder + this.rng = rng + this.builder = new Builder(table, storeMetadata) + this.ctx = new Context(recorder, rng.next, this.builder.varStorage) + } + + /** + * Get the current trace from the context + */ + get trace(): Trace { + return this.ctx.trace + } + + /** + * Update the lookup table, if the same table is used for multiple evaluations but there are new definitions. + * + * @param table + */ + updateTable(table: LookupTable) { + this.builder.table = table + } + + /** + * Shift the context to the next state. That is, updated variables in the next state are moved to the current state, + * and the trace is extended. + */ + shift(): void { + this.ctx.shift() + } + + /** + * Shift the context to the next state. That is, updated variables in the next state are moved to the current state, + * and the trace is extended. + * + * @returns names of the variables that don't have values in the new state. + */ + shiftAndCheck(): string[] { + const missing = this.ctx.varStorage.nextVars.filter(reg => reg.value.isLeft()) + + if (missing.size === this.ctx.varStorage.vars.size) { + // Nothing was changed, don't shift + return [] + } + + this.shift() + return missing + .valueSeq() + .map(reg => reg.name) + .toArray() + } + + /** + * Evaluate a Quint expression. + * + * @param expr + * @returns the result of the evaluation, if successful, or an error if the evaluation failed. + */ + evaluate(expr: QuintEx): Either { + if (expr.kind === 'app' && (expr.opcode == 'q::test' || expr.opcode === 'q::testOnce')) { + return this.evaluateSimulation(expr) + } + + const value = buildExpr(this.builder, expr)(this.ctx) + + return value.map(rv.toQuintEx) + } + + /** + * Reset the evaluator to its initial state (in terms of trace and variables) + */ + reset() { + this.trace.reset() + this.builder.varStorage.reset() + } + + /** + * Simulates the execution of an initial expression followed by a series of step expressions, + * while checking an invariant expression at each step. The simulation is run multiple times, + * up to a specified number of runs, and each run is executed for a specified number of steps. + * The simulation stops if a specified number of traces with errors are found. + * + * @param init - The initial expression to evaluate. + * @param step - The step expression to evaluate repeatedly. + * @param inv - The invariant expression to check after each step. + * @param nruns - The number of simulation runs to perform. + * @param nsteps - The number of steps to perform in each simulation run. + * @param ntraces - The number of error traces to collect before stopping the simulation. + * @returns a boolean expression indicating whether all simulations passed without errors, + or an error if the simulation cannot be completed. + */ + simulate( + init: QuintEx, + step: QuintEx, + inv: QuintEx, + nruns: number, + nsteps: number, + ntraces: number + ): Either { + let errorsFound = 0 + let failure: QuintError | undefined = undefined + + const progressBar = new SingleBar( + { + clearOnComplete: true, + forceRedraw: true, + format: 'Running... [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} samples', + }, + Presets.rect + ) + progressBar.start(Number(nruns), 0) + + const initEval = buildExpr(this.builder, init) + const stepEval = buildExpr(this.builder, step) + const invEval = buildExpr(this.builder, inv) + + for (let runNo = 0; errorsFound < ntraces && !failure && runNo < nruns; runNo++) { + progressBar.update(runNo) + + this.recorder.onRunCall() + this.reset() + // Mocked def for the trace recorder + const initApp: QuintApp = { id: 0n, kind: 'app', opcode: 'q::initAndInvariant', args: [] } + this.recorder.onUserOperatorCall(initApp) + + const initResult = initEval(this.ctx).mapLeft(error => (failure = error)) + if (!isTrue(initResult)) { + this.recorder.onUserOperatorReturn(initApp, [], initResult) + } else { + this.shift() + + const invResult = invEval(this.ctx).mapLeft(error => (failure = error)) + this.recorder.onUserOperatorReturn(initApp, [], invResult) + if (!isTrue(invResult)) { + errorsFound++ + } else { + // check all { step, shift(), inv } in a loop + for (let i = 0; errorsFound < ntraces && !failure && i < nsteps; i++) { + const stepApp: QuintApp = { + id: 0n, + kind: 'app', + opcode: 'q::stepAndInvariant', + args: [], + } + this.recorder.onUserOperatorCall(stepApp) + + const stepResult = stepEval(this.ctx).mapLeft(error => (failure = error)) + if (!isTrue(stepResult)) { + // The run cannot be extended. In some cases, this may indicate a deadlock. + // Since we are doing random simulation, it is very likely + // that we have not generated good values for extending + // the run. Hence, do not report an error here, but simply + // drop the run. Otherwise, we would have a lot of false + // positives, which look like deadlocks but they are not. + + this.recorder.onUserOperatorReturn(stepApp, [], stepResult) + this.recorder.onRunReturn(right(rv.mkBool(true)), this.trace.get()) + break + } + + this.shift() + + const invResult = invEval(this.ctx).mapLeft(error => (failure = error)) + if (!isTrue(invResult)) { + errorsFound++ + } + this.recorder.onUserOperatorReturn(stepApp, [], invResult) + } + } + } + + const outcome = failure ? left(failure) : right(rv.mkBool(errorsFound == 0)) + this.recorder.onRunReturn(outcome, this.trace.get()) + } + progressBar.stop() + + const outcome: Either = failure + ? left(failure) + : right({ id: 0n, kind: 'bool', value: errorsFound == 0 }) + + return outcome + } + + /** + * Run a specified test definition a given number of times, and report the result. + * + * @param testDef - The definition of the test to be run. + * @param maxSamples - The maximum number of times to run the test. + * @param onTrace - A callback function to be called with trace information for each test run. + * @returns The result of the test, including its name, status, any errors, the seed used, frames, + and the number of samples run. + */ + test( + testDef: LookupDefinition, + maxSamples: number, + onTrace: (name: string, status: string, vars: string[], states: QuintEx[]) => void + ): TestResult { + const name = nameWithNamespaces(testDef.name, List(testDef.namespaces)) + const progressBar = new SingleBar( + { + clearOnComplete: true, + forceRedraw: true, + format: ' {test} [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} samples', + }, + Presets.rect + ) + + progressBar.start(maxSamples, 0, { test: name }) + + this.trace.reset() + this.recorder.clear() + + // save the initial seed + let seed = this.rng.getState() + + const testEval = buildDef(this.builder, testDef) + + let nsamples = 1 + // run up to maxSamples, stop on the first failure + for (; nsamples <= maxSamples; nsamples++) { + progressBar.update(nsamples, { test: name }) + // record the seed value + seed = this.rng.getState() + this.recorder.onRunCall() + // reset the trace + this.reset() + // run the test + const result = testEval(this.ctx) + + // extract the trace + const trace = this.trace.get() + if (trace.length > 0) { + this.recorder.onRunReturn(result, trace) + } else { + this.recorder.onRunReturn(result, []) + } + + const states = this.recorder.bestTraces[0]?.frame?.args?.map(rv.toQuintEx) + const frames = this.recorder.bestTraces[0]?.frame?.subframes ?? [] + // evaluate the result + if (result.isLeft()) { + // if there was an error, return immediately + progressBar.stop() + return { + name, + status: 'failed', + errors: [result.value], + seed, + frames, + nsamples, + } + } + + const ex = result.value.toQuintEx(zerog) + if (ex.kind !== 'bool') { + // if the test returned a malformed result, return immediately + progressBar.stop() + return { + name, + status: 'ignored', + errors: [], + seed, + frames, + nsamples, + } + } + + if (!ex.value) { + // if the test returned false, return immediately + const error: QuintError = { + code: 'QNT511', + message: `Test ${name} returned false`, + reference: testDef.id, + } + onTrace(name, 'failed', this.varNames(), states) + progressBar.stop() + return { + name, + status: 'failed', + errors: [error], + seed, + frames, + nsamples, + } + } else { + if (this.rng.getState() === seed) { + // This successful test did not use non-determinism. + // Running it one time is sufficient. + + onTrace(name, 'passed', this.varNames(), states) + progressBar.stop() + return { + name, + status: 'passed', + errors: [], + seed: seed, + frames, + nsamples, + } + } + } + } + + // the test was run maxSamples times, and no errors were found + const states = this.recorder.bestTraces[0]?.frame?.args?.map(rv.toQuintEx) + const frames = this.recorder.bestTraces[0]?.frame?.subframes ?? [] + + onTrace(name, 'passed', this.varNames(), states) + + progressBar.stop() + return { + name, + status: 'passed', + errors: [], + seed: seed, + frames, + nsamples: nsamples - 1, + } + } + + /** + * Variable names in context + * @returns the names of all variables in the current context. + */ + varNames() { + return this.ctx.varStorage.vars + .valueSeq() + .toArray() + .map(v => v.name) + } + + /** + * Special case of `evaluate` where the expression is a call to a simulation. + * + * @param expr + * @returns the result of the simulation, or an error if the simulation cannot be completed. + */ + private evaluateSimulation(expr: QuintApp): Either { + if (expr.opcode === 'q::testOnce') { + const [nsteps, ntraces, init, step, inv] = expr.args + return this.simulate(init, step, inv, 1, toNumber(nsteps), toNumber(ntraces)) + } else { + const [nruns, nsteps, ntraces, init, step, inv] = expr.args + return this.simulate(init, step, inv, toNumber(nruns), toNumber(nsteps), toNumber(ntraces)) + } + } +} + +export function isTrue(value: Either): boolean { + return value.isRight() && value.value.toBool() === true +} + +export function isFalse(value: Either): boolean { + return value.isRight() && value.value.toBool() === false +} + +function toNumber(value: QuintEx): number { + if (value.kind !== 'int') { + throw new Error('Expected an integer') + } + return Number(value.value) +} diff --git a/quint/src/runtime/impl/operatorEvaluator.ts b/quint/src/runtime/impl/operatorEvaluator.ts deleted file mode 100644 index 3c22f17d0..000000000 --- a/quint/src/runtime/impl/operatorEvaluator.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* ---------------------------------------------------------------------------------- - * Copyright 2022-2024 Informal Systems - * Licensed under the Apache License, Version 2.0. - * See LICENSE in the project root for license information. - * --------------------------------------------------------------------------------- */ - -/** - * Stateless implementations for evaluating more complex operators, and helpers - * to operator evaluation. - * - * @author Igor Konnov, Gabriela Moreira - * - * @module - */ - -import { Either, left, mergeInMany, right } from '@sweet-monads/either' -import { Callable, Computable, EvaluationResult } from '../runtime' -import { RuntimeValue, rv } from './runtimeValue' -import { strict as assert } from 'assert' -import { List } from 'immutable' -import { QuintError, quintErrorToString } from '../../quintError' - -// pop nargs computable values, pass them the 'fun' function, and -// push the combined computable value on the stack -export function applyFun( - sourceId: bigint, - fun: (..._args: RuntimeValue[]) => EvaluationResult, - args: Computable[] -): Computable { - // produce the new computable value - return { - eval: () => { - try { - // compute the values of the arguments at this point - const values = args.map(a => a.eval()) - // if they are all defined, apply the function 'fun' to the arguments - return mergeInMany(values) - .mapLeft((errors): QuintError => ({ code: 'QNT501', message: errors.map(quintErrorToString).join('\n') })) - .chain(vs => fun(...vs.map(v => v as RuntimeValue))) - } catch (error) { - const msg = error instanceof Error ? error.message : 'unknown error' - return left({ code: 'QNT501', message: msg, reference: sourceId }) - } - }, - } -} - -export function applyFold(order: 'fwd' | 'rev', args: Computable[]): Computable { - const [iterableComp, initialComp, callableComp] = args - return { - eval: () => { - const iterableResult = iterableComp.eval() - const initialResult = initialComp.eval() - - const callable = callableComp as Callable - assert(callable.nparams === 2) - - if (iterableResult.isLeft()) { - return iterableResult - } - - const iterable = Array.from(iterableResult.value as RuntimeValue as Iterable) - - const reducer = (acc: EvaluationResult, val: RuntimeValue) => { - if (order === 'fwd') { - return callable.eval([acc, right(val)]) - } else { - return callable.eval([right(val), acc]) - } - } - - if (order === 'fwd') { - return iterable.reduce(reducer, initialResult) - } else { - return iterable.reduceRight(reducer, initialResult) - } - }, - } -} -// Access a list via an index -export function getListElem(list: List, idx: number): EvaluationResult { - if (idx >= 0n && idx < list.size) { - const elem = list.get(Number(idx)) - if (elem) { - return right(elem) - } - } - - return left({ code: 'QNT510', message: `Out of bounds, nth(${idx})` }) -} -// Update a list via an index -export function updateList(list: List, idx: number, value: RuntimeValue): EvaluationResult { - if (idx >= 0n && idx < list.size) { - return right(rv.mkList(list.set(Number(idx), value))) - } else { - return left({ code: 'QNT510', message: `Out of bounds, replaceAt(..., ${idx}, ...)` }) - } -} - -// slice a list -export function sliceList(list: List, start: number, end: number) { - return right(rv.mkList(list.slice(start, end))) -} - -// translate one of the Boolean operators with short-circuiting: -// - or { A, ..., C } -// - and { A, ..., C } -// - A implies B -export function applyBoolOp( - defaultValue: RuntimeValue, - shortCircuit: (no: number, r: boolean) => RuntimeValue | undefined, - args: Computable[] -): Computable { - return { - eval: () => { - let result: EvaluationResult = right(defaultValue) - // Evaluate arguments iteratively. - // Stop as soon as shortCircuit tells us to stop. - let no = 0 - for (const arg of args) { - // either the argument is evaluated to a Boolean, or fails - result = arg.eval() - if (result.isLeft()) { - return result - } - - // if shortCircuit returns a value, return the value immediately - const b = shortCircuit(no, (result.value as RuntimeValue).toBool()) - if (b) { - return right(b) - } - no += 1 - } - - return result - }, - } -} - -/** - * A generalized application of a one-argument Callable to a set-like - * runtime value, as required by `exists`, `forall`, `map`, and `filter`. - * - * This method expects `compStack` to look like follows: - * - * - `(top)` translated lambda, as `Callable`. - * - * - `(top - 1)`: a set-like value to iterate over, as `Computable`. - * - * The method evaluates the Callable for each element of the iterable value - * and either produces `none`, if evaluation failed for one of the elements, - * or it applies `mapResultAndElems` to the pairs that consists of the Callable - * result and the original element of the iterable value. - * The final result is stored on the stack. - */ -export function mapLambdaThenReduce( - sourceId: bigint, - reduceFunction: (_array: Array<[RuntimeValue, RuntimeValue]>) => RuntimeValue, - args: Computable[] -): Computable { - const [setComp, callableComp] = args - const callable = callableComp as Callable - // apply the lambda to a single element of the set - const evaluateElem = (elem: RuntimeValue): Either => { - // evaluate the predicate against the actual arguments - const result = callable.eval([right(elem)]) - return result.map(result => [result as RuntimeValue, elem]) - } - return applyFun( - sourceId, - (iterable: Iterable): EvaluationResult => { - const lambdaApplicationResults = mergeInMany(Array.from(iterable).map(value => evaluateElem(value))) - return lambdaApplicationResults - .mapLeft((errors): QuintError => { - return { code: 'QNT501', message: errors.map(quintErrorToString).join('\n') } - }) - .map(array => reduceFunction(array)) - }, - [setComp] - ) -} diff --git a/quint/src/runtime/impl/runtimeValue.ts b/quint/src/runtime/impl/runtimeValue.ts index c9f95f5bc..0957b1dfd 100644 --- a/quint/src/runtime/impl/runtimeValue.ts +++ b/quint/src/runtime/impl/runtimeValue.ts @@ -53,28 +53,24 @@ * whole new layer of abstraction. However, it is deeply rooted in the * semantics of Quint, which, similar to TLA+, heavily utilizes set operators. * - * Igor Konnov, 2022 + * Igor Konnov, Gabriela Moreira 2022-2024 * - * Copyright 2022 Informal Systems + * Copyright 2022-2024 Informal Systems * Licensed under the Apache License, Version 2.0. * See LICENSE in the project root for license information. */ -import { List, Map, OrderedMap, Set, ValueObject, hash, is as immutableIs } from 'immutable' +import { Map as ImmutableMap, List, OrderedMap, Set, ValueObject, hash, is as immutableIs } from 'immutable' import { Maybe, just, merge, none } from '@sweet-monads/maybe' import { strict as assert } from 'assert' -import { IdGenerator } from '../../idGenerator' +import { IdGenerator, zerog } from '../../idGenerator' import { expressionToString } from '../../ir/IRprinting' - -import { Callable, EvalResult } from '../runtime' -import { QuintEx } from '../../ir/quintIr' +import { QuintEx, QuintLambdaParameter, QuintName } from '../../ir/quintIr' import { QuintError, quintErrorToString } from '../../quintError' import { Either, left, mergeInMany, right } from '@sweet-monads/either' -import { toMaybe } from './base' - -/** The default entry point of this module */ -export default rv +import { EvalFunction } from './builder' +import { Context, Register } from './Context' /** * A factory of runtime values that should be used to instantiate new values. @@ -96,8 +92,8 @@ export const rv = { * @param value an integer value * @return a new runtime value that carries the integer value */ - mkInt: (value: bigint): RuntimeValue => { - return new RuntimeValueInt(value) + mkInt: (value: bigint | number): RuntimeValue => { + return new RuntimeValueInt(BigInt(value)) }, /** @@ -159,7 +155,7 @@ export const rv = { mkMap: (elems: Iterable<[RuntimeValue, RuntimeValue]>): RuntimeValue => { // convert the keys to the normal form, as they are hashed const arr: [RuntimeValue, RuntimeValue][] = Array.from(elems).map(([k, v]) => [k.normalForm(), v]) - return new RuntimeValueMap(Map(arr)) + return new RuntimeValueMap(ImmutableMap(arr)) }, /** @@ -168,7 +164,7 @@ export const rv = { * @param value an iterable collection of pairs of runtime values * @return a new runtime value that carries the map */ - fromMap: (map: Map): RuntimeValue => { + fromMap: (map: ImmutableMap): RuntimeValue => { // convert the keys to the normal form, as they are hashed return new RuntimeValueMap(map) }, @@ -211,8 +207,8 @@ export const rv = { * @param last the maximal poitn of the interval (inclusive) * @return a new runtime value that the interval */ - mkInterval: (first: bigint, last: bigint): RuntimeValue => { - return new RuntimeValueInterval(first, last) + mkInterval: (first: bigint | number, last: bigint | number): RuntimeValue => { + return new RuntimeValueInterval(BigInt(first), BigInt(last)) }, /** @@ -249,15 +245,52 @@ export const rv = { /** * Make a runtime value that represents a lambda. * - * @param nparams the number of lambda parameters - * @param callable a callable that evaluates the lambda + * @param params the lambda parameters + * @param body the lambda body expression * @returns a runtime value of lambda */ - mkLambda: (nparams: number, callable: Callable) => { - return new RuntimeValueLambda(nparams, callable) + mkLambda: (params: QuintLambdaParameter[], body: EvalFunction, paramRegistry: Map) => { + const registers = params.map(param => { + const register = paramRegistry.get(param.id)! + if (!register) { + const reg: Register = { value: left({ code: 'QNT501', message: `Parameter ${param.name} not set` }) } + paramRegistry.set(param.id, reg) + return reg + } + + return register + }) + + return new RuntimeValueLambda(body, registers) + }, + + /** + * Make a runtime value from a quint expression. + * @param ex - the Quint expression + * @returns a runtime value for the expression + */ + fromQuintEx: (ex: QuintEx): RuntimeValue => { + const v = fromQuintEx(ex) + if (v.isJust()) { + return v.value + } else { + throw new Error(`Cannot convert ${expressionToString(ex)} to a runtime value`) + } + }, + + /** + * Convert a runtime value to a Quint expression. + * @param value - the runtime value to convert + * @returns a Quint expression for the runtime value + */ + toQuintEx: (value: RuntimeValue): QuintEx => { + return value.toQuintEx(zerog) }, } +/** The default entry point of this module */ +export default rv + /** * Get a ground expression, that is, an expression * that contains only literals and constructors, and @@ -315,11 +348,20 @@ export function fromQuintEx(ex: QuintEx): Maybe { return just(rv.mkRecord(pairs)) } + case 'variant': { + const label = (ex.args[0] as QuintName).name + return fromQuintEx(ex.args[1]).map(v => rv.mkVariant(label, v)) + } + default: // no other case should be possible return none() } + case 'lambda': + // We don't have enough information to convert lambdas directly + return none() + default: // no other case should be possible return none() @@ -336,7 +378,7 @@ export function fromQuintEx(ex: QuintEx): Maybe { * non-iterable ones. Of course, this may lead to ill-typed operations. Type * correctness of the input must be checked by the type checker. */ -export interface RuntimeValue extends EvalResult, ValueObject, Iterable { +export interface RuntimeValue extends ValueObject, Iterable { /** * Can the runtime value behave like a set? Effectively, this means that the * value returns a sequence of elements, when it is iterated over. @@ -377,7 +419,7 @@ export interface RuntimeValue extends EvalResult, ValueObject, Iterable + toMap(): ImmutableMap /** * If the result is a record, transform it to a map of values. @@ -408,6 +450,27 @@ export interface RuntimeValue extends EvalResult, ValueObject, Iterable Either + + /** + * If the result is a variant, return the label and the value. + * + * @return the label and the value of the variant. + */ + toVariant(): [string, RuntimeValue] + /** * If the result is set-like, does it contain contain a value? * If the result is not set-like, return false. @@ -458,6 +521,19 @@ export interface RuntimeValue extends EvalResult, ValueObject, Iterable + + /** + * Convert a runtime value to a Quint expression. + * + * This function always returns sets in the normalized representation, + * that is, in `set(elements)`, the elements are ordered according to their + * string representation. As sorting via strings may be slow, we do not + * recommend using `toQuintEx` in computation-intensive code. + * + * @param gen a generator that produces unique ids + * @return this evaluation result converted to Quint expression. + */ + toQuintEx(gen: IdGenerator): QuintEx } /** @@ -515,11 +591,11 @@ abstract class RuntimeValueBase implements RuntimeValue { if (this instanceof RuntimeValueRecord) { return this.map } else { - throw new Error('Expected a record value') + throw new Error(`Expected a record value but got ${expressionToString(this.toQuintEx(zerog))}`) } } - toMap(): Map { + toMap(): ImmutableMap { if (this instanceof RuntimeValueMap) { return this.map } else { @@ -539,7 +615,7 @@ abstract class RuntimeValueBase implements RuntimeValue { if (this instanceof RuntimeValueInt) { return (this as RuntimeValueInt).value } else { - throw new Error('Expected an integer value') + throw new Error(`Expected an integer value, got ${expressionToString(this.toQuintEx(zerog))}`) } } @@ -551,6 +627,49 @@ abstract class RuntimeValueBase implements RuntimeValue { } } + toTuple2(): [RuntimeValue, RuntimeValue] { + // This is specific for tuples of size 2, as they are expected in many builtins. + if (this instanceof RuntimeValueTupleOrList) { + const list = this.list + + if (list.size === 2) { + return [list.get(0)!, list.get(1)!] + } + } + throw new Error('Expected a 2-tuple') + } + + toArrow(): (ctx: Context, args: RuntimeValue[]) => Either { + if (!(this instanceof RuntimeValueLambda)) { + throw new Error('Expected a lambda value') + } + + const lam = this as RuntimeValueLambda + + return (ctx: Context, args: RuntimeValue[]) => { + if (lam.registers.length !== args.length) { + return left({ + code: 'QNT506', + message: `Lambda expects ${lam.registers.length} arguments, but got ${args.length}`, + }) + } + + lam.registers.forEach((reg, i) => { + reg.value = right(args[i]) + }) + + return lam.body(ctx) + } + } + + toVariant(): [string, RuntimeValue] { + if (this instanceof RuntimeValueVariant) { + return [(this as RuntimeValueVariant).label, (this as RuntimeValueVariant).value] + } else { + throw new Error('Expected a variant value') + } + } + contains(elem: RuntimeValue): boolean { // the default search is done via iteration, which is the worst case let found = false @@ -860,9 +979,9 @@ export class RuntimeValueVariant extends RuntimeValueBase implements RuntimeValu * This is an internal class. */ class RuntimeValueMap extends RuntimeValueBase implements RuntimeValue { - map: Map + map: ImmutableMap - constructor(keyValues: Map) { + constructor(keyValues: ImmutableMap) { super(true) this.map = keyValues } @@ -1547,18 +1666,14 @@ class RuntimeValueInfSet extends RuntimeValueBase implements RuntimeValue { * * RuntimeValueLambda cannot be compared with other values. */ -export class RuntimeValueLambda extends RuntimeValueBase implements RuntimeValue, Callable { - nparams: number - callable: Callable +export class RuntimeValueLambda extends RuntimeValueBase implements RuntimeValue { + body: EvalFunction + registers: Register[] - constructor(nparams: number, callable: Callable) { + constructor(body: EvalFunction, registers: Register[]) { super(false) - this.nparams = nparams - this.callable = callable - } - - eval(args?: any[]) { - return this.callable.eval(args) + this.body = body + this.registers = registers } toQuintEx(gen: IdGenerator): QuintEx { @@ -1568,15 +1683,23 @@ export class RuntimeValueLambda extends RuntimeValueBase implements RuntimeValue return { id: gen.nextId(), kind: 'lambda', - params: Array.from(Array(this.nparams).keys()).map(i => { + params: Array.from(this.registers.keys()).map(i => { return { id: gen.nextId(), name: `_a${i}` } }), qualifier: 'def', expr: { kind: 'str', - value: `lambda_${this.nparams}_params`, + value: `lambda_${this.registers.length}_params`, id: gen.nextId(), }, } } } + +function toMaybe(r: Either): Maybe { + if (r.isRight()) { + return just(r.value) + } else { + return none() + } +} diff --git a/quint/src/runtime/runtime.ts b/quint/src/runtime/runtime.ts deleted file mode 100644 index d0f466a65..000000000 --- a/quint/src/runtime/runtime.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Runtime environment for Quint. This runtime environment is designed for the - * following purposes: - * - * - Give the language user fast feedback via interpretation of expressions. - * - Let the user simulate their specification similar to property-based testing. - * - * Igor Konnov, 2022-2023 - * - * Copyright 2022-2023 Informal Systems - * Licensed under the Apache License, Version 2.0. - * See LICENSE in the project root for license information. - */ - -import { Maybe } from '@sweet-monads/maybe' - -import { ValueObject } from 'immutable' - -import { QuintEx } from '../ir/quintIr' - -import { IdGenerator } from '../idGenerator' - -import { rv } from './impl/runtimeValue' -import { Either, left, right } from '@sweet-monads/either' -import { QuintError } from '../quintError' -import { toMaybe } from './impl/base' - -/** - * Evaluation result. - * The implementation details are hidden behind this interface. - */ -export interface EvalResult extends ValueObject { - /** - * Convert an evaluation result to Quint. - * - * This function always returns sets in the normalized representation, - * that is, in `set(elements)`, the elements are ordered according to their - * string representation. As sorting via strings may be slow, we do not - * recommend using `toQuintEx` in computation-intensive code. - * - * @param gen a generator that produces unique ids - * @return this evaluation result converted to Quint expression. - */ - toQuintEx(gen: IdGenerator): QuintEx -} - -export type EvaluationResult = Either - -/** - * An object that can be evaluated by the runtime. Normally, it is constructed - * from a Quint expression, but it does not have to. - */ -export interface Computable { - /** - * The simplest form of evaluation. Just compute the value. - * This method may return none, if a computation error happens during - * evaluation. - * - * @param args optional arguments to the computable - */ - eval: (args?: Either[]) => EvaluationResult -} - -/** - * The kind of a computable. - */ -export type ComputableKind = 'var' | 'nextvar' | 'arg' | 'callable' - -/** - * Create a key that encodes its name and kind. This is only useful for - * storing registers in a map, as JS objects are hard to use as keys. - * In a good language, we would use (kind, name), but not in JS. - */ -export const kindName = (kind: ComputableKind, name: string | bigint): string => { - return `${kind}:${name}` -} - -/** - * A computable that evaluates to the value of a readable/writeable register. - */ -export interface Register extends Computable { - // register name - name: string - // register kind - kind: ComputableKind - // register is a placeholder where iterators can put their values - registerValue: Maybe -} - -/** - * Create an object that implements Register. - */ -export function mkRegister( - kind: ComputableKind, - registerName: string, - initValue: Maybe, - errorForMissing: QuintError -): Register { - const reg: Register = { - name: registerName, - kind, - registerValue: initValue, - // first, define a fruitless eval, as we cannot refer to registerValue yet - eval: () => { - return left(errorForMissing) - }, - } - // override `eval`, as we can use `reg` now - reg.eval = () => { - // computing a register just evaluates to the contents that it stores - if (reg.registerValue.isNone()) { - return left(errorForMissing) - } - return right(reg.registerValue.value) - } - - return reg -} - -/** - * A callable value like an operator definition. Its body is computable. - * One has to pass the values of the parameters to eval. - */ -export interface Callable extends Computable { - /** - * The number of parameters expected by the callable value. - */ - nparams: number -} - -/** - * Create an object that implements Callable. - */ -export function mkCallable(registers: Register[], body: Computable): Callable { - const callable: Callable = { - nparams: registers.length, - eval: () => body.eval(), - } - callable.eval = args => { - if (registers.length === 0) { - // simply evaluate the body, no parameters are needed - return body.eval() - } - if (args && args.length >= registers.length) { - // All parameters are passed via `args`. Store them in the registers. - registers.forEach((r, i) => (r.registerValue = toMaybe(args[i]))) - // Evaluate the body under for the registers set to `args`. - return body.eval() - } else { - // The lambda is evaluated without giving the arguments. - // All we can do is to return this lambda as a runtime value. - return right(rv.mkLambda(registers.length, callable)) - } - } - return callable -} - -/** - * An implementation of Computable that always fails. - */ -export const fail = { - eval: (): EvaluationResult => { - return left({ code: 'QNT501', message: 'Failed to evaluate something :/' }) - }, -} - -/** - * An error produced during execution. - */ -export interface ExecError { - msg: string - sourceAndLoc: string | undefined -} - -/** - * An error handler that is called on any kind of error that is happening - * during execution. - */ -export type ExecErrorHandler = (_error: ExecError) => void diff --git a/quint/src/runtime/testing.ts b/quint/src/runtime/testing.ts index 99ddc86c6..0100783a8 100644 --- a/quint/src/runtime/testing.ts +++ b/quint/src/runtime/testing.ts @@ -8,19 +8,10 @@ * See LICENSE in the project root for license information. */ -import { Either, left, mergeInMany, right } from '@sweet-monads/either' -import { Presets, SingleBar } from 'cli-progress' - -import { QuintEx, QuintOpDef } from '../ir/quintIr' - -import { CompilationContext, CompilationState, compile } from './compile' -import { zerog } from './../idGenerator' -import { LookupTable } from '../names/base' -import { Computable, kindName } from './runtime' -import { ExecutionFrame, newTraceRecorder } from './trace' +import { QuintEx } from '../ir/quintIr' +import { ExecutionFrame } from './trace' import { Rng } from '../rng' import { QuintError } from '../quintError' -import { newEvaluationState, toMaybe } from './impl/base' /** * Various settings to be passed to the testing framework. @@ -30,7 +21,7 @@ export interface TestOptions { maxSamples: number rng: Rng verbosity: number - onTrace(index: number, name: string, status: string, vars: string[], states: QuintEx[]): void + onTrace: (index: number) => (name: string, status: string, vars: string[], states: QuintEx[]) => void } /** @@ -63,190 +54,3 @@ export interface TestResult { */ nsamples: number } - -/** - * Run a test suite of a single module. - * - * @param modules Quint modules in the intermediate representation - * @param main the module that should be used as a state machine - * @param sourceMap source map as produced by the parser - * @param lookupTable lookup table as produced by the parser - * @param analysisOutput the maps produced by the static analysis - * @param options misc test options - * @returns the `right(results)` of running the tests if the tests could be - * run, or `left(errors)` with any compilation or analysis errors that - * prevent the tests from running - */ -export function compileAndTest( - compilationState: CompilationState, - mainName: string, - lookupTable: LookupTable, - options: TestOptions -): Either { - const main = compilationState.modules.find(m => m.name === mainName) - if (!main) { - return left([{ code: 'QNT405', message: `Main module ${mainName} not found` }]) - } - - const recorder = newTraceRecorder(options.verbosity, options.rng) - const ctx = compile( - compilationState, - newEvaluationState(recorder), - lookupTable, - options.rng.next, - false, - main.declarations - ) - - const ctxErrors = ctx.syntaxErrors.concat(ctx.compileErrors, ctx.analysisErrors) - if (ctxErrors.length > 0) { - // In principle, these errors should have been caught earlier. - // But if they did not, return immediately. - return left(ctxErrors) - } - - const saveTrace = (trace: ExecutionFrame, index: number, name: string, status: string) => { - // Save the best traces that are reported by the recorder: - // If a test failed, it is the first failing trace. - // Otherwise, it is the longest trace explored by the test. - const states = trace.args.map(e => e.toQuintEx(zerog)) - options.onTrace( - index, - name, - status, - ctx.evaluationState.vars.map(v => v.name), - states - ) - } - - const testDefs = main.declarations.filter(d => d.kind === 'def' && options.testMatch(d.name)) as QuintOpDef[] - - return mergeInMany( - testDefs.map((def, index) => { - return getComputableForDef(ctx, def).map(comp => { - recorder.clear() - const name = def.name - // save the initial seed - let seed = options.rng.getState() - - const bar = new SingleBar( - { - clearOnComplete: true, - forceRedraw: true, - format: ' {test} [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} samples', - }, - Presets.rect - ) - - bar.start(options.maxSamples, 0, { test: name }) - - let nsamples = 1 - // run up to maxSamples, stop on the first failure - for (; nsamples <= options.maxSamples; nsamples++) { - bar.update(nsamples, { test: name }) - - // record the seed value - seed = options.rng.getState() - recorder.onRunCall() - // reset the trace - ctx.evaluationState.trace.reset() - // run the test - const result = comp.eval() - // extract the trace - const trace = ctx.evaluationState.trace.get() - - if (trace.length > 0) { - recorder.onRunReturn(toMaybe(result), trace) - } else { - recorder.onRunReturn(toMaybe(result), []) - } - - const bestTrace = recorder.bestTraces[0].frame - // evaluate the result - if (result.isLeft()) { - // if the test failed, return immediately - bar.stop() - return { - name, - status: 'failed', - errors: ctx.getRuntimeErrors().concat(result.value), - seed, - frames: bestTrace.subframes, - nsamples: nsamples, - } - } - - const ex = result.value.toQuintEx(zerog) - if (ex.kind !== 'bool') { - // if the test returned a malformed result, return immediately - bar.stop() - return { - name, - status: 'ignored', - errors: [], - seed: seed, - frames: bestTrace.subframes, - nsamples: nsamples, - } - } - - if (!ex.value) { - // if the test returned false, return immediately - const error: QuintError = { - code: 'QNT511', - message: `Test ${name} returned false`, - reference: def.id, - } - saveTrace(bestTrace, index, name, 'failed') - bar.stop() - return { - name, - status: 'failed', - errors: [error], - seed: seed, - frames: bestTrace.subframes, - nsamples: nsamples, - } - } else { - if (options.rng.getState() === seed) { - // This successful test did not use non-determinism. - // Running it one time is sufficient. - saveTrace(bestTrace, index, name, 'passed') - bar.stop() - return { - name, - status: 'passed', - errors: [], - seed: seed, - frames: bestTrace.subframes, - nsamples: nsamples, - } - } - } - } - - // the test was run maxSamples times, and no errors were found - const bestTrace = recorder.bestTraces[0].frame - saveTrace(bestTrace, index, name, 'passed') - bar.stop() - return { - name, - status: 'passed', - errors: [], - seed: seed, - frames: bestTrace.subframes, - nsamples: nsamples - 1, - } - }) - }) - ) -} - -function getComputableForDef(ctx: CompilationContext, def: QuintOpDef): Either { - const comp = ctx.evaluationState.context.get(kindName('callable', def.id)) - if (comp) { - return right(comp) - } else { - return left({ code: 'QNT501', message: `Cannot find computable for ${def.name}`, reference: def.id }) - } -} diff --git a/quint/src/runtime/trace.ts b/quint/src/runtime/trace.ts index 97502a00c..58dd2fdfd 100644 --- a/quint/src/runtime/trace.ts +++ b/quint/src/runtime/trace.ts @@ -8,14 +8,14 @@ * See LICENSE in the project root for license information. */ -import { Maybe, just, none } from '@sweet-monads/maybe' import { strict as assert } from 'assert' import { QuintApp } from '../ir/quintIr' -import { EvalResult } from './runtime' import { verbosity } from './../verbosity' import { Rng } from './../rng' -import { rv } from './impl/runtimeValue' +import { Either, left, right } from '@sweet-monads/either' +import { QuintError } from '../quintError' +import { RuntimeValue, rv } from './impl/runtimeValue' import { insertSorted } from './../util' /** @@ -40,11 +40,11 @@ export interface ExecutionFrame { /** * The actual runtime values that were used in the call. */ - args: EvalResult[] + args: RuntimeValue[] /** * An optional result of the execution. */ - result: Maybe + result: Either /** * The frames of the operators that were called by this operator. */ @@ -56,6 +56,8 @@ export interface Trace { seed: bigint } +const emptyFrameError: QuintError = { code: 'QNT501', message: 'empty frame' } + /** * A listener that receives events in the course of Quint evaluation. * This listener may be used to collect a trace, or to record profiling data. @@ -77,7 +79,7 @@ export interface ExecutionListener { * @param args the actual arguments obtained in evaluation * @param result optional result of the evaluation */ - onUserOperatorReturn(app: QuintApp, args: EvalResult[], result: Maybe): void + onUserOperatorReturn(app: QuintApp, args: RuntimeValue[], result: Either): void /** * This callback is called *before* one of the arguments of `any {...}` @@ -116,7 +118,7 @@ export interface ExecutionListener { * @param oldState the old state that is about to be discarded * @param newState the new state, from which the execution continues */ - onNextState(oldState: EvalResult, newState: EvalResult): void + onNextState(oldState: RuntimeValue, newState: RuntimeValue): void /** * This callback is called when a new run is executed, @@ -133,7 +135,7 @@ export interface ExecutionListener { * - finished after finding a violation, `just(mkBool(false))` * @param trace the array of produced states (each state is a record) */ - onRunReturn(outcome: Maybe, trace: EvalResult[]): void + onRunReturn(outcome: Either, trace: RuntimeValue[]): void } /** @@ -141,13 +143,13 @@ export interface ExecutionListener { */ export const noExecutionListener: ExecutionListener = { onUserOperatorCall: (_app: QuintApp) => {}, - onUserOperatorReturn: (_app: QuintApp, _args: EvalResult[], _result: Maybe) => {}, + onUserOperatorReturn: (_app: QuintApp, _args: RuntimeValue[], _result: Either) => {}, onAnyOptionCall: (_anyExpr: QuintApp, _position: number) => {}, onAnyOptionReturn: (_anyExpr: QuintApp, _position: number) => {}, onAnyReturn: (_noptions: number, _choice: number) => {}, - onNextState: (_oldState: EvalResult, _newState: EvalResult) => {}, + onNextState: (_oldState: RuntimeValue, _newState: RuntimeValue) => {}, onRunCall: () => {}, - onRunReturn: (_outcome: Maybe, _trace: EvalResult[]) => {}, + onRunReturn: (_outcome: Either, _trace: RuntimeValue[]) => {}, } /** @@ -224,7 +226,12 @@ class TraceRecorderImpl implements TraceRecorder { // For now, we cannot tell apart actions from other user definitions. // https://github.com/informalsystems/quint/issues/747 if (verbosity.hasUserOpTracking(this.verbosityLevel)) { - const newFrame = { app: app, args: [], result: none(), subframes: [] } + const newFrame: ExecutionFrame = { + app: app, + args: [], + result: left(emptyFrameError), + subframes: [], + } if (this.frameStack.length == 0) { // this should not happen, as there is always bottomFrame, // but we do not throw here, as trace collection is not the primary @@ -235,7 +242,7 @@ class TraceRecorderImpl implements TraceRecorder { const frame = this.frameStack[1] frame.app = app frame.args = [] - frame.result = none() + frame.result = left(emptyFrameError) frame.subframes = [] } else { // connect the new frame to the previous frame @@ -246,7 +253,7 @@ class TraceRecorderImpl implements TraceRecorder { } } - onUserOperatorReturn(_app: QuintApp, args: EvalResult[], result: Maybe) { + onUserOperatorReturn(_app: QuintApp, args: RuntimeValue[], result: Either) { if (verbosity.hasUserOpTracking(this.verbosityLevel)) { const top = this.frameStack.pop() if (top) { @@ -265,7 +272,7 @@ class TraceRecorderImpl implements TraceRecorder { const newFrame = { app: anyExpr, args: [], - result: none(), + result: left(emptyFrameError), subframes: [], } if (this.frameStack.length > 0) { @@ -304,11 +311,11 @@ class TraceRecorderImpl implements TraceRecorder { } } - onNextState(_oldState: EvalResult, _newState: EvalResult) { + onNextState(_oldState: RuntimeValue, _newState: RuntimeValue) { // introduce a new frame that is labelled with a dummy operator if (verbosity.hasUserOpTracking(this.verbosityLevel)) { const dummy: QuintApp = { id: 0n, kind: 'app', opcode: '_', args: [] } - const newFrame = { app: dummy, args: [], result: none(), subframes: [] } + const newFrame = { app: dummy, args: [], result: left(emptyFrameError), subframes: [] } // forget the frames, except the bottom one, and push the new one this.frameStack = [this.frameStack[0], newFrame] // connect the new frame to the topmost frame, which effects in a new step @@ -322,7 +329,7 @@ class TraceRecorderImpl implements TraceRecorder { this.runSeed = this.rng.getState() } - onRunReturn(outcome: Maybe, trace: EvalResult[]) { + onRunReturn(outcome: Either, trace: RuntimeValue[]) { assert(this.frameStack.length > 0) const traceToSave = this.frameStack[0] traceToSave.result = outcome @@ -353,7 +360,7 @@ class TraceRecorderImpl implements TraceRecorder { // we will store the sequence of states here args: [], // the result of the trace evaluation - result: just(rv.mkBool(true)), + result: right(rv.mkBool(true)), // and here we store the subframes for the top-level actions subframes: [], } @@ -367,8 +374,8 @@ class TraceRecorderImpl implements TraceRecorder { // - when a has an error: a is shorter or b has no error; // - when a has no error: a is longer and b has no error. function compareTracesByQuality(a: Trace, b: Trace): number { - const fromResult = (r: Maybe) => { - if (r.isNone()) { + const fromResult = (r: Either) => { + if (r.isLeft()) { return true } else { const rex = r.value.toQuintEx({ nextId: () => 0n }) diff --git a/quint/src/simulation.ts b/quint/src/simulation.ts index effa6e451..ea2221e79 100644 --- a/quint/src/simulation.ts +++ b/quint/src/simulation.ts @@ -8,19 +8,10 @@ * See LICENSE in the project root for license information. */ -import { Either } from '@sweet-monads/either' - -import { compileFromCode, contextNameLookup } from './runtime/compile' import { QuintEx } from './ir/quintIr' -import { Computable } from './runtime/runtime' -import { ExecutionFrame, Trace, newTraceRecorder } from './runtime/trace' -import { IdGenerator } from './idGenerator' +import { ExecutionFrame } from './runtime/trace' import { Rng } from './rng' -import { SourceLookupPath } from './parsing/sourceResolver' import { QuintError } from './quintError' -import { mkErrorMessage } from './cliCommands' -import { createFinders, formatError } from './errorReporter' -import assert from 'assert' /** * Various settings that have to be passed to the simulator to run. @@ -55,150 +46,3 @@ export interface SimulatorResult { frames: ExecutionFrame[] seed: bigint } - -function errSimulationResult(errors: QuintError[]): SimulatorResult { - return { - outcome: { status: 'error', errors }, - vars: [], - states: [], - frames: [], - seed: 0n, - } -} - -/** - * Execute a run. - * - * @param idGen a unique generator of identifiers - * @param code the source code of the modules - * @param mainStart the start index of the main module in the code - * @param mainEnd the end index of the main module in the code - * @param mainName the module that should be used as a state machine - * @param mainPath the lookup path that was used to retrieve the main module - * @param options simulator settings - * @returns either error messages (left), - or the trace as an expression (right) - */ -export function compileAndRun( - idGen: IdGenerator, - code: string, - mainStart: number, - mainEnd: number, - mainName: string, - mainPath: SourceLookupPath, - options: SimulatorOptions -): SimulatorResult { - // Once we have 'import from ...' implemented, we should pass - // a filename instead of the source code (see #8) - - // Parse the code once again, but this time include the special definitions - // that are required by the runner. - // This code should be revisited in #618. - const o = options - // Defs required by the simulator, to be added to the main module before compilation - const extraDefs = [ - `def q::test(q::nrunsArg, q::nstepsArg, q::ntracesArg, q::initArg, q::nextArg, q::invArg) = false`, - `action q::init = { ${o.init} }`, - `action q::step = { ${o.step} }`, - `val q::inv = { ${o.invariant} }`, - `val q::runResult = q::test(${o.maxSamples}, ${o.maxSteps}, ${o.numberOfTraces}, q::init, q::step, q::inv)`, - ] - - // Construct the modules' code, adding the extra definitions to the main module - const newMainModuleCode = code.slice(mainStart, mainEnd - 1) + '\n' + extraDefs.join('\n') - const codeWithExtraDefs = code.slice(0, mainStart) + newMainModuleCode + code.slice(mainEnd) - - const recorder = newTraceRecorder(options.verbosity, options.rng, options.numberOfTraces) - const ctx = compileFromCode( - idGen, - codeWithExtraDefs, - mainName, - mainPath, - recorder, - options.rng.next, - options.storeMetadata - ) - - const compilationErrors = ctx.syntaxErrors.concat(ctx.analysisErrors).concat(ctx.compileErrors) - if (compilationErrors.length > 0) { - return errSimulationResult(compilationErrors) - } - - // evaluate q::runResult, which triggers the simulator - const evaluationState = ctx.evaluationState - const res: Either = contextNameLookup(evaluationState.context, 'q::runResult', 'callable') - if (res.isLeft()) { - const errors = [{ code: 'QNT512', message: res.value }] as QuintError[] - return errSimulationResult(errors) - } else { - res.value.eval() - } - - const topTraces: Trace[] = recorder.bestTraces - const vars = evaluationState.vars.map(v => v.name) - - topTraces.forEach((trace, index) => { - const maybeEvalResult = trace.frame.result - assert(maybeEvalResult.isJust(), 'invalid simulation failed to produce a result') - const quintExResult = maybeEvalResult.value.toQuintEx(idGen) - assert(quintExResult.kind === 'bool', 'invalid simulation produced non-boolean value ') - const simulationSucceeded = quintExResult.value - const status = simulationSucceeded ? 'ok' : 'violation' - const states = trace.frame.args.map(e => e.toQuintEx(idGen)) - - options.onTrace(index, status, vars, states) - }) - - const topFrame = topTraces[0].frame - const seed = topTraces[0].seed - // Validate required outcome of correct simulation - const maybeEvalResult = topFrame.result - assert(maybeEvalResult.isJust(), 'invalid simulation failed to produce a result') - const quintExResult = maybeEvalResult.value.toQuintEx(idGen) - assert(quintExResult.kind === 'bool', 'invalid simulation produced non-boolean value ') - const simulationSucceeded = quintExResult.value - - const states = topFrame.args.map(e => e.toQuintEx(idGen)) - const frames = topFrame.subframes - - const runtimeErrors = ctx.getRuntimeErrors() - if (runtimeErrors.length > 0) { - // FIXME(#1052) we shouldn't need to do this if the error id was not some non-sense generated in `compileFromCode` - // The evaluated code source is not included in the context, so we crete a version with it for the error reporter - const code = new Map([...ctx.compilationState.sourceCode.entries(), [mainPath.normalizedPath, codeWithExtraDefs]]) - const finders = createFinders(code) - - const locatedErrors = runtimeErrors.map(error => ({ - code: error.code, - // Include the location information (locs) in the error message - this - // is the hacky part, as it should only be included at the CLI level - message: formatError(code, finders, { - // This will create the `locs` attribute and an explanation - ...mkErrorMessage(ctx.compilationState.sourceMap)(error), - // We override the explanation to keep the original one to avoid - // duplication, since `mkErrorMessage` will be called again at the CLI - // level. `locs` won't be touched then because this error doesn't - // include a `reference` field - explanation: error.message, - }), - })) - - // This should be kept after the hack is removed - return { - vars, - states, - frames, - seed, - outcome: { status: 'error', errors: locatedErrors }, - } - } else { - const status = simulationSucceeded ? 'ok' : 'violation' - return { - vars, - states, - frames, - seed, - outcome: { status }, - } - } -} diff --git a/quint/src/static/callgraph.ts b/quint/src/static/callgraph.ts index 458d08668..30489f101 100644 --- a/quint/src/static/callgraph.ts +++ b/quint/src/static/callgraph.ts @@ -190,9 +190,6 @@ export class CallGraphVisitor implements IRVisitor { const importedModule = this.context.modulesByName.get(decl.protoName) if (importedModule) { this.graphAddAll(decl.id, Set([importedModule.id])) - decl.overrides.forEach(([_param, expr]) => { - this.graphAddAll(expr.id, Set([decl.id])) - }) } } diff --git a/quint/src/types/builtinSignatures.ts b/quint/src/types/builtinSignatures.ts index dcd18b477..1f89f68b5 100644 --- a/quint/src/types/builtinSignatures.ts +++ b/quint/src/types/builtinSignatures.ts @@ -129,6 +129,8 @@ const otherOperators = [ { name: 'assert', type: '(bool) => bool' }, { name: 'q::debug', type: '(str, a) => a' }, { name: 'q::lastTrace', type: 'List[a]' }, + { name: 'q::test', type: '(int, int, int, bool, bool, bool) => bool' }, + { name: 'q::testOnce', type: '(int, int, bool, bool, bool) => bool' }, ] function uniformArgsWithResult(argsType: string, resultType: string): Signature { diff --git a/quint/test/flattening/flattener.test.ts b/quint/test/flattening/flattener.test.ts index 3bf537d38..4c9f86fab 100644 --- a/quint/test/flattening/flattener.test.ts +++ b/quint/test/flattening/flattener.test.ts @@ -143,7 +143,7 @@ describe('flattenModule', () => { const thirdModuleDecls = ['import B.*', 'val a = f(1)'] - const expectedDecls = ['import A(N = 1) as A1', 'def f = ((x) => iadd(x, 1))'] + const expectedDecls = ['import A(N = 1) as A1', 'def f = ((x) => iadd(x, 1))', 'const N: int'] const flattenedDecls = getFlatennedDecls(baseDecls, decls, thirdModuleDecls) assert.deepEqual(flattenedDecls, expectedDecls) diff --git a/quint/test/repl.test.ts b/quint/test/repl.test.ts index 764fe03e0..feeacadcb 100644 --- a/quint/test/repl.test.ts +++ b/quint/test/repl.test.ts @@ -147,12 +147,6 @@ describe('repl ok', () => { |1 + false |^^^^^^^^^ | - | - |runtime error: error: [QNT501] Expected an integer value - |1 + false - |^^^^^^^^^ - | - | |>>> ` ) await assertRepl(input, output) @@ -215,15 +209,14 @@ describe('repl ok', () => { |>>> .clear | |>>> n * n - |syntax error: error: [QNT404] Name 'n' not found + |static analysis error: error: [QNT404] Name 'n' not found |n * n |^ | - |syntax error: error: [QNT404] Name 'n' not found + |static analysis error: error: [QNT404] Name 'n' not found |n * n | ^ | - | |>>> ` ) await assertRepl(input, output) @@ -273,7 +266,6 @@ describe('repl ok', () => { |div(2, 0) | ^^^^^ | - | |>>> ` ) await assertRepl(input, output) @@ -300,10 +292,6 @@ describe('repl ok', () => { |.verbosity=4 |>>> x' = 0 |true - | - |[Frame 0] - |_ => true - | |>>> action step = x' = x + 1 | |>>> action input1 = step @@ -393,7 +381,6 @@ describe('repl ok', () => { |Set(Int) |^^^^^^^^ | - | |>>> ` ) await assertRepl(input, output) diff --git a/quint/test/runtime/compile.test.ts b/quint/test/runtime/compile.test.ts index 580168b97..e18f1fe44 100644 --- a/quint/test/runtime/compile.test.ts +++ b/quint/test/runtime/compile.test.ts @@ -1,27 +1,15 @@ import { describe, it } from 'mocha' import { assert } from 'chai' -import { Either, left, right } from '@sweet-monads/either' import { expressionToString } from '../../src/ir/IRprinting' -import { Callable, Computable, ComputableKind, fail, kindName } from '../../src/runtime/runtime' -import { noExecutionListener } from '../../src/runtime/trace' -import { - CompilationContext, - CompilationState, - compile, - compileDecls, - compileExpr, - compileFromCode, - contextNameLookup, - inputDefName, -} from '../../src/runtime/compile' -import { RuntimeValue } from '../../src/runtime/impl/runtimeValue' +import { newTraceRecorder } from '../../src/runtime/trace' import { dedent } from '../textUtils' import { newIdGenerator } from '../../src/idGenerator' -import { Rng, newRng } from '../../src/rng' -import { SourceLookupPath, stringSourceResolver } from '../../src/parsing/sourceResolver' -import { analyzeModules, parse, parseExpressionOrDeclaration, quintErrorToString } from '../../src' -import { flattenModules } from '../../src/flattening/fullFlattener' -import { newEvaluationState } from '../../src/runtime/impl/base' +import { newRng } from '../../src/rng' +import { stringSourceResolver } from '../../src/parsing/sourceResolver' +import { QuintEx, parseExpressionOrDeclaration, quintErrorToString, walkExpression } from '../../src' +import { parse } from '../../src/parsing/quintParserFrontend' +import { Evaluator } from '../../src/runtime/impl/evaluator' +import { Either, left } from '@sweet-monads/either' // Use a global id generator, limited to this test suite. const idGen = newIdGenerator() @@ -33,136 +21,70 @@ const idGen = newIdGenerator() // `input` depends on. This content will be wrapped in a module and imported unqualified // before the input is evaluated. If not supplied, the context is empty. function assertResultAsString(input: string, expected: string | undefined, evalContext: string = '') { - const moduleText = `module contextM { ${evalContext} } module __runtime { import contextM.*\n val ${inputDefName} = ${input} }` - const mockLookupPath = stringSourceResolver(new Map()).lookupPath('/', './mock') - const context = compileFromCode( - idGen, - moduleText, - '__runtime', - mockLookupPath, - noExecutionListener, - newRng().next, - false - ) - - assert.isEmpty(context.syntaxErrors, `Syntax errors: ${context.syntaxErrors.map(quintErrorToString).join(', ')}`) - assert.isEmpty(context.compileErrors, `Compile errors: ${context.compileErrors.map(quintErrorToString).join(', ')}`) - - assertInputFromContext(context, expected) -} - -function assertInputFromContext(context: CompilationContext, expected: string | undefined) { - contextNameLookup(context.evaluationState.context, inputDefName, 'callable') - .mapLeft(msg => assert(false, `Unexpected error: ${msg}`)) - .mapRight(value => assertComputableAsString(value, expected)) -} - -function assertComputableAsString(computable: Computable, expected: string | undefined) { - const result = computable - .eval() - .map(r => r.toQuintEx(idGen)) - .map(expressionToString) - .map(s => assert(s === expected, `Expected ${expected}, found ${s}`)) - if (result.isLeft()) { - assert(expected === undefined, `Expected ${expected}, found undefined`) - } + const [evaluator, expr] = prepareEvaluator(input, evalContext) + const newEval = evaluator.evaluate(expr) + newEval + .map(val => assert.deepEqual(expressionToString(val), expected, `Input: ${input}`)) + .mapLeft(err => assert(expected === undefined, `Expected ${expected}, found error ${quintErrorToString(err)}`)) } -// Compile an input and evaluate a callback in the context -function evalInContext(input: string, callable: (ctx: CompilationContext) => Either) { - const moduleText = `module __runtime { ${input} }` +function prepareEvaluator(input: string, evalContext: string): [Evaluator, QuintEx] { const mockLookupPath = stringSourceResolver(new Map()).lookupPath('/', './mock') - const context = compileFromCode( - idGen, - moduleText, - '__runtime', - mockLookupPath, - noExecutionListener, - newRng().next, - false - ) - return callable(context) -} + const { resolver, sourceMap } = parse(idGen, '', mockLookupPath, `module contextM { ${evalContext} }`) -// Compile a variable definition and check that the compiled value is defined. -function assertVarExists(kind: ComputableKind, name: string, input: string) { - const callback = (ctx: CompilationContext) => { - return contextNameLookup(ctx.evaluationState.context, `${name}`, kind) - .mapRight(_ => true) - .mapLeft(msg => `Expected a definition for ${name}, found ${msg}, compiled from: ${input}`) + const parseResult = parseExpressionOrDeclaration(input, '', idGen, sourceMap) + if (parseResult.kind !== 'expr') { + assert.fail(`Expected an expression, found ${parseResult.kind}`) } - const res = evalInContext(input, callback) - res.mapLeft(m => assert.fail(m)) -} -// compile a computable for a run definition -function callableFromContext(ctx: CompilationContext, callee: string): Either { - let key = undefined - const lastModule = ctx.compilationState.modules[ctx.compilationState.modules.length - 1] - const def = lastModule.declarations.find(def => def.kind === 'def' && def.name === callee) - if (!def) { - return left(`${callee} definition not found`) - } - key = kindName('callable', def.id) - if (!key) { - return left(`${callee} not found`) - } - const run = ctx.evaluationState.context.get(key) as Callable - if (!run) { - return left(`${callee} not found via ${key}`) + walkExpression(resolver, parseResult.expr) + if (resolver.errors.length > 0) { + assert.fail(`Resolver errors: ${resolver.errors.map(quintErrorToString).join(', ')}`) } + const rng = newRng() + const evaluator = new Evaluator(resolver.table, newTraceRecorder(0, rng), rng) + return [evaluator, parseResult.expr] +} - return right(run) +// Compile a variable definition and check that the compiled value is defined. +function assertVarExists(name: string, context: string, input: string) { + const [evaluator, expr] = prepareEvaluator(input, context) + // Eval the name so we lookup and evaluate the var definition + evaluator.evaluate(expr) + + assert.includeDeepMembers( + [...evaluator.ctx.varStorage.vars.values()].map(r => r.name), + [name], + `Input: ${input}` + ) } // Scan the context for a callable. If found, evaluate it and return the value of the given var. // Assumes the input has a single definition whose name is stored in `callee`. function evalVarAfterRun(varName: string, callee: string, input: string): Either { - // use a combination of Maybe and Either. - // Recall that left(...) is used for errors, - // whereas right(...) is used for non-errors in sweet monads. - const callback = (ctx: CompilationContext): Either => { - return callableFromContext(ctx, callee).chain(run => { - return run - .eval() - .mapLeft(quintErrorToString) - .chain(res => { - if ((res as RuntimeValue).toBool() === true) { - // extract the value of the state variable - const nextVal = (ctx.evaluationState.context.get(kindName('nextvar', varName)) ?? fail).eval() - if (nextVal.isLeft()) { - return left(`Value of the variable ${varName} is undefined`) - } else { - return right(expressionToString(nextVal.value.toQuintEx(idGen))) - } - } else { - const s = expressionToString(res.toQuintEx(idGen)) - const m = `Callable ${callee} was expected to evaluate to true, found: ${s}` - return left(m) - } - }) - }) - } - - return evalInContext(input, callback) + const [evaluator, runExpr] = prepareEvaluator(callee, input) + const newEval = evaluator.evaluate(runExpr) + return newEval + .mapLeft(quintErrorToString) + .chain(runResult => { + if (!(runResult.kind == 'bool' && runResult.value === true)) { + return left(`Callable ${callee} was expected to evaluate to true, found: ${expressionToString(runResult)}`) + } + const registerValue = [...evaluator.ctx.varStorage.nextVars.values()].find(r => r.name === varName)?.value + if (!registerValue) { + return left(`Value of the variable ${varName} is undefined`) + } + return registerValue.mapLeft(quintErrorToString) + }) + .map(res => expressionToString(res.toQuintEx(idGen))) } // Evaluate a run and return the result. function evalRun(callee: string, input: string): Either { - // Recall that left(...) is used for errors, - // whereas right(...) is used for non-errors in sweet monads. - const callback = (ctx: CompilationContext): Either => { - return callableFromContext(ctx, callee).chain(run => { - return run - .eval() - .mapLeft(quintErrorToString) - .chain(res => { - return right(expressionToString(res.toQuintEx(idGen))) - }) - }) - } + const [evaluator, runExpr] = prepareEvaluator(callee, input) + const newEval = evaluator.evaluate(runExpr) - return evalInContext(input, callback) + return newEval.mapLeft(quintErrorToString).map(res => expressionToString(res)) } function assertVarAfterCall(varName: string, expected: string, callee: string, input: string) { @@ -379,8 +301,9 @@ describe('compiling specs to runtime values', () => { describe('compile variables', () => { it('variable definitions', () => { - const input = 'var x: int' - assertVarExists('var', 'x', input) + const context = 'var x: int' + const input = "x' = 1" + assertVarExists('x', context, input) }) }) @@ -1038,7 +961,7 @@ describe('compiling specs to runtime values', () => { evalRun('run1', input) .mapRight(result => assert.fail(`Expected the run to fail, found: ${result}`)) - .mapLeft(m => assert.equal(m, "[QNT513] Cannot continue in A.then(B), A evaluates to 'false'")) + .mapLeft(m => assert.equal(m, '[QNT513] Reps loop could not continue after iteration #6 evaluated to false')) }) it('fail', () => { @@ -1133,155 +1056,3 @@ describe('compiling specs to runtime values', () => { }) }) }) - -describe('incremental compilation', () => { - const dummyRng: Rng = { - getState: () => 0n, - setState: (_: bigint) => {}, - next: () => 0n, - } - /* Adds some quint code to the compilation and evaluation state */ - function compileModules(text: string, mainName: string): CompilationContext { - const idGen = newIdGenerator() - const sourceCode: Map = new Map() - const fake_path: SourceLookupPath = { normalizedPath: 'fake_path', toSourceName: () => 'fake_path' } - const { modules, table, sourceMap, errors } = parse(idGen, 'fake_path', fake_path, text, sourceCode) - assert.isEmpty(errors) - - const [analysisErrors, analysisOutput] = analyzeModules(table, modules) - assert.isEmpty(analysisErrors) - - const { flattenedModules, flattenedAnalysis, flattenedTable } = flattenModules( - modules, - table, - idGen, - sourceMap, - analysisOutput - ) - - const state: CompilationState = { - originalModules: modules, - idGen, - modules: flattenedModules, - mainName, - sourceMap, - analysisOutput: flattenedAnalysis, - sourceCode, - } - - const moduleToCompile = flattenedModules[flattenedModules.length - 1] - - return compile( - state, - newEvaluationState(noExecutionListener), - flattenedTable, - dummyRng.next, - false, - moduleToCompile.declarations - ) - } - - describe('compileExpr', () => { - it('should compile a Quint expression', () => { - const { compilationState, evaluationState } = compileModules('module m { pure val x = 1 }', 'm') - - const parsed = parseExpressionOrDeclaration( - 'x + 2', - 'test.qnt', - compilationState.idGen, - compilationState.sourceMap - ) - const expr = parsed.kind === 'expr' ? parsed.expr : undefined - const context = compileExpr(compilationState, evaluationState, dummyRng, false, expr!) - - assert.deepEqual(context.compilationState.analysisOutput.types.get(expr!.id)?.type, { kind: 'int', id: 3n }) - - assertInputFromContext(context, '3') - }) - }) - - describe('compileDef', () => { - it('should compile a Quint definition', () => { - const { compilationState, evaluationState } = compileModules('module m { pure val x = 1 }', 'm') - - const parsed = parseExpressionOrDeclaration( - 'val y = x + 2', - 'test.qnt', - compilationState.idGen, - compilationState.sourceMap - ) - const defs = parsed.kind === 'declaration' ? parsed.decls : undefined - const context = compileDecls(compilationState, evaluationState, dummyRng, false, defs!) - - assert.deepEqual(context.compilationState.analysisOutput.types.get(defs![0].id)?.type, { kind: 'int', id: 3n }) - - const computable = context.evaluationState?.context.get(kindName('callable', defs![0].id))! - assertComputableAsString(computable, '3') - }) - - it('non-exported imports are not visible in subsequent importing modules', () => { - const { compilationState, evaluationState } = compileModules( - 'module m1 { pure val x1 = 1 }' + 'module m2 { import m1.* pure val x2 = x1 }' + 'module m3 { import m2.* }', // m1 shouldn't be acessible inside m3 - 'm3' - ) - - const parsed = parseExpressionOrDeclaration( - 'def x3 = x1', - 'test.qnt', - compilationState.idGen, - compilationState.sourceMap - ) - const decls = parsed.kind === 'declaration' ? parsed.decls : [] - const context = compileDecls(compilationState, evaluationState, dummyRng, false, decls) - - assert.sameDeepMembers(context.syntaxErrors, [ - { - code: 'QNT404', - message: "Name 'x1' not found", - reference: 10n, - data: {}, - }, - ]) - }) - - it('can complile type alias declarations', () => { - const { compilationState, evaluationState } = compileModules('module m {}', 'm') - const parsed = parseExpressionOrDeclaration( - 'type T = int', - 'test.qnt', - compilationState.idGen, - compilationState.sourceMap - ) - const decls = parsed.kind === 'declaration' ? parsed.decls : [] - const context = compileDecls(compilationState, evaluationState, dummyRng, false, decls) - - const typeDecl = decls[0] - assert(typeDecl.kind === 'typedef') - assert(typeDecl.name === 'T') - assert(typeDecl.type!.kind === 'int') - - assert.sameDeepMembers(context.syntaxErrors, []) - }) - - it('can compile sum type declarations', () => { - const { compilationState, evaluationState } = compileModules('module m {}', 'm') - const parsed = parseExpressionOrDeclaration( - 'type T = A(int) | B(str) | C', - 'test.qnt', - compilationState.idGen, - compilationState.sourceMap - ) - const decls = parsed.kind === 'declaration' ? parsed.decls : [] - const context = compileDecls(compilationState, evaluationState, dummyRng, false, decls) - - assert(decls.find(t => t.kind === 'typedef' && t.name === 'T')) - // Sum type declarations are expanded to add an - // operator declaration for each constructor: - assert(decls.find(t => t.kind === 'def' && t.name === 'A')) - assert(decls.find(t => t.kind === 'def' && t.name === 'B')) - assert(decls.find(t => t.kind === 'def' && t.name === 'C')) - - assert.sameDeepMembers(context.syntaxErrors, []) - }) - }) -}) diff --git a/quint/test/runtime/trace.test.ts b/quint/test/runtime/trace.test.ts index ef210f7cf..fb6799830 100644 --- a/quint/test/runtime/trace.test.ts +++ b/quint/test/runtime/trace.test.ts @@ -1,20 +1,23 @@ import { describe, it } from 'mocha' import { assert } from 'chai' -import { none } from '@sweet-monads/maybe' import { newTraceRecorder } from '../../src/runtime/trace' import { QuintApp } from '../../src/ir/quintIr' import { verbosity } from '../../src/verbosity' import { newRng } from '../../src/rng' +import { QuintError } from '../../src/quintError' +import { left } from '@sweet-monads/either' + +const emptyFrameError: QuintError = { code: 'QNT501', message: 'empty frame' } describe('newTraceRecorder', () => { it('one layer', () => { const rec = newTraceRecorder(verbosity.maxVerbosity, newRng()) const A: QuintApp = { id: 0n, kind: 'app', opcode: 'A', args: [] } rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) const trace = rec.currentFrame assert(trace.subframes.length === 2) assert(trace.subframes[0].app === A) @@ -30,12 +33,12 @@ describe('newTraceRecorder', () => { // (A calls (B, after that it calls A)), after that another A is called rec.onUserOperatorCall(A) rec.onUserOperatorCall(B) - rec.onUserOperatorReturn(B, [], none()) + rec.onUserOperatorReturn(B, [], left(emptyFrameError)) rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) const trace = rec.currentFrame assert(trace.subframes.length === 2) assert(trace.subframes[0].app === A) @@ -59,33 +62,33 @@ describe('newTraceRecorder', () => { } // A() rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) // any { rec.onAnyOptionCall(anyEx, 0) // A() rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) rec.onAnyOptionReturn(anyEx, 0) rec.onAnyOptionCall(anyEx, 1) // B() rec.onUserOperatorCall(B) - rec.onUserOperatorReturn(B, [], none()) + rec.onUserOperatorReturn(B, [], left(emptyFrameError)) // C() rec.onUserOperatorCall(C) - rec.onUserOperatorReturn(C, [], none()) + rec.onUserOperatorReturn(C, [], left(emptyFrameError)) rec.onAnyOptionReturn(anyEx, 1) rec.onAnyOptionCall(anyEx, 2) // C() rec.onUserOperatorCall(C) - rec.onUserOperatorReturn(C, [], none()) + rec.onUserOperatorReturn(C, [], left(emptyFrameError)) rec.onAnyOptionReturn(anyEx, 2) rec.onAnyReturn(3, 1) // } // any rec.onUserOperatorCall(A) - rec.onUserOperatorReturn(A, [], none()) + rec.onUserOperatorReturn(A, [], left(emptyFrameError)) const trace = rec.currentFrame assert(trace.subframes.length === 4) diff --git a/quint/test/static/callgraph.test.ts b/quint/test/static/callgraph.test.ts index 383800391..18074e0bf 100644 --- a/quint/test/static/callgraph.test.ts +++ b/quint/test/static/callgraph.test.ts @@ -130,7 +130,6 @@ describe('compute call graph', () => { | pure val myM = sqr(3) | import B(M = myM) as B1 | pure val quadM = 2 * B1::doubleM - | pure val constRef = B1::M | export B1.* |}` ) @@ -150,7 +149,6 @@ describe('compute call graph', () => { const importB = findInstance(main, imp => imp.protoName === 'B') const quadM = findDef(main, 'quadM') const doubleM = findDef(B, 'doubleM') - const constRef = findDef(main, 'constRef') const exportB1 = findExport(main, exp => exp.protoName === 'B1') expect(graph.get(importA.id)?.toArray()).eql([A.id]) @@ -158,10 +156,5 @@ describe('compute call graph', () => { expect(graph.get(importB.id)?.toArray()).to.eql([B.id, myM.id]) expect(graph.get(quadM.id)?.toArray()).to.eql([doubleM.id, importB.id]) expect(graph.get(exportB1.id)?.toArray()).to.eql([importB.id]) - - // Find the id for B1::M by checking the dependencies of constRef - // A regression for #1183, ensuring constants like B1::M depend on the instance statements - const B1Mid = graph.get(constRef.id)?.toArray()[0]! - expect(graph.get(B1Mid)?.toArray()).to.eql([importB.id]) }) }) diff --git a/quint/testFixture/SuperSpec.json b/quint/testFixture/SuperSpec.json index 3c7d5d182..b92172e6c 100644 --- a/quint/testFixture/SuperSpec.json +++ b/quint/testFixture/SuperSpec.json @@ -1 +1 @@ -{"stage":"parsing","warnings":[],"modules":[{"id":11,"name":"Proto","declarations":[{"kind":"var","name":"x","typeAnnotation":{"id":9,"kind":"int"},"id":10,"depth":0},{"kind":"const","name":"N","typeAnnotation":{"id":7,"kind":"int"},"id":8,"depth":0}]},{"id":3,"name":"M1","declarations":[{"id":2,"kind":"def","name":"foo","qualifier":"val","expr":{"id":1,"kind":"int","value":4}}]},{"id":6,"name":"M2","declarations":[{"id":5,"kind":"def","name":"bar","qualifier":"val","expr":{"id":4,"kind":"int","value":4}}]},{"id":532,"name":"withConsts","declarations":[{"id":103,"kind":"def","name":"sub_1_to_2","qualifier":"val","expr":{"id":102,"kind":"app","opcode":"isub","args":[{"id":100,"kind":"int","value":1},{"id":101,"kind":"int","value":2}]}},{"id":107,"kind":"def","name":"mul_2_to_3","qualifier":"val","expr":{"id":106,"kind":"app","opcode":"imul","args":[{"id":104,"kind":"int","value":2},{"id":105,"kind":"int","value":3}]}},{"id":111,"kind":"def","name":"div_2_to_3","qualifier":"val","expr":{"id":110,"kind":"app","opcode":"idiv","args":[{"id":108,"kind":"int","value":2},{"id":109,"kind":"int","value":3}]}},{"id":115,"kind":"def","name":"mod_2_to_3","qualifier":"val","expr":{"id":114,"kind":"app","opcode":"imod","args":[{"id":112,"kind":"int","value":2},{"id":113,"kind":"int","value":3}]}},{"id":119,"kind":"def","name":"pow_2_to_3","qualifier":"val","expr":{"id":118,"kind":"app","opcode":"ipow","args":[{"id":116,"kind":"int","value":2},{"id":117,"kind":"int","value":3}]}},{"id":122,"kind":"def","name":"uminus","qualifier":"val","expr":{"id":121,"kind":"app","opcode":"iuminus","args":[{"id":120,"kind":"int","value":100}]}},{"id":126,"kind":"def","name":"gt_2_to_3","qualifier":"val","expr":{"id":125,"kind":"app","opcode":"igt","args":[{"id":123,"kind":"int","value":2},{"id":124,"kind":"int","value":3}]}},{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},{"id":130,"kind":"def","name":"ge_2_to_3","qualifier":"val","expr":{"id":129,"kind":"app","opcode":"igte","args":[{"id":127,"kind":"int","value":2},{"id":128,"kind":"int","value":3}]}},{"id":134,"kind":"def","name":"lt_2_to_3","qualifier":"val","expr":{"id":133,"kind":"app","opcode":"ilt","args":[{"id":131,"kind":"int","value":2},{"id":132,"kind":"int","value":3}]}},{"id":138,"kind":"def","name":"le_2_to_3","qualifier":"val","expr":{"id":137,"kind":"app","opcode":"ilte","args":[{"id":135,"kind":"int","value":2},{"id":136,"kind":"int","value":3}]}},{"id":142,"kind":"def","name":"eqeq_2_to_3","qualifier":"val","expr":{"id":141,"kind":"app","opcode":"eq","args":[{"id":139,"kind":"int","value":2},{"id":140,"kind":"int","value":3}]}},{"id":146,"kind":"def","name":"ne_2_to_3","qualifier":"val","expr":{"id":145,"kind":"app","opcode":"neq","args":[{"id":143,"kind":"int","value":2},{"id":144,"kind":"int","value":3}]}},{"id":152,"kind":"def","name":"VeryTrue","qualifier":"val","expr":{"id":151,"kind":"app","opcode":"eq","args":[{"id":149,"kind":"app","opcode":"iadd","args":[{"id":147,"kind":"int","value":2},{"id":148,"kind":"int","value":2}]},{"id":150,"kind":"int","value":4}]}},{"id":156,"kind":"def","name":"nat_includes_one","qualifier":"val","expr":{"id":155,"kind":"app","opcode":"in","args":[{"id":153,"kind":"int","value":1},{"id":154,"kind":"name","name":"Nat"}]}},{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},{"id":168,"kind":"def","name":"withType","qualifier":"val","expr":{"id":167,"kind":"app","opcode":"Set","args":[{"id":165,"kind":"int","value":1},{"id":166,"kind":"int","value":2}]},"typeAnnotation":{"id":164,"kind":"set","elem":{"id":163,"kind":"int"}}},{"id":169,"kind":"typedef","name":"PROC","depth":0},{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},{"id":177,"kind":"def","name":"magicNumber","qualifier":"pureval","expr":{"id":176,"kind":"int","value":42}},{"kind":"const","name":"MySet","typeAnnotation":{"id":18,"kind":"set","elem":{"id":17,"kind":"int"}},"id":19,"depth":0},{"kind":"const","name":"MySeq","typeAnnotation":{"id":21,"kind":"list","elem":{"id":20,"kind":"bool"}},"id":22,"depth":0},{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},{"id":242,"kind":"def","name":"test_and","qualifier":"val","expr":{"id":241,"kind":"app","opcode":"and","args":[{"id":239,"kind":"bool","value":false},{"id":240,"kind":"bool","value":true}]}},{"id":246,"kind":"def","name":"test_or","qualifier":"val","expr":{"id":245,"kind":"app","opcode":"or","args":[{"id":243,"kind":"bool","value":false},{"id":244,"kind":"bool","value":true}]}},{"id":250,"kind":"def","name":"test_implies","qualifier":"val","expr":{"id":249,"kind":"app","opcode":"implies","args":[{"id":247,"kind":"bool","value":false},{"id":248,"kind":"bool","value":true}]}},{"kind":"const","name":"MyFun","typeAnnotation":{"id":25,"kind":"fun","arg":{"id":23,"kind":"int"},"res":{"id":24,"kind":"str"}},"id":26,"depth":0},{"id":283,"kind":"def","name":"test_block_and","qualifier":"val","expr":{"id":282,"kind":"app","opcode":"and","args":[{"id":279,"kind":"bool","value":false},{"id":280,"kind":"bool","value":true},{"id":281,"kind":"bool","value":false}]}},{"id":288,"kind":"def","name":"test_action_and","qualifier":"action","expr":{"id":287,"kind":"app","opcode":"actionAll","args":[{"id":284,"kind":"bool","value":false},{"id":285,"kind":"bool","value":true},{"id":286,"kind":"bool","value":false}]}},{"id":293,"kind":"def","name":"test_block_or","qualifier":"val","expr":{"id":292,"kind":"app","opcode":"or","args":[{"id":289,"kind":"bool","value":false},{"id":290,"kind":"bool","value":true},{"id":291,"kind":"bool","value":false}]}},{"id":298,"kind":"def","name":"test_action_or","qualifier":"action","expr":{"id":297,"kind":"app","opcode":"actionAny","args":[{"id":294,"kind":"bool","value":false},{"id":295,"kind":"bool","value":true},{"id":296,"kind":"bool","value":false}]}},{"id":303,"kind":"def","name":"test_ite","qualifier":"val","expr":{"id":302,"kind":"app","opcode":"ite","args":[{"id":299,"kind":"bool","value":true},{"id":300,"kind":"int","value":1},{"id":301,"kind":"int","value":0}]}},{"kind":"const","name":"MyFunFun","typeAnnotation":{"id":31,"kind":"fun","arg":{"id":29,"kind":"fun","arg":{"id":27,"kind":"int"},"res":{"id":28,"kind":"str"}},"res":{"id":30,"kind":"bool"}},"id":32,"depth":0},{"kind":"var","name":"f1","typeAnnotation":{"id":320,"kind":"fun","arg":{"id":318,"kind":"str"},"res":{"id":319,"kind":"int"}},"id":321,"depth":0},{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}}},{"id":342,"kind":"def","name":"oper_in","qualifier":"val","expr":{"id":341,"kind":"app","opcode":"in","args":[{"id":339,"kind":"int","value":1},{"id":340,"kind":"app","opcode":"Set","args":[]}]}},{"kind":"const","name":"MyOperator","typeAnnotation":{"id":36,"kind":"oper","args":[{"id":33,"kind":"int"},{"id":34,"kind":"str"}],"res":{"id":35,"kind":"bool"}},"id":37,"depth":0},{"id":407,"kind":"def","name":"one","qualifier":"val","expr":{"id":406,"kind":"app","opcode":"head","args":[{"id":405,"kind":"app","opcode":"List","args":[{"id":403,"kind":"int","value":1},{"id":404,"kind":"int","value":2}]}]}},{"id":412,"kind":"def","name":"test_tuple","qualifier":"val","expr":{"id":411,"kind":"app","opcode":"Tup","args":[{"id":408,"kind":"int","value":1},{"id":409,"kind":"int","value":2},{"id":410,"kind":"int","value":3}]}},{"id":417,"kind":"def","name":"test_tuple2","qualifier":"val","expr":{"id":416,"kind":"app","opcode":"Tup","args":[{"id":413,"kind":"int","value":1},{"id":414,"kind":"int","value":2},{"id":415,"kind":"int","value":3}]}},{"kind":"const","name":"MyOperatorWithComma","typeAnnotation":{"id":41,"kind":"oper","args":[{"id":38,"kind":"int"},{"id":39,"kind":"str"}],"res":{"id":40,"kind":"bool"}},"id":42,"depth":0},{"id":421,"kind":"def","name":"test_pair","qualifier":"val","expr":{"id":420,"kind":"app","opcode":"Tup","args":[{"id":418,"kind":"int","value":4},{"id":419,"kind":"int","value":5}]}},{"id":426,"kind":"def","name":"test_list","qualifier":"val","expr":{"id":425,"kind":"app","opcode":"List","args":[{"id":422,"kind":"int","value":1},{"id":423,"kind":"int","value":2},{"id":424,"kind":"int","value":3}]}},{"id":431,"kind":"def","name":"test_list2","qualifier":"val","expr":{"id":430,"kind":"app","opcode":"List","args":[{"id":427,"kind":"int","value":1},{"id":428,"kind":"int","value":2},{"id":429,"kind":"int","value":3}]}},{"id":438,"kind":"def","name":"test_list_nth","qualifier":"val","expr":{"id":437,"kind":"app","opcode":"nth","args":[{"id":435,"kind":"app","opcode":"List","args":[{"id":432,"kind":"int","value":2},{"id":433,"kind":"int","value":3},{"id":434,"kind":"int","value":4}]},{"id":436,"kind":"int","value":2}]}},{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]}},{"id":450,"kind":"def","name":"test_record2","qualifier":"val","expr":{"id":449,"kind":"app","opcode":"Rec","args":[{"id":445,"kind":"str","value":"name"},{"id":446,"kind":"str","value":"igor"},{"id":447,"kind":"str","value":"year"},{"id":448,"kind":"int","value":1981}]}},{"id":463,"kind":"def","name":"test_set","qualifier":"val","expr":{"id":462,"kind":"app","opcode":"Set","args":[{"id":459,"kind":"int","value":1},{"id":460,"kind":"int","value":2},{"id":461,"kind":"int","value":3}]}},{"kind":"const","name":"MyTuple","typeAnnotation":{"id":46,"kind":"tup","fields":{"kind":"row","fields":[{"fieldName":"0","fieldType":{"id":43,"kind":"int"}},{"fieldName":"1","fieldType":{"id":44,"kind":"bool"}},{"fieldName":"2","fieldType":{"id":45,"kind":"str"}}],"other":{"kind":"empty"}}},"id":47,"depth":0},{"id":493,"kind":"def","name":"in_2_empty","qualifier":"val","expr":{"id":492,"kind":"app","opcode":"in","args":[{"id":490,"kind":"int","value":2},{"id":491,"kind":"app","opcode":"Set","args":[]}]}},{"id":497,"kind":"def","name":"subseteq_empty","qualifier":"val","expr":{"id":496,"kind":"app","opcode":"subseteq","args":[{"id":494,"kind":"app","opcode":"Set","args":[]},{"id":495,"kind":"app","opcode":"Set","args":[]}]}},{"id":506,"kind":"def","name":"powersets","qualifier":"val","expr":{"id":505,"kind":"app","opcode":"in","args":[{"id":499,"kind":"app","opcode":"Set","args":[{"id":498,"kind":"int","value":1}]},{"id":504,"kind":"app","opcode":"powerset","args":[{"id":503,"kind":"app","opcode":"Set","args":[{"id":500,"kind":"int","value":1},{"id":501,"kind":"int","value":2},{"id":502,"kind":"int","value":3}]}]}]}},{"id":512,"kind":"def","name":"lists","qualifier":"val","expr":{"id":511,"kind":"app","opcode":"allListsUpTo","args":[{"id":509,"kind":"app","opcode":"Set","args":[{"id":507,"kind":"int","value":1},{"id":508,"kind":"int","value":2}]},{"id":510,"kind":"int","value":3}]}},{"kind":"const","name":"MyTupleWithComma","typeAnnotation":{"id":51,"kind":"tup","fields":{"kind":"row","fields":[{"fieldName":"0","fieldType":{"id":48,"kind":"int"}},{"fieldName":"1","fieldType":{"id":49,"kind":"bool"}},{"fieldName":"2","fieldType":{"id":50,"kind":"str"}}],"other":{"kind":"empty"}}},"id":52,"depth":0},{"id":525,"kind":"typedef","name":"INT_SET","type":{"id":524,"kind":"set","elem":{"id":523,"kind":"int"}},"depth":0},{"id":526,"kind":"typedef","name":"UNINTERPRETED_TYPE","depth":0},{"kind":"const","name":"MyRecord","typeAnnotation":{"id":56,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"i","fieldType":{"id":53,"kind":"int"}},{"fieldName":"b","fieldType":{"id":54,"kind":"bool"}},{"fieldName":"s","fieldType":{"id":55,"kind":"str"}}],"other":{"kind":"empty"}}},"id":57,"depth":0},{"kind":"const","name":"MyRecordWithComma","typeAnnotation":{"id":61,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"i","fieldType":{"id":58,"kind":"int"}},{"fieldName":"b","fieldType":{"id":59,"kind":"bool"}},{"fieldName":"s","fieldType":{"id":60,"kind":"str"}}],"other":{"kind":"empty"}}},"id":62,"depth":0},{"id":64,"kind":"typedef","name":"some::namespace::MyType","type":{"id":63,"kind":"int"},"depth":0},{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},{"kind":"var","name":"i","typeAnnotation":{"id":92,"kind":"int"},"id":93,"depth":0},{"kind":"var","name":"j","typeAnnotation":{"id":94,"kind":"bool"},"id":95,"depth":0},{"id":99,"kind":"def","name":"add_1_to_2","qualifier":"val","expr":{"id":98,"kind":"app","opcode":"iadd","args":[{"id":96,"kind":"int","value":1},{"id":97,"kind":"int","value":2}]}},{"id":162,"kind":"def","name":"there_is_truth","qualifier":"val","expr":{"id":161,"kind":"app","opcode":"exists","args":[{"id":157,"kind":"name","name":"Bool"},{"id":160,"kind":"lambda","params":[{"id":158,"name":"x"}],"qualifier":"def","expr":{"id":159,"kind":"name","name":"x"}}]}},{"id":173,"kind":"def","name":"withUninterpretedType","qualifier":"val","expr":{"id":172,"kind":"app","opcode":"Set","args":[]},"typeAnnotation":{"id":171,"kind":"set","elem":{"id":170,"kind":"const","name":"PROC"}}},{"id":184,"kind":"def","name":"add","qualifier":"puredef","expr":{"id":183,"kind":"lambda","params":[{"id":178,"name":"x"},{"id":179,"name":"y"}],"qualifier":"puredef","expr":{"id":182,"kind":"app","opcode":"iadd","args":[{"id":180,"kind":"name","name":"x"},{"id":181,"kind":"name","name":"y"}]}}},{"id":190,"kind":"def","name":"ofN","qualifier":"def","expr":{"id":189,"kind":"lambda","params":[{"id":185,"name":"factor"}],"qualifier":"def","expr":{"id":188,"kind":"app","opcode":"imul","args":[{"id":186,"kind":"name","name":"factor"},{"id":187,"kind":"name","name":"n"}]}}},{"id":196,"kind":"def","name":"A","qualifier":"action","expr":{"id":195,"kind":"lambda","params":[{"id":191,"name":"x"}],"qualifier":"action","expr":{"id":194,"kind":"app","opcode":"assign","args":[{"id":193,"kind":"name","name":"n"},{"id":192,"kind":"name","name":"x"}]}}},{"id":201,"kind":"def","name":"B","qualifier":"puredef","expr":{"id":200,"kind":"lambda","params":[{"id":197,"name":"x"}],"qualifier":"puredef","expr":{"id":199,"kind":"app","opcode":"not","args":[{"id":198,"kind":"name","name":"x"}]}}},{"id":212,"kind":"def","name":"H","qualifier":"def","expr":{"id":211,"kind":"lambda","params":[{"id":202,"name":"x"},{"id":203,"name":"y"}],"qualifier":"def","expr":{"id":210,"kind":"app","opcode":"iadd","args":[{"id":208,"kind":"name","name":"x"},{"id":209,"kind":"name","name":"y"}]}},"typeAnnotation":{"id":207,"kind":"oper","args":[{"id":204,"kind":"int"},{"id":205,"kind":"int"}],"res":{"id":206,"kind":"int"}}},{"id":219,"kind":"def","name":"Pol","qualifier":"def","expr":{"id":218,"kind":"lambda","params":[{"id":213,"name":"x"}],"qualifier":"def","expr":{"id":217,"kind":"name","name":"x"}},"typeAnnotation":{"id":216,"kind":"oper","args":[{"id":214,"kind":"var","name":"a"}],"res":{"id":215,"kind":"var","name":"a"}}},{"id":225,"kind":"def","name":"asgn","qualifier":"action","expr":{"id":224,"kind":"app","opcode":"assign","args":[{"id":223,"kind":"name","name":"k"},{"id":222,"kind":"int","value":3}]}},{"id":238,"kind":"def","name":"min","qualifier":"puredef","expr":{"id":237,"kind":"lambda","params":[{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"}},{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"}}],"qualifier":"puredef","expr":{"id":236,"kind":"app","opcode":"ite","args":[{"id":233,"kind":"app","opcode":"ilt","args":[{"id":231,"kind":"name","name":"x"},{"id":232,"kind":"name","name":"y"}]},{"id":234,"kind":"name","name":"x"},{"id":235,"kind":"name","name":"y"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":226,"kind":"int"},{"id":228,"kind":"int"}],"res":{"id":230,"kind":"int"}}},{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}}},{"id":317,"kind":"def","name":"test_ite2","qualifier":"def","expr":{"id":316,"kind":"lambda","params":[{"id":304,"name":"x"},{"id":305,"name":"y"}],"qualifier":"def","expr":{"id":315,"kind":"app","opcode":"ite","args":[{"id":308,"kind":"app","opcode":"ilt","args":[{"id":306,"kind":"name","name":"x"},{"id":307,"kind":"int","value":10}]},{"id":311,"kind":"app","opcode":"iadd","args":[{"id":309,"kind":"name","name":"y"},{"id":310,"kind":"int","value":5}]},{"id":314,"kind":"app","opcode":"imod","args":[{"id":312,"kind":"name","name":"y"},{"id":313,"kind":"int","value":5}]}]}}},{"id":325,"kind":"def","name":"funapp","qualifier":"val","expr":{"id":324,"kind":"app","opcode":"get","args":[{"id":322,"kind":"name","name":"f1"},{"id":323,"kind":"str","value":"a"}]}},{"id":334,"kind":"def","name":"oper_app_normal","qualifier":"val","expr":{"id":333,"kind":"app","opcode":"MyOper","args":[{"id":331,"kind":"str","value":"a"},{"id":332,"kind":"int","value":42}]}},{"id":338,"kind":"def","name":"oper_app_ufcs","qualifier":"val","expr":{"id":337,"kind":"app","opcode":"MyOper","args":[{"id":335,"kind":"str","value":"a"},{"id":336,"kind":"int","value":42}]}},{"id":350,"kind":"def","name":"oper_app_dot1","qualifier":"val","expr":{"id":349,"kind":"app","opcode":"filter","args":[{"id":343,"kind":"name","name":"S"},{"id":348,"kind":"lambda","params":[{"id":344,"name":"x"}],"qualifier":"def","expr":{"id":347,"kind":"app","opcode":"igt","args":[{"id":345,"kind":"name","name":"x"},{"id":346,"kind":"int","value":10}]}}]}},{"id":388,"kind":"def","name":"oper_app_dot4","qualifier":"val","expr":{"id":387,"kind":"app","opcode":"filter","args":[{"id":383,"kind":"name","name":"S"},{"id":386,"kind":"lambda","params":[{"id":384,"name":"_"}],"qualifier":"def","expr":{"id":385,"kind":"bool","value":true}}]}},{"id":396,"kind":"def","name":"f","qualifier":"val","expr":{"id":395,"kind":"app","opcode":"mapBy","args":[{"id":389,"kind":"name","name":"S"},{"id":394,"kind":"lambda","params":[{"id":390,"name":"x"}],"qualifier":"def","expr":{"id":393,"kind":"app","opcode":"iadd","args":[{"id":391,"kind":"name","name":"x"},{"id":392,"kind":"int","value":1}]}}]}},{"id":402,"kind":"def","name":"set_difference","qualifier":"val","expr":{"id":401,"kind":"app","opcode":"exclude","args":[{"id":397,"kind":"name","name":"S"},{"id":400,"kind":"app","opcode":"Set","args":[{"id":398,"kind":"int","value":3},{"id":399,"kind":"int","value":4}]}]}},{"id":458,"kind":"def","name":"test_record3","qualifier":"val","expr":{"id":457,"kind":"app","opcode":"with","args":[{"id":456,"kind":"app","opcode":"with","args":[{"id":455,"kind":"name","name":"test_record"},{"id":452,"kind":"str","value":"name"},{"id":451,"kind":"str","value":"quint"}]},{"id":454,"kind":"str","value":"year"},{"id":453,"kind":"int","value":2023}]}},{"id":474,"kind":"def","name":"rec_field","qualifier":"val","expr":{"id":473,"kind":"let","opdef":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]}},"expr":{"id":472,"kind":"app","opcode":"field","args":[{"id":470,"kind":"name","name":"my_rec"},{"id":471,"kind":"str","value":"a"}]}}},{"id":483,"kind":"def","name":"tup_elem","qualifier":"val","expr":{"id":482,"kind":"let","opdef":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]}},"expr":{"id":481,"kind":"app","opcode":"item","args":[{"id":479,"kind":"name","name":"my_tup"},{"id":480,"kind":"int","value":2}]}}},{"id":489,"kind":"def","name":"isEmpty","qualifier":"def","expr":{"id":488,"kind":"lambda","params":[{"id":484,"name":"s"}],"qualifier":"def","expr":{"id":487,"kind":"app","opcode":"eq","args":[{"id":485,"kind":"name","name":"s"},{"id":486,"kind":"app","opcode":"List","args":[]}]}}},{"id":516,"kind":"assume","name":"positive","assumption":{"id":515,"kind":"app","opcode":"igt","args":[{"id":513,"kind":"name","name":"N"},{"id":514,"kind":"int","value":0}]},"depth":0},{"id":520,"kind":"assume","name":"_","assumption":{"id":519,"kind":"app","opcode":"neq","args":[{"id":517,"kind":"name","name":"N"},{"id":518,"kind":"int","value":0}]}},{"id":521,"kind":"import","defName":"foo","protoName":"M1"},{"id":522,"kind":"import","defName":"*","protoName":"M2"},{"kind":"var","name":"S1","typeAnnotation":{"id":527,"kind":"const","name":"INT_SET"},"id":528,"depth":0},{"id":531,"kind":"instance","qualifiedName":"Inst1","protoName":"Proto","overrides":[[{"id":530,"name":"N"},{"id":529,"kind":"name","name":"N"}]],"identityOverride":false},{"id":77,"kind":"def","name":"Circle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":65,"kind":"int"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":76,"kind":"lambda","params":[{"id":73,"name":"__CircleParam"}],"qualifier":"def","expr":{"id":75,"kind":"app","opcode":"variant","args":[{"id":72,"kind":"str","value":"Circle"},{"kind":"name","name":"__CircleParam","id":74}]}}},{"id":83,"kind":"def","name":"Rectangle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":82,"kind":"lambda","params":[{"id":79,"name":"__RectangleParam"}],"qualifier":"def","expr":{"id":81,"kind":"app","opcode":"variant","args":[{"id":78,"kind":"str","value":"Rectangle"},{"kind":"name","name":"__RectangleParam","id":80}]}}},{"id":89,"kind":"def","name":"Dog","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":69,"kind":"str"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":88,"kind":"lambda","params":[{"id":85,"name":"__DogParam"}],"qualifier":"def","expr":{"id":87,"kind":"app","opcode":"variant","args":[{"id":84,"kind":"str","value":"Dog"},{"kind":"name","name":"__DogParam","id":86}]}}},{"kind":"const","name":"MyUnion","typeAnnotation":{"id":90,"kind":"const","name":"MyUnionType"},"id":91,"depth":0},{"id":262,"kind":"def","name":"G","qualifier":"def","expr":{"id":261,"kind":"lambda","params":[{"id":255,"name":"x"}],"qualifier":"def","expr":{"id":260,"kind":"app","opcode":"and","args":[{"id":257,"kind":"app","opcode":"F","args":[{"id":256,"kind":"name","name":"x"}]},{"id":259,"kind":"app","opcode":"not","args":[{"id":258,"kind":"name","name":"x"}]}]}}},{"id":270,"kind":"def","name":"test_and_arg","qualifier":"def","expr":{"id":269,"kind":"lambda","params":[{"id":263,"name":"x"}],"qualifier":"def","expr":{"id":268,"kind":"app","opcode":"and","args":[{"id":265,"kind":"app","opcode":"F","args":[{"id":264,"kind":"name","name":"x"}]},{"id":267,"kind":"app","opcode":"not","args":[{"id":266,"kind":"name","name":"x"}]}]}}},{"id":278,"kind":"def","name":"test_or_arg","qualifier":"def","expr":{"id":277,"kind":"lambda","params":[{"id":271,"name":"x"}],"qualifier":"def","expr":{"id":276,"kind":"app","opcode":"or","args":[{"id":273,"kind":"app","opcode":"F","args":[{"id":272,"kind":"name","name":"x"}]},{"id":275,"kind":"app","opcode":"not","args":[{"id":274,"kind":"name","name":"x"}]}]}}},{"id":370,"kind":"def","name":"tuple_sum","qualifier":"val","expr":{"id":369,"kind":"app","opcode":"map","args":[{"id":353,"kind":"app","opcode":"tuples","args":[{"id":351,"kind":"name","name":"S"},{"id":352,"kind":"name","name":"MySet"}]},{"id":368,"kind":"lambda","params":[{"id":359,"name":"quintTupledLambdaParam359"}],"qualifier":"def","expr":{"id":364,"kind":"let","opdef":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]}},"expr":{"id":360,"kind":"let","opdef":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]}},"expr":{"id":358,"kind":"app","opcode":"iadd","args":[{"id":356,"kind":"name","name":"s"},{"id":357,"kind":"name","name":"mys"}]}}}}]}},{"id":382,"kind":"def","name":"oper_nondet","qualifier":"action","expr":{"id":381,"kind":"let","opdef":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]}},"expr":{"id":380,"kind":"app","opcode":"actionAll","args":[{"id":376,"kind":"app","opcode":"igt","args":[{"id":374,"kind":"name","name":"x"},{"id":375,"kind":"int","value":10}]},{"id":379,"kind":"app","opcode":"assign","args":[{"id":378,"kind":"name","name":"k"},{"id":377,"kind":"name","name":"x"}]}]}}}]}],"table":{"2":{"id":2,"kind":"def","name":"foo","qualifier":"val","expr":{"id":1,"kind":"int","value":4},"depth":0},"5":{"id":5,"kind":"def","name":"bar","qualifier":"val","expr":{"id":4,"kind":"int","value":4},"depth":0},"71":{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},"74":{"id":73,"name":"__CircleParam","kind":"param","depth":1},"77":{"id":77,"kind":"def","name":"Circle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":65,"kind":"int"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":76,"kind":"lambda","params":[{"id":73,"name":"__CircleParam"}],"qualifier":"def","expr":{"id":75,"kind":"app","opcode":"variant","args":[{"id":72,"kind":"str","value":"Circle"},{"kind":"name","name":"__CircleParam","id":74}]}},"depth":0},"80":{"id":79,"name":"__RectangleParam","kind":"param","depth":1},"83":{"id":83,"kind":"def","name":"Rectangle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":82,"kind":"lambda","params":[{"id":79,"name":"__RectangleParam"}],"qualifier":"def","expr":{"id":81,"kind":"app","opcode":"variant","args":[{"id":78,"kind":"str","value":"Rectangle"},{"kind":"name","name":"__RectangleParam","id":80}]}},"depth":0},"86":{"id":85,"name":"__DogParam","kind":"param","depth":1},"89":{"id":89,"kind":"def","name":"Dog","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":69,"kind":"str"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":88,"kind":"lambda","params":[{"id":85,"name":"__DogParam"}],"qualifier":"def","expr":{"id":87,"kind":"app","opcode":"variant","args":[{"id":84,"kind":"str","value":"Dog"},{"kind":"name","name":"__DogParam","id":86}]}},"depth":0},"90":{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},"99":{"id":99,"kind":"def","name":"add_1_to_2","qualifier":"val","expr":{"id":98,"kind":"app","opcode":"iadd","args":[{"id":96,"kind":"int","value":1},{"id":97,"kind":"int","value":2}]},"depth":0},"103":{"id":103,"kind":"def","name":"sub_1_to_2","qualifier":"val","expr":{"id":102,"kind":"app","opcode":"isub","args":[{"id":100,"kind":"int","value":1},{"id":101,"kind":"int","value":2}]},"depth":0},"107":{"id":107,"kind":"def","name":"mul_2_to_3","qualifier":"val","expr":{"id":106,"kind":"app","opcode":"imul","args":[{"id":104,"kind":"int","value":2},{"id":105,"kind":"int","value":3}]},"depth":0},"111":{"id":111,"kind":"def","name":"div_2_to_3","qualifier":"val","expr":{"id":110,"kind":"app","opcode":"idiv","args":[{"id":108,"kind":"int","value":2},{"id":109,"kind":"int","value":3}]},"depth":0},"115":{"id":115,"kind":"def","name":"mod_2_to_3","qualifier":"val","expr":{"id":114,"kind":"app","opcode":"imod","args":[{"id":112,"kind":"int","value":2},{"id":113,"kind":"int","value":3}]},"depth":0},"119":{"id":119,"kind":"def","name":"pow_2_to_3","qualifier":"val","expr":{"id":118,"kind":"app","opcode":"ipow","args":[{"id":116,"kind":"int","value":2},{"id":117,"kind":"int","value":3}]},"depth":0},"122":{"id":122,"kind":"def","name":"uminus","qualifier":"val","expr":{"id":121,"kind":"app","opcode":"iuminus","args":[{"id":120,"kind":"int","value":100}]},"depth":0},"126":{"id":126,"kind":"def","name":"gt_2_to_3","qualifier":"val","expr":{"id":125,"kind":"app","opcode":"igt","args":[{"id":123,"kind":"int","value":2},{"id":124,"kind":"int","value":3}]},"depth":0},"130":{"id":130,"kind":"def","name":"ge_2_to_3","qualifier":"val","expr":{"id":129,"kind":"app","opcode":"igte","args":[{"id":127,"kind":"int","value":2},{"id":128,"kind":"int","value":3}]},"depth":0},"134":{"id":134,"kind":"def","name":"lt_2_to_3","qualifier":"val","expr":{"id":133,"kind":"app","opcode":"ilt","args":[{"id":131,"kind":"int","value":2},{"id":132,"kind":"int","value":3}]},"depth":0},"138":{"id":138,"kind":"def","name":"le_2_to_3","qualifier":"val","expr":{"id":137,"kind":"app","opcode":"ilte","args":[{"id":135,"kind":"int","value":2},{"id":136,"kind":"int","value":3}]},"depth":0},"142":{"id":142,"kind":"def","name":"eqeq_2_to_3","qualifier":"val","expr":{"id":141,"kind":"app","opcode":"eq","args":[{"id":139,"kind":"int","value":2},{"id":140,"kind":"int","value":3}]},"depth":0},"146":{"id":146,"kind":"def","name":"ne_2_to_3","qualifier":"val","expr":{"id":145,"kind":"app","opcode":"neq","args":[{"id":143,"kind":"int","value":2},{"id":144,"kind":"int","value":3}]},"depth":0},"152":{"id":152,"kind":"def","name":"VeryTrue","qualifier":"val","expr":{"id":151,"kind":"app","opcode":"eq","args":[{"id":149,"kind":"app","opcode":"iadd","args":[{"id":147,"kind":"int","value":2},{"id":148,"kind":"int","value":2}]},{"id":150,"kind":"int","value":4}]},"depth":0},"156":{"id":156,"kind":"def","name":"nat_includes_one","qualifier":"val","expr":{"id":155,"kind":"app","opcode":"in","args":[{"id":153,"kind":"int","value":1},{"id":154,"kind":"name","name":"Nat"}]},"depth":0},"159":{"id":158,"name":"x","kind":"param","depth":1},"162":{"id":162,"kind":"def","name":"there_is_truth","qualifier":"val","expr":{"id":161,"kind":"app","opcode":"exists","args":[{"id":157,"kind":"name","name":"Bool"},{"id":160,"kind":"lambda","params":[{"id":158,"name":"x"}],"qualifier":"def","expr":{"id":159,"kind":"name","name":"x"}}]},"depth":0},"168":{"id":168,"kind":"def","name":"withType","qualifier":"val","expr":{"id":167,"kind":"app","opcode":"Set","args":[{"id":165,"kind":"int","value":1},{"id":166,"kind":"int","value":2}]},"typeAnnotation":{"id":164,"kind":"set","elem":{"id":163,"kind":"int"}},"depth":0},"170":{"id":169,"kind":"typedef","name":"PROC","depth":0},"173":{"id":173,"kind":"def","name":"withUninterpretedType","qualifier":"val","expr":{"id":172,"kind":"app","opcode":"Set","args":[]},"typeAnnotation":{"id":171,"kind":"set","elem":{"id":170,"kind":"const","name":"PROC"}},"depth":0},"177":{"id":177,"kind":"def","name":"magicNumber","qualifier":"pureval","expr":{"id":176,"kind":"int","value":42},"depth":0},"180":{"id":178,"name":"x","kind":"param","depth":1,"shadowing":false},"181":{"id":179,"name":"y","kind":"param","depth":1},"184":{"id":184,"kind":"def","name":"add","qualifier":"puredef","expr":{"id":183,"kind":"lambda","params":[{"id":178,"name":"x"},{"id":179,"name":"y"}],"qualifier":"puredef","expr":{"id":182,"kind":"app","opcode":"iadd","args":[{"id":180,"kind":"name","name":"x"},{"id":181,"kind":"name","name":"y"}]}},"depth":0},"186":{"id":185,"name":"factor","kind":"param","depth":1},"187":{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},"190":{"id":190,"kind":"def","name":"ofN","qualifier":"def","expr":{"id":189,"kind":"lambda","params":[{"id":185,"name":"factor"}],"qualifier":"def","expr":{"id":188,"kind":"app","opcode":"imul","args":[{"id":186,"kind":"name","name":"factor"},{"id":187,"kind":"name","name":"n"}]}},"depth":0},"192":{"id":191,"name":"x","kind":"param","depth":1,"shadowing":false},"193":{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},"196":{"id":196,"kind":"def","name":"A","qualifier":"action","expr":{"id":195,"kind":"lambda","params":[{"id":191,"name":"x"}],"qualifier":"action","expr":{"id":194,"kind":"app","opcode":"assign","args":[{"id":193,"kind":"name","name":"n"},{"id":192,"kind":"name","name":"x"}]}},"depth":0},"198":{"id":197,"name":"x","kind":"param","depth":1,"shadowing":false},"201":{"id":201,"kind":"def","name":"B","qualifier":"puredef","expr":{"id":200,"kind":"lambda","params":[{"id":197,"name":"x"}],"qualifier":"puredef","expr":{"id":199,"kind":"app","opcode":"not","args":[{"id":198,"kind":"name","name":"x"}]}},"depth":0},"208":{"id":202,"name":"x","kind":"param","depth":1,"shadowing":false},"209":{"id":203,"name":"y","kind":"param","depth":1,"shadowing":false},"212":{"id":212,"kind":"def","name":"H","qualifier":"def","expr":{"id":211,"kind":"lambda","params":[{"id":202,"name":"x"},{"id":203,"name":"y"}],"qualifier":"def","expr":{"id":210,"kind":"app","opcode":"iadd","args":[{"id":208,"kind":"name","name":"x"},{"id":209,"kind":"name","name":"y"}]}},"typeAnnotation":{"id":207,"kind":"oper","args":[{"id":204,"kind":"int"},{"id":205,"kind":"int"}],"res":{"id":206,"kind":"int"}},"depth":0},"217":{"id":213,"name":"x","kind":"param","depth":1,"shadowing":false},"219":{"id":219,"kind":"def","name":"Pol","qualifier":"def","expr":{"id":218,"kind":"lambda","params":[{"id":213,"name":"x"}],"qualifier":"def","expr":{"id":217,"kind":"name","name":"x"}},"typeAnnotation":{"id":216,"kind":"oper","args":[{"id":214,"kind":"var","name":"a"}],"res":{"id":215,"kind":"var","name":"a"}},"depth":0},"223":{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},"225":{"id":225,"kind":"def","name":"asgn","qualifier":"action","expr":{"id":224,"kind":"app","opcode":"assign","args":[{"id":223,"kind":"name","name":"k"},{"id":222,"kind":"int","value":3}]},"depth":0},"231":{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"232":{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"234":{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"235":{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"238":{"id":238,"kind":"def","name":"min","qualifier":"puredef","expr":{"id":237,"kind":"lambda","params":[{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"}},{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"}}],"qualifier":"puredef","expr":{"id":236,"kind":"app","opcode":"ite","args":[{"id":233,"kind":"app","opcode":"ilt","args":[{"id":231,"kind":"name","name":"x"},{"id":232,"kind":"name","name":"y"}]},{"id":234,"kind":"name","name":"x"},{"id":235,"kind":"name","name":"y"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":226,"kind":"int"},{"id":228,"kind":"int"}],"res":{"id":230,"kind":"int"}},"depth":0},"242":{"id":242,"kind":"def","name":"test_and","qualifier":"val","expr":{"id":241,"kind":"app","opcode":"and","args":[{"id":239,"kind":"bool","value":false},{"id":240,"kind":"bool","value":true}]},"depth":0},"246":{"id":246,"kind":"def","name":"test_or","qualifier":"val","expr":{"id":245,"kind":"app","opcode":"or","args":[{"id":243,"kind":"bool","value":false},{"id":244,"kind":"bool","value":true}]},"depth":0},"250":{"id":250,"kind":"def","name":"test_implies","qualifier":"val","expr":{"id":249,"kind":"app","opcode":"implies","args":[{"id":247,"kind":"bool","value":false},{"id":248,"kind":"bool","value":true}]},"depth":0},"252":{"id":251,"name":"x","kind":"param","depth":1,"shadowing":false},"254":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"256":{"id":255,"name":"x","kind":"param","depth":1,"shadowing":false},"257":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"258":{"id":255,"name":"x","kind":"param","depth":1,"shadowing":false},"262":{"id":262,"kind":"def","name":"G","qualifier":"def","expr":{"id":261,"kind":"lambda","params":[{"id":255,"name":"x"}],"qualifier":"def","expr":{"id":260,"kind":"app","opcode":"and","args":[{"id":257,"kind":"app","opcode":"F","args":[{"id":256,"kind":"name","name":"x"}]},{"id":259,"kind":"app","opcode":"not","args":[{"id":258,"kind":"name","name":"x"}]}]}},"depth":0},"264":{"id":263,"name":"x","kind":"param","depth":1,"shadowing":false},"265":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"266":{"id":263,"name":"x","kind":"param","depth":1,"shadowing":false},"270":{"id":270,"kind":"def","name":"test_and_arg","qualifier":"def","expr":{"id":269,"kind":"lambda","params":[{"id":263,"name":"x"}],"qualifier":"def","expr":{"id":268,"kind":"app","opcode":"and","args":[{"id":265,"kind":"app","opcode":"F","args":[{"id":264,"kind":"name","name":"x"}]},{"id":267,"kind":"app","opcode":"not","args":[{"id":266,"kind":"name","name":"x"}]}]}},"depth":0},"272":{"id":271,"name":"x","kind":"param","depth":1,"shadowing":false},"273":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"274":{"id":271,"name":"x","kind":"param","depth":1,"shadowing":false},"278":{"id":278,"kind":"def","name":"test_or_arg","qualifier":"def","expr":{"id":277,"kind":"lambda","params":[{"id":271,"name":"x"}],"qualifier":"def","expr":{"id":276,"kind":"app","opcode":"or","args":[{"id":273,"kind":"app","opcode":"F","args":[{"id":272,"kind":"name","name":"x"}]},{"id":275,"kind":"app","opcode":"not","args":[{"id":274,"kind":"name","name":"x"}]}]}},"depth":0},"283":{"id":283,"kind":"def","name":"test_block_and","qualifier":"val","expr":{"id":282,"kind":"app","opcode":"and","args":[{"id":279,"kind":"bool","value":false},{"id":280,"kind":"bool","value":true},{"id":281,"kind":"bool","value":false}]},"depth":0},"288":{"id":288,"kind":"def","name":"test_action_and","qualifier":"action","expr":{"id":287,"kind":"app","opcode":"actionAll","args":[{"id":284,"kind":"bool","value":false},{"id":285,"kind":"bool","value":true},{"id":286,"kind":"bool","value":false}]},"depth":0},"293":{"id":293,"kind":"def","name":"test_block_or","qualifier":"val","expr":{"id":292,"kind":"app","opcode":"or","args":[{"id":289,"kind":"bool","value":false},{"id":290,"kind":"bool","value":true},{"id":291,"kind":"bool","value":false}]},"depth":0},"298":{"id":298,"kind":"def","name":"test_action_or","qualifier":"action","expr":{"id":297,"kind":"app","opcode":"actionAny","args":[{"id":294,"kind":"bool","value":false},{"id":295,"kind":"bool","value":true},{"id":296,"kind":"bool","value":false}]},"depth":0},"303":{"id":303,"kind":"def","name":"test_ite","qualifier":"val","expr":{"id":302,"kind":"app","opcode":"ite","args":[{"id":299,"kind":"bool","value":true},{"id":300,"kind":"int","value":1},{"id":301,"kind":"int","value":0}]},"depth":0},"306":{"id":304,"name":"x","kind":"param","depth":1,"shadowing":false},"309":{"id":305,"name":"y","kind":"param","depth":1,"shadowing":false},"312":{"id":305,"name":"y","kind":"param","depth":1,"shadowing":false},"317":{"id":317,"kind":"def","name":"test_ite2","qualifier":"def","expr":{"id":316,"kind":"lambda","params":[{"id":304,"name":"x"},{"id":305,"name":"y"}],"qualifier":"def","expr":{"id":315,"kind":"app","opcode":"ite","args":[{"id":308,"kind":"app","opcode":"ilt","args":[{"id":306,"kind":"name","name":"x"},{"id":307,"kind":"int","value":10}]},{"id":311,"kind":"app","opcode":"iadd","args":[{"id":309,"kind":"name","name":"y"},{"id":310,"kind":"int","value":5}]},{"id":314,"kind":"app","opcode":"imod","args":[{"id":312,"kind":"name","name":"y"},{"id":313,"kind":"int","value":5}]}]}},"depth":0},"322":{"kind":"var","name":"f1","typeAnnotation":{"id":320,"kind":"fun","arg":{"id":318,"kind":"str"},"res":{"id":319,"kind":"int"}},"id":321,"depth":0},"325":{"id":325,"kind":"def","name":"funapp","qualifier":"val","expr":{"id":324,"kind":"app","opcode":"get","args":[{"id":322,"kind":"name","name":"f1"},{"id":323,"kind":"str","value":"a"}]},"depth":0},"330":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"333":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"334":{"id":334,"kind":"def","name":"oper_app_normal","qualifier":"val","expr":{"id":333,"kind":"app","opcode":"MyOper","args":[{"id":331,"kind":"str","value":"a"},{"id":332,"kind":"int","value":42}]},"depth":0},"337":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"338":{"id":338,"kind":"def","name":"oper_app_ufcs","qualifier":"val","expr":{"id":337,"kind":"app","opcode":"MyOper","args":[{"id":335,"kind":"str","value":"a"},{"id":336,"kind":"int","value":42}]},"depth":0},"342":{"id":342,"kind":"def","name":"oper_in","qualifier":"val","expr":{"id":341,"kind":"app","opcode":"in","args":[{"id":339,"kind":"int","value":1},{"id":340,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"343":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"345":{"id":344,"name":"x","kind":"param","depth":1,"shadowing":false},"350":{"id":350,"kind":"def","name":"oper_app_dot1","qualifier":"val","expr":{"id":349,"kind":"app","opcode":"filter","args":[{"id":343,"kind":"name","name":"S"},{"id":348,"kind":"lambda","params":[{"id":344,"name":"x"}],"qualifier":"def","expr":{"id":347,"kind":"app","opcode":"igt","args":[{"id":345,"kind":"name","name":"x"},{"id":346,"kind":"int","value":10}]}}]},"depth":0},"351":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"352":{"kind":"const","name":"MySet","typeAnnotation":{"id":18,"kind":"set","elem":{"id":17,"kind":"int"}},"id":19,"depth":0},"354":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]},"depth":4},"355":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]},"depth":3},"356":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]},"depth":4},"357":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]},"depth":3},"362":{"id":359,"name":"quintTupledLambdaParam359","kind":"param","depth":1},"366":{"id":359,"name":"quintTupledLambdaParam359","kind":"param","depth":1},"370":{"id":370,"kind":"def","name":"tuple_sum","qualifier":"val","expr":{"id":369,"kind":"app","opcode":"map","args":[{"id":353,"kind":"app","opcode":"tuples","args":[{"id":351,"kind":"name","name":"S"},{"id":352,"kind":"name","name":"MySet"}]},{"id":368,"kind":"lambda","params":[{"id":359,"name":"quintTupledLambdaParam359"}],"qualifier":"def","expr":{"id":364,"kind":"let","opdef":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]}},"expr":{"id":360,"kind":"let","opdef":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]}},"expr":{"id":358,"kind":"app","opcode":"iadd","args":[{"id":356,"kind":"name","name":"s"},{"id":357,"kind":"name","name":"mys"}]}}}}]},"depth":0},"371":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"373":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"374":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"377":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"378":{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},"382":{"id":382,"kind":"def","name":"oper_nondet","qualifier":"action","expr":{"id":381,"kind":"let","opdef":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]}},"expr":{"id":380,"kind":"app","opcode":"actionAll","args":[{"id":376,"kind":"app","opcode":"igt","args":[{"id":374,"kind":"name","name":"x"},{"id":375,"kind":"int","value":10}]},{"id":379,"kind":"app","opcode":"assign","args":[{"id":378,"kind":"name","name":"k"},{"id":377,"kind":"name","name":"x"}]}]}},"depth":0},"383":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"388":{"id":388,"kind":"def","name":"oper_app_dot4","qualifier":"val","expr":{"id":387,"kind":"app","opcode":"filter","args":[{"id":383,"kind":"name","name":"S"},{"id":386,"kind":"lambda","params":[{"id":384,"name":"_"}],"qualifier":"def","expr":{"id":385,"kind":"bool","value":true}}]},"depth":0},"389":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"391":{"id":390,"name":"x","kind":"param","depth":1,"shadowing":false},"396":{"id":396,"kind":"def","name":"f","qualifier":"val","expr":{"id":395,"kind":"app","opcode":"mapBy","args":[{"id":389,"kind":"name","name":"S"},{"id":394,"kind":"lambda","params":[{"id":390,"name":"x"}],"qualifier":"def","expr":{"id":393,"kind":"app","opcode":"iadd","args":[{"id":391,"kind":"name","name":"x"},{"id":392,"kind":"int","value":1}]}}]},"depth":0},"397":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"402":{"id":402,"kind":"def","name":"set_difference","qualifier":"val","expr":{"id":401,"kind":"app","opcode":"exclude","args":[{"id":397,"kind":"name","name":"S"},{"id":400,"kind":"app","opcode":"Set","args":[{"id":398,"kind":"int","value":3},{"id":399,"kind":"int","value":4}]}]},"depth":0},"407":{"id":407,"kind":"def","name":"one","qualifier":"val","expr":{"id":406,"kind":"app","opcode":"head","args":[{"id":405,"kind":"app","opcode":"List","args":[{"id":403,"kind":"int","value":1},{"id":404,"kind":"int","value":2}]}]},"depth":0},"412":{"id":412,"kind":"def","name":"test_tuple","qualifier":"val","expr":{"id":411,"kind":"app","opcode":"Tup","args":[{"id":408,"kind":"int","value":1},{"id":409,"kind":"int","value":2},{"id":410,"kind":"int","value":3}]},"depth":0},"417":{"id":417,"kind":"def","name":"test_tuple2","qualifier":"val","expr":{"id":416,"kind":"app","opcode":"Tup","args":[{"id":413,"kind":"int","value":1},{"id":414,"kind":"int","value":2},{"id":415,"kind":"int","value":3}]},"depth":0},"421":{"id":421,"kind":"def","name":"test_pair","qualifier":"val","expr":{"id":420,"kind":"app","opcode":"Tup","args":[{"id":418,"kind":"int","value":4},{"id":419,"kind":"int","value":5}]},"depth":0},"426":{"id":426,"kind":"def","name":"test_list","qualifier":"val","expr":{"id":425,"kind":"app","opcode":"List","args":[{"id":422,"kind":"int","value":1},{"id":423,"kind":"int","value":2},{"id":424,"kind":"int","value":3}]},"depth":0},"431":{"id":431,"kind":"def","name":"test_list2","qualifier":"val","expr":{"id":430,"kind":"app","opcode":"List","args":[{"id":427,"kind":"int","value":1},{"id":428,"kind":"int","value":2},{"id":429,"kind":"int","value":3}]},"depth":0},"438":{"id":438,"kind":"def","name":"test_list_nth","qualifier":"val","expr":{"id":437,"kind":"app","opcode":"nth","args":[{"id":435,"kind":"app","opcode":"List","args":[{"id":432,"kind":"int","value":2},{"id":433,"kind":"int","value":3},{"id":434,"kind":"int","value":4}]},{"id":436,"kind":"int","value":2}]},"depth":0},"444":{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]},"depth":0},"450":{"id":450,"kind":"def","name":"test_record2","qualifier":"val","expr":{"id":449,"kind":"app","opcode":"Rec","args":[{"id":445,"kind":"str","value":"name"},{"id":446,"kind":"str","value":"igor"},{"id":447,"kind":"str","value":"year"},{"id":448,"kind":"int","value":1981}]},"depth":0},"455":{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]},"depth":0},"458":{"id":458,"kind":"def","name":"test_record3","qualifier":"val","expr":{"id":457,"kind":"app","opcode":"with","args":[{"id":456,"kind":"app","opcode":"with","args":[{"id":455,"kind":"name","name":"test_record"},{"id":452,"kind":"str","value":"name"},{"id":451,"kind":"str","value":"quint"}]},{"id":454,"kind":"str","value":"year"},{"id":453,"kind":"int","value":2023}]},"depth":0},"463":{"id":463,"kind":"def","name":"test_set","qualifier":"val","expr":{"id":462,"kind":"app","opcode":"Set","args":[{"id":459,"kind":"int","value":1},{"id":460,"kind":"int","value":2},{"id":461,"kind":"int","value":3}]},"depth":0},"469":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]},"depth":2},"470":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]},"depth":2},"474":{"id":474,"kind":"def","name":"rec_field","qualifier":"val","expr":{"id":473,"kind":"let","opdef":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]}},"expr":{"id":472,"kind":"app","opcode":"field","args":[{"id":470,"kind":"name","name":"my_rec"},{"id":471,"kind":"str","value":"a"}]}},"depth":0},"478":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]},"depth":2},"479":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]},"depth":2},"483":{"id":483,"kind":"def","name":"tup_elem","qualifier":"val","expr":{"id":482,"kind":"let","opdef":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]}},"expr":{"id":481,"kind":"app","opcode":"item","args":[{"id":479,"kind":"name","name":"my_tup"},{"id":480,"kind":"int","value":2}]}},"depth":0},"485":{"id":484,"name":"s","kind":"param","depth":1,"shadowing":false},"489":{"id":489,"kind":"def","name":"isEmpty","qualifier":"def","expr":{"id":488,"kind":"lambda","params":[{"id":484,"name":"s"}],"qualifier":"def","expr":{"id":487,"kind":"app","opcode":"eq","args":[{"id":485,"kind":"name","name":"s"},{"id":486,"kind":"app","opcode":"List","args":[]}]}},"depth":0},"493":{"id":493,"kind":"def","name":"in_2_empty","qualifier":"val","expr":{"id":492,"kind":"app","opcode":"in","args":[{"id":490,"kind":"int","value":2},{"id":491,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"497":{"id":497,"kind":"def","name":"subseteq_empty","qualifier":"val","expr":{"id":496,"kind":"app","opcode":"subseteq","args":[{"id":494,"kind":"app","opcode":"Set","args":[]},{"id":495,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"506":{"id":506,"kind":"def","name":"powersets","qualifier":"val","expr":{"id":505,"kind":"app","opcode":"in","args":[{"id":499,"kind":"app","opcode":"Set","args":[{"id":498,"kind":"int","value":1}]},{"id":504,"kind":"app","opcode":"powerset","args":[{"id":503,"kind":"app","opcode":"Set","args":[{"id":500,"kind":"int","value":1},{"id":501,"kind":"int","value":2},{"id":502,"kind":"int","value":3}]}]}]},"depth":0},"512":{"id":512,"kind":"def","name":"lists","qualifier":"val","expr":{"id":511,"kind":"app","opcode":"allListsUpTo","args":[{"id":509,"kind":"app","opcode":"Set","args":[{"id":507,"kind":"int","value":1},{"id":508,"kind":"int","value":2}]},{"id":510,"kind":"int","value":3}]},"depth":0},"513":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"517":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"527":{"id":525,"kind":"typedef","name":"INT_SET","type":{"id":524,"kind":"set","elem":{"id":523,"kind":"int"}},"depth":0},"529":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"530":{"kind":"const","name":"N","typeAnnotation":{"id":7,"kind":"int"},"id":529,"depth":0,"importedFrom":{"id":531,"kind":"instance","qualifiedName":"Inst1","protoName":"Proto","overrides":[[{"id":530,"name":"N"},{"id":529,"kind":"name","name":"N"}]],"identityOverride":false},"hidden":true,"namespaces":["Inst1","withConsts"]}},"errors":[]} \ No newline at end of file +{"stage":"parsing","warnings":[],"modules":[{"id":11,"name":"Proto","declarations":[{"kind":"var","name":"x","typeAnnotation":{"id":9,"kind":"int"},"id":10,"depth":0},{"kind":"const","name":"N","typeAnnotation":{"id":7,"kind":"int"},"id":8,"depth":0}]},{"id":3,"name":"M1","declarations":[{"id":2,"kind":"def","name":"foo","qualifier":"val","expr":{"id":1,"kind":"int","value":4}}]},{"id":6,"name":"M2","declarations":[{"id":5,"kind":"def","name":"bar","qualifier":"val","expr":{"id":4,"kind":"int","value":4}}]},{"id":532,"name":"withConsts","declarations":[{"id":103,"kind":"def","name":"sub_1_to_2","qualifier":"val","expr":{"id":102,"kind":"app","opcode":"isub","args":[{"id":100,"kind":"int","value":1},{"id":101,"kind":"int","value":2}]}},{"id":107,"kind":"def","name":"mul_2_to_3","qualifier":"val","expr":{"id":106,"kind":"app","opcode":"imul","args":[{"id":104,"kind":"int","value":2},{"id":105,"kind":"int","value":3}]}},{"id":111,"kind":"def","name":"div_2_to_3","qualifier":"val","expr":{"id":110,"kind":"app","opcode":"idiv","args":[{"id":108,"kind":"int","value":2},{"id":109,"kind":"int","value":3}]}},{"id":115,"kind":"def","name":"mod_2_to_3","qualifier":"val","expr":{"id":114,"kind":"app","opcode":"imod","args":[{"id":112,"kind":"int","value":2},{"id":113,"kind":"int","value":3}]}},{"id":119,"kind":"def","name":"pow_2_to_3","qualifier":"val","expr":{"id":118,"kind":"app","opcode":"ipow","args":[{"id":116,"kind":"int","value":2},{"id":117,"kind":"int","value":3}]}},{"id":122,"kind":"def","name":"uminus","qualifier":"val","expr":{"id":121,"kind":"app","opcode":"iuminus","args":[{"id":120,"kind":"int","value":100}]}},{"id":126,"kind":"def","name":"gt_2_to_3","qualifier":"val","expr":{"id":125,"kind":"app","opcode":"igt","args":[{"id":123,"kind":"int","value":2},{"id":124,"kind":"int","value":3}]}},{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},{"id":130,"kind":"def","name":"ge_2_to_3","qualifier":"val","expr":{"id":129,"kind":"app","opcode":"igte","args":[{"id":127,"kind":"int","value":2},{"id":128,"kind":"int","value":3}]}},{"id":134,"kind":"def","name":"lt_2_to_3","qualifier":"val","expr":{"id":133,"kind":"app","opcode":"ilt","args":[{"id":131,"kind":"int","value":2},{"id":132,"kind":"int","value":3}]}},{"id":138,"kind":"def","name":"le_2_to_3","qualifier":"val","expr":{"id":137,"kind":"app","opcode":"ilte","args":[{"id":135,"kind":"int","value":2},{"id":136,"kind":"int","value":3}]}},{"id":142,"kind":"def","name":"eqeq_2_to_3","qualifier":"val","expr":{"id":141,"kind":"app","opcode":"eq","args":[{"id":139,"kind":"int","value":2},{"id":140,"kind":"int","value":3}]}},{"id":146,"kind":"def","name":"ne_2_to_3","qualifier":"val","expr":{"id":145,"kind":"app","opcode":"neq","args":[{"id":143,"kind":"int","value":2},{"id":144,"kind":"int","value":3}]}},{"id":152,"kind":"def","name":"VeryTrue","qualifier":"val","expr":{"id":151,"kind":"app","opcode":"eq","args":[{"id":149,"kind":"app","opcode":"iadd","args":[{"id":147,"kind":"int","value":2},{"id":148,"kind":"int","value":2}]},{"id":150,"kind":"int","value":4}]}},{"id":156,"kind":"def","name":"nat_includes_one","qualifier":"val","expr":{"id":155,"kind":"app","opcode":"in","args":[{"id":153,"kind":"int","value":1},{"id":154,"kind":"name","name":"Nat"}]}},{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},{"id":168,"kind":"def","name":"withType","qualifier":"val","expr":{"id":167,"kind":"app","opcode":"Set","args":[{"id":165,"kind":"int","value":1},{"id":166,"kind":"int","value":2}]},"typeAnnotation":{"id":164,"kind":"set","elem":{"id":163,"kind":"int"}}},{"id":169,"kind":"typedef","name":"PROC","depth":0},{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},{"id":177,"kind":"def","name":"magicNumber","qualifier":"pureval","expr":{"id":176,"kind":"int","value":42}},{"kind":"const","name":"MySet","typeAnnotation":{"id":18,"kind":"set","elem":{"id":17,"kind":"int"}},"id":19,"depth":0},{"kind":"const","name":"MySeq","typeAnnotation":{"id":21,"kind":"list","elem":{"id":20,"kind":"bool"}},"id":22,"depth":0},{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},{"id":242,"kind":"def","name":"test_and","qualifier":"val","expr":{"id":241,"kind":"app","opcode":"and","args":[{"id":239,"kind":"bool","value":false},{"id":240,"kind":"bool","value":true}]}},{"id":246,"kind":"def","name":"test_or","qualifier":"val","expr":{"id":245,"kind":"app","opcode":"or","args":[{"id":243,"kind":"bool","value":false},{"id":244,"kind":"bool","value":true}]}},{"id":250,"kind":"def","name":"test_implies","qualifier":"val","expr":{"id":249,"kind":"app","opcode":"implies","args":[{"id":247,"kind":"bool","value":false},{"id":248,"kind":"bool","value":true}]}},{"kind":"const","name":"MyFun","typeAnnotation":{"id":25,"kind":"fun","arg":{"id":23,"kind":"int"},"res":{"id":24,"kind":"str"}},"id":26,"depth":0},{"id":283,"kind":"def","name":"test_block_and","qualifier":"val","expr":{"id":282,"kind":"app","opcode":"and","args":[{"id":279,"kind":"bool","value":false},{"id":280,"kind":"bool","value":true},{"id":281,"kind":"bool","value":false}]}},{"id":288,"kind":"def","name":"test_action_and","qualifier":"action","expr":{"id":287,"kind":"app","opcode":"actionAll","args":[{"id":284,"kind":"bool","value":false},{"id":285,"kind":"bool","value":true},{"id":286,"kind":"bool","value":false}]}},{"id":293,"kind":"def","name":"test_block_or","qualifier":"val","expr":{"id":292,"kind":"app","opcode":"or","args":[{"id":289,"kind":"bool","value":false},{"id":290,"kind":"bool","value":true},{"id":291,"kind":"bool","value":false}]}},{"id":298,"kind":"def","name":"test_action_or","qualifier":"action","expr":{"id":297,"kind":"app","opcode":"actionAny","args":[{"id":294,"kind":"bool","value":false},{"id":295,"kind":"bool","value":true},{"id":296,"kind":"bool","value":false}]}},{"id":303,"kind":"def","name":"test_ite","qualifier":"val","expr":{"id":302,"kind":"app","opcode":"ite","args":[{"id":299,"kind":"bool","value":true},{"id":300,"kind":"int","value":1},{"id":301,"kind":"int","value":0}]}},{"kind":"const","name":"MyFunFun","typeAnnotation":{"id":31,"kind":"fun","arg":{"id":29,"kind":"fun","arg":{"id":27,"kind":"int"},"res":{"id":28,"kind":"str"}},"res":{"id":30,"kind":"bool"}},"id":32,"depth":0},{"kind":"var","name":"f1","typeAnnotation":{"id":320,"kind":"fun","arg":{"id":318,"kind":"str"},"res":{"id":319,"kind":"int"}},"id":321,"depth":0},{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}}},{"id":342,"kind":"def","name":"oper_in","qualifier":"val","expr":{"id":341,"kind":"app","opcode":"in","args":[{"id":339,"kind":"int","value":1},{"id":340,"kind":"app","opcode":"Set","args":[]}]}},{"kind":"const","name":"MyOperator","typeAnnotation":{"id":36,"kind":"oper","args":[{"id":33,"kind":"int"},{"id":34,"kind":"str"}],"res":{"id":35,"kind":"bool"}},"id":37,"depth":0},{"id":407,"kind":"def","name":"one","qualifier":"val","expr":{"id":406,"kind":"app","opcode":"head","args":[{"id":405,"kind":"app","opcode":"List","args":[{"id":403,"kind":"int","value":1},{"id":404,"kind":"int","value":2}]}]}},{"id":412,"kind":"def","name":"test_tuple","qualifier":"val","expr":{"id":411,"kind":"app","opcode":"Tup","args":[{"id":408,"kind":"int","value":1},{"id":409,"kind":"int","value":2},{"id":410,"kind":"int","value":3}]}},{"id":417,"kind":"def","name":"test_tuple2","qualifier":"val","expr":{"id":416,"kind":"app","opcode":"Tup","args":[{"id":413,"kind":"int","value":1},{"id":414,"kind":"int","value":2},{"id":415,"kind":"int","value":3}]}},{"kind":"const","name":"MyOperatorWithComma","typeAnnotation":{"id":41,"kind":"oper","args":[{"id":38,"kind":"int"},{"id":39,"kind":"str"}],"res":{"id":40,"kind":"bool"}},"id":42,"depth":0},{"id":421,"kind":"def","name":"test_pair","qualifier":"val","expr":{"id":420,"kind":"app","opcode":"Tup","args":[{"id":418,"kind":"int","value":4},{"id":419,"kind":"int","value":5}]}},{"id":426,"kind":"def","name":"test_list","qualifier":"val","expr":{"id":425,"kind":"app","opcode":"List","args":[{"id":422,"kind":"int","value":1},{"id":423,"kind":"int","value":2},{"id":424,"kind":"int","value":3}]}},{"id":431,"kind":"def","name":"test_list2","qualifier":"val","expr":{"id":430,"kind":"app","opcode":"List","args":[{"id":427,"kind":"int","value":1},{"id":428,"kind":"int","value":2},{"id":429,"kind":"int","value":3}]}},{"id":438,"kind":"def","name":"test_list_nth","qualifier":"val","expr":{"id":437,"kind":"app","opcode":"nth","args":[{"id":435,"kind":"app","opcode":"List","args":[{"id":432,"kind":"int","value":2},{"id":433,"kind":"int","value":3},{"id":434,"kind":"int","value":4}]},{"id":436,"kind":"int","value":2}]}},{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]}},{"id":450,"kind":"def","name":"test_record2","qualifier":"val","expr":{"id":449,"kind":"app","opcode":"Rec","args":[{"id":445,"kind":"str","value":"name"},{"id":446,"kind":"str","value":"igor"},{"id":447,"kind":"str","value":"year"},{"id":448,"kind":"int","value":1981}]}},{"id":463,"kind":"def","name":"test_set","qualifier":"val","expr":{"id":462,"kind":"app","opcode":"Set","args":[{"id":459,"kind":"int","value":1},{"id":460,"kind":"int","value":2},{"id":461,"kind":"int","value":3}]}},{"kind":"const","name":"MyTuple","typeAnnotation":{"id":46,"kind":"tup","fields":{"kind":"row","fields":[{"fieldName":"0","fieldType":{"id":43,"kind":"int"}},{"fieldName":"1","fieldType":{"id":44,"kind":"bool"}},{"fieldName":"2","fieldType":{"id":45,"kind":"str"}}],"other":{"kind":"empty"}}},"id":47,"depth":0},{"id":493,"kind":"def","name":"in_2_empty","qualifier":"val","expr":{"id":492,"kind":"app","opcode":"in","args":[{"id":490,"kind":"int","value":2},{"id":491,"kind":"app","opcode":"Set","args":[]}]}},{"id":497,"kind":"def","name":"subseteq_empty","qualifier":"val","expr":{"id":496,"kind":"app","opcode":"subseteq","args":[{"id":494,"kind":"app","opcode":"Set","args":[]},{"id":495,"kind":"app","opcode":"Set","args":[]}]}},{"id":506,"kind":"def","name":"powersets","qualifier":"val","expr":{"id":505,"kind":"app","opcode":"in","args":[{"id":499,"kind":"app","opcode":"Set","args":[{"id":498,"kind":"int","value":1}]},{"id":504,"kind":"app","opcode":"powerset","args":[{"id":503,"kind":"app","opcode":"Set","args":[{"id":500,"kind":"int","value":1},{"id":501,"kind":"int","value":2},{"id":502,"kind":"int","value":3}]}]}]}},{"id":512,"kind":"def","name":"lists","qualifier":"val","expr":{"id":511,"kind":"app","opcode":"allListsUpTo","args":[{"id":509,"kind":"app","opcode":"Set","args":[{"id":507,"kind":"int","value":1},{"id":508,"kind":"int","value":2}]},{"id":510,"kind":"int","value":3}]}},{"kind":"const","name":"MyTupleWithComma","typeAnnotation":{"id":51,"kind":"tup","fields":{"kind":"row","fields":[{"fieldName":"0","fieldType":{"id":48,"kind":"int"}},{"fieldName":"1","fieldType":{"id":49,"kind":"bool"}},{"fieldName":"2","fieldType":{"id":50,"kind":"str"}}],"other":{"kind":"empty"}}},"id":52,"depth":0},{"id":525,"kind":"typedef","name":"INT_SET","type":{"id":524,"kind":"set","elem":{"id":523,"kind":"int"}},"depth":0},{"id":526,"kind":"typedef","name":"UNINTERPRETED_TYPE","depth":0},{"kind":"const","name":"MyRecord","typeAnnotation":{"id":56,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"i","fieldType":{"id":53,"kind":"int"}},{"fieldName":"b","fieldType":{"id":54,"kind":"bool"}},{"fieldName":"s","fieldType":{"id":55,"kind":"str"}}],"other":{"kind":"empty"}}},"id":57,"depth":0},{"kind":"const","name":"MyRecordWithComma","typeAnnotation":{"id":61,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"i","fieldType":{"id":58,"kind":"int"}},{"fieldName":"b","fieldType":{"id":59,"kind":"bool"}},{"fieldName":"s","fieldType":{"id":60,"kind":"str"}}],"other":{"kind":"empty"}}},"id":62,"depth":0},{"id":64,"kind":"typedef","name":"some::namespace::MyType","type":{"id":63,"kind":"int"},"depth":0},{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},{"kind":"var","name":"i","typeAnnotation":{"id":92,"kind":"int"},"id":93,"depth":0},{"kind":"var","name":"j","typeAnnotation":{"id":94,"kind":"bool"},"id":95,"depth":0},{"id":99,"kind":"def","name":"add_1_to_2","qualifier":"val","expr":{"id":98,"kind":"app","opcode":"iadd","args":[{"id":96,"kind":"int","value":1},{"id":97,"kind":"int","value":2}]}},{"id":162,"kind":"def","name":"there_is_truth","qualifier":"val","expr":{"id":161,"kind":"app","opcode":"exists","args":[{"id":157,"kind":"name","name":"Bool"},{"id":160,"kind":"lambda","params":[{"id":158,"name":"x"}],"qualifier":"def","expr":{"id":159,"kind":"name","name":"x"}}]}},{"id":173,"kind":"def","name":"withUninterpretedType","qualifier":"val","expr":{"id":172,"kind":"app","opcode":"Set","args":[]},"typeAnnotation":{"id":171,"kind":"set","elem":{"id":170,"kind":"const","name":"PROC"}}},{"id":184,"kind":"def","name":"add","qualifier":"puredef","expr":{"id":183,"kind":"lambda","params":[{"id":178,"name":"x"},{"id":179,"name":"y"}],"qualifier":"puredef","expr":{"id":182,"kind":"app","opcode":"iadd","args":[{"id":180,"kind":"name","name":"x"},{"id":181,"kind":"name","name":"y"}]}}},{"id":190,"kind":"def","name":"ofN","qualifier":"def","expr":{"id":189,"kind":"lambda","params":[{"id":185,"name":"factor"}],"qualifier":"def","expr":{"id":188,"kind":"app","opcode":"imul","args":[{"id":186,"kind":"name","name":"factor"},{"id":187,"kind":"name","name":"n"}]}}},{"id":196,"kind":"def","name":"A","qualifier":"action","expr":{"id":195,"kind":"lambda","params":[{"id":191,"name":"x"}],"qualifier":"action","expr":{"id":194,"kind":"app","opcode":"assign","args":[{"id":193,"kind":"name","name":"n"},{"id":192,"kind":"name","name":"x"}]}}},{"id":201,"kind":"def","name":"B","qualifier":"puredef","expr":{"id":200,"kind":"lambda","params":[{"id":197,"name":"x"}],"qualifier":"puredef","expr":{"id":199,"kind":"app","opcode":"not","args":[{"id":198,"kind":"name","name":"x"}]}}},{"id":212,"kind":"def","name":"H","qualifier":"def","expr":{"id":211,"kind":"lambda","params":[{"id":202,"name":"x"},{"id":203,"name":"y"}],"qualifier":"def","expr":{"id":210,"kind":"app","opcode":"iadd","args":[{"id":208,"kind":"name","name":"x"},{"id":209,"kind":"name","name":"y"}]}},"typeAnnotation":{"id":207,"kind":"oper","args":[{"id":204,"kind":"int"},{"id":205,"kind":"int"}],"res":{"id":206,"kind":"int"}}},{"id":219,"kind":"def","name":"Pol","qualifier":"def","expr":{"id":218,"kind":"lambda","params":[{"id":213,"name":"x"}],"qualifier":"def","expr":{"id":217,"kind":"name","name":"x"}},"typeAnnotation":{"id":216,"kind":"oper","args":[{"id":214,"kind":"var","name":"a"}],"res":{"id":215,"kind":"var","name":"a"}}},{"id":225,"kind":"def","name":"asgn","qualifier":"action","expr":{"id":224,"kind":"app","opcode":"assign","args":[{"id":223,"kind":"name","name":"k"},{"id":222,"kind":"int","value":3}]}},{"id":238,"kind":"def","name":"min","qualifier":"puredef","expr":{"id":237,"kind":"lambda","params":[{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"}},{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"}}],"qualifier":"puredef","expr":{"id":236,"kind":"app","opcode":"ite","args":[{"id":233,"kind":"app","opcode":"ilt","args":[{"id":231,"kind":"name","name":"x"},{"id":232,"kind":"name","name":"y"}]},{"id":234,"kind":"name","name":"x"},{"id":235,"kind":"name","name":"y"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":226,"kind":"int"},{"id":228,"kind":"int"}],"res":{"id":230,"kind":"int"}}},{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}}},{"id":317,"kind":"def","name":"test_ite2","qualifier":"def","expr":{"id":316,"kind":"lambda","params":[{"id":304,"name":"x"},{"id":305,"name":"y"}],"qualifier":"def","expr":{"id":315,"kind":"app","opcode":"ite","args":[{"id":308,"kind":"app","opcode":"ilt","args":[{"id":306,"kind":"name","name":"x"},{"id":307,"kind":"int","value":10}]},{"id":311,"kind":"app","opcode":"iadd","args":[{"id":309,"kind":"name","name":"y"},{"id":310,"kind":"int","value":5}]},{"id":314,"kind":"app","opcode":"imod","args":[{"id":312,"kind":"name","name":"y"},{"id":313,"kind":"int","value":5}]}]}}},{"id":325,"kind":"def","name":"funapp","qualifier":"val","expr":{"id":324,"kind":"app","opcode":"get","args":[{"id":322,"kind":"name","name":"f1"},{"id":323,"kind":"str","value":"a"}]}},{"id":334,"kind":"def","name":"oper_app_normal","qualifier":"val","expr":{"id":333,"kind":"app","opcode":"MyOper","args":[{"id":331,"kind":"str","value":"a"},{"id":332,"kind":"int","value":42}]}},{"id":338,"kind":"def","name":"oper_app_ufcs","qualifier":"val","expr":{"id":337,"kind":"app","opcode":"MyOper","args":[{"id":335,"kind":"str","value":"a"},{"id":336,"kind":"int","value":42}]}},{"id":350,"kind":"def","name":"oper_app_dot1","qualifier":"val","expr":{"id":349,"kind":"app","opcode":"filter","args":[{"id":343,"kind":"name","name":"S"},{"id":348,"kind":"lambda","params":[{"id":344,"name":"x"}],"qualifier":"def","expr":{"id":347,"kind":"app","opcode":"igt","args":[{"id":345,"kind":"name","name":"x"},{"id":346,"kind":"int","value":10}]}}]}},{"id":388,"kind":"def","name":"oper_app_dot4","qualifier":"val","expr":{"id":387,"kind":"app","opcode":"filter","args":[{"id":383,"kind":"name","name":"S"},{"id":386,"kind":"lambda","params":[{"id":384,"name":"_"}],"qualifier":"def","expr":{"id":385,"kind":"bool","value":true}}]}},{"id":396,"kind":"def","name":"f","qualifier":"val","expr":{"id":395,"kind":"app","opcode":"mapBy","args":[{"id":389,"kind":"name","name":"S"},{"id":394,"kind":"lambda","params":[{"id":390,"name":"x"}],"qualifier":"def","expr":{"id":393,"kind":"app","opcode":"iadd","args":[{"id":391,"kind":"name","name":"x"},{"id":392,"kind":"int","value":1}]}}]}},{"id":402,"kind":"def","name":"set_difference","qualifier":"val","expr":{"id":401,"kind":"app","opcode":"exclude","args":[{"id":397,"kind":"name","name":"S"},{"id":400,"kind":"app","opcode":"Set","args":[{"id":398,"kind":"int","value":3},{"id":399,"kind":"int","value":4}]}]}},{"id":458,"kind":"def","name":"test_record3","qualifier":"val","expr":{"id":457,"kind":"app","opcode":"with","args":[{"id":456,"kind":"app","opcode":"with","args":[{"id":455,"kind":"name","name":"test_record"},{"id":452,"kind":"str","value":"name"},{"id":451,"kind":"str","value":"quint"}]},{"id":454,"kind":"str","value":"year"},{"id":453,"kind":"int","value":2023}]}},{"id":474,"kind":"def","name":"rec_field","qualifier":"val","expr":{"id":473,"kind":"let","opdef":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]}},"expr":{"id":472,"kind":"app","opcode":"field","args":[{"id":470,"kind":"name","name":"my_rec"},{"id":471,"kind":"str","value":"a"}]}}},{"id":483,"kind":"def","name":"tup_elem","qualifier":"val","expr":{"id":482,"kind":"let","opdef":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]}},"expr":{"id":481,"kind":"app","opcode":"item","args":[{"id":479,"kind":"name","name":"my_tup"},{"id":480,"kind":"int","value":2}]}}},{"id":489,"kind":"def","name":"isEmpty","qualifier":"def","expr":{"id":488,"kind":"lambda","params":[{"id":484,"name":"s"}],"qualifier":"def","expr":{"id":487,"kind":"app","opcode":"eq","args":[{"id":485,"kind":"name","name":"s"},{"id":486,"kind":"app","opcode":"List","args":[]}]}}},{"id":516,"kind":"assume","name":"positive","assumption":{"id":515,"kind":"app","opcode":"igt","args":[{"id":513,"kind":"name","name":"N"},{"id":514,"kind":"int","value":0}]},"depth":0},{"id":520,"kind":"assume","name":"_","assumption":{"id":519,"kind":"app","opcode":"neq","args":[{"id":517,"kind":"name","name":"N"},{"id":518,"kind":"int","value":0}]}},{"id":521,"kind":"import","defName":"foo","protoName":"M1"},{"id":522,"kind":"import","defName":"*","protoName":"M2"},{"kind":"var","name":"S1","typeAnnotation":{"id":527,"kind":"const","name":"INT_SET"},"id":528,"depth":0},{"id":531,"kind":"instance","qualifiedName":"Inst1","protoName":"Proto","overrides":[[{"id":530,"name":"N"},{"id":529,"kind":"name","name":"N"}]],"identityOverride":false},{"id":77,"kind":"def","name":"Circle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":65,"kind":"int"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":76,"kind":"lambda","params":[{"id":73,"name":"__CircleParam"}],"qualifier":"def","expr":{"id":75,"kind":"app","opcode":"variant","args":[{"id":72,"kind":"str","value":"Circle"},{"kind":"name","name":"__CircleParam","id":74}]}}},{"id":83,"kind":"def","name":"Rectangle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":82,"kind":"lambda","params":[{"id":79,"name":"__RectangleParam"}],"qualifier":"def","expr":{"id":81,"kind":"app","opcode":"variant","args":[{"id":78,"kind":"str","value":"Rectangle"},{"kind":"name","name":"__RectangleParam","id":80}]}}},{"id":89,"kind":"def","name":"Dog","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":69,"kind":"str"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":88,"kind":"lambda","params":[{"id":85,"name":"__DogParam"}],"qualifier":"def","expr":{"id":87,"kind":"app","opcode":"variant","args":[{"id":84,"kind":"str","value":"Dog"},{"kind":"name","name":"__DogParam","id":86}]}}},{"kind":"const","name":"MyUnion","typeAnnotation":{"id":90,"kind":"const","name":"MyUnionType"},"id":91,"depth":0},{"id":262,"kind":"def","name":"G","qualifier":"def","expr":{"id":261,"kind":"lambda","params":[{"id":255,"name":"x"}],"qualifier":"def","expr":{"id":260,"kind":"app","opcode":"and","args":[{"id":257,"kind":"app","opcode":"F","args":[{"id":256,"kind":"name","name":"x"}]},{"id":259,"kind":"app","opcode":"not","args":[{"id":258,"kind":"name","name":"x"}]}]}}},{"id":270,"kind":"def","name":"test_and_arg","qualifier":"def","expr":{"id":269,"kind":"lambda","params":[{"id":263,"name":"x"}],"qualifier":"def","expr":{"id":268,"kind":"app","opcode":"and","args":[{"id":265,"kind":"app","opcode":"F","args":[{"id":264,"kind":"name","name":"x"}]},{"id":267,"kind":"app","opcode":"not","args":[{"id":266,"kind":"name","name":"x"}]}]}}},{"id":278,"kind":"def","name":"test_or_arg","qualifier":"def","expr":{"id":277,"kind":"lambda","params":[{"id":271,"name":"x"}],"qualifier":"def","expr":{"id":276,"kind":"app","opcode":"or","args":[{"id":273,"kind":"app","opcode":"F","args":[{"id":272,"kind":"name","name":"x"}]},{"id":275,"kind":"app","opcode":"not","args":[{"id":274,"kind":"name","name":"x"}]}]}}},{"id":370,"kind":"def","name":"tuple_sum","qualifier":"val","expr":{"id":369,"kind":"app","opcode":"map","args":[{"id":353,"kind":"app","opcode":"tuples","args":[{"id":351,"kind":"name","name":"S"},{"id":352,"kind":"name","name":"MySet"}]},{"id":368,"kind":"lambda","params":[{"id":359,"name":"quintTupledLambdaParam359"}],"qualifier":"def","expr":{"id":364,"kind":"let","opdef":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]}},"expr":{"id":360,"kind":"let","opdef":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]}},"expr":{"id":358,"kind":"app","opcode":"iadd","args":[{"id":356,"kind":"name","name":"s"},{"id":357,"kind":"name","name":"mys"}]}}}}]}},{"id":382,"kind":"def","name":"oper_nondet","qualifier":"action","expr":{"id":381,"kind":"let","opdef":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]}},"expr":{"id":380,"kind":"app","opcode":"actionAll","args":[{"id":376,"kind":"app","opcode":"igt","args":[{"id":374,"kind":"name","name":"x"},{"id":375,"kind":"int","value":10}]},{"id":379,"kind":"app","opcode":"assign","args":[{"id":378,"kind":"name","name":"k"},{"id":377,"kind":"name","name":"x"}]}]}}}]}],"table":{"2":{"id":2,"kind":"def","name":"foo","qualifier":"val","expr":{"id":1,"kind":"int","value":4},"depth":0},"5":{"id":5,"kind":"def","name":"bar","qualifier":"val","expr":{"id":4,"kind":"int","value":4},"depth":0},"71":{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},"74":{"id":73,"name":"__CircleParam","kind":"param","depth":1},"77":{"id":77,"kind":"def","name":"Circle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":65,"kind":"int"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":76,"kind":"lambda","params":[{"id":73,"name":"__CircleParam"}],"qualifier":"def","expr":{"id":75,"kind":"app","opcode":"variant","args":[{"id":72,"kind":"str","value":"Circle"},{"kind":"name","name":"__CircleParam","id":74}]}},"depth":0},"80":{"id":79,"name":"__RectangleParam","kind":"param","depth":1},"83":{"id":83,"kind":"def","name":"Rectangle","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":82,"kind":"lambda","params":[{"id":79,"name":"__RectangleParam"}],"qualifier":"def","expr":{"id":81,"kind":"app","opcode":"variant","args":[{"id":78,"kind":"str","value":"Rectangle"},{"kind":"name","name":"__RectangleParam","id":80}]}},"depth":0},"86":{"id":85,"name":"__DogParam","kind":"param","depth":1},"89":{"id":89,"kind":"def","name":"Dog","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":69,"kind":"str"}],"res":{"id":71,"kind":"const","name":"MyUnionType"}},"expr":{"id":88,"kind":"lambda","params":[{"id":85,"name":"__DogParam"}],"qualifier":"def","expr":{"id":87,"kind":"app","opcode":"variant","args":[{"id":84,"kind":"str","value":"Dog"},{"kind":"name","name":"__DogParam","id":86}]}},"depth":0},"90":{"id":70,"kind":"typedef","name":"MyUnionType","type":{"id":70,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"Circle","fieldType":{"id":65,"kind":"int"}},{"fieldName":"Rectangle","fieldType":{"id":68,"kind":"rec","fields":{"kind":"row","fields":[{"fieldName":"width","fieldType":{"id":66,"kind":"int"}},{"fieldName":"height","fieldType":{"id":67,"kind":"int"}}],"other":{"kind":"empty"}}}},{"fieldName":"Dog","fieldType":{"id":69,"kind":"str"}}],"other":{"kind":"empty"}}},"depth":0},"99":{"id":99,"kind":"def","name":"add_1_to_2","qualifier":"val","expr":{"id":98,"kind":"app","opcode":"iadd","args":[{"id":96,"kind":"int","value":1},{"id":97,"kind":"int","value":2}]},"depth":0},"103":{"id":103,"kind":"def","name":"sub_1_to_2","qualifier":"val","expr":{"id":102,"kind":"app","opcode":"isub","args":[{"id":100,"kind":"int","value":1},{"id":101,"kind":"int","value":2}]},"depth":0},"107":{"id":107,"kind":"def","name":"mul_2_to_3","qualifier":"val","expr":{"id":106,"kind":"app","opcode":"imul","args":[{"id":104,"kind":"int","value":2},{"id":105,"kind":"int","value":3}]},"depth":0},"111":{"id":111,"kind":"def","name":"div_2_to_3","qualifier":"val","expr":{"id":110,"kind":"app","opcode":"idiv","args":[{"id":108,"kind":"int","value":2},{"id":109,"kind":"int","value":3}]},"depth":0},"115":{"id":115,"kind":"def","name":"mod_2_to_3","qualifier":"val","expr":{"id":114,"kind":"app","opcode":"imod","args":[{"id":112,"kind":"int","value":2},{"id":113,"kind":"int","value":3}]},"depth":0},"119":{"id":119,"kind":"def","name":"pow_2_to_3","qualifier":"val","expr":{"id":118,"kind":"app","opcode":"ipow","args":[{"id":116,"kind":"int","value":2},{"id":117,"kind":"int","value":3}]},"depth":0},"122":{"id":122,"kind":"def","name":"uminus","qualifier":"val","expr":{"id":121,"kind":"app","opcode":"iuminus","args":[{"id":120,"kind":"int","value":100}]},"depth":0},"126":{"id":126,"kind":"def","name":"gt_2_to_3","qualifier":"val","expr":{"id":125,"kind":"app","opcode":"igt","args":[{"id":123,"kind":"int","value":2},{"id":124,"kind":"int","value":3}]},"depth":0},"130":{"id":130,"kind":"def","name":"ge_2_to_3","qualifier":"val","expr":{"id":129,"kind":"app","opcode":"igte","args":[{"id":127,"kind":"int","value":2},{"id":128,"kind":"int","value":3}]},"depth":0},"134":{"id":134,"kind":"def","name":"lt_2_to_3","qualifier":"val","expr":{"id":133,"kind":"app","opcode":"ilt","args":[{"id":131,"kind":"int","value":2},{"id":132,"kind":"int","value":3}]},"depth":0},"138":{"id":138,"kind":"def","name":"le_2_to_3","qualifier":"val","expr":{"id":137,"kind":"app","opcode":"ilte","args":[{"id":135,"kind":"int","value":2},{"id":136,"kind":"int","value":3}]},"depth":0},"142":{"id":142,"kind":"def","name":"eqeq_2_to_3","qualifier":"val","expr":{"id":141,"kind":"app","opcode":"eq","args":[{"id":139,"kind":"int","value":2},{"id":140,"kind":"int","value":3}]},"depth":0},"146":{"id":146,"kind":"def","name":"ne_2_to_3","qualifier":"val","expr":{"id":145,"kind":"app","opcode":"neq","args":[{"id":143,"kind":"int","value":2},{"id":144,"kind":"int","value":3}]},"depth":0},"152":{"id":152,"kind":"def","name":"VeryTrue","qualifier":"val","expr":{"id":151,"kind":"app","opcode":"eq","args":[{"id":149,"kind":"app","opcode":"iadd","args":[{"id":147,"kind":"int","value":2},{"id":148,"kind":"int","value":2}]},{"id":150,"kind":"int","value":4}]},"depth":0},"156":{"id":156,"kind":"def","name":"nat_includes_one","qualifier":"val","expr":{"id":155,"kind":"app","opcode":"in","args":[{"id":153,"kind":"int","value":1},{"id":154,"kind":"name","name":"Nat"}]},"depth":0},"159":{"id":158,"name":"x","kind":"param","depth":1},"162":{"id":162,"kind":"def","name":"there_is_truth","qualifier":"val","expr":{"id":161,"kind":"app","opcode":"exists","args":[{"id":157,"kind":"name","name":"Bool"},{"id":160,"kind":"lambda","params":[{"id":158,"name":"x"}],"qualifier":"def","expr":{"id":159,"kind":"name","name":"x"}}]},"depth":0},"168":{"id":168,"kind":"def","name":"withType","qualifier":"val","expr":{"id":167,"kind":"app","opcode":"Set","args":[{"id":165,"kind":"int","value":1},{"id":166,"kind":"int","value":2}]},"typeAnnotation":{"id":164,"kind":"set","elem":{"id":163,"kind":"int"}},"depth":0},"170":{"id":169,"kind":"typedef","name":"PROC","depth":0},"173":{"id":173,"kind":"def","name":"withUninterpretedType","qualifier":"val","expr":{"id":172,"kind":"app","opcode":"Set","args":[]},"typeAnnotation":{"id":171,"kind":"set","elem":{"id":170,"kind":"const","name":"PROC"}},"depth":0},"177":{"id":177,"kind":"def","name":"magicNumber","qualifier":"pureval","expr":{"id":176,"kind":"int","value":42},"depth":0},"180":{"id":178,"name":"x","kind":"param","depth":1,"shadowing":false},"181":{"id":179,"name":"y","kind":"param","depth":1},"184":{"id":184,"kind":"def","name":"add","qualifier":"puredef","expr":{"id":183,"kind":"lambda","params":[{"id":178,"name":"x"},{"id":179,"name":"y"}],"qualifier":"puredef","expr":{"id":182,"kind":"app","opcode":"iadd","args":[{"id":180,"kind":"name","name":"x"},{"id":181,"kind":"name","name":"y"}]}},"depth":0},"186":{"id":185,"name":"factor","kind":"param","depth":1},"187":{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},"190":{"id":190,"kind":"def","name":"ofN","qualifier":"def","expr":{"id":189,"kind":"lambda","params":[{"id":185,"name":"factor"}],"qualifier":"def","expr":{"id":188,"kind":"app","opcode":"imul","args":[{"id":186,"kind":"name","name":"factor"},{"id":187,"kind":"name","name":"n"}]}},"depth":0},"192":{"id":191,"name":"x","kind":"param","depth":1,"shadowing":false},"193":{"kind":"var","name":"n","typeAnnotation":{"id":174,"kind":"int"},"id":175,"depth":0},"196":{"id":196,"kind":"def","name":"A","qualifier":"action","expr":{"id":195,"kind":"lambda","params":[{"id":191,"name":"x"}],"qualifier":"action","expr":{"id":194,"kind":"app","opcode":"assign","args":[{"id":193,"kind":"name","name":"n"},{"id":192,"kind":"name","name":"x"}]}},"depth":0},"198":{"id":197,"name":"x","kind":"param","depth":1,"shadowing":false},"201":{"id":201,"kind":"def","name":"B","qualifier":"puredef","expr":{"id":200,"kind":"lambda","params":[{"id":197,"name":"x"}],"qualifier":"puredef","expr":{"id":199,"kind":"app","opcode":"not","args":[{"id":198,"kind":"name","name":"x"}]}},"depth":0},"208":{"id":202,"name":"x","kind":"param","depth":1,"shadowing":false},"209":{"id":203,"name":"y","kind":"param","depth":1,"shadowing":false},"212":{"id":212,"kind":"def","name":"H","qualifier":"def","expr":{"id":211,"kind":"lambda","params":[{"id":202,"name":"x"},{"id":203,"name":"y"}],"qualifier":"def","expr":{"id":210,"kind":"app","opcode":"iadd","args":[{"id":208,"kind":"name","name":"x"},{"id":209,"kind":"name","name":"y"}]}},"typeAnnotation":{"id":207,"kind":"oper","args":[{"id":204,"kind":"int"},{"id":205,"kind":"int"}],"res":{"id":206,"kind":"int"}},"depth":0},"217":{"id":213,"name":"x","kind":"param","depth":1,"shadowing":false},"219":{"id":219,"kind":"def","name":"Pol","qualifier":"def","expr":{"id":218,"kind":"lambda","params":[{"id":213,"name":"x"}],"qualifier":"def","expr":{"id":217,"kind":"name","name":"x"}},"typeAnnotation":{"id":216,"kind":"oper","args":[{"id":214,"kind":"var","name":"a"}],"res":{"id":215,"kind":"var","name":"a"}},"depth":0},"223":{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},"225":{"id":225,"kind":"def","name":"asgn","qualifier":"action","expr":{"id":224,"kind":"app","opcode":"assign","args":[{"id":223,"kind":"name","name":"k"},{"id":222,"kind":"int","value":3}]},"depth":0},"231":{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"232":{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"234":{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"235":{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"},"kind":"param","depth":1,"shadowing":false},"238":{"id":238,"kind":"def","name":"min","qualifier":"puredef","expr":{"id":237,"kind":"lambda","params":[{"id":227,"name":"x","typeAnnotation":{"id":226,"kind":"int"}},{"id":229,"name":"y","typeAnnotation":{"id":228,"kind":"int"}}],"qualifier":"puredef","expr":{"id":236,"kind":"app","opcode":"ite","args":[{"id":233,"kind":"app","opcode":"ilt","args":[{"id":231,"kind":"name","name":"x"},{"id":232,"kind":"name","name":"y"}]},{"id":234,"kind":"name","name":"x"},{"id":235,"kind":"name","name":"y"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":226,"kind":"int"},{"id":228,"kind":"int"}],"res":{"id":230,"kind":"int"}},"depth":0},"242":{"id":242,"kind":"def","name":"test_and","qualifier":"val","expr":{"id":241,"kind":"app","opcode":"and","args":[{"id":239,"kind":"bool","value":false},{"id":240,"kind":"bool","value":true}]},"depth":0},"246":{"id":246,"kind":"def","name":"test_or","qualifier":"val","expr":{"id":245,"kind":"app","opcode":"or","args":[{"id":243,"kind":"bool","value":false},{"id":244,"kind":"bool","value":true}]},"depth":0},"250":{"id":250,"kind":"def","name":"test_implies","qualifier":"val","expr":{"id":249,"kind":"app","opcode":"implies","args":[{"id":247,"kind":"bool","value":false},{"id":248,"kind":"bool","value":true}]},"depth":0},"252":{"id":251,"name":"x","kind":"param","depth":1,"shadowing":false},"254":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"256":{"id":255,"name":"x","kind":"param","depth":1,"shadowing":false},"257":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"258":{"id":255,"name":"x","kind":"param","depth":1,"shadowing":false},"262":{"id":262,"kind":"def","name":"G","qualifier":"def","expr":{"id":261,"kind":"lambda","params":[{"id":255,"name":"x"}],"qualifier":"def","expr":{"id":260,"kind":"app","opcode":"and","args":[{"id":257,"kind":"app","opcode":"F","args":[{"id":256,"kind":"name","name":"x"}]},{"id":259,"kind":"app","opcode":"not","args":[{"id":258,"kind":"name","name":"x"}]}]}},"depth":0},"264":{"id":263,"name":"x","kind":"param","depth":1,"shadowing":false},"265":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"266":{"id":263,"name":"x","kind":"param","depth":1,"shadowing":false},"270":{"id":270,"kind":"def","name":"test_and_arg","qualifier":"def","expr":{"id":269,"kind":"lambda","params":[{"id":263,"name":"x"}],"qualifier":"def","expr":{"id":268,"kind":"app","opcode":"and","args":[{"id":265,"kind":"app","opcode":"F","args":[{"id":264,"kind":"name","name":"x"}]},{"id":267,"kind":"app","opcode":"not","args":[{"id":266,"kind":"name","name":"x"}]}]}},"depth":0},"272":{"id":271,"name":"x","kind":"param","depth":1,"shadowing":false},"273":{"id":254,"kind":"def","name":"F","qualifier":"def","expr":{"id":253,"kind":"lambda","params":[{"id":251,"name":"x"}],"qualifier":"def","expr":{"id":252,"kind":"name","name":"x"}},"depth":0},"274":{"id":271,"name":"x","kind":"param","depth":1,"shadowing":false},"278":{"id":278,"kind":"def","name":"test_or_arg","qualifier":"def","expr":{"id":277,"kind":"lambda","params":[{"id":271,"name":"x"}],"qualifier":"def","expr":{"id":276,"kind":"app","opcode":"or","args":[{"id":273,"kind":"app","opcode":"F","args":[{"id":272,"kind":"name","name":"x"}]},{"id":275,"kind":"app","opcode":"not","args":[{"id":274,"kind":"name","name":"x"}]}]}},"depth":0},"283":{"id":283,"kind":"def","name":"test_block_and","qualifier":"val","expr":{"id":282,"kind":"app","opcode":"and","args":[{"id":279,"kind":"bool","value":false},{"id":280,"kind":"bool","value":true},{"id":281,"kind":"bool","value":false}]},"depth":0},"288":{"id":288,"kind":"def","name":"test_action_and","qualifier":"action","expr":{"id":287,"kind":"app","opcode":"actionAll","args":[{"id":284,"kind":"bool","value":false},{"id":285,"kind":"bool","value":true},{"id":286,"kind":"bool","value":false}]},"depth":0},"293":{"id":293,"kind":"def","name":"test_block_or","qualifier":"val","expr":{"id":292,"kind":"app","opcode":"or","args":[{"id":289,"kind":"bool","value":false},{"id":290,"kind":"bool","value":true},{"id":291,"kind":"bool","value":false}]},"depth":0},"298":{"id":298,"kind":"def","name":"test_action_or","qualifier":"action","expr":{"id":297,"kind":"app","opcode":"actionAny","args":[{"id":294,"kind":"bool","value":false},{"id":295,"kind":"bool","value":true},{"id":296,"kind":"bool","value":false}]},"depth":0},"303":{"id":303,"kind":"def","name":"test_ite","qualifier":"val","expr":{"id":302,"kind":"app","opcode":"ite","args":[{"id":299,"kind":"bool","value":true},{"id":300,"kind":"int","value":1},{"id":301,"kind":"int","value":0}]},"depth":0},"306":{"id":304,"name":"x","kind":"param","depth":1,"shadowing":false},"309":{"id":305,"name":"y","kind":"param","depth":1,"shadowing":false},"312":{"id":305,"name":"y","kind":"param","depth":1,"shadowing":false},"317":{"id":317,"kind":"def","name":"test_ite2","qualifier":"def","expr":{"id":316,"kind":"lambda","params":[{"id":304,"name":"x"},{"id":305,"name":"y"}],"qualifier":"def","expr":{"id":315,"kind":"app","opcode":"ite","args":[{"id":308,"kind":"app","opcode":"ilt","args":[{"id":306,"kind":"name","name":"x"},{"id":307,"kind":"int","value":10}]},{"id":311,"kind":"app","opcode":"iadd","args":[{"id":309,"kind":"name","name":"y"},{"id":310,"kind":"int","value":5}]},{"id":314,"kind":"app","opcode":"imod","args":[{"id":312,"kind":"name","name":"y"},{"id":313,"kind":"int","value":5}]}]}},"depth":0},"322":{"kind":"var","name":"f1","typeAnnotation":{"id":320,"kind":"fun","arg":{"id":318,"kind":"str"},"res":{"id":319,"kind":"int"}},"id":321,"depth":0},"325":{"id":325,"kind":"def","name":"funapp","qualifier":"val","expr":{"id":324,"kind":"app","opcode":"get","args":[{"id":322,"kind":"name","name":"f1"},{"id":323,"kind":"str","value":"a"}]},"depth":0},"330":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"333":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"334":{"id":334,"kind":"def","name":"oper_app_normal","qualifier":"val","expr":{"id":333,"kind":"app","opcode":"MyOper","args":[{"id":331,"kind":"str","value":"a"},{"id":332,"kind":"int","value":42}]},"depth":0},"337":{"id":330,"kind":"def","name":"MyOper","qualifier":"def","expr":{"id":329,"kind":"lambda","params":[{"id":326,"name":"a"},{"id":327,"name":"b"}],"qualifier":"def","expr":{"id":328,"kind":"int","value":1}},"depth":0},"338":{"id":338,"kind":"def","name":"oper_app_ufcs","qualifier":"val","expr":{"id":337,"kind":"app","opcode":"MyOper","args":[{"id":335,"kind":"str","value":"a"},{"id":336,"kind":"int","value":42}]},"depth":0},"342":{"id":342,"kind":"def","name":"oper_in","qualifier":"val","expr":{"id":341,"kind":"app","opcode":"in","args":[{"id":339,"kind":"int","value":1},{"id":340,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"343":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"345":{"id":344,"name":"x","kind":"param","depth":1,"shadowing":false},"350":{"id":350,"kind":"def","name":"oper_app_dot1","qualifier":"val","expr":{"id":349,"kind":"app","opcode":"filter","args":[{"id":343,"kind":"name","name":"S"},{"id":348,"kind":"lambda","params":[{"id":344,"name":"x"}],"qualifier":"def","expr":{"id":347,"kind":"app","opcode":"igt","args":[{"id":345,"kind":"name","name":"x"},{"id":346,"kind":"int","value":10}]}}]},"depth":0},"351":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"352":{"kind":"const","name":"MySet","typeAnnotation":{"id":18,"kind":"set","elem":{"id":17,"kind":"int"}},"id":19,"depth":0},"354":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]},"depth":4},"355":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]},"depth":3},"356":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]},"depth":4},"357":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]},"depth":3},"362":{"id":359,"name":"quintTupledLambdaParam359","kind":"param","depth":1},"366":{"id":359,"name":"quintTupledLambdaParam359","kind":"param","depth":1},"370":{"id":370,"kind":"def","name":"tuple_sum","qualifier":"val","expr":{"id":369,"kind":"app","opcode":"map","args":[{"id":353,"kind":"app","opcode":"tuples","args":[{"id":351,"kind":"name","name":"S"},{"id":352,"kind":"name","name":"MySet"}]},{"id":368,"kind":"lambda","params":[{"id":359,"name":"quintTupledLambdaParam359"}],"qualifier":"def","expr":{"id":364,"kind":"let","opdef":{"id":355,"kind":"def","name":"mys","qualifier":"pureval","expr":{"id":365,"kind":"app","opcode":"item","args":[{"id":366,"kind":"name","name":"quintTupledLambdaParam359"},{"id":367,"kind":"int","value":2}]}},"expr":{"id":360,"kind":"let","opdef":{"id":354,"kind":"def","name":"s","qualifier":"pureval","expr":{"id":361,"kind":"app","opcode":"item","args":[{"id":362,"kind":"name","name":"quintTupledLambdaParam359"},{"id":363,"kind":"int","value":1}]}},"expr":{"id":358,"kind":"app","opcode":"iadd","args":[{"id":356,"kind":"name","name":"s"},{"id":357,"kind":"name","name":"mys"}]}}}}]},"depth":0},"371":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"373":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"374":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"377":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]},"depth":2,"shadowing":false},"378":{"kind":"var","name":"k","typeAnnotation":{"id":220,"kind":"int"},"id":221,"depth":0},"382":{"id":382,"kind":"def","name":"oper_nondet","qualifier":"action","expr":{"id":381,"kind":"let","opdef":{"id":373,"kind":"def","name":"x","qualifier":"nondet","expr":{"id":372,"kind":"app","opcode":"oneOf","args":[{"id":371,"kind":"name","name":"S"}]}},"expr":{"id":380,"kind":"app","opcode":"actionAll","args":[{"id":376,"kind":"app","opcode":"igt","args":[{"id":374,"kind":"name","name":"x"},{"id":375,"kind":"int","value":10}]},{"id":379,"kind":"app","opcode":"assign","args":[{"id":378,"kind":"name","name":"k"},{"id":377,"kind":"name","name":"x"}]}]}},"depth":0},"383":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"388":{"id":388,"kind":"def","name":"oper_app_dot4","qualifier":"val","expr":{"id":387,"kind":"app","opcode":"filter","args":[{"id":383,"kind":"name","name":"S"},{"id":386,"kind":"lambda","params":[{"id":384,"name":"_"}],"qualifier":"def","expr":{"id":385,"kind":"bool","value":true}}]},"depth":0},"389":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"391":{"id":390,"name":"x","kind":"param","depth":1,"shadowing":false},"396":{"id":396,"kind":"def","name":"f","qualifier":"val","expr":{"id":395,"kind":"app","opcode":"mapBy","args":[{"id":389,"kind":"name","name":"S"},{"id":394,"kind":"lambda","params":[{"id":390,"name":"x"}],"qualifier":"def","expr":{"id":393,"kind":"app","opcode":"iadd","args":[{"id":391,"kind":"name","name":"x"},{"id":392,"kind":"int","value":1}]}}]},"depth":0},"397":{"kind":"const","name":"S","typeAnnotation":{"id":15,"kind":"set","elem":{"id":14,"kind":"int"}},"id":16,"depth":0},"402":{"id":402,"kind":"def","name":"set_difference","qualifier":"val","expr":{"id":401,"kind":"app","opcode":"exclude","args":[{"id":397,"kind":"name","name":"S"},{"id":400,"kind":"app","opcode":"Set","args":[{"id":398,"kind":"int","value":3},{"id":399,"kind":"int","value":4}]}]},"depth":0},"407":{"id":407,"kind":"def","name":"one","qualifier":"val","expr":{"id":406,"kind":"app","opcode":"head","args":[{"id":405,"kind":"app","opcode":"List","args":[{"id":403,"kind":"int","value":1},{"id":404,"kind":"int","value":2}]}]},"depth":0},"412":{"id":412,"kind":"def","name":"test_tuple","qualifier":"val","expr":{"id":411,"kind":"app","opcode":"Tup","args":[{"id":408,"kind":"int","value":1},{"id":409,"kind":"int","value":2},{"id":410,"kind":"int","value":3}]},"depth":0},"417":{"id":417,"kind":"def","name":"test_tuple2","qualifier":"val","expr":{"id":416,"kind":"app","opcode":"Tup","args":[{"id":413,"kind":"int","value":1},{"id":414,"kind":"int","value":2},{"id":415,"kind":"int","value":3}]},"depth":0},"421":{"id":421,"kind":"def","name":"test_pair","qualifier":"val","expr":{"id":420,"kind":"app","opcode":"Tup","args":[{"id":418,"kind":"int","value":4},{"id":419,"kind":"int","value":5}]},"depth":0},"426":{"id":426,"kind":"def","name":"test_list","qualifier":"val","expr":{"id":425,"kind":"app","opcode":"List","args":[{"id":422,"kind":"int","value":1},{"id":423,"kind":"int","value":2},{"id":424,"kind":"int","value":3}]},"depth":0},"431":{"id":431,"kind":"def","name":"test_list2","qualifier":"val","expr":{"id":430,"kind":"app","opcode":"List","args":[{"id":427,"kind":"int","value":1},{"id":428,"kind":"int","value":2},{"id":429,"kind":"int","value":3}]},"depth":0},"438":{"id":438,"kind":"def","name":"test_list_nth","qualifier":"val","expr":{"id":437,"kind":"app","opcode":"nth","args":[{"id":435,"kind":"app","opcode":"List","args":[{"id":432,"kind":"int","value":2},{"id":433,"kind":"int","value":3},{"id":434,"kind":"int","value":4}]},{"id":436,"kind":"int","value":2}]},"depth":0},"444":{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]},"depth":0},"450":{"id":450,"kind":"def","name":"test_record2","qualifier":"val","expr":{"id":449,"kind":"app","opcode":"Rec","args":[{"id":445,"kind":"str","value":"name"},{"id":446,"kind":"str","value":"igor"},{"id":447,"kind":"str","value":"year"},{"id":448,"kind":"int","value":1981}]},"depth":0},"455":{"id":444,"kind":"def","name":"test_record","qualifier":"val","expr":{"id":443,"kind":"app","opcode":"Rec","args":[{"id":440,"kind":"str","value":"name"},{"id":439,"kind":"str","value":"igor"},{"id":442,"kind":"str","value":"year"},{"id":441,"kind":"int","value":1981}]},"depth":0},"458":{"id":458,"kind":"def","name":"test_record3","qualifier":"val","expr":{"id":457,"kind":"app","opcode":"with","args":[{"id":456,"kind":"app","opcode":"with","args":[{"id":455,"kind":"name","name":"test_record"},{"id":452,"kind":"str","value":"name"},{"id":451,"kind":"str","value":"quint"}]},{"id":454,"kind":"str","value":"year"},{"id":453,"kind":"int","value":2023}]},"depth":0},"463":{"id":463,"kind":"def","name":"test_set","qualifier":"val","expr":{"id":462,"kind":"app","opcode":"Set","args":[{"id":459,"kind":"int","value":1},{"id":460,"kind":"int","value":2},{"id":461,"kind":"int","value":3}]},"depth":0},"469":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]},"depth":2},"470":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]},"depth":2},"474":{"id":474,"kind":"def","name":"rec_field","qualifier":"val","expr":{"id":473,"kind":"let","opdef":{"id":469,"kind":"def","name":"my_rec","qualifier":"val","expr":{"id":468,"kind":"app","opcode":"Rec","args":[{"id":465,"kind":"str","value":"a"},{"id":464,"kind":"int","value":1},{"id":467,"kind":"str","value":"b"},{"id":466,"kind":"str","value":"foo"}]}},"expr":{"id":472,"kind":"app","opcode":"field","args":[{"id":470,"kind":"name","name":"my_rec"},{"id":471,"kind":"str","value":"a"}]}},"depth":0},"478":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]},"depth":2},"479":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]},"depth":2},"483":{"id":483,"kind":"def","name":"tup_elem","qualifier":"val","expr":{"id":482,"kind":"let","opdef":{"id":478,"kind":"def","name":"my_tup","qualifier":"val","expr":{"id":477,"kind":"app","opcode":"Tup","args":[{"id":475,"kind":"str","value":"a"},{"id":476,"kind":"int","value":3}]}},"expr":{"id":481,"kind":"app","opcode":"item","args":[{"id":479,"kind":"name","name":"my_tup"},{"id":480,"kind":"int","value":2}]}},"depth":0},"485":{"id":484,"name":"s","kind":"param","depth":1,"shadowing":false},"489":{"id":489,"kind":"def","name":"isEmpty","qualifier":"def","expr":{"id":488,"kind":"lambda","params":[{"id":484,"name":"s"}],"qualifier":"def","expr":{"id":487,"kind":"app","opcode":"eq","args":[{"id":485,"kind":"name","name":"s"},{"id":486,"kind":"app","opcode":"List","args":[]}]}},"depth":0},"493":{"id":493,"kind":"def","name":"in_2_empty","qualifier":"val","expr":{"id":492,"kind":"app","opcode":"in","args":[{"id":490,"kind":"int","value":2},{"id":491,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"497":{"id":497,"kind":"def","name":"subseteq_empty","qualifier":"val","expr":{"id":496,"kind":"app","opcode":"subseteq","args":[{"id":494,"kind":"app","opcode":"Set","args":[]},{"id":495,"kind":"app","opcode":"Set","args":[]}]},"depth":0},"506":{"id":506,"kind":"def","name":"powersets","qualifier":"val","expr":{"id":505,"kind":"app","opcode":"in","args":[{"id":499,"kind":"app","opcode":"Set","args":[{"id":498,"kind":"int","value":1}]},{"id":504,"kind":"app","opcode":"powerset","args":[{"id":503,"kind":"app","opcode":"Set","args":[{"id":500,"kind":"int","value":1},{"id":501,"kind":"int","value":2},{"id":502,"kind":"int","value":3}]}]}]},"depth":0},"512":{"id":512,"kind":"def","name":"lists","qualifier":"val","expr":{"id":511,"kind":"app","opcode":"allListsUpTo","args":[{"id":509,"kind":"app","opcode":"Set","args":[{"id":507,"kind":"int","value":1},{"id":508,"kind":"int","value":2}]},{"id":510,"kind":"int","value":3}]},"depth":0},"513":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"517":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"527":{"id":525,"kind":"typedef","name":"INT_SET","type":{"id":524,"kind":"set","elem":{"id":523,"kind":"int"}},"depth":0},"529":{"kind":"const","name":"N","typeAnnotation":{"id":12,"kind":"int"},"id":13,"depth":0},"530":{"kind":"const","name":"N","typeAnnotation":{"id":7,"kind":"int"},"id":8,"depth":0,"importedFrom":{"id":531,"kind":"instance","qualifiedName":"Inst1","protoName":"Proto","overrides":[[{"id":530,"name":"N"},{"id":529,"kind":"name","name":"N"}]],"identityOverride":false},"hidden":true,"namespaces":["Inst1","withConsts"]}},"errors":[]} \ No newline at end of file diff --git a/quint/testFixture/_1016nonConstOverride.json b/quint/testFixture/_1016nonConstOverride.json index 16377145c..9050d6c5a 100644 --- a/quint/testFixture/_1016nonConstOverride.json +++ b/quint/testFixture/_1016nonConstOverride.json @@ -1 +1 @@ -{"stage":"parsing","warnings":[],"modules":[{"id":5,"name":"A","declarations":[{"kind":"const","name":"c","typeAnnotation":{"id":1,"kind":"int"},"id":2,"depth":0},{"kind":"var","name":"a","typeAnnotation":{"id":3,"kind":"int"},"id":4,"depth":0}]},{"id":11,"name":"nonConstOverride","declarations":[{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false}]}],"table":{"8":{"kind":"const","name":"c","typeAnnotation":{"id":1,"kind":"int"},"id":6,"depth":0,"importedFrom":{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false},"hidden":true,"namespaces":["A1","nonConstOverride"]},"9":{"kind":"var","name":"a","typeAnnotation":{"id":3,"kind":"int"},"id":4,"depth":0,"importedFrom":{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false},"hidden":true,"namespaces":["A1","nonConstOverride"]}},"errors":[{"explanation":"[QNT406] Instantiation error: 'a' is not a constant in 'A'","locs":[{"source":"mocked_path/testFixture/_1016nonConstOverride.qnt","start":{"line":6,"col":2,"index":70},"end":{"line":6,"col":33,"index":101}}]}]} \ No newline at end of file +{"stage":"parsing","warnings":[],"modules":[{"id":5,"name":"A","declarations":[{"kind":"const","name":"c","typeAnnotation":{"id":1,"kind":"int"},"id":2,"depth":0},{"kind":"var","name":"a","typeAnnotation":{"id":3,"kind":"int"},"id":4,"depth":0}]},{"id":11,"name":"nonConstOverride","declarations":[{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false}]}],"table":{"8":{"kind":"const","name":"c","typeAnnotation":{"id":1,"kind":"int"},"id":2,"depth":0,"importedFrom":{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false},"hidden":true,"namespaces":["A1","nonConstOverride"]},"9":{"kind":"var","name":"a","typeAnnotation":{"id":3,"kind":"int"},"id":4,"depth":0,"importedFrom":{"id":10,"kind":"instance","qualifiedName":"A1","protoName":"A","overrides":[[{"id":8,"name":"c"},{"id":6,"kind":"int","value":1}],[{"id":9,"name":"a"},{"id":7,"kind":"bool","value":false}]],"identityOverride":false},"hidden":true,"namespaces":["A1","nonConstOverride"]}},"errors":[{"explanation":"[QNT406] Instantiation error: 'a' is not a constant in 'A'","locs":[{"source":"mocked_path/testFixture/_1016nonConstOverride.qnt","start":{"line":6,"col":2,"index":70},"end":{"line":6,"col":33,"index":101}}]}]} \ No newline at end of file diff --git a/quint/testFixture/_1031instance.json b/quint/testFixture/_1031instance.json index 7b66f056e..92d6cd850 100644 --- a/quint/testFixture/_1031instance.json +++ b/quint/testFixture/_1031instance.json @@ -1 +1 @@ -{"stage":"parsing","warnings":[],"modules":[{"id":18,"name":"c","declarations":[{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":9,"depth":0},{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":10,"kind":"int"}],"res":{"id":12,"kind":"int"}}}]},{"id":7,"name":"inst","declarations":[{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},{"id":6,"kind":"def","name":"baz","qualifier":"puredef","expr":{"id":5,"kind":"app","opcode":"foo","args":[{"id":4,"kind":"int","value":6}]}}]}],"table":{"2":{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":1,"depth":0,"importedFrom":{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},"hidden":true,"namespaces":["c","inst"]},"5":{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"depth":0,"importedFrom":{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},"hidden":true,"namespaces":["c","inst"]},"6":{"id":6,"kind":"def","name":"baz","qualifier":"puredef","expr":{"id":5,"kind":"app","opcode":"foo","args":[{"id":4,"kind":"int","value":6}]},"depth":0},"13":{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"},"kind":"param","depth":1},"14":{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":9,"depth":0},"17":{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":10,"kind":"int"}],"res":{"id":12,"kind":"int"}},"depth":0}},"errors":[]} \ No newline at end of file +{"stage":"parsing","warnings":[],"modules":[{"id":18,"name":"c","declarations":[{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":9,"depth":0},{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":10,"kind":"int"}],"res":{"id":12,"kind":"int"}}}]},{"id":7,"name":"inst","declarations":[{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},{"id":6,"kind":"def","name":"baz","qualifier":"puredef","expr":{"id":5,"kind":"app","opcode":"foo","args":[{"id":4,"kind":"int","value":6}]}}]}],"table":{"2":{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":9,"depth":0,"importedFrom":{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},"hidden":true,"namespaces":["c","inst"]},"5":{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"depth":0,"importedFrom":{"id":3,"kind":"instance","protoName":"c","overrides":[[{"id":2,"name":"N"},{"id":1,"kind":"int","value":3}]],"identityOverride":true,"fromSource":"./_1030const"},"hidden":true,"namespaces":["c","inst"]},"6":{"id":6,"kind":"def","name":"baz","qualifier":"puredef","expr":{"id":5,"kind":"app","opcode":"foo","args":[{"id":4,"kind":"int","value":6}]},"depth":0},"13":{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"},"kind":"param","depth":1},"14":{"kind":"const","name":"N","typeAnnotation":{"id":8,"kind":"int"},"id":9,"depth":0},"17":{"id":17,"kind":"def","name":"foo","qualifier":"puredef","expr":{"id":16,"kind":"lambda","params":[{"id":11,"name":"i","typeAnnotation":{"id":10,"kind":"int"}}],"qualifier":"puredef","expr":{"id":15,"kind":"app","opcode":"iadd","args":[{"id":13,"kind":"name","name":"i"},{"id":14,"kind":"name","name":"N"}]}},"typeAnnotation":{"kind":"oper","args":[{"id":10,"kind":"int"}],"res":{"id":12,"kind":"int"}},"depth":0}},"errors":[]} \ No newline at end of file