diff --git a/packages/web/spec/pointer/expression.mdx b/packages/web/spec/pointer/expression.mdx index 4c8548a5..971f0099 100644 --- a/packages/web/spec/pointer/expression.mdx +++ b/packages/web/spec/pointer/expression.mdx @@ -6,12 +6,28 @@ import SchemaViewer from "@site/src/components/SchemaViewer"; # Expression syntax +Pointer expressions operate on the domain of bytes representing unsigned +integers. + ## Literal values +An expression can be a literal value. + +Literal values **must** be represented either as JSON numbers or as +`0x`-prefixed hexadecimal strings. Hexadecimal strings always represent a +literal string of bytes. + +For convenience, this schema does not restrict hexadecimal string +representations to those that specify an even-number of digits (i.e., those +that specify complete byte pairs); odd numbers of hexadecimal digits are fine. + +Hexadecimal string representations **may** omit leading zeroes; values are +assumed to be left-padded to the bytes width appropriate for the context. + : [...] }`, where `` +denotes an arithmetic operation. + +## Lookup region definition + +An expression can reference properties defined for a particular region, such as +another region's `"offset"` or `"length"`. Such expressions resolve to the +same value as the expression specified for that corresponding property. + + + ## Reading from the EVM +An expression can be an object of the form `{ "$read": "" }`, where +`` references a particular region defined in some root pointer. + +The value of such an expression is the concatenation of bytes present in the +running machine state that correspond to the bytes addressed by the referenced +region. + + +## Region references + +Regions can be referenced either by name (which **must** be a defined region), +or by use of the literal string value `"$this"` (which indicates that the +referenced region is the region containing the expression itself). + +In cases where an expression is used outside the context of a particular +region definition, the use of `"$this"` is **prohibited**. + +Individual properties **may not** be defined with any reference to themselves. +Properties also **may not** be defined in terms of mutual reference to each +other. (Don't make this harder than it has to be.) + + diff --git a/packages/web/src/schemas.ts b/packages/web/src/schemas.ts index 01e9128e..0216b3fc 100644 --- a/packages/web/src/schemas.ts +++ b/packages/web/src/schemas.ts @@ -62,6 +62,10 @@ export const schemaIndex: SchemaIndex = { href: "/spec/type/complex/function#parameters-schema" }, + "schema:ethdebug/format/pointer": { + href: "/spec/pointer" + }, + "schema:ethdebug/format/pointer/region": { href: "/spec/pointer/region" }, @@ -127,6 +131,11 @@ export const schemaIndex: SchemaIndex = { href: "/spec/pointer/expression#arithmetic-operations" }, + "schema:ethdebug/format/pointer/expression#/$defs/Lookup": { + title: "Lookup expression schema", + href: "/spec/pointer/expression#lookup-region-definition" + }, + "schema:ethdebug/format/pointer/expression#/$defs/Read": { title: "Read expression schema", href: "/spec/pointer/expression#reading-from-the-evm" @@ -136,4 +145,9 @@ export const schemaIndex: SchemaIndex = { title: "Keccak256 hash expression schema", href: "/spec/pointer/expression#keccak256-hashes" }, + + "schema:ethdebug/format/pointer/expression#/$defs/Reference": { + title: "Region reference", + href: "/spec/pointer/expression#region-references" + }, }; diff --git a/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx b/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx index ac1cdd2f..dd20f6fd 100644 --- a/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx +++ b/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx @@ -1,9 +1,17 @@ import React from "react"; import type { JSONSchema } from "json-schema-typed/draft-2020-12"; -import CreateNodes from "@theme-original/JSONSchemaViewer/components/CreateNodes"; +import CreateNodes from "@theme/JSONSchemaViewer/components/CreateNodes"; +import CreateEdge from "@theme-original/JSONSchemaViewer/components/CreateEdge"; +import { SchemaHierarchyComponent } from "@theme-original/JSONSchemaViewer/contexts" +import { Collapsible } from "@theme/JSONSchemaViewer/components"; +import { GenerateFriendlyName, QualifierMessages } from "@theme/JSONSchemaViewer/utils"; +import { internalIdKey } from "@site/src/contexts/SchemaContext"; +import { CreateDescription } from "@theme/JSONSchemaViewer/JSONSchemaElements"; +import { useJSVOptionsContext } from "@theme/JSONSchemaViewer/contexts" export interface UnnecessaryComposition { - schemaWithoutUnnecessaryComposition: JSONSchema; + schemaWithoutUnnecessaryComposition: Exclude; + unnecessaryCompositionKeyword: "allOf" | "oneOf" | "anyOf"; unnecessarilyComposedSchema: JSONSchema; } @@ -14,17 +22,17 @@ export function detectUnnecessaryComposition( return; } - const redundantKeywords = (["allOf", "oneOf", "anyOf"] as const) + const unnecessaryCompositionKeywords = (["allOf", "oneOf", "anyOf"] as const) .filter(keyword => keyword in schema && (schema[keyword] || []).length === 1); - if (redundantKeywords.length !== 1) { + if (unnecessaryCompositionKeywords.length !== 1) { return; } - const [redundantKeyword] = redundantKeywords; + const [unnecessaryCompositionKeyword] = unnecessaryCompositionKeywords; const { - [redundantKeyword]: composition, + [unnecessaryCompositionKeyword]: composition, ...schemaWithoutUnnecessaryComposition } = schema; @@ -33,6 +41,7 @@ export function detectUnnecessaryComposition( return { unnecessarilyComposedSchema, + unnecessaryCompositionKeyword, schemaWithoutUnnecessaryComposition }; } @@ -42,29 +51,94 @@ export interface UnnecessaryCompositionSchemaProps extends UnnecessaryCompositio export default function UnnecessaryCompositionSchema({ schemaWithoutUnnecessaryComposition, + unnecessaryCompositionKeyword, unnecessarilyComposedSchema }: UnnecessaryCompositionSchemaProps): JSX.Element { - const unnecessarilyComposedSchemaNecessities = removeDocumentingFields( - unnecessarilyComposedSchema - ); + const jsvOptions = useJSVOptionsContext(); + + // treat the unnecessary composition to represent the extension of a base + // schema, where the unnecessarily composed schema is the base + const baseSchema = unnecessarilyComposedSchema; + const extensionSchema = schemaWithoutUnnecessaryComposition; + const { + documentation, + semantics + } = separateDocumentationFromSemantics(extensionSchema); + + if (Object.keys(semantics).length === 0) { + const { description } = documentation; + + return <> + + {description && } +
+ + + + + ; + } return ( <> + extensions  + These extensions apply to the base schema below: +

+ +

+ + +   + base schema + + } + detailsProps={{ + open: true + }} + > + + + + + ); } -function removeDocumentingFields(schema: JSONSchema): JSONSchema { +function separateDocumentationFromSemantics(schema: JSONSchema): { + documentation: Exclude, + semantics: JSONSchema +} { if (typeof schema === "boolean") { - return schema; + return { + documentation: {}, + semantics: schema + }; } const { title, description, examples, - ...schemaNecessities + default: default_, + // @ts-ignore + [internalIdKey]: _id, + ...semantics } = schema; - return schemaNecessities; + return { + documentation: { + title, + description, + examples, + default: default_ + }, + semantics + }; } diff --git a/schemas/pointer/collection/group.schema.yaml b/schemas/pointer/collection/group.schema.yaml index 8ac03ae5..5be69724 100644 --- a/schemas/pointer/collection/group.schema.yaml +++ b/schemas/pointer/collection/group.schema.yaml @@ -3,7 +3,7 @@ $id: "schema:ethdebug/format/pointer/collection/group" title: ethdebug/format/pointer/collection/group description: | - A schema for ... + A composite collection of pointers type: object properties: group: diff --git a/schemas/pointer/collection/list.schema.yaml b/schemas/pointer/collection/list.schema.yaml index 84764e16..32af2a33 100644 --- a/schemas/pointer/collection/list.schema.yaml +++ b/schemas/pointer/collection/list.schema.yaml @@ -3,7 +3,7 @@ $id: "schema:ethdebug/format/pointer/collection/list" title: ethdebug/format/pointer/collection/list description: | - A schema for ... + An ordered list of pointers, indexed starting at zero. type: object properties: @@ -11,10 +11,17 @@ properties: type: object properties: count: + description: | + The size of the list that this collection represents. $ref: "schema:ethdebug/format/pointer/expression" each: - type: string + description: | + An identifier name whose value as an expression resolves to the index + in the list + $ref: "schema:ethdebug/format/pointer/identifier" is: + description: | + The dynamically-generated pointer repeated as a list $ref: "schema:ethdebug/format/pointer" required: - count diff --git a/schemas/pointer/expression.schema.yaml b/schemas/pointer/expression.schema.yaml index 971ae596..55fbdbb3 100644 --- a/schemas/pointer/expression.schema.yaml +++ b/schemas/pointer/expression.schema.yaml @@ -53,18 +53,39 @@ $defs: type: object properties: "$sum": + description: | + A list of expressions to be added together. $ref: "#/$defs/Operands" "$difference": + description: | + A tuple of two expressions where the second is to be subtracted from + the first. + + (i.e., `{ "$difference": [a, b] }` equals `a` minus `b`.) $ref: "#/$defs/Operands" minItems: 2 maxItems: 2 "$product": + description: | + A list of expressions to be multipled. $ref: "#/$defs/Operands" "$quotient": + description: | + A tuple of two expressions where the first corresponds to the + dividend and the second corresponds to the divisor, for the purposes + of doing integer division. + + (i.e., `{ "$quotient": [a, b] }` equals `a` divided by `b`.) $ref: "#/$defs/Operands" minItems: 2 maxItems: 2 "$remainder": + description: | + A tuple of two expressions where the first corresponds to the + dividend and the second corresponds to the divisor, for the purposes + of computing the modular-arithmetic remainder. + + (i.e., `{ "$remainder": [a, b] }` equals `a` mod `b`.) $ref: "#/$defs/Operands" minItems: 2 maxItems: 2 @@ -90,28 +111,20 @@ $defs: $ref: "schema:ethdebug/format/pointer/expression" Lookup: - title: Region definition reference + title: Lookup region definition description: | - An object of the form \`{ ".": "" }\`, to + An object of the form `{ ".": "" }`, to denote that this expression is equivalent to the defined value for - the property named \`\` inside the region named - \`\`. - - \`\` **must** correspond to at least one region declared - with \`{ "name": "" }\` previously in some root pointer - representation. - - If more than one region is defined with the same name, resolution is - defined as resolving, first, to the latest earlier sibling that declares - the matching name, then second, to parent then parent's earlier siblings, - and so on. + the property named `` inside the region referenced as + ``. - \`\` **must** be a valid and present property on the - corresponding region. + `` **must** be a valid and present property on the + corresponding region, or it **must** correspond to an optional property + whose schema specifies a default value for that property. type: object patternProperties: "^\\.(offset|length|slot)$": - $ref: "schema:ethdebug/format/pointer/identifier" + $ref: "#/$defs/Reference" additionalProperties: false minProperties: 1 maxProperties: 1 @@ -119,23 +132,45 @@ $defs: examples: - .offset: "array-count" - .length: "array-item" + - .offset: $this + Read: - title: Read machine state + title: Read region bytes description: | - An object of the form `{ "read": "" }`, where `` - indicates the identifier for the region whose bytes to read in order to - find the value indicated by this expression. + An object of the form `{ "$read": "" }`. The value of this + expression equals the raw bytes present in the running machine state + in the referenced region. type: object properties: $read: - $ref: "schema:ethdebug/format/pointer/identifier" + $ref: "#/$defs/Reference" required: - $read additionalProperties: false examples: - $read: "struct-start" + Reference: + title: Region reference + description: | + A string value that **must** either be the `"name"` of at least one + region declared with `{ "name": "" }` previously in some root + pointer representation, or it **must** be the literal value `"$this"`, + which indicates a reference to the region containing this expression. + + If more than one region is defined with the same name, resolution is + defined as firstly resolving to the latest earlier sibling that declares + the matching name, then secondly resolving to the parent if it matches, + then to parent's earlier siblings, and so on. + type: string + + oneOf: + - $ref: "schema:ethdebug/format/pointer/identifier" + - const: "$this" + description: | + Indicates a reference to the region containing this expression. + Keccak256: title: Keccak256 hash description: | diff --git a/schemas/pointer/scheme/segment.schema.yaml b/schemas/pointer/scheme/segment.schema.yaml index 03ddab87..447356ed 100644 --- a/schemas/pointer/scheme/segment.schema.yaml +++ b/schemas/pointer/scheme/segment.schema.yaml @@ -21,15 +21,38 @@ properties: slot: $ref: "schema:ethdebug/format/pointer/expression" offset: + description: | + The starting byte index within the slot. + + This field is **optional**. If unspecified, it has the default value of + `0`, indicating that the segment begins at the start of the specified + slot. + + This field's expression must resolve to a value _n_ such that + 0 ≤ _n_ \< `$wordsize` (i.e., the offset **must** + begin inside the slot). $ref: "schema:ethdebug/format/pointer/expression" default: 0 length: + description: | + The length of the bytes range this segment represents. + + This field is **optional**. If unspecified, its default value indicates + that the segment ends at the end of the slot. + + If this field has value larger than the default value, i.e., if the + segment extends beyond the last byte in the slot, then this segment is + defined to be the concatenation of the sequentially-addressed slot(s) + following following the slot specified. $ref: "schema:ethdebug/format/pointer/expression" - default: $wordsize + default: + $difference: + - $wordsize + - .offset: $this + required: - slot - examples: - slot: 0 - slot: 1