1use crate::{
2 fun::{book_to_hvm, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Term},
3 hvm::{
4 add_recursive_priority::add_recursive_priority,
5 check_net_size::{check_net_sizes, MAX_NET_SIZE_CUDA},
6 eta_reduce::eta_reduce_hvm_net,
7 hvm_book_show_pretty,
8 inline::inline_hvm_book,
9 mutual_recursion,
10 prune::prune_hvm_book,
11 },
12};
13use diagnostics::{Diagnostics, DiagnosticsConfig, ERR_INDENT_SIZE};
14use net::hvm_to_net::hvm_to_net;
15
16pub mod diagnostics;
17#[allow(clippy::mutable_key_type)]
19pub mod fun;
20pub mod hvm;
21pub mod imp;
22pub mod imports;
23pub mod net;
24mod utils;
25
26pub use fun::load_book::{load_file_to_book, load_to_book};
27
28pub const ENTRY_POINT: &str = "main";
29pub const HVM1_ENTRY_POINT: &str = "Main";
30pub const HVM_OUTPUT_END_MARKER: &str = "Result: ";
31
32pub fn check_book(
33 book: &mut Book,
34 diagnostics_cfg: DiagnosticsConfig,
35 compile_opts: CompileOpts,
36) -> Result<Diagnostics, Diagnostics> {
37 let res = compile_book(book, compile_opts, diagnostics_cfg, None)?;
39 Ok(res.diagnostics)
40}
41
42pub fn compile_book(
43 book: &mut Book,
44 opts: CompileOpts,
45 diagnostics_cfg: DiagnosticsConfig,
46 args: Option<Vec<Term>>,
47) -> Result<CompileResult, Diagnostics> {
48 let mut diagnostics = desugar_book(book, opts.clone(), diagnostics_cfg, args)?;
49
50 let (mut hvm_book, labels) = book_to_hvm(book, &mut diagnostics)?;
51
52 if opts.eta {
53 hvm_book.defs.values_mut().for_each(eta_reduce_hvm_net);
54 }
55
56 mutual_recursion::check_cycles(&hvm_book, &mut diagnostics)?;
57
58 if opts.eta {
59 hvm_book.defs.values_mut().for_each(eta_reduce_hvm_net);
60 }
61
62 if opts.inline {
63 if let Err(e) = inline_hvm_book(&mut hvm_book) {
64 diagnostics.add_book_error(format!("During inlining:\n{:ERR_INDENT_SIZE$}{}", "", e));
65 }
66 diagnostics.fatal(())?;
67 }
68
69 if opts.prune {
70 let prune_entrypoints = vec![book.hvm_entrypoint().to_string()];
71 prune_hvm_book(&mut hvm_book, &prune_entrypoints);
72 }
73
74 if opts.check_net_size {
75 check_net_sizes(&hvm_book, &mut diagnostics, &opts.target_architecture)?;
76 }
77
78 add_recursive_priority(&mut hvm_book);
79
80 Ok(CompileResult { hvm_book, labels, diagnostics })
81}
82
83pub fn desugar_book(
84 book: &mut Book,
85 opts: CompileOpts,
86 diagnostics_cfg: DiagnosticsConfig,
87 args: Option<Vec<Term>>,
88) -> Result<Diagnostics, Diagnostics> {
89 let mut ctx = Ctx::new(book, diagnostics_cfg);
90
91 ctx.check_shared_names();
92
93 ctx.set_entrypoint();
94
95 ctx.book.encode_adts(opts.adt_encoding);
96
97 ctx.fix_match_defs()?;
98
99 ctx.apply_args(args)?;
100
101 ctx.desugar_open()?;
102
103 ctx.book.encode_builtins();
104
105 ctx.resolve_refs()?;
106
107 ctx.desugar_match_defs()?;
108
109 ctx.fix_match_terms()?;
110
111 ctx.book.lift_local_defs();
112
113 ctx.desugar_bend()?;
114 ctx.desugar_fold()?;
115 ctx.desugar_with_blocks()?;
116
117 ctx.check_unbound_vars()?;
118
119 ctx.book.make_var_names_unique();
121 ctx.book.desugar_use();
122
123 match opts.linearize_matches {
124 OptLevel::Disabled => (),
125 OptLevel::Alt => ctx.book.linearize_match_binds(),
126 OptLevel::Enabled => ctx.book.linearize_matches(),
127 }
128 ctx.book.linearize_match_with();
130
131 if opts.type_check {
132 type_check_book(&mut ctx)?;
133 }
134
135 ctx.book.encode_matches(opts.adt_encoding);
136
137 ctx.check_unbound_vars()?;
139
140 ctx.book.make_var_names_unique();
141 ctx.book.desugar_use();
142
143 ctx.book.make_var_names_unique();
144 ctx.book.linearize_vars();
145
146 ctx.check_unbound_vars()?;
148
149 if opts.float_combinators {
150 ctx.book.float_combinators(MAX_NET_SIZE_CUDA);
151 }
152 ctx.check_unbound_refs()?;
154
155 ctx.prune(opts.prune);
157 if opts.merge {
158 ctx.book.merge_definitions();
159 }
160
161 ctx.book.expand_main();
162
163 ctx.book.make_var_names_unique();
164
165 if !ctx.info.has_errors() {
166 Ok(ctx.info)
167 } else {
168 Err(ctx.info)
169 }
170}
171
172pub fn type_check_book(ctx: &mut Ctx) -> Result<(), Diagnostics> {
173 ctx.check_untyped_terms()?;
174 ctx.resolve_type_ctrs()?;
175 ctx.type_check()?;
176 Ok(())
177}
178
179pub fn run_book(
180 mut book: Book,
181 run_opts: RunOpts,
182 compile_opts: CompileOpts,
183 diagnostics_cfg: DiagnosticsConfig,
184 args: Option<Vec<Term>>,
185 cmd: &str,
186) -> Result<Option<(Term, String, Diagnostics)>, Diagnostics> {
187 let CompileResult { hvm_book: core_book, labels, diagnostics } =
188 compile_book(&mut book, compile_opts.clone(), diagnostics_cfg, args)?;
189
190 eprint!("{diagnostics}");
194
195 let out = run_hvm(&core_book, cmd, &run_opts)?;
196 let (net, stats) = parse_hvm_output(&out)?;
197 let (term, diags) =
198 readback_hvm_net(&net, &book, &labels, run_opts.linear_readback, compile_opts.adt_encoding);
199
200 Ok(Some((term, stats, diags)))
201}
202
203pub fn readback_hvm_net(
204 net: &::hvm::ast::Net,
205 book: &Book,
206 labels: &Labels,
207 linear: bool,
208 adt_encoding: AdtEncoding,
209) -> (Term, Diagnostics) {
210 let mut diags = Diagnostics::default();
211 let net = hvm_to_net(net);
212 let mut term = net_to_term(&net, book, labels, linear, &mut diags);
213 #[allow(clippy::mutable_key_type)] let recursive_defs = book.recursive_defs();
215 term.expand_generated(book, &recursive_defs);
216 term.resugar_strings(adt_encoding);
217 term.resugar_lists(adt_encoding);
218 (term, diags)
219}
220
221fn run_hvm(book: &::hvm::ast::Book, cmd: &str, run_opts: &RunOpts) -> Result<String, String> {
223 let out_path = ".out.hvm";
224 std::fs::write(out_path, hvm_book_show_pretty(book)).map_err(|x| x.to_string())?;
225 let mut process = std::process::Command::new(run_opts.hvm_path.clone())
226 .arg(cmd)
227 .arg(out_path)
228 .stdout(std::process::Stdio::piped())
229 .stderr(std::process::Stdio::inherit())
230 .spawn()
231 .map_err(|e| format!("Failed to start hvm process.\n{e}"))?;
232
233 let child_out = std::mem::take(&mut process.stdout).expect("Failed to attach to hvm output");
234 let thread_out = std::thread::spawn(move || filter_hvm_output(child_out, std::io::stdout()));
235
236 let _ = process.wait().expect("Failed to wait on hvm subprocess");
237 if let Err(e) = std::fs::remove_file(out_path) {
238 eprintln!("Error removing HVM output file. {e}");
239 }
240
241 let result = thread_out.join().map_err(|_| "HVM output thread panicked.".to_string())??;
242 Ok(result)
243}
244
245fn parse_hvm_output(out: &str) -> Result<(::hvm::ast::Net, String), String> {
247 let Some((result, stats)) = out.split_once('\n') else {
248 return Err(format!(
249 "Failed to parse result from HVM (unterminated result).\nOutput from HVM was:\n{:?}",
250 out
251 ));
252 };
253 let mut p = ::hvm::ast::CoreParser::new(result);
254 let Ok(net) = p.parse_net() else {
255 return Err(format!("Failed to parse result from HVM (invalid net).\nOutput from HVM was:\n{:?}", out));
256 };
257 Ok((net, stats.to_string()))
258}
259
260fn filter_hvm_output(
265 mut stream: impl std::io::Read + Send,
266 mut output: impl std::io::Write + Send,
267) -> Result<String, String> {
268 let mut capturing = false;
269 let mut result = String::new();
270 let mut buf = [0u8; 1024];
271 loop {
272 let num_read = match stream.read(&mut buf) {
273 Ok(n) => n,
274 Err(e) => {
275 eprintln!("{e}");
276 break;
277 }
278 };
279 if num_read == 0 {
280 break;
281 }
282 let new_buf = &buf[..num_read];
283 let new_str = String::from_utf8_lossy(new_buf);
285 if capturing {
286 result.push_str(&new_str);
288 } else if let Some((before, after)) = new_str.split_once(HVM_OUTPUT_END_MARKER) {
289 if let Err(e) = output.write_all(before.as_bytes()) {
291 eprintln!("Error writing HVM output. {e}");
292 };
293 result.push_str(after);
294 capturing = true;
295 } else {
296 if let Err(e) = output.write_all(new_buf) {
298 eprintln!("Error writing HVM output. {e}");
299 }
300 }
301 }
302
303 if capturing {
304 Ok(result)
305 } else {
306 output.flush().map_err(|e| format!("Error flushing HVM output. {e}"))?;
307 let msg = "HVM output had no result (An error likely occurred)".to_string();
308 Err(msg)
309 }
310}
311
312#[derive(Clone, Debug)]
313pub struct RunOpts {
314 pub linear_readback: bool,
315 pub pretty: bool,
316 pub hvm_path: String,
317}
318
319impl Default for RunOpts {
320 fn default() -> Self {
321 RunOpts { linear_readback: false, pretty: false, hvm_path: "hvm".to_string() }
322 }
323}
324
325#[derive(Clone, Copy, Debug, Default)]
326pub enum OptLevel {
327 Disabled,
328 #[default]
329 Enabled,
330 Alt,
331}
332
333impl OptLevel {
334 pub fn enabled(&self) -> bool {
335 !matches!(self, OptLevel::Disabled)
336 }
337
338 pub fn is_extra(&self) -> bool {
339 matches!(self, OptLevel::Enabled)
340 }
341}
342
343#[derive(Clone, Debug, PartialEq, Eq)]
344pub enum CompilerTarget {
345 C,
346 Cuda,
347 Unknown,
348}
349
350#[derive(Clone, Debug)]
351pub struct CompileOpts {
352 pub target_architecture: CompilerTarget,
354
355 pub eta: bool,
357
358 pub prune: bool,
360
361 pub linearize_matches: OptLevel,
363
364 pub float_combinators: bool,
366
367 pub merge: bool,
369
370 pub inline: bool,
372
373 pub check_net_size: bool,
375
376 pub type_check: bool,
378
379 pub adt_encoding: AdtEncoding,
381}
382
383impl CompileOpts {
384 #[must_use]
386 pub fn set_all(self) -> Self {
387 Self {
388 target_architecture: self.target_architecture,
389 eta: true,
390 prune: true,
391 float_combinators: true,
392 merge: true,
393 linearize_matches: OptLevel::Enabled,
394 type_check: true,
395 inline: true,
396 check_net_size: self.check_net_size,
397 adt_encoding: self.adt_encoding,
398 }
399 }
400
401 #[must_use]
403 pub fn set_no_all(self) -> Self {
404 Self {
405 target_architecture: self.target_architecture,
406 eta: false,
407 prune: false,
408 linearize_matches: OptLevel::Disabled,
409 float_combinators: false,
410 merge: false,
411 inline: false,
412 type_check: self.type_check,
413 check_net_size: self.check_net_size,
414 adt_encoding: self.adt_encoding,
415 }
416 }
417
418 pub fn check_for_strict(&self) {
419 if !self.float_combinators {
420 println!(
421 "Warning: Running in strict mode without enabling the float_combinators pass can lead to some functions expanding infinitely."
422 );
423 }
424 if !self.linearize_matches.enabled() {
425 println!(
426 "Warning: Running in strict mode without enabling the linearize_matches pass can lead to some functions expanding infinitely."
427 );
428 }
429 }
430}
431
432impl Default for CompileOpts {
433 fn default() -> Self {
436 Self {
437 target_architecture: CompilerTarget::Unknown,
438 eta: true,
439 prune: false,
440 linearize_matches: OptLevel::Enabled,
441 float_combinators: true,
442 merge: false,
443 inline: false,
444 check_net_size: true,
445 type_check: true,
446 adt_encoding: AdtEncoding::NumScott,
447 }
448 }
449}
450
451#[derive(Clone, Copy, Debug)]
452pub enum AdtEncoding {
453 Scott,
454 NumScott,
455}
456
457impl std::fmt::Display for AdtEncoding {
458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459 match self {
460 AdtEncoding::Scott => write!(f, "Scott"),
461 AdtEncoding::NumScott => write!(f, "NumScott"),
462 }
463 }
464}
465
466pub struct CompileResult {
467 pub diagnostics: Diagnostics,
468 pub hvm_book: ::hvm::ast::Book,
469 pub labels: Labels,
470}
471
472fn maybe_grow<R, F>(f: F) -> R
473where
474 F: FnOnce() -> R,
475{
476 stacker::maybe_grow(1024 * 32, 1024 * 1024, f)
477}