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.
@@ -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 };