use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use crate::ast::PortType;
use crate::dsl::ast::{Arg, CallExpr, Expr, ExternPort, PolydatFile, Statement};
use crate::dsl::compile::{CompileOptions as DslOptions, compile_ast_interpreter_with_options};
use crate::dsl::lexer::{Span, lex};
use crate::dsl::parser::parse;
use crate::kernel::PolydatKernel;
use super::error::{ContractViolation, SourceContext};
use super::kernel::{Child, SharedCellInScope};
use super::module::{BodyFragment, ScopeContract, ScopeModule, WriteThroughBinding};
use super::pull::{PullConsumer, RegisteredPullConsumer};
use super::spec::{ExportSpec, ImportSpec};
const WRITE_THROUGH_PREFIX: &str = "__write_";
fn port_type_keyword(pt: PortType) -> &'static str {
match pt {
PortType::U64 | PortType::U32 => "u64",
PortType::I64 | PortType::I32 => "i64",
PortType::F64 | PortType::F32 => "f64",
PortType::Bool => "bool",
_ => "String",
}
}
#[derive(Clone, Debug, Default)]
pub struct CompileOptions {
pub workload_dir: Option<PathBuf>,
pub polydat_lib_paths: Vec<PathBuf>,
pub strict: bool,
pub required_outputs: Vec<String>,
pub context_label: Option<String>,
pub cursor_limit: Option<u64>,
pub kernel_opt: crate::kernel::KernelOptLevel,
}
impl CompileOptions {
fn is_default(&self) -> bool {
self.workload_dir.is_none()
&& self.polydat_lib_paths.is_empty()
&& !self.strict
&& self.required_outputs.is_empty()
&& self.context_label.is_none()
&& self.cursor_limit.is_none()
&& self.kernel_opt == crate::kernel::KernelOptLevel::default()
}
}
#[derive(Clone)]
pub struct ParentView {
output_names: Vec<String>,
input_names: Vec<String>,
output_modifiers: std::collections::HashMap<String, crate::dsl::ast::BindingModifier>,
ledger: Arc<crate::kernel::CompileLedger>,
shared_cells: Vec<SharedCellInScope>,
}
impl ParentView {
pub fn of_kernel(parent: &dyn crate::kernel::Kernel) -> Self {
let output_names: Vec<String> = parent.output_names();
let output_modifiers = output_names
.iter()
.map(|n| (n.clone(), parent.output_modifier(n)))
.collect();
Self {
output_names,
input_names: parent.input_names(),
output_modifiers,
ledger: crate::kernel::Kernel::ledger(parent).clone(),
shared_cells: Self::cells_of(parent.cells_in_scope()),
}
}
pub fn of(parent: &PolydatKernel) -> Self {
Self::of_kernel(parent)
}
pub fn output_names(&self) -> &[String] {
&self.output_names
}
pub fn input_names(&self) -> &[String] {
&self.input_names
}
pub fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
self.output_modifiers
.get(name)
.copied()
.unwrap_or(crate::dsl::ast::BindingModifier::NONE)
}
pub fn ledger(&self) -> &Arc<crate::kernel::CompileLedger> {
&self.ledger
}
fn cells_of(entries: Vec<crate::kernel::SharedCellEntry>) -> Vec<SharedCellInScope> {
entries
.into_iter()
.map(|e| SharedCellInScope {
name: e.name,
port_type: e.port_type,
cell: e.cell,
})
.collect()
}
pub fn shared_cells(&self) -> &[SharedCellInScope] {
&self.shared_cells
}
}
pub struct SubcontextBuilder<P> {
parent: ParentView,
imports: Vec<ImportSpec>,
exports: Vec<ExportSpec>,
body: Vec<BodyFragment>,
consumers: Vec<RegisteredPullConsumer>,
context: SourceContext,
inherited_outputs: Vec<String>,
compile_options: CompileOptions,
_parent_marker: PhantomData<fn() -> P>,
}
impl<P> SubcontextBuilder<P> {
pub(crate) fn new(parent: ParentView) -> Self {
Self {
parent,
_parent_marker: PhantomData,
imports: Vec::new(),
exports: Vec::new(),
body: Vec::new(),
consumers: Vec::new(),
context: SourceContext::default(),
inherited_outputs: Vec::new(),
compile_options: CompileOptions::default(),
}
}
pub fn with_compile_options(&mut self, options: CompileOptions) -> &mut Self {
self.compile_options = options;
self
}
pub fn mark_inherited_outputs(&mut self, names: Vec<String>) -> &mut Self {
self.inherited_outputs = names;
self
}
pub fn parent(&self) -> &ParentView {
&self.parent
}
pub fn import(&mut self, spec: ImportSpec) -> &mut Self {
self.imports.push(spec);
self
}
pub fn export(&mut self, spec: ExportSpec) -> &mut Self {
self.exports.push(spec);
self
}
pub fn body(&mut self, fragment: BodyFragment) -> &mut Self {
self.body.push(fragment);
self
}
pub fn context(&mut self, ctx: SourceContext) -> &mut Self {
self.context = ctx;
self
}
pub fn register_pull(&mut self, consumer: Arc<dyn PullConsumer>) -> &mut Self {
self.consumers.push(RegisteredPullConsumer::new(consumer));
self
}
pub fn add_result_bindings(&mut self, source: &str) -> Result<&mut Self, ContractViolation> {
let trimmed = source.trim();
if trimmed.is_empty() {
return Ok(self);
}
let tokens = lex(source).map_err(|e| ContractViolation::Compile(e.to_string()))?;
let file = parse(tokens).map_err(|e| ContractViolation::Compile(e.to_string()))?;
let mut local_decls: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut result_lhs: Vec<String> = Vec::new();
for stmt in &file.statements {
match stmt {
Statement::Binding(b) => {
for t in &b.targets {
local_decls.insert(t.clone());
if !result_lhs.contains(t) {
result_lhs.push(t.clone());
}
}
}
Statement::ExternPort(ep) => {
local_decls.insert(ep.name.clone());
}
Statement::InputDecl(d) => {
local_decls.insert(d.name.clone());
}
_ => {}
}
}
let mut free_idents: std::collections::HashSet<String> = std::collections::HashSet::new();
for stmt in &file.statements {
collect_free_idents(stmt, &mut free_idents);
}
for forbidden in ["body", "count", "ok"] {
if result_lhs.iter().any(|n| n == forbidden) {
return Err(ContractViolation::Compile(format!(
"result-bindings: '{forbidden}' is a runtime-injected wire and \
cannot be reassigned in `result:`. SRD-66 Surface 1 §Schema."
)));
}
}
let magic_externs: &[(&str, PortType, &str)] = &[
("body", PortType::Json, "Json"),
("count", PortType::U64, "u64"),
("ok", PortType::Bool, "bool"),
];
let span0 = Span { line: 0, col: 0 };
let mut prepended: Vec<Statement> = Vec::new();
let force_all = self.compile_options.kernel_opt.keep_unreferenced_slots();
for (name, _pt, type_kw) in magic_externs {
let referenced = free_idents.contains(*name);
let already_local = local_decls.contains(*name);
if (force_all || referenced) && !already_local {
prepended.push(Statement::ExternPort(ExternPort {
name: (*name).to_string(),
typ: (*type_kw).to_string(),
default: None,
span: span0,
}));
}
}
{
let in_scope_cells = self.parent.shared_cells();
let parent_shared_by_name: std::collections::HashMap<&str, PortType> = in_scope_cells
.iter()
.map(|c| (c.name.as_str(), c.port_type))
.collect();
for name in &result_lhs {
if let Some(&pt) = parent_shared_by_name.get(name.as_str()) {
self.exports.push(ExportSpec::shared(name.clone(), pt));
}
}
}
let mut combined: Vec<Statement> = prepended;
combined.extend(file.statements);
self.body.push(BodyFragment::Statements(combined));
Ok(self)
}
pub fn finalize(self) -> Result<ScopeModule<Child<P>>, ContractViolation> {
let SubcontextBuilder {
parent,
imports,
exports,
body,
consumers,
context,
inherited_outputs,
compile_options,
_parent_marker,
} = self;
let mut diagnostics: Vec<String> = Vec::new();
let parent_outputs: std::collections::HashSet<&String> =
parent.output_names().iter().collect();
let parent_inputs: std::collections::HashSet<&String> =
parent.input_names().iter().collect();
for imp in &imports {
if !parent_outputs.contains(&imp.name) && !parent_inputs.contains(&imp.name) {
return Err(ContractViolation::UnboundImport {
import: imp.name.clone(),
site: context.clone(),
});
}
}
let in_scope_cells = parent.shared_cells();
let in_scope_cells_by_name: std::collections::HashMap<&str, &SharedCellInScope> =
in_scope_cells
.iter()
.map(|c| (c.name.as_str(), c))
.collect();
let ledger = parent.ledger().clone();
let mut write_through_specs: Vec<(String, PortType)> = Vec::new();
for exp in &exports {
let parent_modifier = parent.output_modifier(&exp.name);
if parent_modifier.is_const() && parent_outputs.contains(&exp.name) {
return Err(ContractViolation::FinalShadow {
export: exp.name.clone(),
site: context.clone(),
});
}
if let Some(in_scope) = in_scope_cells_by_name.get(exp.name.as_str()) {
write_through_specs.push((exp.name.clone(), in_scope.port_type));
}
}
if body.is_empty() {
return Err(ContractViolation::Compile(
"scope module body is empty — at least one fragment is required".into(),
));
}
let mut statements: Vec<Statement> = Vec::new();
for fragment in &body {
match fragment {
BodyFragment::PolydatSource(src) => {
let tokens = lex(src).map_err(|e| ContractViolation::Compile(e.to_string()))?;
let file =
parse(tokens).map_err(|e| ContractViolation::Compile(e.to_string()))?;
statements.extend(file.statements);
}
BodyFragment::Statements(stmts) => statements.extend(stmts.iter().cloned()),
}
}
let mut write_throughs: Vec<WriteThroughBinding> = Vec::new();
if !write_through_specs.is_empty() {
let already_extern: std::collections::HashSet<String> = statements
.iter()
.filter_map(|s| match s {
Statement::ExternPort(p) => Some(p.name.clone()),
_ => None,
})
.collect();
let span0 = Span { line: 0, col: 0 };
let mut prepended: Vec<Statement> = Vec::new();
for (name, pt) in &write_through_specs {
if already_extern.contains(name) {
continue;
}
prepended.push(Statement::ExternPort(ExternPort {
name: name.clone(),
typ: port_type_keyword(*pt).to_string(),
default: None,
span: span0,
}));
}
for stmt in statements.iter_mut() {
if let Statement::Binding(b) = stmt
&& b.targets.len() == 1
{
let target = &b.targets[0];
if write_through_specs.iter().any(|(n, _)| n == target) {
let original = target.clone();
let renamed = format!("{WRITE_THROUGH_PREFIX}{original}");
b.targets[0] = renamed.clone();
write_throughs.push(WriteThroughBinding {
export_name: original,
source_output: renamed,
});
}
}
}
prepended.extend(statements);
statements = prepended;
}
let dsl_options = DslOptions {
source_dir: compile_options.workload_dir.clone(),
lib_paths: compile_options.polydat_lib_paths.clone(),
required_outputs: compile_options.required_outputs.clone(),
strict: compile_options.strict,
context: compile_options
.context_label
.clone()
.unwrap_or_else(|| context.label.clone()),
cursor_limit: compile_options.cursor_limit,
ledger: Some(ledger.clone()),
engine: crate::Engine::default(),
};
let mut kernel = if compile_options.is_default() {
compile_ast_interpreter_with_options(
&PolydatFile {
statements: statements.clone(),
},
"",
&DslOptions {
ledger: Some(ledger),
..DslOptions::default()
},
None,
)
.map_err(|e| ContractViolation::Compile(e.to_string()))?
} else if !write_throughs.is_empty()
|| body
.iter()
.any(|f| matches!(f, BodyFragment::Statements(_)))
{
compile_ast_interpreter_with_options(
&PolydatFile {
statements: statements.clone(),
},
"",
&dsl_options,
None,
)
.map_err(|e| ContractViolation::Compile(e.to_string()))?
} else {
let mut src = String::new();
for fragment in &body {
match fragment {
BodyFragment::PolydatSource(s) => {
src.push_str(s);
if !s.ends_with('\n') {
src.push('\n');
}
}
BodyFragment::Statements(_) => unreachable!(
"Statements fragments routed through compile_ast_with_libs above"
),
}
}
crate::dsl::compile::compile_polydat_interpreter_with_options(&src, &dsl_options, None)
.map_err(|e| ContractViolation::Compile(e.to_string()))?
};
if !inherited_outputs.is_empty() {
kernel.mark_inherited_outputs(inherited_outputs);
}
let kernel_write_throughs: Vec<crate::kernel::KernelWriteThrough> = write_throughs
.iter()
.map(|wt| crate::kernel::KernelWriteThrough {
export_name: wt.export_name.clone(),
source_output: wt.source_output.clone(),
})
.collect();
if !kernel_write_throughs.is_empty() {
kernel.bake_write_throughs(kernel_write_throughs);
}
for imp in &imports {
if kernel.program().find_input(&imp.name).is_none()
&& kernel.program().output_map_lookup(&imp.name).is_none()
{
diagnostics.push(format!(
"import `{}` declared but unused in body — Rule 5 closure-binding economy will drop it at spawn",
imp.name
));
}
}
for wt in &write_throughs {
if kernel.program().find_input(&wt.export_name).is_none() {
return Err(ContractViolation::Compile(format!(
"Rule 2 write-through rewrite for `{}` produced no input slot — \
check that the body's binding compiled to an input/output pair",
wt.export_name
)));
}
if kernel
.program()
.output_map_lookup(&wt.source_output)
.is_none()
{
return Err(ContractViolation::Compile(format!(
"Rule 2 write-through rewrite produced no `{}` output — \
the rewritten binding did not surface as a kernel output",
wt.source_output
)));
}
}
let program = kernel.program().clone();
let contract = ScopeContract::from_specs(&imports, &exports);
let seeded: std::sync::Arc<dyn crate::kernel::KernelProgram> = program.clone();
Ok(ScopeModule {
imports,
exports,
program,
statements,
options: dsl_options.clone(),
programs: std::sync::Mutex::new(std::collections::HashMap::from([(
crate::Engine::Interpreter(crate::JitMode::Auto),
seeded,
)])),
contract,
context,
consumers,
write_throughs,
diagnostics,
_module: PhantomData,
})
}
}
fn collect_free_idents(stmt: &Statement, out: &mut std::collections::HashSet<String>) {
match stmt {
Statement::Binding(b) => collect_expr_idents(&b.value, out),
Statement::Cursor(c) => collect_expr_idents(&c.constructor, out),
Statement::ModuleDef(_)
| Statement::ExternPort(_)
| Statement::InputDecl(_)
| Statement::Pragma { .. }
| Statement::For(_)
| Statement::Tile(_) => {}
}
}
fn collect_expr_idents(expr: &Expr, out: &mut std::collections::HashSet<String>) {
match expr {
Expr::Ident(name, _) => {
out.insert(name.clone());
}
Expr::IntLit(_, _) | Expr::FloatLit(_, _) => {}
Expr::StringLit(_, _) => {
}
Expr::ArrayLit(items, _) => {
for e in items {
collect_expr_idents(e, out);
}
}
Expr::Call(call) => collect_call_idents(call, out),
Expr::BinOp(a, _, b) => {
collect_expr_idents(a, out);
collect_expr_idents(b, out);
}
Expr::For(_) => {}
Expr::UnaryNeg(e, _) | Expr::UnaryBitNot(e, _) | Expr::Cast(e, _, _) => {
collect_expr_idents(e, out)
}
Expr::FieldAccess { source, .. } => {
out.insert(source.clone());
}
}
}
fn collect_call_idents(call: &CallExpr, out: &mut std::collections::HashSet<String>) {
for arg in &call.args {
match arg {
Arg::Positional(e) => collect_expr_idents(e, out),
Arg::Named(_, e) => collect_expr_idents(e, out),
}
}
}