Skip to content

skills/das_macros: document AST-match helpers, qmatch idiom, push cluster consolidation#2800

Merged
borisbat merged 1 commit into
masterfrom
bbatkin/das-macros-skill-helpers-doc
May 22, 2026
Merged

skills/das_macros: document AST-match helpers, qmatch idiom, push cluster consolidation#2800
borisbat merged 1 commit into
masterfrom
bbatkin/das-macros-skill-helpers-doc

Conversation

@borisbat
Copy link
Copy Markdown
Collaborator

Summary

Codifies the patterns established by PRs #2793-#2799 (the linq_fold / sqlite_linq / ast_match harvest ladder) so future macro work follows the documented forms instead of rediscovering them.

Three new sections:

  1. "Shared AST-match helpers" — table of 11 public helpers in daslib/ast_match.das + daslib/templates_boost.das (match_call_in_module, match_call_in_linq, peel_lambda_* 4 forms, peel_tuple_field_read, extract_const_string, qn, qm_peel_ref2value, push_block_list) with signatures + when-to-reach-for-each. Includes a "when patterns apply vs don't" note: introspection-heavy files (linq_fold, sqlite_linq, ast_match) benefit; emit-only files (decs_boost, the emitter half of templates_boost) don't.

  2. "qmatch — predicate-style pattern matching" — anti-pattern (hand-rolled is X / as X cascades) vs preferred predicate form. Documents that $e/$f/$v/$i tags bind to pre-declared outer variables, not result-struct fields (a subtle but easy-to-get-wrong API shape). Points to sqlite_linq.das for 37+ adoption sites + tests/ast_match/test_qmatch_*.das for grammar exercises.

  3. "Push cluster consolidation" (new subsection under "qmacro vs quote") — consecutive arr |> push <| qmacro_expr() { ... } runs into the same array collapse into one emission via either Form A (push_from + qmacro_block_to_array, preferred, no clone) or Form B (push_block_list + qmacro_block, clones, use when the source block stays alive). Includes "when NOT to collapse" guard.

One section updated:

  • "Peel ExprRef2Value before qmatch" now routes through qm_peel_ref2value (single source of truth) instead of showing the manual if-peel snippet. Adds note on why the helper still uses while-peel (conservative until ast_block_folding.cpp synthesis paths are audited).

PR 6 (decs_boost migration from the original plan) intentionally skipped:
Audit confirmed decs_boost has zero hand-rolled is_*_call helpers, zero qname construction, zero ExprRef2Value while-loops, zero push qmacro_expr clusters, and zero peel_lambda candidates. The file is mostly code-generation plumbing, not AST introspection — the migration would manufacture work. The "when patterns apply" note in the new helpers section codifies this finding so future audits start with the question instead of mechanical search.

Test plan

  • Read the doc cold — section flow is logical (probing → matching → emitting), helper signatures match daslib/ast_match.das:2199-2332 verbatim, qmatch example uses the actual var lhs, rhs : ExpressionPtr; qmatch(...).matched shape verified against tests/ast_match/test_capture_e.das
  • Anchor links resolve: #peel-exprref2value-before-qmatch and #push-cluster-consolidation are GitHub-flavored markdown auto-anchors that match the new section headings
  • All code samples are syntactically valid daslang gen2 (multi-var decl var lhs, rhs : ExpressionPtr exists in 5+ codebase sites incl. daslib/random.das:85)
  • No code/test impact — doc-only PR

🤖 Generated with Claude Code

…ush cluster consolidation

Codifies the patterns established by PRs #2793-#2799 so future macro work
follows the documented forms instead of rediscovering them.

Three new sections:
- "Shared AST-match helpers" — table of 11 public helpers in
  daslib/ast_match.das + daslib/templates_boost.das (match_call_in_module,
  match_call_in_linq, peel_lambda_*, peel_tuple_field_read,
  extract_const_string, qn, qm_peel_ref2value, push_block_list) with
  signatures + when-to-reach-for-each. Includes a "when patterns apply
  vs don't" note: introspection-heavy files (linq_fold, sqlite_linq,
  ast_match) benefit; emit-only files (decs_boost, the emitter half of
  templates_boost) don't.
- "qmatch — predicate-style pattern matching" — anti-pattern (hand-rolled
  is X / as X cascades) vs preferred predicate form with $e/$f/$v/$i tags
  bound to PRE-DECLARED outer variables (not result-struct fields).
  Documents the QMatchResult shape, points to sqlite_linq for 37+ adoption
  sites + tests/ast_match for grammar exercises.
- "Push cluster consolidation" (new subsection under "qmacro vs quote") —
  consecutive `arr |> push <| qmacro_expr() { ... }` runs into the same
  array collapse into a single emission via either Form A (push_from +
  qmacro_block_to_array, preferred, no clone) or Form B (push_block_list
  + qmacro_block, clones, use when the source block stays alive).
  Includes "when NOT to collapse" guard.

One section updated:
- "Peel ExprRef2Value before qmatch" now routes through qm_peel_ref2value
  (single source of truth) instead of showing the manual if-peel snippet.
  Adds note on why the helper still uses while-peel (conservative until
  ast_block_folding.cpp synthesis paths are audited).

PR 6 (decs_boost migration from the original ladder plan) intentionally
skipped: audit confirmed decs_boost has zero hand-rolled is_*_call
helpers, zero qname construction, zero ExprRef2Value while-loops, zero
push qmacro_expr clusters, and zero peel_lambda candidates. The file is
already lean — the migration would manufacture work.

