Skip to content

Commit eeb6cee

Browse files
authored
Merge pull request #5 from OxfordAbstracts/codegen
JS codegen
2 parents 3360852 + d359808 commit eeb6cee

37 files changed

+3648
-4
lines changed

src/build/mod.rs

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub struct BuildOptions {
3030
/// typecheck, it is skipped and a `TypecheckTimeout` error is recorded.
3131
/// `None` means no timeout (the default).
3232
pub module_timeout: Option<std::time::Duration>,
33+
34+
/// Output directory for generated JavaScript files.
35+
/// `None` means skip codegen. `Some(path)` writes JS to `path/<Module.Name>/index.js`.
36+
pub output_dir: Option<PathBuf>,
3337
}
3438

3539
// ===== Public types =====
@@ -89,7 +93,7 @@ fn extract_foreign_import_names(module: &Module) -> Vec<String> {
8993
// ===== Public API =====
9094

9195
/// Build all PureScript modules matching the given glob patterns.
92-
pub fn build(globs: &[&str]) -> BuildResult {
96+
pub fn build(globs: &[&str], output_dir: Option<PathBuf>) -> BuildResult {
9397
let build_start = Instant::now();
9498
let mut build_errors = Vec::new();
9599

@@ -158,7 +162,12 @@ pub fn build(globs: &[&str]) -> BuildResult {
158162
.map(|(k, v)| (k.as_str(), v.as_str()))
159163
.collect();
160164

161-
let mut result = build_from_sources_with_js(&source_refs, &Some(js_refs), None).0;
165+
let options = BuildOptions {
166+
output_dir,
167+
..Default::default()
168+
};
169+
let mut result =
170+
build_from_sources_with_options(&source_refs, &Some(js_refs), None, &options).0;
162171
// Prepend file-level errors before source-level errors
163172
build_errors.append(&mut result.build_errors);
164173
result.build_errors = build_errors;
@@ -658,6 +667,94 @@ pub fn build_from_sources_with_options(
658667
);
659668
} // end if js_sources.is_some()
660669

670+
// Phase 6: Code generation (only when output_dir is specified)
671+
if let Some(ref output_dir) = options.output_dir {
672+
log::debug!("Phase 6: JavaScript code generation to {}", output_dir.display());
673+
let phase_start = Instant::now();
674+
let mut codegen_count = 0;
675+
676+
// Build a set of module names that typechecked successfully (zero errors)
677+
let ok_modules: HashSet<String> = module_results
678+
.iter()
679+
.filter(|m| m.type_errors.is_empty())
680+
.map(|m| m.module_name.clone())
681+
.collect();
682+
683+
for pm in &parsed {
684+
if !ok_modules.contains(&pm.module_name) {
685+
log::debug!(" skipping {} (has type errors)", pm.module_name);
686+
continue;
687+
}
688+
689+
// Look up this module's exports from the registry
690+
let module_exports = match registry.lookup(&pm.module_parts) {
691+
Some(exports) => exports,
692+
None => {
693+
log::debug!(" skipping {} (no exports in registry)", pm.module_name);
694+
continue;
695+
}
696+
};
697+
698+
let has_ffi = pm.js_source.is_some();
699+
700+
log::debug!(" generating JS for {}", pm.module_name);
701+
let js_module = crate::codegen::js::module_to_js(
702+
&pm.module,
703+
&pm.module_name,
704+
&pm.module_parts,
705+
module_exports,
706+
&registry,
707+
has_ffi,
708+
);
709+
710+
let js_text = crate::codegen::printer::print_module(&js_module);
711+
712+
// Write output/<Module.Name>/index.js
713+
let module_dir = output_dir.join(&pm.module_name);
714+
if let Err(e) = std::fs::create_dir_all(&module_dir) {
715+
log::debug!(" failed to create dir {}: {}", module_dir.display(), e);
716+
build_errors.push(BuildError::FileReadError {
717+
path: module_dir.clone(),
718+
error: format!("Failed to create output directory: {e}"),
719+
});
720+
continue;
721+
}
722+
723+
let index_path = module_dir.join("index.js");
724+
if let Err(e) = std::fs::write(&index_path, &js_text) {
725+
log::debug!(" failed to write {}: {}", index_path.display(), e);
726+
build_errors.push(BuildError::FileReadError {
727+
path: index_path,
728+
error: format!("Failed to write JS output: {e}"),
729+
});
730+
continue;
731+
}
732+
log::debug!(" wrote {} ({} bytes)", index_path.display(), js_text.len());
733+
734+
// Copy FFI companion file
735+
if let Some(ref js_src) = pm.js_source {
736+
let foreign_path = module_dir.join("foreign.js");
737+
if let Err(e) = std::fs::write(&foreign_path, js_src) {
738+
log::debug!(" failed to write {}: {}", foreign_path.display(), e);
739+
build_errors.push(BuildError::FileReadError {
740+
path: foreign_path,
741+
error: format!("Failed to write foreign JS: {e}"),
742+
});
743+
continue;
744+
}
745+
log::debug!(" copied foreign.js for {}", pm.module_name);
746+
}
747+
748+
codegen_count += 1;
749+
}
750+
751+
log::debug!(
752+
"Phase 6 complete: generated JS for {} modules in {:.2?}",
753+
codegen_count,
754+
phase_start.elapsed()
755+
);
756+
}
757+
661758
log::debug!(
662759
"Build pipeline finished in {:.2?} ({} modules, {} errors)",
663760
pipeline_start.elapsed(),

0 commit comments

Comments
 (0)