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 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 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 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 ..
123 } = peel_node(target)
124 else {
125 unreachable!("pipeline target was matched above");
126 };
127 let expects_harness = params.first().is_some_and(|param| {
128 matches!(
129 param.type_expr.as_ref(),
130 Some(harn_parser::TypeExpr::Named(name)) if name == "Harness"
131 )
132 });
133 let callable =
134 self.compile_pipeline_callable(program, name, params, body, extends.as_deref())?;
135 let function_index = self.chunk.functions.len();
136 self.chunk.functions.push(Arc::new(callable));
137 self.chunk
138 .emit_u16(Op::Closure, function_index as u16, self.line);
139 if fixture_name.is_some() {
140 self.chunk.emit_u16(Op::BuildList, 2, self.line);
141 }
142 self.chunk.emit(Op::Return, self.line);
143 ensure_chunk_addressable(&self.chunk, "the callable entry", self.line)?;
144 Ok(CompiledCallableEntry {
145 bootstrap: std::mem::take(&mut self.chunk),
146 has_fixture: fixture_name.is_some(),
147 fixture_expects_harness,
148 expects_harness,
149 })
150 }
151
152 pub fn compile_named_function_entry(
154 self,
155 program: &[SNode],
156 function_name: &str,
157 ) -> Result<CompiledCallableEntry, CompileError> {
158 self.compile_named_callable_entries(program, &[], &[function_name])?
159 .functions
160 .pop()
161 .ok_or_else(|| CompileError {
162 message: "named function entry request was empty".to_string(),
163 line: 0,
164 })?
165 }
166
167 fn finish_named_function_entry(
168 &mut self,
169 program: &[SNode],
170 function_name: &str,
171 ) -> Result<CompiledCallableEntry, CompileError> {
172 let target = program.iter().find(
173 |node| matches!(peel_node(node), Node::FnDecl { name, .. } if name == function_name),
174 );
175 let Some(target) = target else {
176 return Err(CompileError {
177 message: format!("Unknown function: {function_name}"),
178 line: self.line,
179 });
180 };
181 let expects_harness = matches!(
182 peel_node(target),
183 Node::FnDecl { params, .. }
184 if params.first().is_some_and(|param| matches!(
185 param.type_expr.as_ref(),
186 Some(harn_parser::TypeExpr::Named(name)) if name == "Harness"
187 ))
188 );
189 let function = self.string_constant(function_name);
190 self.chunk.emit_u16(Op::GetVar, function, self.line);
191 self.chunk.emit(Op::Return, self.line);
192 ensure_chunk_addressable(&self.chunk, "the callable entry", self.line)?;
193 Ok(CompiledCallableEntry {
194 bootstrap: std::mem::take(&mut self.chunk),
195 has_fixture: false,
196 fixture_expects_harness: false,
197 expects_harness,
198 })
199 }
200
201 fn compile_entry_imports(&mut self, program: &[SNode]) -> Result<(), CompileError> {
202 for node in program {
203 if matches!(
204 &node.node,
205 Node::ImportDecl { .. }
206 | Node::SelectiveImport { .. }
207 | Node::NamespaceImport { .. }
208 ) {
209 self.compile_node(node)?;
210 }
211 }
212 Ok(())
213 }
214}