1use std::sync::Arc;
21
22use polydat::Kernel;
23use polydat::ast::Value;
24use polydat::kernel::interp::{KernelLookup, Lookup};
25use polydat::kernel::{KernelProgram, PolydatKernel, PolydatProgram};
26
27use crate::fiber_engine::OpTemplateModule;
28
29pub struct ScopeKernel {
31 kernel: Box<dyn Kernel>,
32 program: Arc<PolydatProgram>,
34 image: Arc<dyn KernelProgram>,
36 module: Option<Arc<OpTemplateModule>>,
39}
40
41impl std::fmt::Debug for ScopeKernel {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("ScopeKernel")
44 .field("engine", &self.kernel.engine())
45 .field("outputs", &self.program.output_names())
46 .finish()
47 }
48}
49
50impl std::ops::Deref for ScopeKernel {
51 type Target = dyn Kernel;
52 fn deref(&self) -> &Self::Target {
53 self.kernel.as_ref()
54 }
55}
56
57impl std::ops::DerefMut for ScopeKernel {
58 fn deref_mut(&mut self) -> &mut Self::Target {
59 self.kernel.as_mut()
60 }
61}
62
63impl From<PolydatKernel> for ScopeKernel {
64 fn from(kernel: PolydatKernel) -> Self {
68 let program = kernel.program().clone();
69 Self {
70 image: program.clone(),
71 program,
72 kernel: Box::new(kernel),
73 module: None,
74 }
75 }
76}
77
78impl ScopeKernel {
79 pub fn root(interpreter: PolydatKernel, image: Option<Arc<dyn KernelProgram>>) -> Self {
84 match image {
85 Some(image) => Self {
86 program: interpreter.program().clone(),
87 kernel: image.clone().create_kernel(),
88 image,
89 module: None,
90 },
91 None => Self::from(interpreter),
92 }
93 }
94
95 pub fn compile(source: &str) -> Result<Self, String> {
98 crate::bindings::compile_scope_kernel(source, &Default::default())
99 }
100
101 pub fn build_under(parent: &dyn Kernel, matter: SourceMatter) -> Result<Self, String> {
107 use polydat::kernel::subcontext::{
108 ContractViolation, RootMarker, SourceContext, SubcontextBuilder,
109 };
110 let SourceMatter {
111 label,
112 body,
113 inherited_outputs,
114 options,
115 result_bindings,
116 } = matter;
117 let strict = options.strict;
118 let mut builder: SubcontextBuilder<RootMarker> = SubcontextBuilder::under(parent);
119 builder
120 .context(SourceContext::new(label.clone()))
121 .mark_inherited_outputs(inherited_outputs)
122 .with_compile_options(options)
123 .body(if strict {
124 declare_chain_coordinates(parent, body)
125 } else {
126 body
127 });
128 if let Some(src) = result_bindings {
129 builder
130 .add_result_bindings(&src)
131 .map_err(|e| e.to_string())?;
132 }
133 let module = Arc::new(builder.finalize().map_err(|e| e.to_string())?);
134 let program = module.program().clone();
135 let image = crate::fiber_engine::module_image(&module, &label)
136 .unwrap_or_else(|| program.clone() as Arc<dyn KernelProgram>);
137 let kernel = module
138 .instantiate_under(parent, image.engine(), &[])
139 .map_err(|e| e.to_string())?;
140 let scope = Self {
141 kernel,
142 program,
143 image,
144 module: Some(module),
145 };
146 if strict {
147 let fell_through = scope.silent_fall_throughs();
148 if !fell_through.is_empty() {
149 return Err(ContractViolation::StrictNonePropagation {
150 bindings: fell_through,
151 site: SourceContext::new(label),
152 }
153 .to_string());
154 }
155 }
156 Ok(scope)
157 }
158
159 pub fn synthesize_under(parent: &ScopeKernel, matter: SourceMatter) -> Result<Self, String> {
164 let mut child = Self::build_under(parent.kernel(), matter)?;
165 polydat::kernel::propagate_inputs(parent.kernel(), child.kernel_mut())
166 .map_err(|e| e.to_string())?;
167 Ok(child)
168 }
169
170 pub fn bind_under(
175 &self,
176 parent: &dyn Kernel,
177 bindings: &[(String, Value)],
178 ) -> Result<Self, String> {
179 let kernel = match &self.module {
180 Some(module) => module.instantiate_under(parent, self.image.engine(), bindings),
181 None => polydat::kernel::bind_under(parent, self.image.clone(), bindings),
182 }
183 .map_err(|e| e.to_string())?;
184 Ok(self.with_kernel(kernel))
185 }
186
187 pub fn fork(&self) -> Self {
191 self.with_kernel(self.kernel.fork())
192 }
193
194 fn with_kernel(&self, kernel: Box<dyn Kernel>) -> Self {
195 Self {
196 kernel,
197 program: self.program.clone(),
198 image: self.image.clone(),
199 module: self.module.clone(),
200 }
201 }
202
203 pub fn program(&self) -> &Arc<PolydatProgram> {
206 &self.program
207 }
208
209 pub fn image(&self) -> &Arc<dyn KernelProgram> {
211 &self.image
212 }
213
214 pub fn module(&self) -> Option<&Arc<OpTemplateModule>> {
217 self.module.as_ref()
218 }
219
220 pub fn kernel(&self) -> &dyn Kernel {
222 self.kernel.as_ref()
223 }
224
225 pub fn into_kernel(self) -> Box<dyn Kernel> {
228 self.kernel
229 }
230
231 pub fn kernel_mut(&mut self) -> &mut dyn Kernel {
233 self.kernel.as_mut()
234 }
235
236 pub fn lookup(&self, name: &str) -> Option<Value> {
241 KernelLookup::new(self.kernel.as_ref()).lookup(name)
242 }
243
244 pub fn pull_value(&self, name: &str) -> Option<Value> {
247 self.lookup(name).or_else(|| {
248 let idx = self.program.output_index(name)?;
249 Some(self.kernel.fork().pull_at(idx))
250 })
251 }
252
253 pub fn scope_values(&self) -> Vec<(String, Value)> {
258 self.program
259 .input_names()
260 .into_iter()
261 .enumerate()
262 .filter(|(i, _)| self.program.input_kind(*i) != Some(polydat::kernel::InputKind::Const))
263 .filter_map(|(i, name)| match self.kernel.input_value_at(i) {
264 Some(Value::None) | None => None,
265 Some(value) => Some((name, value)),
266 })
267 .collect()
268 }
269
270 fn silent_fall_throughs(&self) -> Vec<String> {
274 let mut probe: Option<Box<dyn Kernel>> = None;
275 let inits = self.kernel.const_inits();
276 self.program
277 .const_outputs()
278 .into_iter()
279 .filter(|name| {
280 let own = inits
281 .iter()
282 .find(|c| c.name == *name)
283 .map_or(*name, |c| c.source.as_str());
284 let Some(idx) = self.program.output_index(own) else {
285 return false;
286 };
287 let probe = probe.get_or_insert_with(|| self.kernel.fork());
288 matches!(probe.pull_at(idx), Value::None)
289 })
290 .map(str::to_string)
291 .collect()
292 }
293}
294
295pub trait IntoSharedScope {
298 fn into_shared_scope(self) -> Arc<ScopeKernel>;
300}
301
302impl IntoSharedScope for ScopeKernel {
303 fn into_shared_scope(self) -> Arc<ScopeKernel> {
304 Arc::new(self)
305 }
306}
307
308impl IntoSharedScope for Arc<ScopeKernel> {
309 fn into_shared_scope(self) -> Arc<ScopeKernel> {
310 self
311 }
312}
313
314impl IntoSharedScope for PolydatKernel {
315 fn into_shared_scope(self) -> Arc<ScopeKernel> {
316 Arc::new(ScopeKernel::from(self))
317 }
318}
319
320impl Lookup for ScopeKernel {
321 fn lookup(&self, name: &str) -> Option<Value> {
322 ScopeKernel::lookup(self, name)
323 }
324
325 fn ledger(&self) -> &Arc<polydat::kernel::CompileLedger> {
326 self.kernel.ledger()
327 }
328}
329
330pub struct SourceMatter {
333 pub label: String,
335 pub body: polydat::kernel::subcontext::BodyFragment,
337 pub inherited_outputs: Vec<String>,
339 pub options: polydat::kernel::subcontext::CompileOptions,
341 pub result_bindings: Option<String>,
343}
344
345impl SourceMatter {
346 pub fn source(
348 label: impl Into<String>,
349 source: impl Into<String>,
350 options: polydat::kernel::subcontext::CompileOptions,
351 ) -> Self {
352 Self {
353 label: label.into(),
354 body: polydat::kernel::subcontext::BodyFragment::PolydatSource(source.into()),
355 inherited_outputs: Vec::new(),
356 options,
357 result_bindings: None,
358 }
359 }
360
361 pub fn statements(
363 label: impl Into<String>,
364 statements: Vec<polydat::dsl::ast::Statement>,
365 options: polydat::kernel::subcontext::CompileOptions,
366 ) -> Self {
367 Self {
368 label: label.into(),
369 body: polydat::kernel::subcontext::BodyFragment::Statements(statements),
370 inherited_outputs: Vec::new(),
371 options,
372 result_bindings: None,
373 }
374 }
375
376 pub fn inherited(mut self, names: Vec<String>) -> Self {
378 self.inherited_outputs = names;
379 self
380 }
381
382 pub fn results(mut self, source: impl Into<String>) -> Self {
384 self.result_bindings = Some(source.into());
385 self
386 }
387}
388
389fn declare_chain_coordinates(
397 parent: &dyn Kernel,
398 body: polydat::kernel::subcontext::BodyFragment,
399) -> polydat::kernel::subcontext::BodyFragment {
400 use polydat::kernel::subcontext::BodyFragment;
401 let BodyFragment::PolydatSource(source) = body else {
402 return body;
403 };
404 let coords = parent.coord_count();
405 if coords == 0 || source.lines().any(|l| l.trim_start().starts_with("input ")) {
406 return BodyFragment::PolydatSource(source);
407 }
408 let declarations: String = parent.input_names()[..coords]
409 .iter()
410 .map(|name| format!("input {name}: u64\n"))
411 .collect();
412 let pragmas: String = source
413 .lines()
414 .take_while(|l| l.trim_start().starts_with("pragma ") || l.trim().is_empty())
415 .map(|l| format!("{l}\n"))
416 .collect();
417 let rest: String = source
418 .lines()
419 .skip(pragmas.lines().count())
420 .map(|l| format!("{l}\n"))
421 .collect();
422 BodyFragment::PolydatSource(format!("{pragmas}{declarations}{rest}"))
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use polydat::kernel::subcontext::BodyFragment;
429
430 fn source(body: BodyFragment) -> String {
431 match body {
432 BodyFragment::PolydatSource(s) => s,
433 _ => panic!("expected source"),
434 }
435 }
436
437 #[test]
438 fn strict_declares_the_parents_coordinates_after_any_pragmas() {
439 let parent = ScopeKernel::compile("input cycle: u64\nx := 1\n").expect("parent");
440 let body = BodyFragment::PolydatSource("pragma strict\ny := cycle\n".into());
441 assert_eq!(
442 source(declare_chain_coordinates(parent.kernel(), body)),
443 "pragma strict\ninput cycle: u64\ny := cycle\n"
444 );
445 }
446
447 #[test]
448 fn a_body_that_declares_its_inputs_is_left_alone() {
449 let parent = ScopeKernel::compile("input cycle: u64\nx := 1\n").expect("parent");
450 let own = "input n: u64\ny := n\n";
451 let body = BodyFragment::PolydatSource(own.into());
452 assert_eq!(
453 source(declare_chain_coordinates(parent.kernel(), body)),
454 own
455 );
456 }
457}