The dereference()
function
Summary
The pages in this section cover the internals of the dereference()
function
in the @ethdebug/pointers reference implementation.
The full signature of this function is as follows:
/**
* Dereference an ethdebug/format/pointer document into a Cursor object,
* which allows inspecting machine state corresponding to the given pointer.
*
* Note that `options.state` is required if `pointer` contains any stack
* regions.
*/
declare async function dereference(
pointer: Pointer,
dereferenceOptions?: DereferenceOptions
): Promise<Cursor>;
Remember from the
Cursors section that a Cursor
provides a view(state: Machine.State)
method, which returns an ordered
collection of concrete Cursor.Region
objects.
DereferenceOptions
Note the optional options: DereferenceOptions
argument. This argument
allows for specifying additional information upfront that is necessary for
viewing the cursor later. Currently, this is needed only for pointers that
compose stack-located regions.
export interface DereferenceOptions {
/*
* Initial machine state
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
}
Control flow architecture
The dereference()
function itself performs two tasks:
- Create a "simple cursor": a function that takes a machine state and
produces an asynchronous list of
Cursor.Region
s. - Adapt this simple cursor to conform to the full
Cursor
interface
Within the process of creating this simple cursor it gets more interesting: by leveraging JavaScript's AsyncIterators, the implementation can compute regions on the fly by recursively breaking down pointers into their nested child pointers.
Since the desired end-result of dereference()
is an object that can turn
a pointer into its composite ordered list of concrete regions at a particular
machine state, this implementation separates the concerns of generating this
list from converting this list into the promised return interface.
To generate this list asynchronously on the fly, the implementation uses a stack of processing requests (which it calls "memos"), initially populated with a request to dereference the root pointer. Each memo represents a state or context change in some form: either a request to dereference a pointer or sub-pointer, a request to save a set of regions by their names, or a request to save the computed values of a set of variables by their identifiers.
The other pages in this section proceed to go into more detail.
See the full src/dereference/index.ts
module
import type { Pointer } from "../pointer.js";
import type { Machine } from "../machine.js";
import type { Cursor } from "../cursor.js";
import { generateRegions, type GenerateRegionsOptions } from "./generate.js";
import { createCursor } from "./cursor.js";
export interface DereferenceOptions {
/*
* Initial machine state
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
}
/**
* Dereference an ethdebug/format/pointer document into a Cursor object,
* which allows inspecting machine state corresponding to the given pointer.
*
* Note that `options.state` is required if `pointer` contains any stack
* regions.
*/
export async function dereference(
pointer: Pointer,
dereferenceOptions: DereferenceOptions = {}
): Promise<Cursor> {
const options = await initializeGenerateRegionsOptions(dereferenceOptions);
// use a closure to build a simple Cursor-like interface for accepting
// a machine state and producing a collection of regions.
const simpleCursor =
(state: Machine.State): AsyncIterable<Cursor.Region> => ({
async *[Symbol.asyncIterator]() {
yield* generateRegions(pointer, { ...options, state });
}
});
return createCursor(simpleCursor);
}
/**
* Convert DereferenceOptions into the specific pieces of information that
* `generateRegions()` will potentially need.
*/
async function initializeGenerateRegionsOptions({
state: initialState
}: DereferenceOptions): Promise<Omit<GenerateRegionsOptions, "state">> {
const initialStackLength = initialState
? await initialState.stack.length
: 0n;
return { initialStackLength };
}