Skip to content

quoted! covers all three recording vocabularies - #766

Closed
0-jake-0 wants to merge 2 commits into
quoted-macrofrom
quoted-full
Closed

quoted! covers all three recording vocabularies#766
0-jake-0 wants to merge 2 commits into
quoted-macrofrom
quoted-full

Conversation

@0-jake-0

@0-jake-0 0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Stacked on #765#764#763#762#761#760#759. Base is quoted-macro; this diff is only the top commit.

What this changes

quoted! previously handled a plain closure. That left the interesting case — a per-instrument parameter — needing the manual three-line func! + .with_src() form, which is exactly backwards: the shorthand worked for the easy case and not for the one the generator exists to serve.

One rule now covers everything, marked in the argument list:

Write Records
map(|x| ..) the closure
map([fee] move |x| ..) the closure and the captured value
ticker(cfg period) the data config
fold(cfg 0u64, |a, v| ..) both
join(&other, |x, y| ..) the closure; &other is an edge, left alone

So a per-instrument leg reads without leaving the macro:

let ticks = quoted!(g => ticker(cfg inst.tick)).count();
let px    = quoted!(ticks => map(|n: &u64| *n as f64));
let net   = quoted!(px => map([fee] move |p: &f64| p - fee));

cfg also removes a redundancy worth naming on its own: ticker(p).with_cfg(&p) wrote the value twice, so it could be forgotten in one place or — worse — changed in one place.

How it was verified

  • cargo fmt --all
  • cargo lint ✅ — cargo lint-all not run: --all-features cannot build here (aeron needs CMake ≥3.30, box has 3.28; etcd needs protoc). Documented prerequisites, unrelated to this diff; CI is the first real check on that feature set.
  • --all-features blocked as above. Full default suite plus bench,async,csv,cache,augurs,market,ws,redis,postgres,kafka,web,kdb,prometheus,fix,dynamic-graph,tracing — green. Doctests green (16 passed).
  • New behaviour covered by tests

Twelve tests, one per arm plus the composite. Two assert the shorthand records byte-identically to the manual form, so it stays a convenience rather than a second mechanism. The last one builds a two-instrument desk and asserts no node with a closure config is left unrecorded — which is the property a user actually cares about, rather than any individual arm working.

Notes for the reviewer

Arm order is load-bearing, and the reason is a macro_rules! sharp edge. Each arm is discriminated by a literal token (cfg, [) before any fragment is parsed, and arms run most-specific first. macro_rules! falls through cleanly on a literal mismatch, but a failed $x:expr parse is a hard error, not a fallthrough — so an arm that could swallow another's input has to come second. The comment in the macro says this, because the ordering looks arbitrary otherwise and would be "tidied" into breakage.

A cfg-marked argument is cloned into the call so the original survives to be rendered. Every EmitLiteral type is Clone, and for the common Copy ones (Duration, numbers) the clone compiles away.

Still not detected: an undeclared capture. map(move |p| p - fee) without [fee] records a body that will not resolve in an artifact. It surfaces as a generator refusal — or, if the name happens to resolve to something else at the splice site, as a pass-2 compile error. That is the one remaining hole in the quotation story, and closing it needs the fn-pointer coercion §3 specifies, which cannot be written arity-generically.

One doctest caught me: the markdown pipe-escaping (\|) needed for the table leaked into the adjacent code block, so the example was invalid Rust until the doctest run caught it. Worth knowing if you add rows to that table.

Stack: nine PRs, none reviewed, eight chained on #759.


Generated by Claude Code

claude added 2 commits August 8, 2026 11:17
The previous arms handled a plain closure. That left the *interesting*
case — a per-instrument parameter — needing the manual three-line form,
which is exactly backwards: the shorthand worked for the easy case and not
for the one the generator exists to serve.

One rule now covers everything, marked in the argument list:

    map(|x| ..)                     records the closure
    map([fee] move |x| ..)          the closure and the captured value
    ticker(cfg period)              the data config
    fold(cfg 0u64, |a, v| ..)       both
    join(&other, |x, y| ..)         the closure; `&other` is an edge

So a per-instrument leg reads without leaving the macro:

    let ticks = quoted!(g => ticker(cfg inst.tick)).count();
    let px    = quoted!(ticks => map(|n: &u64| *n as f64));
    let net   = quoted!(px => map([fee] move |p: &f64| p - fee));

`cfg` also removes a redundancy worth naming: `ticker(p).with_cfg(&p)`
wrote the value twice, so it could be forgotten in one place or — worse —
changed in one place.

Two implementation notes:

- **Arm order is load-bearing.** Each arm is discriminated by a *literal*
  token (`cfg`, `[`) before any fragment is parsed, and the arms are
  ordered most-specific first. `macro_rules!` falls through cleanly on a
  literal mismatch, but a failed `$x:expr` parse is a hard error rather
  than a fallthrough — so an arm that could swallow another's input has to
  come second.
- A `cfg`-marked argument is **cloned** into the call so the original can
  be rendered. Every `EmitLiteral` type is `Clone`, and for the common
  `Copy` ones (`Duration`, numbers) the clone is free.

Still not detected: an *undeclared* capture. `map(move |p| p - fee)`
without `[fee]` records a body that will not resolve in an artifact. It
surfaces as a generator refusal — or, if the name happens to resolve to
something else at the splice site, as a pass-2 compile error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4eojqRWBJ6uK5v5JDzmkd
`ticker`'s parameter is `period: Duration` — an interval between synthetic
ticks. In a trading context a "tick" is a price increment (tick size) or a
single market-data update, and neither is a `Duration`, so an `Instrument`
field called `tick` reads as the wrong thing entirely to the audience most
likely to read these tests.

The doc comment also records why the field exists at all, which is the
more useful correction: it is there because `ticker` stands in for a
market-data feed. A real instrument config carries a symbol and a
subscription, and data arrives when it arrives. `external`/`channel`
sources are still excluded from compiled graphs, so the placeholder is
load-bearing for the test rather than representative of the domain — worth
saying, so nobody reads these fixtures as a suggested config shape.

Test-only rename plus a doc example; no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4eojqRWBJ6uK5v5JDzmkd

0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #769, and like #765 not carried forward — quoted! was retired rather than extended, for the reason given there.

Closing.


Generated by Claude Code

@0-jake-0 0-jake-0 closed this Aug 8, 2026
@0-jake-0
0-jake-0 deleted the quoted-full branch August 9, 2026 23:07
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