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 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 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}