1mod body;
2mod constant;
3mod declaration;
4mod link;
5
6use crate::frontend::{HostedTypedProgram, HostedTypedProgramModule};
7use crate::host::{HostProfile, RegisteredHostProviderModule};
8use crate::plan::{HostImplementationBinding, HostedModulePlan, ModuleId};
9use crate::planner::error::PlanError;
10
11pub fn plan_host_program<Profile: HostProfile>(
12 program: HostedTypedProgram<Profile>,
13) -> Result<HostedModulePlan<Profile>, PlanError> {
14 let (root_index, modules, providers, implementations) = program.into_parts();
15 plan_host_program_schema(root_index, modules, providers).map(|planned| {
16 let implementation_bindings = planned
17 .implementations
18 .into_iter()
19 .map(|(template, constructions, implementation)| {
20 HostImplementationBinding::new(
21 template,
22 constructions,
23 implementations.implementation(implementation),
24 )
25 })
26 .collect();
27 HostedModulePlan::new(
28 planned.root,
29 planned.entry,
30 planned.modules,
31 implementation_bindings,
32 )
33 })
34}
35
36fn plan_host_program_schema(
37 root_index: usize,
38 modules: Vec<HostedTypedProgramModule>,
39 providers: Vec<RegisteredHostProviderModule>,
40) -> Result<body::PlannedHostedProgram, PlanError> {
41 let root = ModuleId::new(root_index);
42 declaration::collect_hosted_module_declarations(modules, providers)
43 .and_then(|declarations| link::link_hosted_modules(root, declarations))
44 .and_then(constant::reserve_hosted_constants)
45 .and_then(|(registry, modules)| constant::plan_hosted_constant_bodies(registry, modules))
46 .and_then(|(registry, modules)| body::plan_hosted_modules(root, ®istry, modules))
47}
48
49#[cfg(test)]
50mod tests {
51 use super::plan_host_program;
52 use crate::frontend::{ModuleSource, PackageSource, compile_typed_host_program};
53 use crate::host::{HostModule, HostParameter, HostProviderSet};
54 use crate::plan::{
55 FunctionShape, FunctionTemplateId, FunctionType, ModuleId, ValueShape, ValueType,
56 };
57 use crate::planner::{ExternalTypeProviderLinkReason, PlanError, UnsupportedFunctionReason};
58 use ecow::EcoString;
59 use num_bigint::BigInt;
60
61 #[test]
62 fn plan_host_program_bodyless_templates_with_module_qualified_ids() {
63 let choose = |condition: bool, left: BigInt, right: BigInt| {
64 if condition { left } else { right }
65 };
66 assert_eq!(
67 choose(false, BigInt::from(10), BigInt::from(20)),
68 BigInt::from(20),
69 );
70 assert_eq!(
71 choose(true, BigInt::from(10), BigInt::from(20)),
72 BigInt::from(10),
73 );
74 let all = |a: bool, b: bool, c: bool, d: bool, e: bool, f: bool, g: bool| {
75 a && b && c && d && e && f && g
76 };
77 assert!(all(true, true, true, true, true, true, true));
78
79 let hosts = HostProviderSet::new([HostModule::new("host_support", "host/math")
80 .expect("host module should be valid")
81 .with_function("add", <BigInt as std::ops::Add>::add)
82 .expect("host function should be valid")
83 .with_function("subtract", <BigInt as std::ops::Sub>::sub)
84 .expect("host function should be valid")
85 .with_function("ready", <bool as Default>::default)
86 .expect("host function should be valid")
87 .with_function("choose", choose)
88 .expect("host function should be valid")
89 .with_function("all", all)
90 .expect("host function should be valid")
91 .with_function(
92 "consume",
93 |_: BigInt,
94 _: f64,
95 _: EcoString,
96 _: crate::BitArrayValue,
97 _: char,
98 _: bool,
99 (): ()| (),
100 )
101 .expect("host function should be valid")])
102 .expect("host modules should be unique");
103 let typed = compile_typed_host_program(
104 "application",
105 "main",
106 [PackageSource::new(
107 "application",
108 ["host_support"],
109 [ModuleSource::new(
110 "main",
111 "main.gleam",
112 r#"
113import host/math.{add}
114
115pub fn main() {
116 add(1, 2)
117}
118"#,
119 )],
120 )],
121 hosts,
122 )
123 .expect("host program should compile");
124 let plan = plan_host_program(typed).expect("host program should plan");
125
126 assert_eq!(plan.root(), ModuleId::new(1));
127 assert_eq!(
128 plan.entry(),
129 FunctionTemplateId::in_module(ModuleId::new(1), 0)
130 );
131 assert_eq!(
132 plan.modules()
133 .iter()
134 .map(|module| (module.package().as_str(), module.module().as_str()))
135 .collect::<Vec<_>>(),
136 [("host_support", "host/math"), ("application", "main")],
137 );
138 assert_eq!(plan.modules()[0].id(), ModuleId::new(0));
139 assert_eq!(plan.modules()[1].id(), ModuleId::new(1));
140 assert!(plan.modules()[0].source_context().is_none());
141 assert!(plan.modules()[1].source_context().is_some());
142 let host = &plan.modules()[0];
143 assert_eq!(host.id(), ModuleId::new(0));
144 assert_eq!(host.functions().len(), 6);
145 let functions = host
146 .functions()
147 .iter()
148 .map(|function| {
149 function
150 .host_template()
151 .expect("source-less module should retain host templates")
152 })
153 .collect::<Vec<_>>();
154 assert_eq!(functions[0].name(), "add");
155 assert_eq!(
156 functions[0].id(),
157 FunctionTemplateId::in_module(ModuleId::new(0), 0),
158 );
159 assert_eq!(functions[0].package(), "host_support");
160 assert_eq!(functions[0].module(), "host/math");
161 assert_eq!(functions[0].scheme().parameters(), &[]);
162 assert_eq!(
163 functions[0].signature().shape(),
164 &FunctionShape::new(vec![ValueShape::Int, ValueShape::Int], ValueShape::Int,),
165 );
166 assert_eq!(
167 functions[0].type_(),
168 &FunctionType::new(vec![ValueType::Int, ValueType::Int], ValueType::Int),
169 );
170 assert!(matches!(
171 functions[0].layout(),
172 [HostParameter::Int(left), HostParameter::Int(right)]
173 if left.index() == 0 && right.index() == 1
174 ));
175 assert_eq!(functions[1].name(), "subtract");
176 assert_eq!(functions[2].name(), "ready");
177 assert_eq!(
178 functions[2].signature().shape(),
179 &FunctionShape::new(Vec::new(), ValueShape::Bool),
180 );
181 assert_eq!(
182 functions[2].type_(),
183 &FunctionType::new(Vec::new(), ValueType::Bool),
184 );
185 assert_eq!(functions[3].name(), "choose");
186 assert_eq!(
187 functions[3].signature().shape(),
188 &FunctionShape::new(
189 vec![ValueShape::Bool, ValueShape::Int, ValueShape::Int],
190 ValueShape::Int,
191 ),
192 );
193 assert_eq!(
194 functions[3].type_(),
195 &FunctionType::new(
196 vec![ValueType::Bool, ValueType::Int, ValueType::Int],
197 ValueType::Int,
198 ),
199 );
200 assert!(matches!(
201 functions[3].layout(),
202 [
203 HostParameter::Bool(condition),
204 HostParameter::Int(left),
205 HostParameter::Int(right),
206 ] if condition.index() == 0 && left.index() == 0 && right.index() == 1
207 ));
208 assert_eq!(functions[4].name(), "all");
209 assert_eq!(
210 functions[4].signature().shape(),
211 &FunctionShape::new(vec![ValueShape::Bool; 7], ValueShape::Bool),
212 );
213 assert_eq!(
214 functions[4].type_(),
215 &FunctionType::new(vec![ValueType::Bool; 7], ValueType::Bool),
216 );
217 assert!(matches!(
218 functions[4].layout(),
219 [
220 HostParameter::Bool(first),
221 HostParameter::Bool(second),
222 HostParameter::Bool(third),
223 HostParameter::Bool(fourth),
224 HostParameter::Bool(fifth),
225 HostParameter::Bool(sixth),
226 HostParameter::Bool(seventh),
227 ] if first.index() == 0
228 && second.index() == 1
229 && third.index() == 2
230 && fourth.index() == 3
231 && fifth.index() == 4
232 && sixth.index() == 5
233 && seventh.index() == 6
234 ));
235 assert_eq!(functions[5].name(), "consume");
236 assert_eq!(
237 functions[5].signature().shape(),
238 &FunctionShape::new(
239 vec![
240 ValueShape::Int,
241 ValueShape::Float,
242 ValueShape::String,
243 ValueShape::BitArray,
244 ValueShape::UtfCodepoint,
245 ValueShape::Bool,
246 ValueShape::Nil,
247 ],
248 ValueShape::Nil,
249 ),
250 );
251 assert_eq!(
252 functions[5].type_(),
253 &FunctionType::new(
254 vec![
255 ValueType::Int,
256 ValueType::Float,
257 ValueType::String,
258 ValueType::BitArray,
259 ValueType::UtfCodepoint,
260 ValueType::Bool,
261 ValueType::Nil,
262 ],
263 ValueType::Nil,
264 ),
265 );
266 assert!(matches!(
267 functions[5].layout(),
268 [
269 HostParameter::Int(int),
270 HostParameter::Float(float),
271 HostParameter::String(string),
272 HostParameter::BitArray(bit_array),
273 HostParameter::UtfCodepoint(utf_codepoint),
274 HostParameter::Bool(bool_),
275 HostParameter::Nil(nil),
276 ] if int.index() == 0
277 && float.index() == 0
278 && string.index() == 0
279 && bit_array.index() == 0
280 && utf_codepoint.index() == 0
281 && bool_.index() == 0
282 && nil.index() == 0
283 ));
284 let source = plan.modules()[1].functions()[0]
285 .gleam_body()
286 .expect("root module should retain its source function");
287 assert_eq!(source.name(), "main");
288 }
289
290 #[test]
291 fn plan_host_program_source_dependencies_as_dependency_modules() {
292 let typed = compile_typed_host_program(
293 "application",
294 "main",
295 [
296 PackageSource::new(
297 "application",
298 ["library"],
299 [ModuleSource::new(
300 "main",
301 "main.gleam",
302 "pub fn main() { 1 }",
303 )],
304 ),
305 PackageSource::new(
306 "library",
307 Vec::<EcoString>::new(),
308 [ModuleSource::new(
309 "support",
310 "support.gleam",
311 "pub fn unused() { 2 }",
312 )],
313 ),
314 ],
315 HostProviderSet::new(Vec::<HostModule>::new())
316 .expect("empty host modules should be valid"),
317 )
318 .expect("hosted source program should compile");
319 let plan = plan_host_program(typed).expect("hosted source program should plan");
320
321 assert_eq!(plan.root(), ModuleId::new(1));
322 assert_eq!(
323 plan.modules()
324 .iter()
325 .map(|module| (module.package().as_str(), module.module().as_str()))
326 .collect::<Vec<_>>(),
327 [("library", "support"), ("application", "main")],
328 );
329 assert_eq!(
330 plan.modules()[0].functions()[0]
331 .gleam_body()
332 .expect("dependency should remain a source function")
333 .name(),
334 "unused",
335 );
336 }
337
338 #[test]
339 fn reject_profile_host_program_source_owner_boundaries() {
340 let cases = [
341 (
342 "pub fn other() { 1 }",
343 PlanError::UnsupportedFunction {
344 name: "main".into(),
345 reason: UnsupportedFunctionReason::MissingMain,
346 },
347 ),
348 (
349 r#"
350@external(erlang, "external", "thing")
351pub type Thing
352
353pub fn main() { 1 }
354"#,
355 PlanError::ExternalTypeProviderLink {
356 package: "application".into(),
357 module: "main".into(),
358 type_: "Thing".into(),
359 reason: Box::new(ExternalTypeProviderLinkReason::MissingRegistration),
360 },
361 ),
362 (
363 r#"
364const unsupported = <<1:native>>
365
366pub fn main() { 1 }
367"#,
368 PlanError::UnsupportedBitArraySegment {
369 reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
370 },
371 ),
372 (
373 r#"
374fn unsupported() { <<1:native>> }
375
376pub fn main() { 1 }
377"#,
378 PlanError::UnsupportedBitArraySegment {
379 reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
380 },
381 ),
382 ];
383
384 for (source, expected) in cases {
385 let typed = compile_typed_host_program(
386 "application",
387 "main",
388 [PackageSource::new(
389 "application",
390 Vec::<EcoString>::new(),
391 [ModuleSource::new("main", "main.gleam", source)],
392 )],
393 HostProviderSet::new(Vec::<HostModule>::new())
394 .expect("empty host modules should be valid"),
395 )
396 .expect("profile-out source should still compile");
397 assert_eq!(plan_host_program(typed).err(), Some(expected));
398 }
399 }
400}