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,241 @@
|
|
|
1
|
+
import type { Definition, Expression, Module, SourceLocation } from "./ast";
|
|
2
|
+
import { TlaSyntaxError, TlaUnsupportedError } from "./errors";
|
|
3
|
+
|
|
4
|
+
interface PoolEntry {
|
|
5
|
+
tag: number;
|
|
6
|
+
text: string;
|
|
7
|
+
value: number;
|
|
8
|
+
first: number;
|
|
9
|
+
second: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface JavaMethod {
|
|
13
|
+
name: string;
|
|
14
|
+
arity: number;
|
|
15
|
+
code: Uint8Array | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ObjectReference {
|
|
19
|
+
className: string;
|
|
20
|
+
value: Expression | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type StackItem = number | string | Expression | ObjectReference;
|
|
24
|
+
|
|
25
|
+
const CLASS_MAGIC = 0xcafebabe;
|
|
26
|
+
const ACCESS_PUBLIC = 0x0001;
|
|
27
|
+
const ACCESS_STATIC = 0x0008;
|
|
28
|
+
const VALUE_PACKAGE = "tlc2/value/impl/";
|
|
29
|
+
|
|
30
|
+
const createReader = (bytes: Uint8Array) => {
|
|
31
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
32
|
+
const cursor = { offset: 0 };
|
|
33
|
+
const advance = (length: number) => {
|
|
34
|
+
const start = cursor.offset;
|
|
35
|
+
cursor.offset += length;
|
|
36
|
+
return start;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
readUint8: () => view.getUint8(advance(1)),
|
|
40
|
+
readUint16: () => view.getUint16(advance(2)),
|
|
41
|
+
readUint32: () => view.getUint32(advance(4)),
|
|
42
|
+
readInt32: () => view.getInt32(advance(4)),
|
|
43
|
+
readBytes: (length: number) => bytes.subarray(advance(length), cursor.offset),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getParameterCount = (descriptor: string) => {
|
|
48
|
+
const parameters = descriptor.slice(1, descriptor.indexOf(")"));
|
|
49
|
+
return [...parameters.matchAll(/\[*(?:L[^;]*;|[BCDFIJSZ])/g)].length;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The public static methods of a class file and their bytecode, as java.lang.Class lists them.
|
|
53
|
+
const readMethods = (bytes: Uint8Array) => {
|
|
54
|
+
const reader = createReader(bytes);
|
|
55
|
+
if (reader.readUint32() !== CLASS_MAGIC)
|
|
56
|
+
throw new TlaSyntaxError("A module override is not a class file");
|
|
57
|
+
reader.readUint16();
|
|
58
|
+
reader.readUint16();
|
|
59
|
+
const pool: PoolEntry[] = [];
|
|
60
|
+
const count = reader.readUint16();
|
|
61
|
+
const decoder = new TextDecoder();
|
|
62
|
+
for (let index = 1; index < count; index++) {
|
|
63
|
+
const tag = reader.readUint8();
|
|
64
|
+
const entry = { tag, text: "", value: 0, first: 0, second: 0 };
|
|
65
|
+
pool[index] = entry;
|
|
66
|
+
if (tag === 1) entry.text = decoder.decode(reader.readBytes(reader.readUint16()));
|
|
67
|
+
else if (tag === 3) entry.value = reader.readInt32();
|
|
68
|
+
else if (tag === 4) reader.readUint32();
|
|
69
|
+
else if (tag === 5 || tag === 6) {
|
|
70
|
+
reader.readBytes(8);
|
|
71
|
+
index++;
|
|
72
|
+
} else if (tag === 7 || tag === 8 || tag === 16 || tag === 19 || tag === 20) {
|
|
73
|
+
entry.first = reader.readUint16();
|
|
74
|
+
} else if (tag === 15) {
|
|
75
|
+
reader.readUint8();
|
|
76
|
+
entry.first = reader.readUint16();
|
|
77
|
+
} else {
|
|
78
|
+
entry.first = reader.readUint16();
|
|
79
|
+
entry.second = reader.readUint16();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
reader.readUint16();
|
|
83
|
+
reader.readUint16();
|
|
84
|
+
reader.readUint16();
|
|
85
|
+
reader.readBytes(reader.readUint16() * 2);
|
|
86
|
+
for (let fields = reader.readUint16(); fields > 0; fields--) {
|
|
87
|
+
reader.readBytes(6);
|
|
88
|
+
for (let attributes = reader.readUint16(); attributes > 0; attributes--) {
|
|
89
|
+
reader.readUint16();
|
|
90
|
+
reader.readBytes(reader.readUint32());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const methods: JavaMethod[] = [];
|
|
94
|
+
for (let remaining = reader.readUint16(); remaining > 0; remaining--) {
|
|
95
|
+
const access = reader.readUint16();
|
|
96
|
+
const name = pool[reader.readUint16()].text;
|
|
97
|
+
const descriptor = pool[reader.readUint16()].text;
|
|
98
|
+
const method: JavaMethod = { name, arity: getParameterCount(descriptor), code: undefined };
|
|
99
|
+
for (let attributes = reader.readUint16(); attributes > 0; attributes--) {
|
|
100
|
+
const attributeName = pool[reader.readUint16()].text;
|
|
101
|
+
const attribute = reader.readBytes(reader.readUint32());
|
|
102
|
+
if (attributeName === "Code") {
|
|
103
|
+
const length = new DataView(attribute.buffer, attribute.byteOffset + 4, 4).getUint32(0);
|
|
104
|
+
method.code = attribute.subarray(8, 8 + length);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const isOverride = (access & ACCESS_PUBLIC) !== 0 && (access & ACCESS_STATIC) !== 0;
|
|
108
|
+
if (isOverride) methods.push(method);
|
|
109
|
+
}
|
|
110
|
+
return { pool, methods };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getClassName = (pool: PoolEntry[], index: number) => pool[pool[index].first].text;
|
|
114
|
+
|
|
115
|
+
const getMember = (pool: PoolEntry[], index: number) => {
|
|
116
|
+
const reference = pool[index];
|
|
117
|
+
const nameAndType = pool[reference.second];
|
|
118
|
+
return {
|
|
119
|
+
className: getClassName(pool, reference.first),
|
|
120
|
+
name: pool[nameAndType.first].text,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Runs the bytecode of an override that returns a constant TLC value, such as
|
|
125
|
+
// BoolValue.ValTrue, IntValue.gen(3), or new StringValue("a"), and returns that value as a
|
|
126
|
+
// literal.
|
|
127
|
+
const getReturnedLiteral = (
|
|
128
|
+
method: JavaMethod,
|
|
129
|
+
pool: PoolEntry[],
|
|
130
|
+
className: string,
|
|
131
|
+
location: SourceLocation,
|
|
132
|
+
) => {
|
|
133
|
+
const unsupported = () =>
|
|
134
|
+
new TlaUnsupportedError(
|
|
135
|
+
`The Java override ${className}.${method.name} computes its value, which tla-ts cannot run`,
|
|
136
|
+
);
|
|
137
|
+
const code = method.code;
|
|
138
|
+
if (!code) throw unsupported();
|
|
139
|
+
const stack: StackItem[] = [];
|
|
140
|
+
const pop = () => {
|
|
141
|
+
const item = stack.pop();
|
|
142
|
+
if (item === undefined) throw unsupported();
|
|
143
|
+
return item;
|
|
144
|
+
};
|
|
145
|
+
const getConstant = (index: number) => {
|
|
146
|
+
const entry = pool[index];
|
|
147
|
+
if (entry.tag === 3) return entry.value;
|
|
148
|
+
if (entry.tag === 8) return pool[entry.first].text;
|
|
149
|
+
throw unsupported();
|
|
150
|
+
};
|
|
151
|
+
for (let offset = 0; offset < code.length;) {
|
|
152
|
+
const opcode = code[offset];
|
|
153
|
+
const getIndex = () => (code[offset + 1] << 8) | code[offset + 2];
|
|
154
|
+
if (opcode >= 0x02 && opcode <= 0x08) {
|
|
155
|
+
stack.push(opcode - 0x03);
|
|
156
|
+
offset += 1;
|
|
157
|
+
} else if (opcode === 0x10) {
|
|
158
|
+
stack.push((code[offset + 1] << 24) >> 24);
|
|
159
|
+
offset += 2;
|
|
160
|
+
} else if (opcode === 0x11) {
|
|
161
|
+
stack.push((getIndex() << 16) >> 16);
|
|
162
|
+
offset += 3;
|
|
163
|
+
} else if (opcode === 0x12) {
|
|
164
|
+
stack.push(getConstant(code[offset + 1]));
|
|
165
|
+
offset += 2;
|
|
166
|
+
} else if (opcode === 0x13) {
|
|
167
|
+
stack.push(getConstant(getIndex()));
|
|
168
|
+
offset += 3;
|
|
169
|
+
} else if (opcode === 0x59) {
|
|
170
|
+
const top = pop();
|
|
171
|
+
stack.push(top, top);
|
|
172
|
+
offset += 1;
|
|
173
|
+
} else if (opcode === 0xb2) {
|
|
174
|
+
const field = getMember(pool, getIndex());
|
|
175
|
+
if (field.className !== `${VALUE_PACKAGE}BoolValue`) throw unsupported();
|
|
176
|
+
if (field.name !== "ValTrue" && field.name !== "ValFalse") throw unsupported();
|
|
177
|
+
stack.push({
|
|
178
|
+
kind: "Identifier",
|
|
179
|
+
name: field.name === "ValTrue" ? "TRUE" : "FALSE",
|
|
180
|
+
location,
|
|
181
|
+
});
|
|
182
|
+
offset += 3;
|
|
183
|
+
} else if (opcode === 0xb8) {
|
|
184
|
+
const callee = getMember(pool, getIndex());
|
|
185
|
+
const value = pop();
|
|
186
|
+
if (callee.className !== `${VALUE_PACKAGE}IntValue` || callee.name !== "gen") {
|
|
187
|
+
throw unsupported();
|
|
188
|
+
}
|
|
189
|
+
if (typeof value !== "number") throw unsupported();
|
|
190
|
+
stack.push({ kind: "Number", value, location });
|
|
191
|
+
offset += 3;
|
|
192
|
+
} else if (opcode === 0xbb) {
|
|
193
|
+
stack.push({ className: getClassName(pool, getIndex()), value: undefined });
|
|
194
|
+
offset += 3;
|
|
195
|
+
} else if (opcode === 0xb7) {
|
|
196
|
+
const constructor = getMember(pool, getIndex());
|
|
197
|
+
const text = pop();
|
|
198
|
+
const instance = pop();
|
|
199
|
+
const isStringValue =
|
|
200
|
+
constructor.className === `${VALUE_PACKAGE}StringValue` &&
|
|
201
|
+
constructor.name === "<init>" &&
|
|
202
|
+
typeof text === "string" &&
|
|
203
|
+
typeof instance === "object" &&
|
|
204
|
+
"className" in instance;
|
|
205
|
+
if (!isStringValue) throw unsupported();
|
|
206
|
+
instance.value = { kind: "String", value: text, location };
|
|
207
|
+
offset += 3;
|
|
208
|
+
} else if (opcode === 0xb0) {
|
|
209
|
+
const returned = pop();
|
|
210
|
+
if (typeof returned !== "object") throw unsupported();
|
|
211
|
+
if ("className" in returned) {
|
|
212
|
+
if (!returned.value) throw unsupported();
|
|
213
|
+
return returned.value;
|
|
214
|
+
}
|
|
215
|
+
return returned;
|
|
216
|
+
} else {
|
|
217
|
+
throw unsupported();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
throw unsupported();
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// TLC loads a class named after each module and replaces every operator the module defines
|
|
224
|
+
// with the public static method of the same name and arity.
|
|
225
|
+
export const overrideDefinitions = (module: Module, bytes: Uint8Array | undefined): Module => {
|
|
226
|
+
if (!bytes) return module;
|
|
227
|
+
const classFile = readMethods(bytes);
|
|
228
|
+
const definitions = module.definitions.map((definition): Definition => {
|
|
229
|
+
if (definition.kind !== "Operator") return definition;
|
|
230
|
+
const method = classFile.methods.find(
|
|
231
|
+
(candidate) =>
|
|
232
|
+
candidate.name === definition.name && candidate.arity === definition.params.length,
|
|
233
|
+
);
|
|
234
|
+
if (!method) return definition;
|
|
235
|
+
return {
|
|
236
|
+
...definition,
|
|
237
|
+
body: getReturnedLiteral(method, classFile.pool, module.name, definition.location),
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
return { ...module, definitions };
|
|
241
|
+
};
|
package/src/json.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createJavaOverrideError,
|
|
3
|
+
defineStateOperator,
|
|
4
|
+
getJavaClassName,
|
|
5
|
+
getValue,
|
|
6
|
+
} from "./builtins";
|
|
7
|
+
import { TlaModuleError } from "./errors";
|
|
3
8
|
import type { Builtin } from "./evaluate";
|
|
4
9
|
import {
|
|
5
|
-
checkInteger,
|
|
6
10
|
createRecord,
|
|
7
11
|
createTuple,
|
|
8
12
|
formatValue,
|
|
@@ -12,6 +16,7 @@ import {
|
|
|
12
16
|
toFiniteFunction,
|
|
13
17
|
toFiniteSet,
|
|
14
18
|
type Value,
|
|
19
|
+
rankString,
|
|
15
20
|
} from "./values";
|
|
16
21
|
|
|
17
22
|
export interface FileSystem {
|
|
@@ -19,116 +24,192 @@ export interface FileSystem {
|
|
|
19
24
|
writeFile: ((path: string, content: string) => void) | undefined;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
interface ParseContext {
|
|
28
|
+
source?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TO_JSON =
|
|
32
|
+
"public static tlc2.value.impl.StringValue tlc2.module.Json.toJson(tlc2.value.IValue) throws java.io.IOException";
|
|
33
|
+
const TO_JSON_ARRAY =
|
|
34
|
+
"public static tlc2.value.impl.StringValue tlc2.module.Json.toJsonArray(tlc2.value.IValue) throws java.io.IOException";
|
|
35
|
+
const TO_JSON_OBJECT =
|
|
36
|
+
"public static tlc2.value.impl.StringValue tlc2.module.Json.toJsonObject(tlc2.value.IValue) throws java.io.IOException";
|
|
37
|
+
const SERIALIZE =
|
|
38
|
+
"public static synchronized tlc2.value.impl.BoolValue tlc2.module.Json.serialize(tlc2.value.impl.StringValue,tlc2.value.impl.Value) throws java.io.IOException";
|
|
39
|
+
const ND_SERIALIZE =
|
|
40
|
+
"public static synchronized tlc2.value.impl.BoolValue tlc2.module.Json.ndSerialize(tlc2.value.impl.StringValue,tlc2.value.impl.Value) throws java.io.IOException";
|
|
41
|
+
const DESERIALIZE =
|
|
42
|
+
"public static tlc2.value.IValue tlc2.module.Json.deserialize(tlc2.value.impl.StringValue) throws java.io.IOException";
|
|
43
|
+
const ND_DESERIALIZE =
|
|
44
|
+
"public static tlc2.value.IValue tlc2.module.Json.ndDeserialize(tlc2.value.impl.StringValue) throws java.io.IOException";
|
|
23
45
|
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return Object.values(toObject(value));
|
|
46
|
+
const isRecord = (value: Value) => {
|
|
47
|
+
if (!isFunctionValue(value)) return false;
|
|
48
|
+
const domain = toFiniteFunction(value).domain;
|
|
49
|
+
return domain.length > 0 && domain.every((key) => typeof key === "string");
|
|
29
50
|
};
|
|
30
51
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const fn = toFiniteFunction(value);
|
|
36
|
-
return Object.fromEntries(
|
|
37
|
-
fn.domain.map((key, index): [string, JsonValue] => [
|
|
38
|
-
typeof key === "string" ? key : formatValue(key),
|
|
39
|
-
toJson(fn.range[index]),
|
|
40
|
-
]),
|
|
52
|
+
const createUnsupportedError = (method: string, value: Value) =>
|
|
53
|
+
createJavaOverrideError(
|
|
54
|
+
method,
|
|
55
|
+
`Cannot convert value: unsupported value type tlc2.value.impl.${getJavaClassName(value)}`,
|
|
41
56
|
);
|
|
42
|
-
};
|
|
43
57
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
const createCastError = (method: string, value: Value) =>
|
|
59
|
+
createJavaOverrideError(
|
|
60
|
+
method,
|
|
61
|
+
`Cannot cast tlc2.value.impl.${getJavaClassName(value)} to tlc2.value.impl.StringValue`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const toJsonString = (text: string) =>
|
|
65
|
+
JSON.stringify(text).replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
66
|
+
|
|
67
|
+
const toSetJson = (value: Value, method: string) => {
|
|
68
|
+
if (typeof value === "object" && (value.kind === "InfiniteSet" || value.kind === "SequenceSet")) {
|
|
69
|
+
throw createUnsupportedError(method, value);
|
|
47
70
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
return `[${toFiniteSet(value)
|
|
72
|
+
.elements.map((element) => toJson(element, method))
|
|
73
|
+
.join(",")}]`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const toFunctionJson = (value: Value, method: string) => {
|
|
77
|
+
const functionValue = toFiniteFunction(value);
|
|
78
|
+
const elements = getSequenceElements(functionValue);
|
|
79
|
+
if (elements) return `[${elements.map((element) => toJson(element, method)).join(",")}]`;
|
|
80
|
+
return `{${functionValue.domain
|
|
81
|
+
.map(
|
|
82
|
+
(key, index) =>
|
|
83
|
+
`${toJsonString(typeof key === "string" ? key : formatValue(key))}:${toJson(functionValue.range[index], method)}`,
|
|
84
|
+
)
|
|
85
|
+
.join(",")}}`;
|
|
52
86
|
};
|
|
53
87
|
|
|
54
|
-
const
|
|
55
|
-
|
|
88
|
+
const toJson = (value: Value, method: string): string => {
|
|
89
|
+
if (typeof value === "string") return toJsonString(value);
|
|
90
|
+
if (typeof value !== "object") return String(value);
|
|
91
|
+
if (value.kind === "ModelValue") return toJsonString(value.name);
|
|
92
|
+
if (isSetValue(value)) return toSetJson(value, method);
|
|
93
|
+
return toFunctionJson(value, method);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const toJsonArray = (value: Value) => {
|
|
97
|
+
if (isSetValue(value)) return toSetJson(value, TO_JSON_ARRAY);
|
|
98
|
+
if (!isFunctionValue(value) || isRecord(value))
|
|
99
|
+
throw createUnsupportedError(TO_JSON_ARRAY, value);
|
|
100
|
+
return toFunctionJson(value, TO_JSON_ARRAY);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const toJsonObject = (value: Value) => {
|
|
104
|
+
if (!isFunctionValue(value)) throw createUnsupportedError(TO_JSON_OBJECT, value);
|
|
105
|
+
return toFunctionJson(value, TO_JSON_OBJECT);
|
|
106
|
+
};
|
|
56
107
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
108
|
+
// TLC reads a JSON number with Gson's intValue, which truncates the exact decimal toward zero and
|
|
109
|
+
// keeps its low 32 bits, so 3000000000 becomes -1294967296 and 1.5 becomes 1.
|
|
110
|
+
const toInteger = (source: string) => {
|
|
111
|
+
const [, sign, whole = "0", fraction = "", exponent = "0"] =
|
|
112
|
+
/^(-?)(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(source) ?? [];
|
|
113
|
+
const digits = BigInt(whole + fraction);
|
|
114
|
+
const shift = Number(exponent) - fraction.length;
|
|
115
|
+
const magnitude = shift >= 0 ? digits * 10n ** BigInt(shift) : digits / 10n ** BigInt(-shift);
|
|
116
|
+
return Number(BigInt.asIntN(32, sign === "-" ? -magnitude : magnitude));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const fromJson = (json: unknown, method: string): Value => {
|
|
120
|
+
if (typeof json === "boolean" || typeof json === "number") return json;
|
|
121
|
+
if (typeof json === "string") return rankString(json);
|
|
122
|
+
if (Array.isArray(json)) return createTuple(json.map((element) => fromJson(element, method)));
|
|
61
123
|
if (json !== null && typeof json === "object") {
|
|
62
124
|
return createRecord(
|
|
63
|
-
Object.entries(json).map(([key, field]): [string, Value] => [
|
|
125
|
+
Object.entries(json).map(([key, field]): [string, Value] => [
|
|
126
|
+
rankString(key),
|
|
127
|
+
fromJson(field, method),
|
|
128
|
+
]),
|
|
64
129
|
);
|
|
65
130
|
}
|
|
66
|
-
throw
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (typeof value !== "string") {
|
|
71
|
-
throw new TlaEvaluationError(`${name} expects a file name but received ${formatValue(value)}`);
|
|
72
|
-
}
|
|
73
|
-
return value;
|
|
131
|
+
throw createJavaOverrideError(
|
|
132
|
+
method,
|
|
133
|
+
`Cannot convert value: unsupported JSON value ${String(json)}`,
|
|
134
|
+
);
|
|
74
135
|
};
|
|
75
136
|
|
|
76
|
-
const parseJson = (text: string
|
|
137
|
+
const parseJson = (text: string, method: string) => {
|
|
138
|
+
if (text.trim() === "") return fromJson(null, method);
|
|
77
139
|
try {
|
|
78
|
-
return
|
|
140
|
+
return fromJson(
|
|
141
|
+
JSON.parse(text, (_, value: unknown, context?: ParseContext) =>
|
|
142
|
+
typeof value === "number" ? toInteger(context?.source ?? String(value)) : value,
|
|
143
|
+
),
|
|
144
|
+
method,
|
|
145
|
+
);
|
|
79
146
|
} catch (error) {
|
|
80
|
-
|
|
147
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
148
|
+
throw createJavaOverrideError(method, error.message);
|
|
81
149
|
}
|
|
82
150
|
};
|
|
83
151
|
|
|
84
|
-
const
|
|
152
|
+
const getPath = (value: Value, method: string) => {
|
|
153
|
+
if (typeof value !== "string") throw createCastError(method, value);
|
|
154
|
+
return value;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const readText = (files: FileSystem, path: string, method: string) => {
|
|
85
158
|
const text = files.readFile?.(path);
|
|
86
|
-
if (text === undefined)
|
|
159
|
+
if (text === undefined) {
|
|
160
|
+
throw createJavaOverrideError(method, `${path} (No such file or directory)`);
|
|
161
|
+
}
|
|
87
162
|
return text;
|
|
88
163
|
};
|
|
89
164
|
|
|
90
165
|
export const createJsonOperators = (files: FileSystem): Builtin[] => [
|
|
91
|
-
defineStateOperator("ToJson", 1, (args) =>
|
|
166
|
+
defineStateOperator("ToJson", 1, (args) =>
|
|
167
|
+
rankString(toJson(getValue(args, 0, "ToJson"), TO_JSON)),
|
|
168
|
+
),
|
|
92
169
|
defineStateOperator("ToJsonArray", 1, (args) =>
|
|
93
|
-
|
|
170
|
+
rankString(toJsonArray(getValue(args, 0, "ToJsonArray"))),
|
|
171
|
+
),
|
|
172
|
+
defineStateOperator("ToJsonObject", 1, (args) =>
|
|
173
|
+
rankString(toJsonObject(getValue(args, 0, "ToJsonObject"))),
|
|
94
174
|
),
|
|
95
|
-
defineStateOperator("ToJsonObject", 1, (args) => {
|
|
96
|
-
const value = getValue(args, 0, "ToJsonObject");
|
|
97
|
-
return serialize(getSequenceElements(value) ? toArray(value) : toObject(value));
|
|
98
|
-
}),
|
|
99
175
|
defineStateOperator("JsonSerialize", 2, (args) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
const path = getPath(getValue(args, 0, "JsonSerialize"), SERIALIZE);
|
|
177
|
+
const value = getValue(args, 1, "JsonSerialize");
|
|
178
|
+
if (!getSequenceElements(value) && !isRecord(value)) {
|
|
179
|
+
throw new TlaModuleError(
|
|
180
|
+
`The second argument of JsonSerialize should be a sequence or record, but instead it is:\n${formatValue(value)}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
files.writeFile?.(path, toJson(value, SERIALIZE));
|
|
104
184
|
return true;
|
|
105
185
|
}),
|
|
106
186
|
defineStateOperator("ndJsonSerialize", 2, (args) => {
|
|
187
|
+
const path = getPath(getValue(args, 0, "ndJsonSerialize"), ND_SERIALIZE);
|
|
107
188
|
const value = getValue(args, 1, "ndJsonSerialize");
|
|
108
189
|
const elements = getSequenceElements(value);
|
|
109
190
|
if (!elements) {
|
|
110
|
-
throw new
|
|
191
|
+
throw new TlaModuleError(
|
|
111
192
|
`The second argument of ndJsonSerialize should be a sequence, but instead it is:\n${formatValue(value)}`,
|
|
112
193
|
);
|
|
113
194
|
}
|
|
114
195
|
files.writeFile?.(
|
|
115
|
-
|
|
116
|
-
elements.map((element) => `${
|
|
196
|
+
path,
|
|
197
|
+
elements.map((element) => `${toJson(element, ND_SERIALIZE)}\n`).join(""),
|
|
117
198
|
);
|
|
118
199
|
return true;
|
|
119
200
|
}),
|
|
120
|
-
defineStateOperator("JsonDeserialize", 1, (args) =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
createTuple(
|
|
127
|
-
readText(files,
|
|
128
|
-
.split(
|
|
201
|
+
defineStateOperator("JsonDeserialize", 1, (args) => {
|
|
202
|
+
const path = getPath(getValue(args, 0, "JsonDeserialize"), DESERIALIZE);
|
|
203
|
+
return parseJson(readText(files, path, DESERIALIZE), DESERIALIZE);
|
|
204
|
+
}),
|
|
205
|
+
defineStateOperator("ndJsonDeserialize", 1, (args) => {
|
|
206
|
+
const path = getPath(getValue(args, 0, "ndJsonDeserialize"), ND_DESERIALIZE);
|
|
207
|
+
return createTuple(
|
|
208
|
+
readText(files, path, ND_DESERIALIZE)
|
|
209
|
+
.split(/\r\n|\r|\n/)
|
|
129
210
|
.map((line) => line.trim())
|
|
130
211
|
.filter((line) => line !== "")
|
|
131
|
-
.map((line) =>
|
|
132
|
-
)
|
|
133
|
-
),
|
|
212
|
+
.map((line) => parseJson(line, ND_DESERIALIZE)),
|
|
213
|
+
);
|
|
214
|
+
}),
|
|
134
215
|
];
|