Skip to main content

nmbrs_runtime/scope_synth/
cascade_emit.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Cascade-extern emission helpers — building blocks the scope
5//! builders use to push individual lines of Polydat source into a
6//! string that's later folded into a [`polydat::kernel::subcontext::PolydatMatter`]
7//! and handed to the builder.
8//!
9//! These are the "build" half of walk + build + synthesize: the
10//! walker has decided that a particular name should be cascaded;
11//! these helpers do the per-name source-line emission consistent
12//! with nmbrs-runtime's Polydat conventions (chain-aware lookup, type
13//! widening rules, escape handling).
14
15use std::collections::HashSet;
16
17use crate::scope_kernel::ScopeKernel;
18
19use super::helpers::{format_workload_param_as_polydat_literal, value_to_param_string};
20
21/// Emit `const NAME := <literal>` for one workload param into
22/// `source`, choosing the literal value via the canonical
23/// chain-aware lookup order:
24///
25/// 1. `parent_kernel.lookup(name)` — if `Some`, this is the
26///    chain-resolved value. A `bindings:` / `set:` scope above
27///    us may have shadowed the workload-param default; whichever
28///    `const NAME := <override>` is closest in the chain wins
29///    via the local-final transit-suppression rule
30///    (SRD-13f §"Local-authoritative shadow"). If the lookup is
31///    non-scalar (vectors etc.), skip the chain path and fall
32///    back.
33/// 2. `hashmap_default` — the workload-load-time default from
34///    `params:` plus CLI overrides. Used when nothing in the
35///    chain has a value for `name`.
36///
37/// Pre-marks `name` in `emitted` so subsequent cascade passes in
38/// the caller skip it. Optionally appends to `inherited_names`
39/// for the inherited-output bookkeeping consumers also do.
40///
41/// **Why this helper exists**: pre-helper, seven distinct
42/// synthesizer sites each rolled their own `const NAME := <hashmap>`
43/// emission, reading the HashMap directly without consulting the
44/// chain. Any name a scenario-tree `set:` or `bindings:` scope
45/// shadowed kept the stale HashMap default inside any sub-scope
46/// synthesized via one of those sites — breaking the SRD-21 /
47/// SRD-18 contract that the kernel chain is the single
48/// resolution surface. Routing through this helper closes that
49/// hole categorically.
50pub fn emit_workload_param_chain_aware(
51    name: &str,
52    hashmap_default: &str,
53    parent_kernel: &ScopeKernel,
54    source: &mut String,
55    emitted: &mut HashSet<String>,
56    inherited_names: Option<&mut Vec<String>>,
57) {
58    if emitted.contains(name) {
59        return;
60    }
61    let value_str = parent_kernel
62        .lookup(name)
63        .and_then(|v| value_to_param_string(&v))
64        .unwrap_or_else(|| hashmap_default.to_string());
65    let literal = format_workload_param_as_polydat_literal(&value_str);
66    source.push_str(&format!("const {name} := {literal}\n"));
67    emitted.insert(name.to_string());
68    if let Some(inh) = inherited_names {
69        inh.push(name.to_string());
70    }
71}