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.
Files changed (71) hide show
  1. package/README.md +81 -42
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +215 -30
  4. package/dist/index.d.ts +215 -30
  5. package/dist/index.js +1 -1
  6. package/dist/parallel-worker.cjs +2 -2
  7. package/dist/parallel-worker.js +2 -2
  8. package/dist/parallel.cjs +1 -1
  9. package/dist/parallel.js +2 -2
  10. package/dist/run-cli.cjs +20 -1
  11. package/dist/run-cli.js +20 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +6 -2
  15. package/src/ast.ts +122 -5
  16. package/src/bags.ts +161 -71
  17. package/src/builtins.ts +203 -23
  18. package/src/bundled-modules.ts +63 -32
  19. package/src/check.ts +945 -238
  20. package/src/cli-options.ts +58 -0
  21. package/src/codegen.ts +52 -18
  22. package/src/config.ts +39 -11
  23. package/src/environment.ts +485 -140
  24. package/src/errors.ts +59 -1
  25. package/src/evaluate.ts +489 -137
  26. package/src/exit-codes.ts +75 -0
  27. package/src/fingerprints.ts +2 -2
  28. package/src/index.ts +9 -4
  29. package/src/instances.ts +221 -66
  30. package/src/java-overrides.ts +241 -0
  31. package/src/json.ts +153 -72
  32. package/src/levels.ts +850 -0
  33. package/src/lexer.ts +103 -28
  34. package/src/liveness.ts +47 -91
  35. package/src/obligation-action.ts +384 -0
  36. package/src/obligation-anon.ts +575 -0
  37. package/src/obligation-deref.ts +637 -0
  38. package/src/obligation-expression.ts +1060 -0
  39. package/src/obligation-format.ts +406 -0
  40. package/src/obligation-generate.ts +1074 -0
  41. package/src/obligation-levels.ts +329 -0
  42. package/src/obligation-module.ts +1042 -0
  43. package/src/obligation-printer.ts +889 -0
  44. package/src/obligation-proof.ts +76 -0
  45. package/src/obligation-source.ts +1084 -0
  46. package/src/obligation-tuples.ts +184 -0
  47. package/src/obligations.ts +316 -0
  48. package/src/parallel-worker.ts +57 -28
  49. package/src/parallel.ts +116 -62
  50. package/src/parser.ts +958 -205
  51. package/src/pretty-print.ts +259 -0
  52. package/src/randomization.ts +24 -113
  53. package/src/resolve.ts +472 -41
  54. package/src/run-cli.ts +427 -61
  55. package/src/runtime.ts +63 -28
  56. package/src/spec-description.ts +509 -0
  57. package/src/standard-levels.ts +105 -0
  58. package/src/standard-modules.ts +223 -92
  59. package/src/subexpressions.ts +21 -35
  60. package/src/substitution-levels.ts +184 -0
  61. package/src/successors.ts +261 -46
  62. package/src/symmetry.ts +65 -21
  63. package/src/tableau.ts +43 -37
  64. package/src/tlc-ext.ts +120 -33
  65. package/src/tlc-module.ts +71 -47
  66. package/src/tlc-modules.ts +1022 -0
  67. package/src/tlc-order.ts +497 -0
  68. package/src/tlc-random.ts +411 -0
  69. package/src/tlc-summary.ts +109 -0
  70. package/src/transfer.ts +26 -5
  71. 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 { defineStateOperator, getValue } from "./builtins";
2
- import { TlaEvaluationError } from "./errors";
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
- type JsonValue = boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
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 toArray = (value: Value): JsonValue[] => {
25
- if (isSetValue(value)) return toFiniteSet(value).elements.map(toJson);
26
- const elements = getSequenceElements(value);
27
- if (elements) return elements.map(toJson);
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 toObject = (value: Value): { [key: string]: JsonValue } => {
32
- if (!isFunctionValue(value)) {
33
- throw new TlaEvaluationError(`Cannot convert ${formatValue(value)} to a JSON object`);
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 toJson = (value: Value): JsonValue => {
45
- if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
46
- return value;
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
- if (value.kind === "ModelValue") return value.name;
49
- if (isSetValue(value)) return toArray(value);
50
- if (getSequenceElements(value)) return toArray(value);
51
- return toObject(value);
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 serialize = (json: JsonValue) =>
55
- JSON.stringify(json).replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
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
- const fromJson = (json: unknown): Value => {
58
- if (typeof json === "boolean" || typeof json === "string") return json;
59
- if (typeof json === "number") return checkInteger(Math.trunc(json));
60
- if (Array.isArray(json)) return createTuple(json.map(fromJson));
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] => [key, fromJson(field)]),
125
+ Object.entries(json).map(([key, field]): [string, Value] => [
126
+ rankString(key),
127
+ fromJson(field, method),
128
+ ]),
64
129
  );
65
130
  }
66
- throw new TlaEvaluationError(`Cannot convert value: unsupported JSON value ${String(json)}`);
67
- };
68
-
69
- const getPath = (value: Value, name: string) => {
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): unknown => {
137
+ const parseJson = (text: string, method: string) => {
138
+ if (text.trim() === "") return fromJson(null, method);
77
139
  try {
78
- return JSON.parse(text);
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
- throw new TlaEvaluationError(`Cannot parse JSON: ${text}`, error);
147
+ if (!(error instanceof SyntaxError)) throw error;
148
+ throw createJavaOverrideError(method, error.message);
81
149
  }
82
150
  };
83
151
 
84
- const readText = (files: FileSystem, path: string) => {
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) throw new TlaEvaluationError(`Cannot read the file ${path}`);
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) => serialize(toJson(getValue(args, 0, "ToJson")))),
166
+ defineStateOperator("ToJson", 1, (args) =>
167
+ rankString(toJson(getValue(args, 0, "ToJson"), TO_JSON)),
168
+ ),
92
169
  defineStateOperator("ToJsonArray", 1, (args) =>
93
- serialize(toArray(getValue(args, 0, "ToJsonArray"))),
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
- files.writeFile?.(
101
- getPath(getValue(args, 0, "JsonSerialize"), "JsonSerialize"),
102
- serialize(toJson(getValue(args, 1, "JsonSerialize"))),
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 TlaEvaluationError(
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
- getPath(getValue(args, 0, "ndJsonSerialize"), "ndJsonSerialize"),
116
- elements.map((element) => `${serialize(toJson(element))}\n`).join(""),
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
- fromJson(
122
- parseJson(readText(files, getPath(getValue(args, 0, "JsonDeserialize"), "JsonDeserialize"))),
123
- ),
124
- ),
125
- defineStateOperator("ndJsonDeserialize", 1, (args) =>
126
- createTuple(
127
- readText(files, getPath(getValue(args, 0, "ndJsonDeserialize"), "ndJsonDeserialize"))
128
- .split("\n")
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) => fromJson(parseJson(line))),
132
- ),
133
- ),
212
+ .map((line) => parseJson(line, ND_DESERIALIZE)),
213
+ );
214
+ }),
134
215
  ];