Introduce path expressions as a shared reference DSL - #141
Conversation
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.
trevor-cortex
left a comment
There was a problem hiding this comment.
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.jsonanddocs/are regenerated in lockstep withsrc/;schema.jsonis 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 (name→path) is the right lens for consumers even though the in-branch rename isidentifier→path. - The
every path attribute carries the path constraintinvariant test is a nice guard — future nodes can't reuse thepathname 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.
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
left a comment
There was a problem hiding this comment.
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].bananaand${data[0]}are now expressible, and the change is threaded consistently through the normative docs intypes.ts, the glossary, the changeset, andspec.json. interpolatedIntentprecision: the docs now split the roots explicitly — full path grammar afterdata, exactly one account identifier afteraccounts, with the rationale ("accounts resolve to addresses, so nothing nests") spelled out. This also keeps it consistent withaccountValueNodestaying flat.- Glossary segment semantics: the
PathStringentry now defines what.identifierand[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.
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
left a comment
There was a problem hiding this comment.
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.
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
pathStringConstraintdocs):first ( "." identifier | "[" integer "]" )*wherefirst := 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 afterdata, exactly one account identifier afteraccounts.Converted references:
argumentValueNodeandfieldDiscriminatorNodenow carrypath(their v1namewas a flat spelling of the same thing);accountFieldValueNode.pathbecomes 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 (name→pathon 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.