Skip to main content

harn_kernel/compiler/
callable_entry.rs

1use std::sync::Arc;
2
3use harn_parser::{Node, SNode};
4
5use super::{
6    ensure_chunk_addressable, peel_node, CompileError, CompiledCallableBatch,
7    CompiledCallableEntry, Compiler,
8};
9use crate::chunk::Op;
10
11impl Compiler {
12    /// Compile a named pipeline for invocation with explicit host values.
13    pub fn compile_named_pipeline_entry(
14        self,
15        program: &[SNode],
16        pipeline_name: &str,
17        fixture_name: Option<&str>,
18    ) -> Result<CompiledCallableEntry, CompileError> {
19        self.compile_named_pipeline_entries(program, &[(pipeline_name, fixture_name)])?
20            .pop()
21            .ok_or_else(|| CompileError {
22                message: "named pipeline entry request was empty".to_string(),
23                line: 0,
24            })?
25    }
26
27    /// Compile several named pipelines while lowering the file's imports and
28    /// top-level declarations exactly once.
29    ///
30    /// Each returned bootstrap remains an immutable, self-contained artifact;
31    /// runtimes can therefore instantiate it in a fresh VM without sharing
32    /// module state between invocations. The request and result orders match.
33    pub fn compile_named_pipeline_entries(
34        self,
35        program: &[SNode],
36        entries: &[(&str, Option<&str>)],
37    ) -> Result<Vec<Result<CompiledCallableEntry, CompileError>>, CompileError> {
38        self.compile_named_callable_entries(program, entries, &[])
39            .map(|batch| batch.pipelines)
40    }
41
42    /// Compile pipeline and function entry artifacts through one shared file
43    /// lowering. This is the owning batch boundary for hosts that need several
44    /// independently executable callables from the same source module.
45    pub fn compile_named_callable_entries(
46        mut self,
47        program: &[SNode],
48        pipelines: &[(&str, Option<&str>)],
49        functions: &[&str],
50    ) -> Result<CompiledCallableBatch, CompileError> {
51        if pipelines.is_empty() && functions.is_empty() {
52            return Ok(CompiledCallableBatch {
53                pipelines: Vec::new(),
54                functions: Vec::new(),
55            });
56        }
57        self.prepare_module_context(program);
58        self.compile_entry_imports(program)?;
59        self.compile_top_level_declarations(program)?;
60
61        let base_chunk = self.chunk.clone();
62        let base_string_constants = self.string_constants.clone();
63        let mut compiled_pipelines = Vec::with_capacity(pipelines.len());
64        for (pipeline_name, fixture_name) in pipelines {
65            self.chunk = base_chunk.clone();
66            self.string_constants.clone_from(&base_string_constants);
67            compiled_pipelines.push(self.finish_named_pipeline_entry(
68                program,
69                pipeline_name,
70                *fixture_name,
71            ));
72        }
73        let mut compiled_functions = Vec::with_capacity(functions.len());
74        for function_name in functions {
75            self.chunk = base_chunk.clone();
76            self.string_constants.clone_from(&base_string_constants);
77            compiled_functions.push(self.finish_named_function_entry(program, function_name));
78        }
79        Ok(CompiledCallableBatch {
80            pipelines: compiled_pipelines,
81            functions: compiled_functions,
82        })
83    }
84
85    fn finish_named_pipeline_entry(
86        &mut self,
87        program: &[SNode],
88        pipeline_name: &str,
89        fixture_name: Option<&str>,
90    ) -> Result<CompiledCallableEntry, CompileError> {
91        let fixture_expects_harness = fixture_name.is_some_and(|fixture_name| {
92            program.iter().any(|node| {
93                matches!(
94                    peel_node(node),
95                    Node::FnDecl { name, params, .. }
96                        if name == fixture_name
97                            && params.first().is_some_and(|param| matches!(
98                                param.type_expr.as_ref(),
99                                Some(harn_parser::TypeExpr::Named(name)) if name == "Harness"
100                            ))
101                )
102            })
103        });
104        if let Some(fixture_name) = fixture_name {
105            let fixture = self.string_constant(fixture_name);
106            self.chunk.emit_u16(Op::GetVar, fixture, self.line);
107        }
108        let target = program.iter().find(
109            |node| matches!(peel_node(node), Node::Pipeline { name, .. } if name == pipeline_name),
110        );
111        let Some(target) = target else {
112            return Err(CompileError {
113                message: format!("Unknown pipeline: {pipeline_name}"),
114                line: self.line,
115            });
116        };
117        let Node::Pipeline {
118            name,
119            body,
120            extends,
121            params,
122            throws,
123            ..
124        } = peel_node(target)
125        else {
126            unreachable!("pipeline target was matched above");
127        };
128        let expects_harness = params.first().is_some_and(|param| {
129            matches!(
130                param.type_expr.as_ref(),
131                Some(harn_parser::TypeExpr::Named(name)) if name == "Harness"
132            )
133        });
134        let callable = self.compile_pipeline_callable(
135            program,
136            name,
137            params,
138            body,
139            extends.as_deref(),
140            throws.as_ref(),
141        )?;
142        let function_index = self.chunk.functions.len();
143        self.chunk.functions.push(Arc::new(callable));
144        self.chunk
145            .emit_u16(Op::Closure, function_index as u16, self.line);
146        if fixture_name.is_some() {
147            self.chunk.emit_u16(Op::BuildList, 2, self.line);
148        }
149        self.chunk.emit(Op::Return, self.line);
150        ensure_chunk_addressable(&self.chunk, "the callable entry", self.line)?;
151        Ok(CompiledCallableEntry {
152            bootstrap: std::mem::take(&mut self.chunk),
153            has_fixture: fixture_name.is_some(),
154            fixture_expects_harness,
155            expects_harness,
156        })
157    }
158
159    /// Compile a named function as a top-level callable entry.
160    pub fn compile_named_function_entry(
161        self,
162        program: &[SNode],
163        function_name: &str,
164    ) -> Result<CompiledCallableEntry, CompileError> {
165        self.compile_named_callable_entries(program, &[], &[function_name])?
166            .functions
167            .pop()
168            .ok_or_else(|| CompileError {
169                message: "named function entry request was empty".to_string(),
170                line: 0,
171            })?
172    }
173
174    fn finish_named_function_entry(
175        &mut self,
176        program: &[SNode],
177        function_name: &str,
178    ) -> Result<CompiledCallableEntry, CompileError> {
179        let target = program.iter().find(
180            |node| matches!(peel_node(node), Node::FnDecl { name, .. } if name == function_name),
181        );
182        let Some(target) = target else {
183            return Err(CompileError {
184                message: format!("Unknown function: {function_name}"),
185                line: self.line,
186            });
187        };
188        let expects_harness = matches!(
189            peel_node(target),
190            Node::FnDecl { params, .. }
191                if params.first().is_some_and(|param| matches!(
192                    param.type_expr.as_ref(),
193                    Some(harn_parser::TypeExpr::Named(name)) if name == "Harness"
194                ))
195        );
196        let function = self.string_constant(function_name);
197        self.chunk.emit_u16(Op::GetVar, function, self.line);
198        self.chunk.emit(Op::Return, self.line);
199        ensure_chunk_addressable(&self.chunk, "the callable entry", self.line)?;
200        Ok(CompiledCallableEntry {
201            bootstrap: std::mem::take(&mut self.chunk),
202            has_fixture: false,
203            fixture_expects_harness: false,
204            expects_harness,
205        })
206    }
207
208    fn compile_entry_imports(&mut self, program: &[SNode]) -> Result<(), CompileError> {
209        for node in program {
210            if matches!(
211                &node.node,
212                Node::ImportDecl { .. }
213                    | Node::SelectiveImport { .. }
214                    | Node::NamespaceImport { .. }
215            ) {
216                self.compile_node(node)?;
217            }
218        }
219        Ok(())
220    }
221}