pub const SIGNAL_JS: &str = "// Fine-grained reactivity for ZDeceptron.\n//\n// Dependencies are discovered at READ time, at runtime \u{2014} never from the\n// shape of the source. Spec \u{a7}5.5 requires this: Svelte documented that\n// compile-time dependency detection silently breaks when an expression is\n// extracted into a helper function, because the dependency stops being\n// visible at the declaration site. Tracking reads means refactoring cannot\n// break reactivity.\n//\n// The model is SolidJS\'s: a signal write marks its readers stale and\n// schedules them; a read inside a running computation registers an edge.\n// There is no virtual DOM and no component re-render \u{2014} a write reaches\n// exactly the bindings that read it.\n\n// **Nothing in the runtime may `for\u{2026}of`, spread, or `Array.from` a `Set`\n// or a `Map`. Call `forEach`.** That is a workaround for an engine bug and\n// not a preference: simplifying it back reintroduces a crash.\n//\n// `boa_engine` 0.21.1 \u{2014} the engine every test in this repository runs\n// emitted JavaScript through \u{2014} panics with `Object already borrowed:\n// BorrowMutError` from `builtins/set/ordered_set.rs:182`, and identically\n// from `builtins/map/ordered_map.rs:225`. `Set.prototype.values` hands its\n// iterator a lock on the set, and that lock is released only from the\n// iterator\'s `finalize` \u{2014} so an iterator that is finished with, or\n// abandoned, keeps it until the collector reaches it. `SetIterator::next`\n// then holds a *borrow* of the same set across an allocation, and a\n// collection landing on that allocation runs the pending `finalize`, which\n// borrows the set again. boa\'s own comment at the panic site reads\n// `TODO: try_downcast_mut`.\n//\n// Iterating one collection twice is therefore enough, and which program\n// crashes depends only on where the allocations happen to fall \u{2014} so it\n// presents as a bug in whatever changed last, and fixing one call site\n// only moves it to another. Restoring the spreads in this file alone, with\n// the other three left converted, still failed two `algorithms` tests but\n// no longer the same two: the crash follows the allocations, not the\n// algorithm. `forEach` is unaffected \u{2014} it keeps its lock in a Rust local\n// rather than in a collectable iterator object, and it drops the borrow\n// before calling back. 0.21.1 is the newest published `boa_engine`; when\n// there is a newer one, check whether this still bites.\n\n/** Every member of `set`, as an array: the `[...set]` the note forbids.\n *\n * The specific name is load-bearing. `zdc-bench` flattens every runtime\n * file into one scope to measure it, so a top-level name here collides\n * with one there, and the loser is called with the wrong arity rather\n * than reported \u{2014} a `snapshot` here silently became `instrument.js`\'s. */\nfunction membersOf(set) {\n const out = [];\n set.forEach((member) => out.push(member));\n return out;\n}\n\n/** The computation currently running, or null at the top level. */\nlet listener = null;\n\n/** Collects disposers created inside `owned`, or null outside one. */\nlet owner = null;\n\n/** Depth of the current batch. Writes flush when it returns to zero. */\nlet batchDepth = 0;\n\nlet flushing = false;\n\n/** Computations marked stale during the current batch. */\nconst pending = new Set();\n\n/** Register a teardown with the scope `owned` opened, if any. */\nexport function onCleanup(fn) {\n if (owner) owner.push(fn);\n}\n\n/**\n * A mutable value that tracks who reads it.\n *\n * Returns a [read, write] pair rather than an object so that reading is a\n * call \u{2014} which is what makes the dependency edge observable.\n */\nexport function signal(initial) {\n let value = initial;\n const readers = new Set();\n\n function read() {\n if (listener) {\n readers.add(listener);\n listener.sources.add(readers);\n }\n return value;\n }\n\n function write(next) {\n const resolved = typeof next === \'function\' ? next(value) : next;\n // Reference equality is the right test here: ZDeceptron values are\n // immutable, so a structurally-equal new object is a genuine change\n // from the language\'s point of view.\n if (Object.is(resolved, value)) return value;\n value = resolved;\n // One flush per write: one at a time, a diamond ran its effect on a\n // pair of values that never existed together.\n batch(() => {\n for (const reader of membersOf(readers)) invalidate(reader);\n });\n return value;\n }\n\n return [read, write];\n}\n\n/**\n * A value computed from other signals, recomputed when they change.\n *\n * This is `from` in the language. It is lazy: the body does not run until\n * something reads it, and it does not re-run until a dependency changes.\n */\nexport function derived(compute) {\n let value;\n let stale = true;\n const readers = new Set();\n\n const node = {\n sources: new Set(),\n run() {\n stale = true;\n for (const reader of membersOf(readers)) invalidate(reader);\n },\n };\n\n return function read() {\n if (listener) {\n readers.add(listener);\n listener.sources.add(readers);\n }\n if (stale) {\n clearSources(node);\n const previous = listener;\n listener = node;\n try {\n value = compute();\n } finally {\n listener = previous;\n }\n stale = false;\n }\n return value;\n };\n}\n\n/**\n * Run a function now, and again whenever anything it read changes.\n *\n * Every DOM binding is one of these, which is why an update touches only\n * the nodes that actually read the changed signal.\n */\nexport function effect(fn) {\n // `clearSources` cannot retract a run the drain has snapshotted.\n let live = true;\n const node = {\n sources: new Set(),\n run() {\n if (!live) return;\n clearSources(node);\n const previous = listener;\n const scope = owner;\n listener = node;\n owner = null;\n try {\n fn();\n } finally {\n listener = previous;\n owner = scope;\n }\n },\n };\n node.run();\n const dispose = () => {\n live = false;\n clearSources(node);\n };\n onCleanup(dispose);\n return dispose;\n}\n\n/**\n * Run `fn`, collecting every effect it creates so they can be torn down\n * together.\n *\n * Without this a removed list row stays subscribed to whatever it read\n * for the life of the page. It is not visible as wrong output \u{2014} a row\n * nobody writes to simply never re-runs \u{2014} which is exactly why it needs\n * an explicit mechanism rather than being noticed.\n */\nexport function owned(fn) {\n const previous = owner;\n const disposers = [];\n const dispose = () => {\n while (disposers.length > 0) disposers.pop()();\n };\n // Linked, so a parent reaches it: unlinked, 2000 mount/unmount cycles\n // of three rows kept all 6000 effects live.\n if (previous) previous.push(dispose);\n owner = disposers;\n try {\n return [fn(), dispose];\n } finally {\n owner = previous;\n }\n}\n\n/**\n * Apply several writes and flush once.\n *\n * An event handler is implicitly batched, so `add 1 to a` followed by\n * `set b to 2` repaints once rather than twice.\n */\nexport function batch(fn) {\n batchDepth += 1;\n try {\n return fn();\n } finally {\n batchDepth -= 1;\n if (batchDepth === 0) flush();\n }\n}\n\nfunction invalidate(node) {\n pending.add(node);\n if (batchDepth === 0) flush();\n}\n\n/** Computations one update may run before it is a cycle. */\nconst STEP_LIMIT = 1e5;\n\nfunction flush() {\n // A re-entrant flush is the same drain: flushing from inside a running\n // computation cost a stack frame per link, and 200 chained bindings\n // exhausted the budget.\n if (flushing) return;\n flushing = true;\n let steps = 0;\n // A throwing binding must not take the rest of the drain with it.\n let failure = null;\n try {\n // Draining rather than iterating: a computation may invalidate another,\n // and that one must run in the same flush or the DOM ends up showing a\n // value that is already out of date.\n while (pending.size > 0) {\n const ready = membersOf(pending);\n pending.clear();\n for (const node of ready) {\n if ((steps += 1) > STEP_LIMIT) {\n throw new Error(`An update ran ${STEP_LIMIT} steps without settling.`);\n }\n try {\n node.run();\n } catch (error) {\n failure ??= error;\n }\n }\n }\n } finally {\n flushing = false;\n }\n if (failure) throw failure;\n}\n\nfunction clearSources(node) {\n node.sources.forEach((readers) => readers.delete(node));\n node.sources.clear();\n}\n";Expand description
The reactivity core: signals, derived values, effects, batching.