spyret 0.1.0
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/LICENSE +21 -0
- package/README.md +81 -0
- package/THIRD_PARTY_NOTICES.txt +84 -0
- package/dist/spyret.d.mts +746 -0
- package/dist/spyret.d.ts +746 -0
- package/dist/spyret.global.js +14 -0
- package/dist/spyret.js +14 -0
- package/dist/spyret.mjs +14 -0
- package/docs/PYRET_CAPTURE.md +137 -0
- package/docs/RELEASING.md +69 -0
- package/package.json +67 -0
package/dist/spyret.d.ts
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
import { Graph } from 'graphlib';
|
|
2
|
+
|
|
3
|
+
type DataInstanceEventType = 'atomAdded' | 'atomRemoved' | 'relationTupleAdded' | 'relationTupleRemoved';
|
|
4
|
+
interface DataInstanceEvent {
|
|
5
|
+
type: DataInstanceEventType;
|
|
6
|
+
data: {
|
|
7
|
+
atom?: IAtom;
|
|
8
|
+
atomId?: string;
|
|
9
|
+
relationId?: string;
|
|
10
|
+
tuple?: ITuple;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
type DataInstanceEventListener = (event: DataInstanceEvent) => void;
|
|
14
|
+
interface IAtom {
|
|
15
|
+
id: string;
|
|
16
|
+
type: string;
|
|
17
|
+
label: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional key-value labels associated with this atom.
|
|
20
|
+
* Used for language-specific metadata that should be displayed prominently on nodes
|
|
21
|
+
* (e.g., Skolems in Alloy, annotations in other languages).
|
|
22
|
+
* These labels are styled differently from regular attributes - typically in the node's color.
|
|
23
|
+
*/
|
|
24
|
+
labels?: Record<string, string[]>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One tuple in a relation.
|
|
28
|
+
*
|
|
29
|
+
* A tuple owns its own arity: `atoms.length` is the truth, and `types` is
|
|
30
|
+
* this tuple's signature, one entry per atom. A relation may hold tuples of
|
|
31
|
+
* different arity (see {@link IRelation}), so nothing may read one tuple's
|
|
32
|
+
* arity as the arity of the relation around it.
|
|
33
|
+
*/
|
|
34
|
+
interface ITuple {
|
|
35
|
+
atoms: string[];
|
|
36
|
+
types: string[];
|
|
37
|
+
}
|
|
38
|
+
interface IType {
|
|
39
|
+
id: string;
|
|
40
|
+
types: string[];
|
|
41
|
+
atoms: IAtom[];
|
|
42
|
+
isBuiltin: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A named set of tuples.
|
|
46
|
+
*
|
|
47
|
+
* Relations are RAGGED-TOLERANT: the tuples need not all have the same arity.
|
|
48
|
+
* Host languages hand us this routinely — two unrelated Python classes can both
|
|
49
|
+
* have a `foo` field, one holding pairs and one holding triples, and both are
|
|
50
|
+
* the relation `foo`. Alloy reaches the same place from the other side: two
|
|
51
|
+
* sigs may each declare a field `foo`, and their ids differ (`A<:foo`,
|
|
52
|
+
* `B<:foo`) while the name does not.
|
|
53
|
+
*
|
|
54
|
+
* Records with distinct IDs stay distinct in storage. The name is what
|
|
55
|
+
* selectors see: a name denotes the set union of all matching records.
|
|
56
|
+
* IDs are preserved for host reconstruction and exact-ID mutation. Rendering, the
|
|
57
|
+
* evaluators and the constraint layer all work tuple by tuple, so a ragged
|
|
58
|
+
* relation draws and queries correctly.
|
|
59
|
+
*/
|
|
60
|
+
interface IRelation {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
/**
|
|
64
|
+
* A SUMMARY of the tuples' column types, one entry per column, positional.
|
|
65
|
+
*
|
|
66
|
+
* It is only meaningful when every tuple has the same arity. When the tuples
|
|
67
|
+
* disagree — a ragged relation — no positional list can describe them, and
|
|
68
|
+
* this is `[]`.
|
|
69
|
+
*
|
|
70
|
+
* So `types.length` is NOT the relation's arity, and must never be read as
|
|
71
|
+
* one: on a ragged relation it is 0 no matter how wide the tuples are. Arity
|
|
72
|
+
* lives on the tuple (`ITuple.atoms.length`).
|
|
73
|
+
*/
|
|
74
|
+
types: string[];
|
|
75
|
+
tuples: ITuple[];
|
|
76
|
+
}
|
|
77
|
+
interface IDataInstance {
|
|
78
|
+
getAtomType(id: string): IType;
|
|
79
|
+
getTypes(): readonly IType[];
|
|
80
|
+
getAtoms(): readonly IAtom[];
|
|
81
|
+
getRelations(): readonly IRelation[];
|
|
82
|
+
generateGraph(hideDisconnected: boolean, hideDisconnectedBuiltIns: boolean): Graph;
|
|
83
|
+
}
|
|
84
|
+
interface IInputDataInstance extends IDataInstance {
|
|
85
|
+
addAtom(atom: IAtom): void;
|
|
86
|
+
addRelationTuple(relationId: string, t: ITuple): void;
|
|
87
|
+
removeAtom(id: string): void;
|
|
88
|
+
removeRelationTuple(relationId: string, t: ITuple): void;
|
|
89
|
+
addEventListener(type: DataInstanceEventType, listener: DataInstanceEventListener): void;
|
|
90
|
+
removeEventListener(type: DataInstanceEventType, listener: DataInstanceEventListener): void;
|
|
91
|
+
reify(): unknown;
|
|
92
|
+
/**
|
|
93
|
+
* Adds atoms and relations from another data instance to this one.
|
|
94
|
+
* @param dataInstance The data instance to add atoms and relations from.
|
|
95
|
+
* @param unifyBuiltIns If true, values of built-in types will be unified with existing ones.
|
|
96
|
+
* @returns true if the data instance was added successfully, false if there were conflicts.
|
|
97
|
+
*/
|
|
98
|
+
addFromDataInstance(dataInstance: IDataInstance, unifyBuiltIns: boolean): boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Spyret's data view of a validated capture, implementing Core's public contract. */
|
|
102
|
+
declare class CapturedDataInstance implements IDataInstance {
|
|
103
|
+
private datum;
|
|
104
|
+
private atoms;
|
|
105
|
+
private types;
|
|
106
|
+
constructor(datum: {
|
|
107
|
+
atoms: IAtom[];
|
|
108
|
+
relations: IRelation[];
|
|
109
|
+
types: IType[];
|
|
110
|
+
});
|
|
111
|
+
getAtoms(): readonly IAtom[];
|
|
112
|
+
getRelations(): readonly IRelation[];
|
|
113
|
+
getTypes(): readonly IType[];
|
|
114
|
+
getAtomType(id: string): IType;
|
|
115
|
+
generateGraph(hideDisconnected?: boolean, hideDisconnectedBuiltIns?: boolean): Graph;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Event plumbing for Spyret's mutable Pyret data instance.
|
|
120
|
+
*
|
|
121
|
+
* `emitEvent` is protected: only the instance itself decides when a change
|
|
122
|
+
* happened. A listener that throws is logged and skipped so one bad listener
|
|
123
|
+
* cannot starve the others.
|
|
124
|
+
*/
|
|
125
|
+
declare abstract class DataInstanceEventEmitter {
|
|
126
|
+
private eventListeners;
|
|
127
|
+
/**
|
|
128
|
+
* Add an event listener for data instance changes
|
|
129
|
+
*/
|
|
130
|
+
addEventListener(type: DataInstanceEventType, listener: DataInstanceEventListener): void;
|
|
131
|
+
/**
|
|
132
|
+
* Remove an event listener for data instance changes
|
|
133
|
+
*/
|
|
134
|
+
removeEventListener(type: DataInstanceEventType, listener: DataInstanceEventListener): void;
|
|
135
|
+
/**
|
|
136
|
+
* Emit an event to all registered listeners
|
|
137
|
+
*/
|
|
138
|
+
protected emitEvent(event: DataInstanceEvent): void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Configuration options for primitive value idempotency in PyretDataInstance
|
|
143
|
+
*/
|
|
144
|
+
interface PyretInstanceOptions {
|
|
145
|
+
/** Whether to make string values idempotent (reuse atoms for same string values) */
|
|
146
|
+
stringsIdempotent?: boolean;
|
|
147
|
+
/** Whether to make number values idempotent (reuse atoms for same number values) */
|
|
148
|
+
numbersIdempotent?: boolean;
|
|
149
|
+
/** Whether to make boolean values idempotent (reuse atoms for same boolean values) */
|
|
150
|
+
booleansIdempotent?: boolean;
|
|
151
|
+
/** Whether to include function/method fields in parsing */
|
|
152
|
+
showFunctions?: boolean;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Result of evaluating a Pyret expression
|
|
156
|
+
*/
|
|
157
|
+
interface PyretEvaluationResult {
|
|
158
|
+
/** The raw Pyret JS value (if successful) */
|
|
159
|
+
result?: unknown;
|
|
160
|
+
/** Exception information (if failed) */
|
|
161
|
+
exn?: unknown;
|
|
162
|
+
/** Whether the evaluation was successful */
|
|
163
|
+
success?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* An external Pyret evaluator — in practice `window.__internalRepl`, which the
|
|
167
|
+
* Pyret IDE installs.
|
|
168
|
+
*
|
|
169
|
+
* This lived in the REPL's expression parser until that component was removed,
|
|
170
|
+
* but it was never a REPL type: it describes the runtime a `PyretDataInstance`
|
|
171
|
+
* evaluates against, which is why `fromExpression` and `setExternalEvaluator`
|
|
172
|
+
* take one. It sits here now, next to the result type it returns — which this
|
|
173
|
+
* file had already redeclared privately rather than import across that
|
|
174
|
+
* boundary.
|
|
175
|
+
*/
|
|
176
|
+
interface PyretEvaluator {
|
|
177
|
+
/**
|
|
178
|
+
* Run a Pyret expression and return the result
|
|
179
|
+
* @param code - Pyret code to evaluate
|
|
180
|
+
* @param sourceLocation - Optional source location identifier
|
|
181
|
+
* @returns Promise that resolves to evaluation result
|
|
182
|
+
*/
|
|
183
|
+
run(code: string, sourceLocation?: string): Promise<PyretEvaluationResult>;
|
|
184
|
+
/**
|
|
185
|
+
* Runtime utilities for checking result types
|
|
186
|
+
*/
|
|
187
|
+
runtime: {
|
|
188
|
+
isSuccessResult(result: PyretEvaluationResult): boolean;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Global constructor cache entry with pattern and instantiation priority */
|
|
192
|
+
interface ConstructorCacheEntry {
|
|
193
|
+
pattern: string[];
|
|
194
|
+
instantiation: number;
|
|
195
|
+
}
|
|
196
|
+
declare function generateEdgeId(relation: IRelation, tuple: ITuple): string;
|
|
197
|
+
/**
|
|
198
|
+
* Pyret data instance implementation for parsing Pyret runtime objects
|
|
199
|
+
*
|
|
200
|
+
* Handles Pyret's object representation where:
|
|
201
|
+
* - Objects have a `dict` property containing field values
|
|
202
|
+
* - Objects have a `brands` property indicating their type
|
|
203
|
+
* - All dict entries are treated as relations
|
|
204
|
+
* - Pyret tables are parsed as semantic relations: each row becomes an n-ary tuple
|
|
205
|
+
* - Non-table arrays are parsed as relations with Array atoms
|
|
206
|
+
* - Nested arrays are supported with intermediate Array atoms
|
|
207
|
+
* - Cycles are handled gracefully without infinite recursion
|
|
208
|
+
* - Primitive idempotency is configurable via constructor options
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* // Tree data
|
|
213
|
+
* const pyretData = {
|
|
214
|
+
* dict: { value: 11, left: {...}, right: {...} },
|
|
215
|
+
* brands: { "$brandtnode989": true }
|
|
216
|
+
* };
|
|
217
|
+
* const instance1 = new PyretDataInstance(pyretData);
|
|
218
|
+
*
|
|
219
|
+
* // Table data - creates semantic relational tuples
|
|
220
|
+
* const tableData = {
|
|
221
|
+
* dict: {
|
|
222
|
+
* r: {
|
|
223
|
+
* dict: {
|
|
224
|
+
* "_header-raw-array": ["origin", "destination"],
|
|
225
|
+
* "_rows-raw-array": [["PVD", "ORD"], ["ORD", "PVD"]]
|
|
226
|
+
* },
|
|
227
|
+
* brands: { "$brandtable168": true }
|
|
228
|
+
* }
|
|
229
|
+
* }
|
|
230
|
+
* };
|
|
231
|
+
* const instance2 = new PyretDataInstance(tableData);
|
|
232
|
+
* // Creates column(table, index, name) and row(table, index, ...cells).
|
|
233
|
+
* // Project with Index.(Table.row) for the cell-only (PVD, ORD), (ORD, PVD) view.
|
|
234
|
+
*
|
|
235
|
+
* // Custom idempotency settings
|
|
236
|
+
* const instance3 = new PyretDataInstance(pyretData, {
|
|
237
|
+
* stringsIdempotent: false, // Different string instances won't be unified
|
|
238
|
+
* numbersIdempotent: true, // Same numbers will be unified
|
|
239
|
+
* booleansIdempotent: true // Same booleans will be unified
|
|
240
|
+
* });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
declare class PyretDataInstance extends DataInstanceEventEmitter implements IInputDataInstance {
|
|
244
|
+
/** Capture normalized roots together so aliases across roots retain one atom. */
|
|
245
|
+
static fromValues(values: readonly unknown[]): {
|
|
246
|
+
instance: PyretDataInstance;
|
|
247
|
+
rootIds: string[];
|
|
248
|
+
};
|
|
249
|
+
private atoms;
|
|
250
|
+
private relations;
|
|
251
|
+
private types;
|
|
252
|
+
private objectToAtomId;
|
|
253
|
+
private atomCounter;
|
|
254
|
+
/** Opaque relation identities; their names and tuples carry the semantics. */
|
|
255
|
+
private valueRelations;
|
|
256
|
+
private indexAtoms;
|
|
257
|
+
/** Map to keep track of label counts per type */
|
|
258
|
+
private typeLabelCounters;
|
|
259
|
+
/** Map to store the original Pyret objects with their dict key order */
|
|
260
|
+
private originalObjects;
|
|
261
|
+
/** Configuration options for primitive handling */
|
|
262
|
+
private readonly options;
|
|
263
|
+
/** Global map to store constructor patterns and field order for types across all instances */
|
|
264
|
+
private static globalConstructorCache;
|
|
265
|
+
/** Global counter for instantiation priority - higher numbers mean newer/higher priority */
|
|
266
|
+
private static instantiationCounter;
|
|
267
|
+
/** Optional external Pyret evaluator for enhanced features */
|
|
268
|
+
private externalEvaluator;
|
|
269
|
+
/**
|
|
270
|
+
* Creates a PyretDataInstance from a Pyret runtime object
|
|
271
|
+
*
|
|
272
|
+
* @param pyretData - The root Pyret object to parse, or null/undefined for an empty instance
|
|
273
|
+
* @param options - Configuration options for primitive handling and other behaviors
|
|
274
|
+
* @param externalEvaluator - Optional external Pyret evaluator for enhanced features
|
|
275
|
+
*/
|
|
276
|
+
constructor(pyretData?: PyretObject | unknown[] | number | string | boolean | null, options?: PyretInstanceOptions, externalEvaluator?: any);
|
|
277
|
+
/**
|
|
278
|
+
* Set an external Pyret evaluator for enhanced features
|
|
279
|
+
* @param evaluator - External Pyret evaluator (e.g., window.__internalRepl)
|
|
280
|
+
*/
|
|
281
|
+
setExternalEvaluator(evaluator: any): void;
|
|
282
|
+
/**
|
|
283
|
+
* Get the current external evaluator
|
|
284
|
+
*/
|
|
285
|
+
getExternalEvaluator(): any | null;
|
|
286
|
+
/**
|
|
287
|
+
* Get the current primitive idempotency configuration
|
|
288
|
+
*/
|
|
289
|
+
getOptions(): Required<PyretInstanceOptions>;
|
|
290
|
+
/**
|
|
291
|
+
* Cache constructor field order for a type when we successfully parse an original object
|
|
292
|
+
* This now uses a global cache with instantiation-based priority where newer patterns
|
|
293
|
+
* can override older ones for the same constructor name
|
|
294
|
+
*/
|
|
295
|
+
private cacheConstructorPattern;
|
|
296
|
+
/**
|
|
297
|
+
* Get the global constructor cache (for debugging or advanced use cases)
|
|
298
|
+
* Returns a map of type names to their patterns
|
|
299
|
+
*/
|
|
300
|
+
static getGlobalConstructorCache(): Map<string, string[]>;
|
|
301
|
+
/**
|
|
302
|
+
* Get the global constructor cache with instantiation info (for debugging)
|
|
303
|
+
* Returns the raw cache with instantiation numbers
|
|
304
|
+
*/
|
|
305
|
+
static getGlobalConstructorCacheWithPriority(): Map<string, ConstructorCacheEntry>;
|
|
306
|
+
/**
|
|
307
|
+
* Clear the global constructor cache (for testing or reset scenarios)
|
|
308
|
+
*/
|
|
309
|
+
static clearGlobalConstructorCache(): void;
|
|
310
|
+
/**
|
|
311
|
+
* Creates a PyretDataInstance from a Pyret expression.
|
|
312
|
+
*
|
|
313
|
+
* @param expr - The Pyret expression to evaluate.
|
|
314
|
+
* @param options - Configuration options for primitive handling and other behaviors
|
|
315
|
+
* @param externalEvaluator - External Pyret evaluator with a `run` method for enhanced features.
|
|
316
|
+
* @returns A new PyretDataInstance created from the evaluated expression.
|
|
317
|
+
* @throws {Error} If the expression cannot be evaluated or parsed.
|
|
318
|
+
*/
|
|
319
|
+
static fromExpression(expr: string, options: PyretInstanceOptions | undefined, externalEvaluator: {
|
|
320
|
+
run: (code: string) => Promise<unknown>;
|
|
321
|
+
}): Promise<PyretDataInstance>;
|
|
322
|
+
/**
|
|
323
|
+
* Evaluates a Pyret expression using an external evaluator
|
|
324
|
+
*
|
|
325
|
+
* @param expr - The Pyret expression to evaluate
|
|
326
|
+
* @param externalEvaluator - External Pyret evaluator with a `run` method
|
|
327
|
+
* @returns Promise resolving to evaluation result
|
|
328
|
+
*/
|
|
329
|
+
private static evaluateExpression;
|
|
330
|
+
/**
|
|
331
|
+
* Recursively searches for a key at any level in an object
|
|
332
|
+
*/
|
|
333
|
+
private static findKeyAtAnyLevel;
|
|
334
|
+
/**
|
|
335
|
+
* Checks if a value is a primitive type (string, number, boolean)
|
|
336
|
+
*/
|
|
337
|
+
private static isPrimitive;
|
|
338
|
+
/**
|
|
339
|
+
* Format Pyret evaluation errors for display
|
|
340
|
+
*/
|
|
341
|
+
private static formatError;
|
|
342
|
+
hasExternalEvaluator(): boolean;
|
|
343
|
+
/**
|
|
344
|
+
* Adds an atom to the instance, updating types accordingly.
|
|
345
|
+
* If the atom already exists, it is replaced.
|
|
346
|
+
* @param atom - The atom to add
|
|
347
|
+
*/
|
|
348
|
+
addAtom(atom: IAtom): void;
|
|
349
|
+
/**
|
|
350
|
+
* Removes an atom by id, and removes it from all types and relations.
|
|
351
|
+
* @param id - The atom id to remove
|
|
352
|
+
*/
|
|
353
|
+
removeAtom(id: string): void;
|
|
354
|
+
removeRelationTuple(relationId: string, t: ITuple): void;
|
|
355
|
+
/**
|
|
356
|
+
* Converts the current data instance back to Pyret constructor notation
|
|
357
|
+
*
|
|
358
|
+
* This is the REPL-equivalent string form: the value is first reconstructed
|
|
359
|
+
* from the relations (`reifyToValue`, which memoizes by atom id so a shared
|
|
360
|
+
* atom becomes one shared object and a cycle becomes a real back-reference),
|
|
361
|
+
* then rendered (`replit`). Supported reference graphs emit bindings and
|
|
362
|
+
* mutable-field updates. The legacy ref-free path repeats shared subtrees
|
|
363
|
+
* and prints a `<cyclic>` marker for synthetic object cycles.
|
|
364
|
+
*
|
|
365
|
+
* @param rootId Atom to reconstruct. Required when no unique root can be inferred,
|
|
366
|
+
* including a fully cyclic graph. The datum does not store an observation point.
|
|
367
|
+
* @returns A string representation of the data in Pyret constructor syntax
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* const pyretCode = instance.reify();
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
reify(rootId?: string): string;
|
|
375
|
+
/**
|
|
376
|
+
* Parses Pyret objects iteratively to avoid stack overflow and handle cycles
|
|
377
|
+
*/
|
|
378
|
+
private parseObjectIteratively;
|
|
379
|
+
private addValueFact;
|
|
380
|
+
private valueRelationId;
|
|
381
|
+
private createIndexAtom;
|
|
382
|
+
private valueInfo;
|
|
383
|
+
/** One ordered header relation and one row relation per width; IDs stay opaque. */
|
|
384
|
+
private processTableSemantics;
|
|
385
|
+
/**
|
|
386
|
+
* Creates an atom from a Pyret object and stores the mapping
|
|
387
|
+
*/
|
|
388
|
+
private createAtomFromObject;
|
|
389
|
+
/**
|
|
390
|
+
* Creates an atom from a primitive value, optionally reusing existing atoms based on configuration
|
|
391
|
+
*/
|
|
392
|
+
private createAtomFromPrimitive;
|
|
393
|
+
/**
|
|
394
|
+
* Maps JavaScript primitive types to Pyret-appropriate type names
|
|
395
|
+
*/
|
|
396
|
+
private mapPrimitiveType;
|
|
397
|
+
/**
|
|
398
|
+
* Extracts the most specific brand name from a Pyret brands object.
|
|
399
|
+
* Returns the brand with the highest trailing number, with prefix and number removed.
|
|
400
|
+
* If no brands have trailing numbers, returns the lexicographically last brand.
|
|
401
|
+
*
|
|
402
|
+
* @param brands - The brands object from a Pyret object
|
|
403
|
+
* @returns The most specific brand name (without $brand and trailing number), or undefined if none found
|
|
404
|
+
*/
|
|
405
|
+
private extractMostSpecificBrand;
|
|
406
|
+
/**
|
|
407
|
+
* Extracts the type name from a Pyret object
|
|
408
|
+
*/
|
|
409
|
+
private extractType;
|
|
410
|
+
/**
|
|
411
|
+
* Extracts a display label from a Pyret object, using a per-type counter.
|
|
412
|
+
* Labels will be of the form Type$<num>
|
|
413
|
+
*/
|
|
414
|
+
private extractLabel;
|
|
415
|
+
/**
|
|
416
|
+
* Adds a tuple to a relation, creating the relation if it doesn't exist
|
|
417
|
+
*/
|
|
418
|
+
addRelationTuple(relationId: string, tuple: ITuple, relationName?: string): void;
|
|
419
|
+
/**
|
|
420
|
+
* Ensures a type exists in the types map
|
|
421
|
+
*/
|
|
422
|
+
private ensureTypeExists;
|
|
423
|
+
/**
|
|
424
|
+
* Initializes common builtin types
|
|
425
|
+
*/
|
|
426
|
+
private initializeBuiltinTypes;
|
|
427
|
+
/**
|
|
428
|
+
* Checks if a type is a builtin type
|
|
429
|
+
*/
|
|
430
|
+
private isBuiltinType;
|
|
431
|
+
/** Primitive JS values, runtime numeric objects, and structural numeric carriers. */
|
|
432
|
+
private isAtomicValue;
|
|
433
|
+
/**
|
|
434
|
+
* Type guard for Pyret objects
|
|
435
|
+
*/
|
|
436
|
+
private isPyretObject;
|
|
437
|
+
/**
|
|
438
|
+
* Generates a unique atom ID
|
|
439
|
+
*/
|
|
440
|
+
private generateAtomId;
|
|
441
|
+
getAtoms(): readonly IAtom[];
|
|
442
|
+
getRelations(): readonly IRelation[];
|
|
443
|
+
getTypes(): readonly IType[];
|
|
444
|
+
getAtomType(atomId: string): IType;
|
|
445
|
+
/**
|
|
446
|
+
* Generates a graphlib Graph representation of this data instance.
|
|
447
|
+
*
|
|
448
|
+
* This method creates a directed multigraph where:
|
|
449
|
+
* - Each atom becomes a node with its label and type as metadata
|
|
450
|
+
* - Each relation tuple becomes an edge between atoms
|
|
451
|
+
* - Multi-atom tuples (arity > 2) are handled by connecting first to last atom
|
|
452
|
+
* - Disconnected nodes can be optionally filtered out
|
|
453
|
+
*
|
|
454
|
+
* @param hideDisconnected - Whether to hide atoms with no relations
|
|
455
|
+
* @param hideDisconnectedBuiltIns - Whether to hide disconnected built-in types
|
|
456
|
+
* @returns A graphlib Graph object ready for layout algorithms
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const graph = instance.generateGraph(true, true);
|
|
461
|
+
* // Use with WebCola or other layout algorithms
|
|
462
|
+
* const layout = new cola.Layout().nodes(graph.nodes()).edges(graph.edges());
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
generateGraph(hideDisconnected?: boolean, hideDisconnectedBuiltIns?: boolean): Graph;
|
|
466
|
+
/**
|
|
467
|
+
* Adds a PyretDataInstance to this instance, optionally unifying built-in types
|
|
468
|
+
*
|
|
469
|
+
* @param dataInstance - The PyretDataInstance to add
|
|
470
|
+
* @param unifyBuiltIns - Whether to unify built-in atoms
|
|
471
|
+
* @returns True if the instance was added successfully, false otherwise
|
|
472
|
+
*/
|
|
473
|
+
addFromDataInstance(dataInstance: IDataInstance, unifyBuiltIns: boolean): boolean;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Type definitions for Pyret runtime objects
|
|
477
|
+
*/
|
|
478
|
+
interface PyretObject {
|
|
479
|
+
dict?: Record<string, unknown>;
|
|
480
|
+
brands?: Record<string, boolean>;
|
|
481
|
+
$name?: string;
|
|
482
|
+
$loc?: unknown[];
|
|
483
|
+
$mut_fields_mask?: unknown[];
|
|
484
|
+
$arity?: number;
|
|
485
|
+
$constructor?: unknown;
|
|
486
|
+
[key: string]: unknown;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Factory function to create PyretDataInstance from JSON string
|
|
490
|
+
*
|
|
491
|
+
* @param jsonString - JSON representation of a Pyret object
|
|
492
|
+
* @param options - Configuration options for primitive handling and other behaviors
|
|
493
|
+
* @returns New PyretDataInstance
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* const jsonData = '{"dict": {"value": 42}, "brands": {"$brandleaf": true}}';
|
|
498
|
+
* const instance = createPyretDataInstance(jsonData, { stringsIdempotent: false });
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
declare const createPyretDataInstance: (jsonString: string, options?: PyretInstanceOptions) => PyretDataInstance;
|
|
502
|
+
/**
|
|
503
|
+
* Type guard to check if an IInputDataInstance is a PyretDataInstance
|
|
504
|
+
*
|
|
505
|
+
* @param instance - IInputDataInstance to check
|
|
506
|
+
* @returns True if the instance is a PyretDataInstance
|
|
507
|
+
*/
|
|
508
|
+
declare const isPyretDataInstance: (instance: IInputDataInstance) => instance is PyretDataInstance;
|
|
509
|
+
|
|
510
|
+
/** Internal lossless numbers. IDataInstance stores their literal in the atom label. */
|
|
511
|
+
type PyretNumberPayload = {
|
|
512
|
+
version: 1;
|
|
513
|
+
} & ({
|
|
514
|
+
kind: 'integer';
|
|
515
|
+
value: string;
|
|
516
|
+
} | {
|
|
517
|
+
kind: 'rational';
|
|
518
|
+
numerator: string;
|
|
519
|
+
denominator: string;
|
|
520
|
+
} | {
|
|
521
|
+
kind: 'roughnum';
|
|
522
|
+
value: string;
|
|
523
|
+
});
|
|
524
|
+
/** Structural reification uses this carrier without requiring a live runtime. */
|
|
525
|
+
interface ReifiedNumber {
|
|
526
|
+
$pyretNumber: PyretNumberPayload;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Structural reify for PyretDataInstance.
|
|
531
|
+
*
|
|
532
|
+
* This is the *inverse of relationalization*: given a data instance (atoms +
|
|
533
|
+
* relations only — NO live Pyret value, NO runtime), reconstruct a value.
|
|
534
|
+
*
|
|
535
|
+
* The key design choice (see the fidelity design notes): reify reconstructs a
|
|
536
|
+
* **synthetic `PyretObject`** — the exact `{ dict, brands/$name }` shape that
|
|
537
|
+
* `PyretDataInstance.parseObjectIteratively` already consumes. That makes the
|
|
538
|
+
* round-trip self-contained: we can feed the reified value straight back into
|
|
539
|
+
* `new PyretDataInstance(...)` and compare, with no Pyret runtime in the loop.
|
|
540
|
+
*
|
|
541
|
+
* Sharing and cycles are carried by **real JS object identity** in the
|
|
542
|
+
* reconstructed graph (a memo keyed by atom id), so a shared atom becomes one
|
|
543
|
+
* shared JS object and a cyclic atom becomes a real JS back-reference — exactly
|
|
544
|
+
* mirroring how the relationalizer's `WeakMap` captured them in the first place.
|
|
545
|
+
*
|
|
546
|
+
* NOTE: this is a *structural* reify (the analog of Python's live-object
|
|
547
|
+
* `reify`). The string form (the analog of Python's `repl`/`repr(reify(...))`)
|
|
548
|
+
* is `replit` in ./replit.ts.
|
|
549
|
+
*/
|
|
550
|
+
|
|
551
|
+
/** A reconstructed value, independent of a live Pyret runtime. */
|
|
552
|
+
type ReifiedValue = ReifiedNumber | PyretObject | ReifiedValue[] | number | string | boolean | null;
|
|
553
|
+
/**
|
|
554
|
+
* Reconstruct a value from a data instance.
|
|
555
|
+
*
|
|
556
|
+
* @param di the data instance (atoms + relations)
|
|
557
|
+
* @param rootId atom to reconstruct; otherwise requires exactly one atom with
|
|
558
|
+
* no incoming tuples. Cycles and multiple roots require this argument.
|
|
559
|
+
* @returns a synthetic, re-relationalizable value (PyretObject / array / primitive)
|
|
560
|
+
*/
|
|
561
|
+
declare function reifyToValue(di: IDataInstance, rootId?: string, options?: {
|
|
562
|
+
allowContainerCycles?: boolean;
|
|
563
|
+
}): ReifiedValue;
|
|
564
|
+
/** Reconstruct roots with a common identity memo, including cross-root aliases. */
|
|
565
|
+
declare function reifyToValues(di: IDataInstance, rootIds?: readonly string[], options?: {
|
|
566
|
+
allowContainerCycles?: boolean;
|
|
567
|
+
}): ReifiedValue[];
|
|
568
|
+
|
|
569
|
+
declare function readConstructorTypeId(id: string): {
|
|
570
|
+
scope: string;
|
|
571
|
+
index: number;
|
|
572
|
+
name: string;
|
|
573
|
+
} | undefined;
|
|
574
|
+
declare function constructorDisplayName(id: string): string;
|
|
575
|
+
interface ConstructorInfo {
|
|
576
|
+
name: string;
|
|
577
|
+
arity: number;
|
|
578
|
+
fields: string[];
|
|
579
|
+
mutableFields?: number[];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The adapter returns observations, never serialized values or display output. */
|
|
583
|
+
type PyretObservation = {
|
|
584
|
+
kind: 'primitive';
|
|
585
|
+
value: string | boolean | {
|
|
586
|
+
$pyretNumber: PyretNumberPayload;
|
|
587
|
+
};
|
|
588
|
+
} | {
|
|
589
|
+
kind: 'nothing';
|
|
590
|
+
} | {
|
|
591
|
+
kind: 'constructor';
|
|
592
|
+
identity: object;
|
|
593
|
+
info: ConstructorInfo;
|
|
594
|
+
values: unknown[];
|
|
595
|
+
} | {
|
|
596
|
+
kind: 'object';
|
|
597
|
+
fields: Array<[string, unknown]>;
|
|
598
|
+
} | {
|
|
599
|
+
kind: 'raw-array' | 'tuple';
|
|
600
|
+
values: unknown[];
|
|
601
|
+
} | {
|
|
602
|
+
kind: 'reference';
|
|
603
|
+
value: unknown;
|
|
604
|
+
} | {
|
|
605
|
+
kind: 'string-dict';
|
|
606
|
+
mutable: boolean;
|
|
607
|
+
sealed: boolean;
|
|
608
|
+
entries: Array<[string, unknown]>;
|
|
609
|
+
} | {
|
|
610
|
+
kind: 'table';
|
|
611
|
+
headers: string[];
|
|
612
|
+
rows: unknown[][];
|
|
613
|
+
};
|
|
614
|
+
interface PyretRuntimeAdapter {
|
|
615
|
+
/** Throw for unsupported state; capture adds the selected root and path. */
|
|
616
|
+
observe(value: unknown): PyretObservation;
|
|
617
|
+
}
|
|
618
|
+
/** Predicates must come from the runtime that owns the value, not another realm. */
|
|
619
|
+
interface PyretCaptureRuntime {
|
|
620
|
+
Any: unknown;
|
|
621
|
+
isNumber(value: unknown): boolean;
|
|
622
|
+
isNothing(value: unknown): boolean;
|
|
623
|
+
isDataValue(value: unknown): boolean;
|
|
624
|
+
isTuple(value: unknown): boolean;
|
|
625
|
+
isRef(value: unknown): boolean;
|
|
626
|
+
isFunction(value: unknown): boolean;
|
|
627
|
+
isMethod(value: unknown): boolean;
|
|
628
|
+
isOpaque(value: unknown): boolean;
|
|
629
|
+
isObject(value: unknown): boolean;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Adapter for upstream Pyret's JS runtime. All runtime representation reads are
|
|
633
|
+
* isolated here (including library backing stores). No printers, annotations,
|
|
634
|
+
* user functions, field dereferencing, or evaluator operations are invoked.
|
|
635
|
+
*/
|
|
636
|
+
declare function createPyretRuntimeAdapter(runtime: PyretCaptureRuntime): PyretRuntimeAdapter;
|
|
637
|
+
|
|
638
|
+
type PyretCaptureJson = null | boolean | number | string | PyretCaptureJson[] | {
|
|
639
|
+
[key: string]: PyretCaptureJson;
|
|
640
|
+
};
|
|
641
|
+
interface PyretCaptureRoot {
|
|
642
|
+
name: string;
|
|
643
|
+
value: unknown;
|
|
644
|
+
observation?: PyretCaptureJson;
|
|
645
|
+
}
|
|
646
|
+
interface PyretCaptureSnapshot {
|
|
647
|
+
format: 'spytial-pyret-capture';
|
|
648
|
+
version: 1;
|
|
649
|
+
datum: {
|
|
650
|
+
atoms: IAtom[];
|
|
651
|
+
relations: IRelation[];
|
|
652
|
+
types: IType[];
|
|
653
|
+
};
|
|
654
|
+
roots: Array<{
|
|
655
|
+
name: string;
|
|
656
|
+
atomId: string;
|
|
657
|
+
observation?: PyretCaptureJson;
|
|
658
|
+
}>;
|
|
659
|
+
provenance?: PyretCaptureJson;
|
|
660
|
+
}
|
|
661
|
+
declare class PyretCaptureError extends Error {
|
|
662
|
+
readonly root: string;
|
|
663
|
+
readonly path: readonly (string | number)[];
|
|
664
|
+
readonly reason: string;
|
|
665
|
+
constructor(root: string, path: readonly (string | number)[], reason: string);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Snapshot declared state without running user code. Adapter observations are
|
|
669
|
+
* materialized as transient structural carriers for the existing relationalizer;
|
|
670
|
+
* only its datum is exported. The carriers and all live identities die here.
|
|
671
|
+
*/
|
|
672
|
+
declare function capturePyret(roots: readonly PyretCaptureRoot[], adapter: PyretRuntimeAdapter, provenance?: PyretCaptureJson): PyretCaptureSnapshot;
|
|
673
|
+
/** Import with no runtime, producer caches, browser or evaluator. Never repair invalid capture data. */
|
|
674
|
+
declare function importPyretCapture(input: unknown): {
|
|
675
|
+
snapshot: PyretCaptureSnapshot;
|
|
676
|
+
instance: CapturedDataInstance;
|
|
677
|
+
values: ReadonlyMap<string, ReifiedValue>;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* replit — the REPL-equivalent string form of a reified value.
|
|
682
|
+
*
|
|
683
|
+
* The Pyret analog of Python's `repr(reify(...))` / Rust's
|
|
684
|
+
* `format!("{:?}", from_datum(...))`: reconstruct the value (./reify.ts), then
|
|
685
|
+
* render it to the source/REPL string a programmer would read.
|
|
686
|
+
*
|
|
687
|
+
* Rendering rules:
|
|
688
|
+
* - primitives: 5 "hi" true nothing
|
|
689
|
+
* - raw arrays: [raw-array: a, b, c]
|
|
690
|
+
* - tuples: {a; b}
|
|
691
|
+
* - built-in sets: public collection syntax (list-set adds preserve order)
|
|
692
|
+
* - dictionaries: public collection syntax (include string-dict at evaluation)
|
|
693
|
+
* - tables: table literals / public constructors (include tables at evaluation)
|
|
694
|
+
* - plain objects: {field: value}
|
|
695
|
+
* - data variants: type(field0, field1, ...) (fields in reconstructed order)
|
|
696
|
+
*
|
|
697
|
+
* Field ORDER here comes from the reconstructed object's dict order, which reify
|
|
698
|
+
* takes from serialized field IDs for v6 constructor data. Only legacy data
|
|
699
|
+
* falls back to the constructor cache / alphabetical field order.
|
|
700
|
+
*
|
|
701
|
+
* References, shared mutable dictionaries, and shared table cells use bindings and updates to
|
|
702
|
+
* preserve sharing/cycles (see reference-source.ts for the construction subset).
|
|
703
|
+
* The legacy ref-free renderer repeats DAGs and emits a `<cyclic>` marker for
|
|
704
|
+
* synthetic object cycles, which are not claimed as evaluable Pyret source.
|
|
705
|
+
*/
|
|
706
|
+
|
|
707
|
+
/** Reconstruct and render the selected atom; infer it only when there is a unique root. */
|
|
708
|
+
declare function replit(di: IDataInstance, rootId?: string): string;
|
|
709
|
+
|
|
710
|
+
/** Convert a value owned by this runtime into Core's IDataInstance contract. */
|
|
711
|
+
declare function toDataInstance(value: unknown, runtime: Parameters<typeof createPyretRuntimeAdapter>[0]): IDataInstance;
|
|
712
|
+
/** The Pyret-specific part of diagramming. Rendering is supplied by the host. */
|
|
713
|
+
declare function prepareDiagram(value: unknown, runtime: Parameters<typeof createPyretRuntimeAdapter>[0]): {
|
|
714
|
+
snapshot: PyretCaptureSnapshot;
|
|
715
|
+
instance: CapturedDataInstance;
|
|
716
|
+
sourcePreview: string;
|
|
717
|
+
};
|
|
718
|
+
/** Format a presentation copy only, after selector and layout evaluation. */
|
|
719
|
+
declare function diagramTypeNames<N extends {
|
|
720
|
+
mostSpecificType: string;
|
|
721
|
+
}, L extends {
|
|
722
|
+
nodes: N[];
|
|
723
|
+
}>(layout: L): L;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Canonicalization of a data instance.
|
|
727
|
+
*
|
|
728
|
+
* Atom ids (`tno_3`, `num_7`, ...) are arbitrary gensyms, so two instances that
|
|
729
|
+
* are equal *up to renaming* must be made byte-identical before comparison.
|
|
730
|
+
* `canon` produces a stable string with:
|
|
731
|
+
* - atom ids renamed to integers via a deterministic traversal from the roots,
|
|
732
|
+
* - primitive atoms keyed by (type, label) — the label IS the data and is kept;
|
|
733
|
+
* structured atoms retain type; their display labels are dropped,
|
|
734
|
+
* - relations keyed by name; constructor fields retain their semantic IDs,
|
|
735
|
+
* with tuples sorted lexicographically.
|
|
736
|
+
*
|
|
737
|
+
* v6 constructor field IDs include position. Canon preserves those IDs. Legacy
|
|
738
|
+
* fields retain only names; canonicalizing cannot recover their argument order.
|
|
739
|
+
*
|
|
740
|
+
* This is the substrate for the self-contained (Tier A) fidelity oracles
|
|
741
|
+
* (see tests/pyret/oracles.ts).
|
|
742
|
+
*/
|
|
743
|
+
|
|
744
|
+
declare function canon(di: IDataInstance, rootId?: string): string;
|
|
745
|
+
|
|
746
|
+
export { PyretCaptureError, type PyretCaptureJson, type PyretCaptureRoot, type PyretCaptureRuntime, type PyretCaptureSnapshot, PyretDataInstance, type PyretEvaluationResult, type PyretEvaluator, type PyretInstanceOptions, type PyretObject, type PyretObservation, type PyretRuntimeAdapter, type ReifiedValue, canon, capturePyret, constructorDisplayName, createPyretDataInstance, createPyretRuntimeAdapter, diagramTypeNames, generateEdgeId, importPyretCapture, isPyretDataInstance, prepareDiagram, replit as pyretCaptureSource, readConstructorTypeId, reifyToValue, reifyToValues, replit, toDataInstance };
|