Skip to content

Introduce path expressions as a shared reference DSL - #141

Merged
lorisleiva merged 3 commits into
mainfrom
path-expressions
Sep 1, 2026
Merged

Introduce path expressions as a shared reference DSL#141
lorisleiva merged 3 commits into
mainfrom
path-expressions

Conversation

@lorisleiva

@lorisleiva lorisleiva commented Sep 1, 2026

Copy link
Copy Markdown
Member

This PR implements #130: one small string grammar, defined once, used everywhere a node or a template points into data — required now that account data (#137) and soon instruction data (#131) can nest arbitrarily.

Grammar (normative home: the path StringConstraint docs): first ( "." identifier | "[" integer "]" )* where first := identifier | "[" integer "]", non-negative indices, .identifier = struct field by exact match (valid only where the type resolves to a struct, following links), [n] = n-th item of an array/tuple/set. The first segment may be an index so tuple- and array-rooted data is reachable ([0].banana). Identifiers excluding dots (#140) keeps the grammar unambiguous with zero quoting or escaping; extensions ([-1], slices) can land in minors. Attributes carrying paths document their anchor; interpolation names the anchor explicitly via the ${root…} roots — the full grammar after data, exactly one account identifier after accounts.

Converted references: argumentValueNode and fieldDiscriminatorNode now carry path (their v1 name was a flat spelling of the same thing); accountFieldValueNode.path becomes path-constrained. Deliberately not converted: single-identifier references to flat sibling sets (accountValueNode, pdaSeedValueNode, enumValueNode.variant, link nodes) — nothing nests there.

Docs discoverability (from the #140 review): the generated docs root page gains a "Constrained strings" glossary defining all four constrained string types, so the grammars are readable without opening the TS sources.

Upgrade lens note (for upgradeV1ToV2): attribute renames only (namepath on the two nodes); every v1 flat name is a valid v2 path, values pass through.

Closes #130 (manual close — default branch is 1.x). Part of #102.

One small string grammar, defined once, used everywhere a node or a template points into data: identifier ( "." identifier | "[" integer "]" )* with non-negative indices, carried by the new path string constraint and stringPath helper, rendered as PathString. argumentValueNode and fieldDiscriminatorNode now carry path attributes, accountFieldValueNode.path becomes path-constrained, and interpolated intents embed the same expressions after their data/accounts roots. The generated docs root page gains a Constrained strings glossary defining IdentifierString, NamespaceString, PathString and SemverString.
@lorisleiva

Copy link
Copy Markdown
Member Author

@trevor-cortex

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces the path string constraint as the shared reference DSL: a stringPath() primitive rendered as PathString, applied to argumentValueNode.path (renamed from identifier), fieldDiscriminatorNode.path (renamed from identifier), and accountFieldValueNode.path (widened from identifier to path). The interpolatedIntent docs drop the flat-only restriction after the ${root.…} placeholder roots, and the generated docs root page gains a "Constrained strings" glossary. Tests cover the new primitive, the renderer mapping, and two spec-level invariants (the renamed nodes carry path and drop identifier; every path attribute carries the path constraint).

Conventions all check out:

  • spec.json and docs/ are regenerated in lockstep with src/; schema.json is a stub per the README, so no change there is expected.
  • The changeset is a major, correct under CONTRIBUTING's policy for breaking attribute renames, and the v1-relative framing (namepath) is the right lens for consumers even though the in-branch rename is identifierpath.
  • The every path attribute carries the path constraint invariant test is a nice guard — future nodes can't reuse the path name with a weaker type.

One design question, non-blocking since the grammar is explicitly extensible in minors: a path must start with an identifier, so data whose top-level type resolves to a tuple/array can't be referenced at all — neither argumentValueNode nor fieldDiscriminatorNode can express "element 0 of tuple-shaped instruction data", and the template form ${data[0]} falls outside the documented ${root.path} shape. If tuple-shaped instruction/account data is legal in the spec, it may be worth a sentence somewhere acknowledging the limitation (or confirming struct-shaped data is assumed at the anchor). If it's already impossible elsewhere, feel free to ignore.

Two small doc-precision suggestions inline. Nice work keeping the grammar's normative home in exactly one place (the path docs in types.ts) with everything else deferring to it.

Comment thread src/spec/nodes/displayNodes/InstructionDisplayNode.ts Outdated
Comment thread generators/docs/render/constants.ts
A path's first segment may now be an index, so tuple- and array-rooted data is reachable ([0].banana) — required since account data (and soon instruction data) can be any type node. The interpolation docs stop over-promising for the accounts root (exactly one account identifier; accounts resolve to addresses), and the docs glossary folds in the segment semantics so grammars are readable from the generated docs alone.

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after the updates — everything from my previous pass has been addressed, and the resolutions are cleaner than what I'd suggested:

  • Tuple-rooted data (my non-blocking design question): resolved by extending the grammar itself — first := identifier | "[" integer "]" — rather than documenting a limitation. [0].banana and ${data[0]} are now expressible, and the change is threaded consistently through the normative docs in types.ts, the glossary, the changeset, and spec.json.
  • interpolatedIntent precision: the docs now split the roots explicitly — full path grammar after data, exactly one account identifier after accounts, with the rationale ("accounts resolve to addresses, so nothing nests") spelled out. This also keeps it consistent with accountValueNode staying flat.
  • Glossary segment semantics: the PathString entry now defines what .identifier and [n] mean, not just the syntax, so the docs-root glossary stands alone as intended.

Generated artifacts (spec.json, docs/) are regenerated in lockstep with the source changes, and the tests still pin both invariants (renamed nodes carry path/drop identifier; every path attribute carries the path constraint).

One tiny non-blocking nit inline about the normative docs omitting the link-following rule that every derived text includes. Ship it.

Comment thread src/api/types.ts Outdated
The derived texts (glossary, node attribute docs) already state that .identifier matches a struct field following links; the normative path constraint docs now carry the rule too, so the semantics don't live only in the copies.

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest push. The single remaining point from my last pass is resolved: the normative path definition in src/api/types.ts now includes the link-following rule — ".identifier accesses a struct field by exact identifier match (following links)" — so the semantics no longer live only in the derived texts (glossary, node attribute docs), which all remain consistent with it.

Everything else is unchanged from my previous approval: grammar allows index-rooted paths ([0].banana), interpolatedIntent splits the data/accounts roots precisely, generated artifacts (spec.json, docs/) are in lockstep with src/, the changeset is a correctly-scoped major, and the spec tests still pin both invariants (renamed nodes carry path and drop identifier; every path attribute carries the path constraint).

No further comments — ship it.

@lorisleiva
lorisleiva merged commit 9c38fda into main Sep 1, 2026
3 checks passed
@lorisleiva
lorisleiva deleted the path-expressions branch September 1, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants