Skip to content

Commit

Permalink
Support pointer templates in ref. implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gnidan committed Sep 28, 2024
1 parent e0a65ae commit 29df1e1
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/pointers/src/dereference/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { processPointer, type ProcessOptions } from "./process.js";
* for a particular pointer at runtime.
*/
export interface GenerateRegionsOptions {
templates: Pointer.Templates;
state: Machine.State;
initialStackLength: bigint;
}
Expand Down Expand Up @@ -62,6 +63,7 @@ export async function* generateRegions(
}

async function initializeProcessOptions({
templates,
state,
initialStackLength
}: GenerateRegionsOptions): Promise<ProcessOptions> {
Expand All @@ -72,6 +74,7 @@ async function initializeProcessOptions({
const variables: Record<string, Data> = {};

return {
templates,
state,
stackLengthChange,
regions,
Expand Down
31 changes: 31 additions & 0 deletions packages/pointers/src/dereference/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,35 @@ describe("dereference", () => {
expect(regions[0].offset).toEqual(Data.fromNumber(0));
expect(regions[0].length).toEqual(Data.fromNumber(32));
});

it("works for templates", async () => {
const templates: Pointer.Templates = {
"memory-range": {
expect: ["offset", "length"],
for: {
location: "memory",
offset: "offset",
length: "length"
}
}
};

const pointer: Pointer = {
define: {
"offset": 0,
"length": 32
},
in: {
template: "memory-range"
}
};

const cursor = await dereference(pointer, { templates });

const { regions } = await cursor.view(state);

expect(regions).toHaveLength(1);
expect(regions[0].offset).toEqual(Data.fromNumber(0));
expect(regions[0].length).toEqual(Data.fromNumber(32));
});
});
7 changes: 6 additions & 1 deletion packages/pointers/src/dereference/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface DereferenceOptions {
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
templates?: Pointer.Templates
}

/**
Expand Down Expand Up @@ -43,11 +44,15 @@ export async function dereference(
* `generateRegions()` will potentially need.
*/
async function initializeGenerateRegionsOptions({
templates = {},
state: initialState
}: DereferenceOptions): Promise<Omit<GenerateRegionsOptions, "state">> {
const initialStackLength = initialState
? await initialState.stack.length
: 0n;

return { initialStackLength };
return {
templates,
initialStackLength
};
}
43 changes: 43 additions & 0 deletions packages/pointers/src/dereference/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { adjustStackLength, evaluateRegion } from "./region.js";
* Contextual information for use within a pointer dereference process
*/
export interface ProcessOptions {
templates: Pointer.Templates;
state: Machine.State;
stackLengthChange: bigint;
regions: Record<string, Cursor.Region>;
Expand Down Expand Up @@ -56,6 +57,10 @@ export async function* processPointer(
return yield* processScope(collection, options);
}

if (Pointer.Collection.isReference(collection)) {
return yield* processReference(collection, options);
}

console.error("%s", JSON.stringify(pointer, undefined, 2));
throw new Error("Unexpected unknown kind of pointer");
}
Expand Down Expand Up @@ -150,3 +155,41 @@ async function* processScope(
Memo.dereferencePointer(in_)
];
}

async function* processReference(
collection: Pointer.Collection.Reference,
options: ProcessOptions
): Process {
const { template: templateName } = collection;

const { templates, variables } = options;

const template = templates[templateName];

if (!template) {
throw new Error(
`Unknown pointer template named ${templateName}`
);
}

const {
expect: expectedVariables,
for: pointer
} = template;

const definedVariables = new Set(Object.keys(variables));
const missingVariables = expectedVariables
.filter(identifier => !definedVariables.has(identifier));

if (missingVariables.length > 0) {
throw new Error([
`Invalid reference to template named ${templateName}; missing expected `,
`variables with identifiers: ${missingVariables.join(", ")}. `,
`Please ensure these variables are defined prior to this reference.`
].join(""));
}

return [
Memo.dereferencePointer(pointer)
];
}
6 changes: 6 additions & 0 deletions packages/pointers/src/pointer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ describe("type guards", () => {
},
guard: isPointer
},
{
schema: {
id: "schema:ethdebug/format/pointer/template"
},
guard: Pointer.isTemplate
},
] as const;

it.each(schemaGuards)("matches its examples", ({
Expand Down
43 changes: 40 additions & 3 deletions packages/pointers/src/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@ export namespace Pointer {
| Collection.Group
| Collection.List
| Collection.Conditional
| Collection.Scope;
| Collection.Scope
| Collection.Reference;

export const isCollection = (value: unknown): value is Collection =>
[
Collection.isGroup,
Collection.isList,
Collection.isConditional,
Collection.isScope
Collection.isScope,
Collection.isReference
].some(guard => guard(value));

export namespace Collection {
Expand Down Expand Up @@ -202,6 +205,16 @@ export namespace Pointer {
Object.keys(value.define).every(key => isIdentifier(key)) &&
"in" in value &&
isPointer(value.in);

export interface Reference {
template: string;
}

export const isReference = (value: unknown): value is Reference =>
!!value &&
typeof value === "object" &&
"template" in value &&
typeof value.template === "string" && !!value.template
}

export type Expression =
Expand Down Expand Up @@ -421,8 +434,32 @@ export namespace Pointer {
"$wordsized" in value &&
typeof value.$wordsized !== "undefined" &&
isExpression(value.$wordsized);
}
}

export interface Templates {
[identifier: string]: Pointer.Template;
}

}
export const isTemplates = (value: unknown): value is Templates =>
!!value &&
typeof value === "object" &&
Object.keys(value).every(isIdentifier) &&
Object.values(value).every(isTemplate);

export interface Template {
expect: string[];
for: Pointer;
}

export const isTemplate = (value: unknown): value is Template =>
!!value &&
typeof value === "object" &&
Object.keys(value).length === 2 &&
"expect" in value &&
value.expect instanceof Array &&
value.expect.every(isIdentifier) &&
"for" in value &&
isPointer(value.for);

}
8 changes: 7 additions & 1 deletion packages/pointers/test/observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface ObserveTraceOptions<V> {
*/
pointer: Pointer;

/**
* Pointer templates that may be referenced by the given pointer
*/
templates?: Pointer.Templates;

/**
* The necessary metadata and the Solidity source code for a contract whose
* `constructor()` manages the lifecycle of the variable that the specified
Expand Down Expand Up @@ -58,6 +63,7 @@ export interface ObserveTraceOptions<V> {
*/
export async function observeTrace<V>({
pointer,
templates = {},
compileOptions,
observe,
equals = (a, b) => a === b,
Expand Down Expand Up @@ -89,7 +95,7 @@ export async function observeTrace<V>({
}

if (!cursor) {
cursor = await dereference(pointer, { state });
cursor = await dereference(pointer, { state, templates });
}

const { regions, read } = await cursor.view(state);
Expand Down

0 comments on commit 29df1e1

Please sign in to comment.