Skip to main content

polydat_core/kernel/subcontext/
module.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! [`ScopeModule<M>`] — closed, immutable module-matter artifact.
5//!
6//! Per SRD-67 §"Step 3 — Artifact is a closed value": the
7//! artifact carries everything the parent needs to spawn — type
8//! contracts, the compiled program, registered pull consumers —
9//! but holds no live reference to the parent. The artifact can
10//! be moved, hashed, debug-printed, and (per Phase 4) cached for
11//! reuse.
12
13use std::marker::PhantomData;
14use std::sync::Arc;
15
16use crate::dsl::ast::Statement;
17use crate::kernel::PolydatProgram;
18
19use super::error::SourceContext;
20use super::pull::RegisteredPullConsumer;
21use super::spec::{ExportSpec, ImportSpec};
22
23/// Body fragment — what the builder accepts via
24/// [`super::SubcontextBuilder::body`].
25///
26/// Per SRD-67 §"Decision 4". `PolydatSource` is for user-facing
27/// `bindings:` / `result:` content (parsed at finalize);
28/// `Statements` is for synthesisers that already produce GK
29/// programmatically.
30#[derive(Debug, Clone)]
31pub enum BodyFragment {
32    /// User-facing Polydat source. Parsed via the existing
33    /// `lexer + parser` pipeline at finalize.
34    PolydatSource(String),
35    /// Pre-parsed statements — submitted directly without
36    /// round-tripping through Polydat source strings. Reuses
37    /// [`Statement`] from the existing AST, so synthesisers
38    /// don't carry a parallel enum.
39    Statements(Vec<Statement>),
40}
41
42/// Typed handle bundle (per SRD-13e §1.2).
43///
44/// The bundle is minimal by design: a handle for each declared
45/// import / export, identified by name. Slot resolution happens
46/// against the compiled program (`find_input` /
47/// `output_map_lookup`) rather than through cached indices here.
48///
49/// `M` is the module-identity phantom — [`super::Child<P>`] for
50/// modules built under parent `P`. Handles issued by one module
51/// can't be applied to a sibling at the type level.
52pub struct ScopeContract<M> {
53    imports: Vec<ImportHandle<M>>,
54    exports: Vec<ExportHandle<M>>,
55    _module: PhantomData<fn() -> M>,
56}
57
58impl<M> ScopeContract<M> {
59    pub(crate) fn from_specs(imports: &[ImportSpec], exports: &[ExportSpec]) -> Self {
60        Self {
61            imports: imports
62                .iter()
63                .map(|s| ImportHandle {
64                    name: s.name.clone(),
65                    _module: PhantomData,
66                })
67                .collect(),
68            exports: exports
69                .iter()
70                .map(|s| ExportHandle {
71                    name: s.name.clone(),
72                    _module: PhantomData,
73                })
74                .collect(),
75            _module: PhantomData,
76        }
77    }
78
79    /// The imports the module declares, in declaration order.
80    pub fn imports(&self) -> &[ImportHandle<M>] {
81        &self.imports
82    }
83
84    /// The exports the module declares, in declaration order.
85    pub fn exports(&self) -> &[ExportHandle<M>] {
86        &self.exports
87    }
88}
89
90impl<M> std::fmt::Debug for ScopeContract<M> {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_struct("ScopeContract")
93            .field("imports", &self.imports)
94            .field("exports", &self.exports)
95            .finish()
96    }
97}
98
99/// A typed handle to a named import slot. Brand `M` ties it to
100/// the module that issued it.
101pub struct ImportHandle<M> {
102    name: String,
103    _module: PhantomData<fn() -> M>,
104}
105
106impl<M> ImportHandle<M> {
107    pub fn name(&self) -> &str {
108        &self.name
109    }
110}
111
112impl<M> std::fmt::Debug for ImportHandle<M> {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.debug_tuple("ImportHandle").field(&self.name).finish()
115    }
116}
117
118/// A typed handle to a named export slot. Brand `M` ties it to
119/// the module that issued it.
120pub struct ExportHandle<M> {
121    name: String,
122    _module: PhantomData<fn() -> M>,
123}
124
125impl<M> ExportHandle<M> {
126    pub fn name(&self) -> &str {
127        &self.name
128    }
129}
130
131impl<M> std::fmt::Debug for ExportHandle<M> {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.debug_tuple("ExportHandle").field(&self.name).finish()
134    }
135}
136
137/// A Rule 2 write-through binding produced by the builder when a
138/// child's `X := <expr>` collides with a parent `shared X`
139/// export. The child's compiled program produces a synthetic
140/// output named [`Self::source_output`] (typically
141/// `__write_<X>`); spawn wires the parent's `SharedCell` into
142/// the child's `X` input slot via `materialize_wiring_from_outer`. After
143/// per-cycle eval, [`super::ScopeKernel::commit_write_throughs`]
144/// pulls the synthetic output and stores its value through the
145/// child's input slot, which propagates to the cell.
146#[derive(Debug, Clone)]
147pub struct WriteThroughBinding {
148    /// The name as declared on the parent (and as the child sees
149    /// it via `extern`). The parent's shared cell is keyed on
150    /// this name.
151    pub export_name: String,
152    /// The synthetic output the rewrite emits in the child
153    /// program — its `pull` produces the value to write through.
154    pub source_output: String,
155}
156
157/// Closed, immutable module-matter artifact.
158///
159/// Produced by [`super::SubcontextBuilder::finalize`]; consumed
160/// by [`super::ScopeKernel::spawn`]. The artifact carries no
161/// live reference to its parent — it can be stored, hashed,
162/// inspected, or moved freely.
163pub struct ScopeModule<M> {
164    pub(crate) imports: Vec<ImportSpec>,
165    pub(crate) exports: Vec<ExportSpec>,
166    pub(crate) program: Arc<PolydatProgram>,
167    pub(crate) contract: ScopeContract<M>,
168    pub(crate) context: SourceContext,
169    pub(crate) consumers: Vec<RegisteredPullConsumer>,
170    /// Rule 2 write-through bindings — child exports rewritten
171    /// at finalize to feed parent `shared` cells. Empty for the
172    /// vast majority of modules; populated only when the parent
173    /// has a `shared` export with a name the child redefines.
174    pub(crate) write_throughs: Vec<WriteThroughBinding>,
175    /// Diagnostics emitted during finalize (warnings, etc.). Free-
176    /// form strings; downstream tooling can surface them.
177    pub(crate) diagnostics: Vec<String>,
178    pub(crate) _module: PhantomData<fn() -> M>,
179}
180
181impl<M> ScopeModule<M> {
182    /// The imports the module declares.
183    pub fn imports(&self) -> &[ImportSpec] {
184        &self.imports
185    }
186
187    /// The exports the module declares.
188    pub fn exports(&self) -> &[ExportSpec] {
189        &self.exports
190    }
191
192    /// The body's compiled program.
193    pub fn program(&self) -> &Arc<PolydatProgram> {
194        &self.program
195    }
196
197    /// The contract the module was built against.
198    pub fn contract(&self) -> &ScopeContract<M> {
199        &self.contract
200    }
201
202    /// Where the module comes from.
203    pub fn context(&self) -> &SourceContext {
204        &self.context
205    }
206
207    /// The pull consumers registered on the module's outputs.
208    pub fn consumers(&self) -> &[RegisteredPullConsumer] {
209        &self.consumers
210    }
211
212    /// Diagnostics the build recorded.
213    pub fn diagnostics(&self) -> &[String] {
214        &self.diagnostics
215    }
216
217    /// Rule 2 write-through bindings produced by the builder for
218    /// child exports that collide with a parent `shared` export.
219    pub fn write_throughs(&self) -> &[WriteThroughBinding] {
220        &self.write_throughs
221    }
222}
223
224impl<M> std::fmt::Debug for ScopeModule<M> {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        f.debug_struct("ScopeModule")
227            .field("imports", &self.imports)
228            .field("exports", &self.exports)
229            .field("context", &self.context)
230            .field("consumer_count", &self.consumers.len())
231            .field("write_throughs", &self.write_throughs)
232            .field("diagnostics", &self.diagnostics)
233            .finish()
234    }
235}