+100 / -15 LOC. Doc-only; no code changes.
Copilot AI review requested due to automatic review settings May 22, 2026 01:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates skills/das_macros.md to document established macro/AST-introspection patterns, including shared AST-match helpers, the qmatch idiom, and how to consolidate consecutive qmacro push clusters.

Changes:

  • Added a “Shared AST-match helpers” section describing public helpers from daslib/ast_match.das and daslib/templates_boost.das.
  • Added a “qmatch — predicate-style pattern matching” section with recommended usage and tag explanations.
  • Added “Push cluster consolidation” guidance for collapsing consecutive qmacro_expr pushes, and updated the ExprRef2Value peeling section to reference qm_peel_ref2value.
Comments suppressed due to low confidence (1)

skills/das_macros.md:340

  • This subsection states that qmatch can't match through ExprRef2Value and that auto-peel inside qmatch is still TODO, but the matcher already peels ExprRef2Value on both source and pattern sides via qm_peel_ref2value emitted by generate_match (and has dedicated transparency tests). Please update this section to avoid sending readers toward unnecessary (or contradictory) manual peeling, and refocus it on cases where qm_peel_ref2value is needed outside qmatch (e.g., hand-written is/as probes).
Post-Mode-2-expansion AST walking will see field reads wrapped in `ExprRef2Value`. `qmatch` is RTTI-strict — it matches `ExprField` but not `ExprRef2Value(ExprField(...))`. **Route through `qm_peel_ref2value`** (the single source of truth in `daslib/ast_match.das`) instead of hand-rolling either a `while`-peel or an `if`-peel:

```das
require daslib/ast_match

qm_peel_ref2value(node)
if (node == null) {
    macro_error(prog, at, "_where: ExprRef2Value with null subexpr")
    return ""
}
// now `qmatch(node, _.$f(name))` etc. work as expected

qm_peel_ref2value currently uses while (e is ExprRef2Value) rather than single-if-peeling. The conservative loop is intentional until block-folding is fully audited — tests/ast_match/test_ref2value_skip.das exercises a triple-wrap shape, and src/ast/ast_block_folding.cpp synthesis paths could theoretically produce a nested wrapper. Once that audit lands, the helper switches to single-if peel in one place and every consumer follows automatically.

Auto-peel inside qmatch itself remains a TODO documented in daslib/ast_match.das. Until then, every analyzer entry point that takes an expression coming out of post-expansion (predicate body, projection body, classifier helpers like is_const_or_captured_var) needs the qm_peel_ref2value call at the top.

</details>



---

💡 <a href="/GaijinEntertainment/daScript/new/master?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.

Comment thread skills/das_macros.md
Comment on lines +118 to +125
| `qm_peel_ref2value` | `(var e : Expression?&) → void` | Single source of truth for `ExprRef2Value` peeling. Always call this instead of hand-rolling `while (... is ExprRef2Value)` or `if`-peel — see ["Peel ExprRef2Value before qmatch"](#peel-exprref2value-before-qmatch). |
| `push_block_list` | `(var stmts, var blockExpr)` in `daslib/templates_boost.das` | Splices every statement from a `qmacro_block(...)` result into `stmts`, cloning each. See ["Push cluster consolidation"](#push-cluster-consolidation). |

**When the patterns apply (and when they don't).** These helpers earn their keep in files that **probe AST shape** to route macro emission — `linq_fold`, `sqlite_linq`, `ast_match` itself. Files that only **emit code** without introspecting it — `decs_boost`, the emitter half of `templates_boost` — won't find adoption sites. Audit before mechanically searching: if a file has zero hand-rolled `is X / as X` call-cascades and zero qname construction, the patterns don't apply there.

## `qmatch` — predicate-style pattern matching

Prefer `qmatch(expr, <pattern>).matched` over hand-rolled `is X / as X` cascades when matching structural AST shapes. `qmatch` is RTTI-strict and won't traverse `ExprRef2Value` — peel first via `qm_peel_ref2value`.
Comment thread skills/das_macros.md
- `_` — anonymous wildcard (no bind)
- Concrete operators (`&&`, `||`, `+`, `==`, `<`, dot-field, function-call) and literals match literally

Result is `QMatchResult` with `.matched : bool` and `.error : QMatchError` — captured bindings live in the pre-declared outer variables, NOT on the result struct. On match failure the bindings are left untouched.
Comment thread skills/das_macros.md
| `peel_lambda_rename_2vars` | `(expr, a, b) → Expression?` | 2-arg form for `aggregate`-style `block<(acc, x) : AGG>` lambdas. Returns `null` on shape mismatch — caller decides fallback. |
| `peel_tuple_field_read` | `(expr, bindName, fieldIndex) → bool` | `true` when `expr` matches `<bindName>._<fieldIndex>` — tuple-slot read on a named bind. Single-level `ExprRef2Value` peel on each side. |
| `extract_const_string` | `(e) → tuple<bool; string>` | For `ExprConstString` returns `(true, value)`, else `(false, "")`. Use to consume compile-time string literals threaded through macro args. |
| `qn` | `(prefix, at) → string` | Synthesizes ``` `<prefix>`<at.line>`<at.column> ``` — qualified-name helper for macro-emitted locals. Deterministic per `(prefix, at)`; synthesized `LineInfo()` (line=0, col=0) WILL collide across distinct synth sites with the same prefix — build a synth-specific name if it matters. |
Comment thread skills/das_macros.md
@borisbat borisbat merged commit 766f15f into master May 22, 2026
29 of 31 checks passed
@borisbat borisbat deleted the bbatkin/das-macros-skill-helpers-doc branch May 30, 2026 15:21
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