tla-ts 0.0.1 → 0.0.3-dev.a50fc89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +215 -30
- package/dist/index.d.ts +215 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +2 -2
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +57 -28
- package/src/parallel.ts +116 -62
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +65 -21
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- package/src/values.ts +1231 -176
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// A port of TLC's tlc2.pprint package, which re-parses a printed value and breaks it across
|
|
2
|
+
// lines of at most 80 columns. TLC prints the original text whenever the parse fails.
|
|
3
|
+
|
|
4
|
+
import { skipWhile } from "./lexer";
|
|
5
|
+
|
|
6
|
+
interface PrintedNode {
|
|
7
|
+
kind: "Constant" | "Set" | "Sequence" | "Record" | "Function" | "Subset" | "Pair";
|
|
8
|
+
first: number;
|
|
9
|
+
last: number;
|
|
10
|
+
children: PrintedNode[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Delimiters {
|
|
14
|
+
open: string;
|
|
15
|
+
close: string;
|
|
16
|
+
pad: string;
|
|
17
|
+
separator: string;
|
|
18
|
+
divider: string;
|
|
19
|
+
dividerPad: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const WIDTH = 80;
|
|
23
|
+
|
|
24
|
+
const DELIMITERS: Record<string, Delimiters> = {
|
|
25
|
+
Set: { open: "{ ", close: " }", pad: " ", separator: ",", divider: "", dividerPad: "" },
|
|
26
|
+
Sequence: { open: "<< ", close: " >>", pad: " ", separator: ",", divider: "", dividerPad: "" },
|
|
27
|
+
Subset: { open: "SUBSET ", close: " ", pad: " ", separator: ",", divider: "", dividerPad: "" },
|
|
28
|
+
Function: {
|
|
29
|
+
open: "( ",
|
|
30
|
+
close: " )",
|
|
31
|
+
pad: " ",
|
|
32
|
+
separator: " @@",
|
|
33
|
+
divider: " :>",
|
|
34
|
+
dividerPad: " ",
|
|
35
|
+
},
|
|
36
|
+
Record: {
|
|
37
|
+
open: "[ ",
|
|
38
|
+
close: " ]",
|
|
39
|
+
pad: " ",
|
|
40
|
+
separator: ",",
|
|
41
|
+
divider: " |->",
|
|
42
|
+
dividerPad: " ",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isWhitespace = (character: string | undefined) => {
|
|
47
|
+
if (character === undefined) return false;
|
|
48
|
+
const code = character.charCodeAt(0);
|
|
49
|
+
return (
|
|
50
|
+
(code >= 0x09 && code <= 0x0d) ||
|
|
51
|
+
(code >= 0x1c && code <= 0x20) ||
|
|
52
|
+
code === 0x1680 ||
|
|
53
|
+
(code >= 0x2000 && code <= 0x200a && code !== 0x2007) ||
|
|
54
|
+
code === 0x2028 ||
|
|
55
|
+
code === 0x2029 ||
|
|
56
|
+
code === 0x205f ||
|
|
57
|
+
code === 0x3000
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const isDigit = (character: string | undefined) =>
|
|
62
|
+
character !== undefined && /^\p{Nd}$/u.test(character);
|
|
63
|
+
|
|
64
|
+
const isLetterOrDigit = (character: string | undefined) =>
|
|
65
|
+
character !== undefined && /^[\p{L}\p{Nd}]$/u.test(character);
|
|
66
|
+
|
|
67
|
+
const parsePrinted = (text: string) => {
|
|
68
|
+
const skipWhitespace = (index: number) => {
|
|
69
|
+
const end = skipWhile(text, index, isWhitespace);
|
|
70
|
+
return text[end] === undefined ? undefined : end;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const parseItems = (
|
|
74
|
+
kind: PrintedNode["kind"],
|
|
75
|
+
first: number,
|
|
76
|
+
start: number,
|
|
77
|
+
close: string,
|
|
78
|
+
parseItem: (index: number) => PrintedNode | undefined,
|
|
79
|
+
separator: string,
|
|
80
|
+
) => {
|
|
81
|
+
const node: PrintedNode = { kind, first, last: first, children: [] };
|
|
82
|
+
const position = { index: skipWhitespace(start) };
|
|
83
|
+
while (position.index !== undefined && text[position.index] !== close) {
|
|
84
|
+
const item = parseItem(position.index);
|
|
85
|
+
if (!item) return undefined;
|
|
86
|
+
node.children.push(item);
|
|
87
|
+
const afterItem = skipWhitespace(item.last + 1);
|
|
88
|
+
if (afterItem === undefined) return undefined;
|
|
89
|
+
const afterSeparator = text.startsWith(separator, afterItem)
|
|
90
|
+
? afterItem + separator.length
|
|
91
|
+
: afterItem;
|
|
92
|
+
position.index = skipWhitespace(afterSeparator);
|
|
93
|
+
}
|
|
94
|
+
if (position.index === undefined) return undefined;
|
|
95
|
+
node.last = position.index;
|
|
96
|
+
return node;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const parsePair = (index: number, divider: string): PrintedNode | undefined => {
|
|
100
|
+
const left = parse(index);
|
|
101
|
+
if (!left) return undefined;
|
|
102
|
+
const dividerIndex = skipWhitespace(left.last + 1);
|
|
103
|
+
if (dividerIndex === undefined || !text.startsWith(divider, dividerIndex)) return undefined;
|
|
104
|
+
const rightIndex = skipWhitespace(dividerIndex + divider.length);
|
|
105
|
+
const right = rightIndex === undefined ? undefined : parse(rightIndex);
|
|
106
|
+
if (!right) return undefined;
|
|
107
|
+
return { kind: "Pair", first: index, last: right.last, children: [left, right] };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const parseInterval = (first: number): PrintedNode | undefined => {
|
|
111
|
+
const lowEnd = skipWhile(text, first, isDigit);
|
|
112
|
+
if (!text.startsWith("..", lowEnd) || !isDigit(text[lowEnd + 2])) return undefined;
|
|
113
|
+
return {
|
|
114
|
+
kind: "Constant",
|
|
115
|
+
first,
|
|
116
|
+
last: skipWhile(text, lowEnd + 2, isDigit) - 1,
|
|
117
|
+
children: [],
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const parseConstant = (first: number): PrintedNode | undefined => {
|
|
122
|
+
const character = text[first];
|
|
123
|
+
if (character === '"') {
|
|
124
|
+
const closing = text.indexOf('"', first + 1);
|
|
125
|
+
return closing === -1 ? undefined : { kind: "Constant", first, last: closing, children: [] };
|
|
126
|
+
}
|
|
127
|
+
const interval = isDigit(character) ? parseInterval(first) : undefined;
|
|
128
|
+
if (interval) return interval;
|
|
129
|
+
if (character === "-") {
|
|
130
|
+
if (first + 1 >= text.length) return undefined;
|
|
131
|
+
return {
|
|
132
|
+
kind: "Constant",
|
|
133
|
+
first,
|
|
134
|
+
last: skipWhile(text, first + 1, isDigit) - 1,
|
|
135
|
+
children: [],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!isLetterOrDigit(character)) return undefined;
|
|
139
|
+
return {
|
|
140
|
+
kind: "Constant",
|
|
141
|
+
first,
|
|
142
|
+
last: skipWhile(text, first, isLetterOrDigit) - 1,
|
|
143
|
+
children: [],
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const parse = (index: number): PrintedNode | undefined => {
|
|
148
|
+
const first = skipWhitespace(index);
|
|
149
|
+
if (first === undefined) return undefined;
|
|
150
|
+
switch (text[first]) {
|
|
151
|
+
case "{":
|
|
152
|
+
return parseItems("Set", first, first + 1, "}", parse, ",");
|
|
153
|
+
case "<": {
|
|
154
|
+
const node = parseItems("Sequence", first, first + 2, ">", parse, ",");
|
|
155
|
+
if (!node || node.last + 1 >= text.length) return undefined;
|
|
156
|
+
return { ...node, last: node.last + 1 };
|
|
157
|
+
}
|
|
158
|
+
case "[":
|
|
159
|
+
return parseItems("Record", first, first + 1, "]", (item) => parsePair(item, "|->"), ",");
|
|
160
|
+
case "(":
|
|
161
|
+
return parseItems("Function", first, first + 1, ")", (item) => parsePair(item, ":>"), "@@");
|
|
162
|
+
default: {
|
|
163
|
+
if (!text.startsWith("SUBSET", first)) return parseConstant(first);
|
|
164
|
+
const elementIndex = skipWhitespace(first + 6);
|
|
165
|
+
const element = elementIndex === undefined ? undefined : parse(elementIndex);
|
|
166
|
+
return element
|
|
167
|
+
? { kind: "Subset", first, last: element.last, children: [element] }
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return parse(0);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const prettyPrint = (text: string) => {
|
|
177
|
+
const tree = text.length === 0 ? undefined : parsePrinted(text);
|
|
178
|
+
if (!tree || tree.last < text.length - 1) return text;
|
|
179
|
+
|
|
180
|
+
const getText = (node: PrintedNode) => text.slice(node.first, node.last + 1);
|
|
181
|
+
const getLength = (node: PrintedNode) => node.last - node.first + 1;
|
|
182
|
+
|
|
183
|
+
const format = (
|
|
184
|
+
node: PrintedNode,
|
|
185
|
+
columnWidth: number,
|
|
186
|
+
trailerWidth: number,
|
|
187
|
+
leftPad: string,
|
|
188
|
+
): string | undefined => {
|
|
189
|
+
if (getLength(node) <= columnWidth - trailerWidth || node.kind === "Constant") {
|
|
190
|
+
return getText(node);
|
|
191
|
+
}
|
|
192
|
+
if (node.kind === "Pair") return undefined;
|
|
193
|
+
const delimiters = DELIMITERS[node.kind];
|
|
194
|
+
const innerWidth = columnWidth - Math.max(delimiters.open.length, delimiters.pad.length);
|
|
195
|
+
const innerTrailer = trailerWidth + delimiters.close.length;
|
|
196
|
+
const innerPad = leftPad + delimiters.pad;
|
|
197
|
+
const inner =
|
|
198
|
+
node.kind === "Function" || node.kind === "Record"
|
|
199
|
+
? formatPairs(node.children, innerWidth, innerTrailer, innerPad, delimiters)
|
|
200
|
+
: formatItems(node.children, innerWidth, innerTrailer, innerPad, delimiters.separator);
|
|
201
|
+
return inner === undefined ? undefined : delimiters.open + inner + delimiters.close;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const formatItems = (
|
|
205
|
+
items: PrintedNode[],
|
|
206
|
+
columnWidth: number,
|
|
207
|
+
trailerWidth: number,
|
|
208
|
+
leftPad: string,
|
|
209
|
+
separator: string,
|
|
210
|
+
) => {
|
|
211
|
+
const parts = items.map((item, index) => {
|
|
212
|
+
const isLast = index === items.length - 1;
|
|
213
|
+
const formatted = format(
|
|
214
|
+
item,
|
|
215
|
+
columnWidth,
|
|
216
|
+
isLast ? trailerWidth : separator.length,
|
|
217
|
+
leftPad,
|
|
218
|
+
);
|
|
219
|
+
return formatted === undefined || isLast ? formatted : `${formatted}${separator}\n${leftPad}`;
|
|
220
|
+
});
|
|
221
|
+
return parts.includes(undefined) ? undefined : parts.join("");
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const formatPairs = (
|
|
225
|
+
pairs: PrintedNode[],
|
|
226
|
+
columnWidth: number,
|
|
227
|
+
trailerWidth: number,
|
|
228
|
+
leftPad: string,
|
|
229
|
+
delimiters: Delimiters,
|
|
230
|
+
) => {
|
|
231
|
+
const parts = pairs.map((pair, index) => {
|
|
232
|
+
const isLast = index === pairs.length - 1;
|
|
233
|
+
const left = pair.children[0];
|
|
234
|
+
const right = pair.children[1];
|
|
235
|
+
const ending = isLast ? "" : `${delimiters.separator}\n${leftPad}`;
|
|
236
|
+
const pairLength = getLength(left) + delimiters.divider.length + 1 + getLength(right);
|
|
237
|
+
const fitsOnOneLine = isLast
|
|
238
|
+
? pairLength <= columnWidth - trailerWidth
|
|
239
|
+
: pairLength + delimiters.separator.length <= columnWidth;
|
|
240
|
+
if (fitsOnOneLine) return `${getText(left)}${delimiters.divider} ${getText(right)}${ending}`;
|
|
241
|
+
const formattedLeft =
|
|
242
|
+
getLength(left) + delimiters.divider.length <= columnWidth
|
|
243
|
+
? getText(left)
|
|
244
|
+
: format(left, columnWidth, trailerWidth + delimiters.divider.length, leftPad);
|
|
245
|
+
const formattedRight = format(
|
|
246
|
+
right,
|
|
247
|
+
columnWidth - delimiters.dividerPad.length,
|
|
248
|
+
isLast ? trailerWidth : delimiters.separator.length,
|
|
249
|
+
leftPad + delimiters.dividerPad,
|
|
250
|
+
);
|
|
251
|
+
return formattedLeft === undefined || formattedRight === undefined
|
|
252
|
+
? undefined
|
|
253
|
+
: `${formattedLeft}${delimiters.divider}\n${leftPad}${delimiters.dividerPad}${formattedRight}${ending}`;
|
|
254
|
+
});
|
|
255
|
+
return parts.includes(undefined) ? undefined : parts.join("");
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return format(tree, WIDTH, 0, "") ?? text;
|
|
259
|
+
};
|
package/src/randomization.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { defineStateOperator, getValue } from "./builtins";
|
|
2
|
-
import { TlaEvaluationError } from "./errors";
|
|
2
|
+
import { TlaEvaluationError, TlaModuleError } from "./errors";
|
|
3
3
|
import type { Builtin, BuiltinArgument } from "./evaluate";
|
|
4
4
|
import type { CheckerRuntime } from "./runtime";
|
|
5
|
+
import { getRandomSubset, type JavaRandom } from "./tlc-random";
|
|
5
6
|
import {
|
|
6
|
-
createRecord,
|
|
7
7
|
createSet,
|
|
8
|
-
createSortedFunction,
|
|
9
8
|
createTuple,
|
|
10
9
|
formatValue,
|
|
11
|
-
getCardinality,
|
|
12
10
|
isEmptySet,
|
|
13
11
|
isEnumerable,
|
|
14
12
|
isSetValue,
|
|
@@ -16,105 +14,48 @@ import {
|
|
|
16
14
|
type Value,
|
|
17
15
|
} from "./values";
|
|
18
16
|
|
|
19
|
-
const
|
|
20
|
-
new
|
|
17
|
+
const createArgumentError = (position: string, name: string, expected: string, value: string) =>
|
|
18
|
+
new TlaModuleError(
|
|
21
19
|
`The ${position} argument of ${name} should be a ${expected}, but instead it is:\n${value}`,
|
|
22
20
|
);
|
|
23
21
|
|
|
24
22
|
const getCount = (value: Value, position: string, name: string) => {
|
|
25
23
|
if (typeof value !== "number" || value < 0) {
|
|
26
|
-
throw
|
|
24
|
+
throw createArgumentError(position, name, "nonnegative integer", formatValue(value));
|
|
27
25
|
}
|
|
28
26
|
return value;
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
const getFiniteElements = (value: Value, position: string, name: string, expected: string) => {
|
|
32
30
|
if (!isSetValue(value) || !isEnumerable(value)) {
|
|
33
|
-
throw
|
|
31
|
+
throw createArgumentError(position, name, expected, formatValue(value));
|
|
34
32
|
}
|
|
35
33
|
return toFiniteSet(value).elements;
|
|
36
34
|
};
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return lists.reduceRight<{ remaining: number; elements: Value[] }>(
|
|
41
|
-
(current, list) => ({
|
|
42
|
-
remaining: Math.floor(current.remaining / list.length),
|
|
43
|
-
elements: [list[current.remaining % list.length], ...current.elements],
|
|
44
|
-
}),
|
|
45
|
-
{ remaining: index, elements: [] },
|
|
46
|
-
).elements;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const getElementAt = (set: Value, index: number): Value => {
|
|
50
|
-
if (typeof set === "object" && set.kind === "Interval") return set.low + index;
|
|
51
|
-
if (typeof set === "object" && set.kind === "ProductSet") {
|
|
52
|
-
return createTuple(getMixedRadixElement(set.factors, index));
|
|
53
|
-
}
|
|
54
|
-
if (typeof set === "object" && set.kind === "RecordSet") {
|
|
55
|
-
const values = getMixedRadixElement(set.sets, index);
|
|
56
|
-
return createRecord(
|
|
57
|
-
set.names.map((name, position): [string, Value] => [name, values[position]]),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
if (typeof set === "object" && set.kind === "FunctionSet") {
|
|
61
|
-
const domain = toFiniteSet(set.domain).elements;
|
|
62
|
-
return createSortedFunction(
|
|
63
|
-
domain,
|
|
64
|
-
getMixedRadixElement(
|
|
65
|
-
domain.map(() => set.range),
|
|
66
|
-
index,
|
|
67
|
-
),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return toFiniteSet(set).elements[index];
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const getSampleSize = (set: Value): number => {
|
|
74
|
-
if (isEmptySet(set)) return 0;
|
|
75
|
-
if (typeof set === "object" && set.kind === "ProductSet") {
|
|
76
|
-
return set.factors.reduce<number>((size, factor) => size * getSampleSize(factor), 1);
|
|
77
|
-
}
|
|
78
|
-
if (typeof set === "object" && set.kind === "RecordSet") {
|
|
79
|
-
return set.sets.reduce<number>((size, factor) => size * getSampleSize(factor), 1);
|
|
80
|
-
}
|
|
81
|
-
if (typeof set === "object" && set.kind === "FunctionSet") {
|
|
82
|
-
return getSampleSize(set.range) ** getCardinality(set.domain);
|
|
83
|
-
}
|
|
84
|
-
return getCardinality(set);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const pickSubset = (set: Value, count: number, random: () => number) => {
|
|
88
|
-
const size = getSampleSize(set);
|
|
89
|
-
const indices = new Set<number>();
|
|
90
|
-
while (indices.size < Math.min(count, size)) indices.add(Math.floor(random() * size));
|
|
91
|
-
return [...indices].map((index) => getElementAt(set, index));
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const tossSubsets = (elements: Value[], picks: number, probability: number, random: () => number) =>
|
|
36
|
+
// SubsetValue.CoinTossingSubsetEnumerator: each pick keeps each element with the probability.
|
|
37
|
+
const tossSubsets = (elements: Value[], picks: number, probability: number, random: JavaRandom) =>
|
|
95
38
|
createSet(
|
|
96
|
-
Array.from({ length: picks }, () =>
|
|
39
|
+
Array.from({ length: picks }, () =>
|
|
40
|
+
createSet(elements.filter(() => random.nextDouble() < probability)),
|
|
41
|
+
),
|
|
97
42
|
);
|
|
98
43
|
|
|
99
|
-
const checkPicks = (picks: number, size: number, name: string) => {
|
|
100
|
-
if (size < 31 && picks > 2 ** size) {
|
|
101
|
-
throw describeArgument(
|
|
102
|
-
"first",
|
|
103
|
-
name,
|
|
104
|
-
`nonnegative integer that is smaller than the subset's size of 2^${size}`,
|
|
105
|
-
String(picks),
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
44
|
const getRandomSetOfSubsets = (args: BuiltinArgument[], runtime: CheckerRuntime) => {
|
|
111
45
|
const name = "RandomSetOfSubsets";
|
|
112
46
|
const picks = getCount(getValue(args, 0, name), "first", name);
|
|
113
47
|
const size = getCount(getValue(args, 1, name), "second", name);
|
|
114
48
|
const elements = getFiniteElements(getValue(args, 2, name), "third", name, "finite set");
|
|
115
|
-
|
|
49
|
+
if (elements.length < 31 && picks > 2 ** elements.length) {
|
|
50
|
+
throw createArgumentError(
|
|
51
|
+
"first",
|
|
52
|
+
name,
|
|
53
|
+
`nonnegative integer that is smaller than the subset's size of 2^${elements.length}`,
|
|
54
|
+
String(picks),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
116
57
|
if (elements.length < size) {
|
|
117
|
-
throw
|
|
58
|
+
throw createArgumentError(
|
|
118
59
|
"second",
|
|
119
60
|
name,
|
|
120
61
|
"nonnegative integer in range 0..Cardinality(S)",
|
|
@@ -125,7 +66,7 @@ const getRandomSetOfSubsets = (args: BuiltinArgument[], runtime: CheckerRuntime)
|
|
|
125
66
|
elements,
|
|
126
67
|
picks,
|
|
127
68
|
elements.length === 0 ? 0 : size / elements.length,
|
|
128
|
-
runtime.
|
|
69
|
+
runtime.getRandom(),
|
|
129
70
|
);
|
|
130
71
|
};
|
|
131
72
|
|
|
@@ -133,7 +74,7 @@ export const createRandomizationOperators = (runtime: CheckerRuntime): Builtin[]
|
|
|
133
74
|
defineStateOperator("RandomSubset", 2, (args) => {
|
|
134
75
|
const count = getValue(args, 0, "RandomSubset");
|
|
135
76
|
if (typeof count !== "number") {
|
|
136
|
-
throw
|
|
77
|
+
throw createArgumentError("first", "RandomSubset", "nonnegative integer", formatValue(count));
|
|
137
78
|
}
|
|
138
79
|
if (count < 0) {
|
|
139
80
|
throw new TlaEvaluationError(
|
|
@@ -142,9 +83,9 @@ export const createRandomizationOperators = (runtime: CheckerRuntime): Builtin[]
|
|
|
142
83
|
}
|
|
143
84
|
const set = getValue(args, 1, "RandomSubset");
|
|
144
85
|
if (!isSetValue(set) || (!isEmptySet(set) && !isEnumerable(set))) {
|
|
145
|
-
throw
|
|
86
|
+
throw createArgumentError("second", "RandomSubset", "a finite set", formatValue(set));
|
|
146
87
|
}
|
|
147
|
-
return createSet(
|
|
88
|
+
return createSet(getRandomSubset(count, set, runtime.getRandom()));
|
|
148
89
|
}),
|
|
149
90
|
defineStateOperator("RandomSetOfSubsets", 3, (args) => getRandomSetOfSubsets(args, runtime)),
|
|
150
91
|
defineStateOperator("TestRandomSetOfSubsets", 3, (args) =>
|
|
@@ -155,34 +96,4 @@ export const createRandomizationOperators = (runtime: CheckerRuntime): Builtin[]
|
|
|
155
96
|
),
|
|
156
97
|
),
|
|
157
98
|
),
|
|
158
|
-
defineStateOperator("RandomSubsetSet", 3, (args) => {
|
|
159
|
-
const name = "RandomSubsetSetProbability";
|
|
160
|
-
const picks = getCount(getValue(args, 0, "RandomSubsetSet"), "first", name);
|
|
161
|
-
const text = getValue(args, 1, "RandomSubsetSet");
|
|
162
|
-
if (typeof text !== "string") {
|
|
163
|
-
throw describeArgument(
|
|
164
|
-
"second",
|
|
165
|
-
name,
|
|
166
|
-
"string literal representing a probability",
|
|
167
|
-
formatValue(text),
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
const probability = Number(text);
|
|
171
|
-
if (text.trim() === "" || Number.isNaN(probability) || probability < 0 || probability > 1) {
|
|
172
|
-
throw describeArgument(
|
|
173
|
-
"second",
|
|
174
|
-
name,
|
|
175
|
-
"string literal does not represent a parsable probability",
|
|
176
|
-
formatValue(text),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
const elements = getFiniteElements(
|
|
180
|
-
getValue(args, 2, "RandomSubsetSet"),
|
|
181
|
-
"third",
|
|
182
|
-
name,
|
|
183
|
-
"finite set",
|
|
184
|
-
);
|
|
185
|
-
checkPicks(picks, elements.length, name);
|
|
186
|
-
return tossSubsets(elements, picks, probability, runtime.random);
|
|
187
|
-
}),
|
|
188
99
|
];
|