This folder isolates every requirement that only concerns the equality-saturation engine, independent from UI/architecture choices. Treat it as a language/runtime contract the rest of the app consumes.
- Define the canonical e-graph data model, rewrite semantics, and equality-saturation loop.
- Keep all engine surfaces immutable at the boundary: callers only see persistent snapshots.
- Support two engine implementations (naive + deferred rebuild) behind the same API.
- Document a reproducible testing methodology that can be run without the Svelte app.
Anything unrelated to those goals (pane layout, timeline stores, etc.) lives outside specs/egraph.
- Immutability at the API boundary – Every call returning an
EGraphStatemust provide an object safe to reuse and compare by reference. Internals may mutate while building a step, but nothing mutable escapes. - Determinism – Same preset, rewrite ordering, and implementation flag must yield identical state timelines. Hashing, id allocation, and iteration limits must therefore be deterministic.
- Stable identifiers –
ENodeIds increment monotonically; canonical ids may change after merges but are always discoverable throughfind(). - Implementation parity – Naive and deferred modes must be feature-complete and produce the same observable results (states, diffs, invariant reports).
- Preset-driven – Inputs (initial expression, rewrites) arrive via a schema defined in
PRESETS.md; no implicit global rewrites. - Mutative snapshots – Snapshot emission must use mutative (Immer-compatible)
producecalls so exported objects remain plain JS arrays/maps with structural sharing.
interface EGraphEngine {
loadPreset(preset: PresetConfig, options: EngineOptions): void;
runUntilHalt(): EGraphTimeline; // precompute full history
step(): EGraphState | null; // live/authoring mode
getTimeline(): EGraphTimeline; // last computed timeline
}
interface EngineOptions {
implementation: 'naive' | 'deferred';
iterationCap?: number;
recordDiffs?: boolean;
debugInvariants?: boolean;
}PresetConfig(seePRESETS.md) encodes the initial expression, rewrite list, and preset-specific hints/limits.loadPresetmust validate it eagerly and reset internal runtime state.EGraphState(seeDATA_MODEL.md) is the immutable snapshot emitted after each phase. It contains flattened union-find/e-class/hashcons data plus metadata diffs and invariant flags.EGraphTimeline(seeDATA_MODEL.md) is a deterministic sequence ofEGraphStates along with preset/implementation identifiers.runUntilHaltbuilds a new one;getTimelinereturns the last computed timeline without mutation.step()advances the engine by exactly one phase (read, write, or rebuild depending on implementation). It returns the newEGraphState, ornullonce saturation halts.getTimeline()must never mutate or clone partial data; consumers rely on structural sharing between states for fast scrubbing.- Engines must throw descriptive errors (
PresetValidationError,IterationCapExceededError, etc.) rather than returning partial data.
| File | Purpose |
|---|---|
| DATA_MODEL.md | Core types, runtime vs snapshot layout, identifier rules. |
| OPERATIONS.md | find/add/merge/rebuild, naive vs deferred behavior, instrumentation hooks. |
| LANGUAGE.md | Supported expression language, literal/variable policy, serialization format. |
| REWRITES.md | Pattern syntax, rewrite rule spec, equality-saturation loop, halt conditions. |
| PRESETS.md | JSON schema for presets, versioning, validation requirements. |
| TESTING.md | Engine-level unit/contract tests independent of the UI. |
Use MAIN.md purely as an orientation layer; edits to core behavior belong in the specialized documents.