rukalang/
driver.rs

1use std::convert::Infallible;
2use std::path::Path;
3
4use cranelift_entity::SecondaryMap;
5use ruka_frontend::{FileId, SourceLocation, SourceSpan};
6use thiserror::Error;
7
8use crate::check::{self, CheckedProgram};
9use crate::codegen;
10use crate::elab;
11use crate::hir::browser_graph::BrowserHirGraph;
12use crate::hir::{ExprId, HirProgram};
13use crate::meta;
14use crate::mir::browser_graph::BrowserMirGraph;
15use crate::mir::{self, MirFuncId, MirInstr, MirLocalId, MirProgram, MirSourcePos};
16use crate::modules;
17use crate::pass::{
18    PassContext, PassSnapshot, PassSnapshotField, PassSnapshotKind, PassSnapshotValue, PassTiming,
19    ProvenanceSummary,
20};
21use crate::syntax::browser_graph::BrowserAstGraph;
22use crate::syntax::{self, ast::Program};
23
24mod passes;
25
26/// Hook interface for observing compiler pipeline outputs.
27pub trait Driver {
28    /// Error type returned by hook implementations.
29    type Error: std::error::Error + 'static;
30
31    /// Observe the parsed AST before meta expansion.
32    fn on_ast(&mut self, _program: &Program) -> Result<(), Self::Error> {
33        Ok(())
34    }
35
36    /// Observe the AST after meta expansion and elaboration.
37    fn on_expanded_ast(&mut self, _program: &Program) -> Result<(), Self::Error> {
38        Ok(())
39    }
40
41    /// Observe lowered HIR.
42    fn on_hir(&mut self, _program: &HirProgram) -> Result<(), Self::Error> {
43        Ok(())
44    }
45
46    /// Observe side-table provenance ids keyed by HIR expression ids.
47    fn on_hir_origins(&mut self, _origins: &HirOriginTable) -> Result<(), Self::Error> {
48        Ok(())
49    }
50
51    /// Observe semantic checker annotations.
52    fn on_checked(&mut self, _program: &CheckedProgram) -> Result<(), Self::Error> {
53        Ok(())
54    }
55
56    /// Observe lowered MIR.
57    fn on_mir(&mut self, _program: &MirProgram) -> Result<(), Self::Error> {
58        Ok(())
59    }
60
61    /// Observe side-table provenance ids keyed by MIR function/local ids.
62    fn on_mir_origins(&mut self, _origins: &MirOriginTable) -> Result<(), Self::Error> {
63        Ok(())
64    }
65
66    /// Observe one pass snapshot emitted by the pipeline.
67    fn on_pass_snapshot(&mut self, _snapshot: &PassSnapshot) -> Result<(), Self::Error> {
68        Ok(())
69    }
70
71    /// Observe emitted Rust source.
72    fn on_rust(&mut self, _source: &str) -> Result<(), Self::Error> {
73        Ok(())
74    }
75
76    /// Observe emitted WAT source.
77    fn on_wat(&mut self, _source: &str) -> Result<(), Self::Error> {
78        Ok(())
79    }
80
81    /// Observe emitted WASM bytes when available.
82    fn on_wasm(&mut self, _bytes: &[u8]) -> Result<(), Self::Error> {
83        Ok(())
84    }
85
86    /// Observe non-fatal diagnostics from WASM emission.
87    fn on_wasm_diagnostics(
88        &mut self,
89        _diagnostics: &[codegen::wasm::WasmDiagnostic],
90    ) -> Result<(), Self::Error> {
91        Ok(())
92    }
93
94    /// Observe per-pass timing information.
95    fn on_pass_timings(&mut self, _timings: &[PassTiming]) -> Result<(), Self::Error> {
96        Ok(())
97    }
98
99    /// Observe aggregate provenance counters for one compile run.
100    fn on_provenance_summary(&mut self, _summary: ProvenanceSummary) -> Result<(), Self::Error> {
101        Ok(())
102    }
103}
104
105/// Driver that discards all pipeline outputs.
106pub struct NullDriver;
107
108impl Driver for NullDriver {
109    type Error = Infallible;
110}
111
112/// Side-table provenance ids keyed by HIR expression ids.
113#[derive(Debug, Default)]
114pub struct HirOriginTable {
115    /// Origin ids for HIR expressions that preserve source provenance.
116    pub expr_origins: SecondaryMap<ExprId, Option<crate::pass::OriginId>>,
117}
118
119impl HirOriginTable {
120    /// Return the number of expression-origin mappings in this table.
121    pub fn mapping_count(&self) -> usize {
122        self.expr_origins
123            .iter()
124            .filter(|(_, origin)| origin.is_some())
125            .count()
126    }
127}
128
129/// Side-table provenance ids keyed by MIR function/local ids.
130#[derive(Debug, Default)]
131pub struct MirOriginTable {
132    /// Origin ids for MIR locals that preserve source provenance.
133    pub local_origins:
134        SecondaryMap<MirFuncId, Option<SecondaryMap<MirLocalId, Option<crate::pass::OriginId>>>>,
135}
136
137impl MirOriginTable {
138    /// Return the number of local-origin mappings in this table.
139    pub fn mapping_count(&self) -> usize {
140        self.local_origins
141            .iter()
142            .map(|(_, origins)| {
143                origins.as_ref().map_or(0usize, |origins| {
144                    origins
145                        .iter()
146                        .filter(|(_, origin)| origin.is_some())
147                        .count()
148                })
149            })
150            .sum()
151    }
152}
153
154/// Browser-facing compilation artifacts captured from the pipeline.
155#[derive(Debug, Clone, Default, PartialEq, Eq)]
156pub struct BrowserArtifacts {
157    /// Browser graph derived from the expanded AST.
158    pub ast_graph: BrowserAstGraph,
159    /// Browser graph derived from HIR.
160    pub hir_graph: BrowserHirGraph,
161    /// Browser graph derived from MIR.
162    pub mir_graph: BrowserMirGraph,
163    /// Generated Rust source text.
164    pub rust_source: String,
165    /// Generated WAT source text.
166    pub wat_source: String,
167    /// Generated WASM bytes when validation succeeds.
168    pub wasm_bytes: Option<Vec<u8>>,
169    /// Diagnostics emitted while building WASM artifacts.
170    pub wasm_diagnostics: Vec<codegen::wasm::WasmDiagnostic>,
171}
172
173/// Driver that records browser-facing artifacts during compilation.
174#[derive(Debug, Default)]
175pub struct BrowserDriver {
176    artifacts: BrowserArtifacts,
177}
178
179impl BrowserDriver {
180    /// Consume the driver and return the accumulated browser artifacts.
181    pub fn into_artifacts(self) -> BrowserArtifacts {
182        self.artifacts
183    }
184}
185
186impl Driver for BrowserDriver {
187    type Error = Infallible;
188
189    fn on_expanded_ast(&mut self, program: &Program) -> Result<(), Self::Error> {
190        self.artifacts.ast_graph = crate::syntax::browser_graph::program_to_browser_graph(program);
191        Ok(())
192    }
193
194    fn on_hir(&mut self, program: &HirProgram) -> Result<(), Self::Error> {
195        self.artifacts.hir_graph = crate::hir::browser_graph::program_to_browser_graph(program);
196        Ok(())
197    }
198
199    fn on_mir(&mut self, program: &MirProgram) -> Result<(), Self::Error> {
200        self.artifacts.mir_graph = crate::mir::browser_graph::program_to_browser_graph(program);
201        Ok(())
202    }
203
204    fn on_rust(&mut self, source: &str) -> Result<(), Self::Error> {
205        self.artifacts.rust_source = source.to_owned();
206        Ok(())
207    }
208
209    fn on_wat(&mut self, source: &str) -> Result<(), Self::Error> {
210        self.artifacts.wat_source = source.to_owned();
211        Ok(())
212    }
213
214    fn on_wasm(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
215        self.artifacts.wasm_bytes = Some(bytes.to_vec());
216        Ok(())
217    }
218
219    fn on_wasm_diagnostics(
220        &mut self,
221        diagnostics: &[codegen::wasm::WasmDiagnostic],
222    ) -> Result<(), Self::Error> {
223        self.artifacts.wasm_diagnostics = diagnostics.to_vec();
224        Ok(())
225    }
226}
227
228/// Error produced while compiling a source file through the full pipeline.
229#[derive(Debug, Error)]
230pub enum CompileError<DE>
231where
232    DE: std::error::Error + 'static,
233{
234    /// Module resolution failed.
235    #[error("{0}")]
236    Module(#[from] modules::ModuleError),
237    /// Parsing failed.
238    #[error("{0}")]
239    Syntax(#[from] syntax::SyntaxError),
240    /// Meta expansion failed.
241    #[error("{0}")]
242    Meta(#[from] meta::MetaEvalError),
243    /// Type elaboration failed.
244    #[error("{0}")]
245    Elab(#[from] elab::ElabError),
246    /// Semantic checking failed.
247    #[error("{0}")]
248    Check(#[from] check::CheckError),
249    /// HIR-to-MIR lowering failed.
250    #[error("{0}")]
251    MirLower(#[from] mir::lower::LowerError),
252    /// Rust code generation failed.
253    #[error("{0}")]
254    Codegen(#[from] codegen::rust::CodegenError),
255    /// Driver hook callback failed.
256    #[error("{0}")]
257    Driver(#[source] DE),
258}
259
260/// Compile a native source file through the full pipeline using `driver` hooks.
261pub fn compile_with_driver<D>(
262    source_file: &Path,
263    source_text: &str,
264    driver: &mut D,
265) -> Result<(), CompileError<D::Error>>
266where
267    D: Driver,
268{
269    let ast = modules::resolve_for_native(source_file, source_text)?;
270    driver.on_ast(&ast).map_err(CompileError::Driver)?;
271
272    compile_resolved_program(source_file, source_text, &ast, driver)
273}
274
275/// Compile a browser source file through the full pipeline using `driver` hooks.
276pub fn compile_with_driver_for_browser<D>(
277    source_file: &Path,
278    source_text: &str,
279    driver: &mut D,
280) -> Result<(), CompileError<D::Error>>
281where
282    D: Driver,
283{
284    let ast = modules::resolve_for_browser(source_file, source_text)?;
285    driver.on_ast(&ast).map_err(CompileError::Driver)?;
286
287    compile_resolved_program(source_file, source_text, &ast, driver)
288}
289
290fn compile_resolved_program<D>(
291    source_file: &Path,
292    source_text: &str,
293    ast: &Program,
294    driver: &mut D,
295) -> Result<(), CompileError<D::Error>>
296where
297    D: Driver,
298{
299    let mut pass_context = PassContext::new();
300
301    let mut expand_pass = passes::ExpandProgramPass { program: ast };
302    let (meta_pass_id, expanded_ast) = pass_context.run_pass_with_id(&mut expand_pass, ())?;
303    driver
304        .on_pass_snapshot(&PassSnapshot {
305            kind: PassSnapshotKind::MetaProgram,
306            name: "meta.expand_program",
307            detail: format!(
308                "functions={} structs={} enums={}",
309                expanded_ast.functions.len(),
310                expanded_ast.structs.len(),
311                expanded_ast.enums.len()
312            ),
313            fields: vec![
314                PassSnapshotField {
315                    key: "functions",
316                    value: PassSnapshotValue::U64(expanded_ast.functions.len() as u64),
317                },
318                PassSnapshotField {
319                    key: "structs",
320                    value: PassSnapshotValue::U64(expanded_ast.structs.len() as u64),
321                },
322                PassSnapshotField {
323                    key: "enums",
324                    value: PassSnapshotValue::U64(expanded_ast.enums.len() as u64),
325                },
326            ],
327        })
328        .map_err(CompileError::Driver)?;
329
330    let mut elaborate_pass = passes::ElaborateProgramPass {
331        program: &expanded_ast,
332        subpass_timings: Vec::new(),
333    };
334    let (elab_pass_id, elaborated_ast) = pass_context.run_pass_with_id(&mut elaborate_pass, ())?;
335    pass_context.extend_pass_timings(elaborate_pass.take_subpass_timings());
336    driver
337        .on_pass_snapshot(&PassSnapshot {
338            kind: PassSnapshotKind::ElabProgram,
339            name: "elab.elaborate_program",
340            detail: format!(
341                "functions={} structs={} enums={}",
342                elaborated_ast.functions.len(),
343                elaborated_ast.structs.len(),
344                elaborated_ast.enums.len()
345            ),
346            fields: vec![
347                PassSnapshotField {
348                    key: "functions",
349                    value: PassSnapshotValue::U64(elaborated_ast.functions.len() as u64),
350                },
351                PassSnapshotField {
352                    key: "structs",
353                    value: PassSnapshotValue::U64(elaborated_ast.structs.len() as u64),
354                },
355                PassSnapshotField {
356                    key: "enums",
357                    value: PassSnapshotValue::U64(elaborated_ast.enums.len() as u64),
358                },
359            ],
360        })
361        .map_err(CompileError::Driver)?;
362    driver
363        .on_expanded_ast(&elaborated_ast)
364        .map_err(CompileError::Driver)?;
365
366    let mut lower_hir_pass = passes::LowerHirPass {
367        program: &elaborated_ast,
368    };
369    let (hir_pass_id, hir) = pass_context
370        .run_pass_with_id(&mut lower_hir_pass, ())
371        .unwrap_or_else(|never| match never {});
372    driver
373        .on_pass_snapshot(&PassSnapshot {
374            kind: PassSnapshotKind::HirProgram,
375            name: "hir.lower_program",
376            detail: format!(
377                "functions={} exprs={} stmts={}",
378                hir.functions.len(),
379                hir.expressions.len(),
380                hir.statements.len()
381            ),
382            fields: vec![
383                PassSnapshotField {
384                    key: "functions",
385                    value: PassSnapshotValue::U64(hir.functions.len() as u64),
386                },
387                PassSnapshotField {
388                    key: "exprs",
389                    value: PassSnapshotValue::U64(hir.expressions.len() as u64),
390                },
391                PassSnapshotField {
392                    key: "stmts",
393                    value: PassSnapshotValue::U64(hir.statements.len() as u64),
394                },
395            ],
396        })
397        .map_err(CompileError::Driver)?;
398    driver.on_hir(&hir).map_err(CompileError::Driver)?;
399    record_hir_provenance_origins(
400        driver,
401        &mut pass_context,
402        &hir,
403        meta_pass_id,
404        elab_pass_id,
405        hir_pass_id,
406    )
407    .map_err(CompileError::Driver)?;
408
409    let mut check_pass = passes::CheckProgramPass { program: &hir };
410    let (_check_pass_id, checked) = pass_context.run_pass_with_id(&mut check_pass, ())?;
411    driver
412        .on_pass_snapshot(&PassSnapshot {
413            kind: PassSnapshotKind::CheckProgram,
414            name: "check.check_program",
415            detail: format!(
416                "signatures={} local_symbols={} occurrences={}",
417                checked.signatures.len(),
418                checked.local_symbols().len(),
419                checked.local_symbol_occurrences().len()
420            ),
421            fields: vec![
422                PassSnapshotField {
423                    key: "signatures",
424                    value: PassSnapshotValue::U64(checked.signatures.len() as u64),
425                },
426                PassSnapshotField {
427                    key: "local_symbols",
428                    value: PassSnapshotValue::U64(checked.local_symbols().len() as u64),
429                },
430                PassSnapshotField {
431                    key: "occurrences",
432                    value: PassSnapshotValue::U64(checked.local_symbol_occurrences().len() as u64),
433                },
434            ],
435        })
436        .map_err(CompileError::Driver)?;
437    driver.on_checked(&checked).map_err(CompileError::Driver)?;
438
439    let mut lower_mir_pass = passes::LowerMirPass {
440        hir: &hir,
441        checked: &checked,
442    };
443    let (mir_pass_id, mir) = pass_context.run_pass_with_id(&mut lower_mir_pass, ())?;
444    let (mir_local_count, mir_instr_count) = mir_function_totals(&mir);
445    driver
446        .on_pass_snapshot(&PassSnapshot {
447            kind: PassSnapshotKind::MirProgram,
448            name: "mir.lower_program",
449            detail: format!(
450                "functions={} locals={} instrs={}",
451                mir.functions.len(),
452                mir_local_count,
453                mir_instr_count
454            ),
455            fields: vec![
456                PassSnapshotField {
457                    key: "functions",
458                    value: PassSnapshotValue::U64(mir.functions.len() as u64),
459                },
460                PassSnapshotField {
461                    key: "locals",
462                    value: PassSnapshotValue::U64(mir_local_count as u64),
463                },
464                PassSnapshotField {
465                    key: "instrs",
466                    value: PassSnapshotValue::U64(mir_instr_count as u64),
467                },
468            ],
469        })
470        .map_err(CompileError::Driver)?;
471    driver.on_mir(&mir).map_err(CompileError::Driver)?;
472    record_mir_provenance_origins(
473        driver,
474        &mut pass_context,
475        &mir,
476        meta_pass_id,
477        elab_pass_id,
478        hir_pass_id,
479        mir_pass_id,
480    )
481    .map_err(CompileError::Driver)?;
482
483    let mut emit_rust_pass = passes::EmitRustPass {
484        mir: &mir,
485        source_file,
486        source_text,
487    };
488    let (_emit_rust_pass_id, rust_source) =
489        pass_context.run_pass_with_id(&mut emit_rust_pass, ())?;
490    driver
491        .on_pass_snapshot(&PassSnapshot {
492            kind: PassSnapshotKind::CodegenRust,
493            name: "codegen.rust.emit_program",
494            detail: format!(
495                "lines={} bytes={}",
496                rust_source.lines().count(),
497                rust_source.len()
498            ),
499            fields: vec![
500                PassSnapshotField {
501                    key: "lines",
502                    value: PassSnapshotValue::U64(rust_source.lines().count() as u64),
503                },
504                PassSnapshotField {
505                    key: "bytes",
506                    value: PassSnapshotValue::U64(rust_source.len() as u64),
507                },
508            ],
509        })
510        .map_err(CompileError::Driver)?;
511    driver.on_rust(&rust_source).map_err(CompileError::Driver)?;
512
513    let mut emit_wasm_pass = passes::EmitWasmPass {
514        mir: &mir,
515        source_text,
516    };
517    let (_emit_wasm_pass_id, wasm_artifacts) = pass_context
518        .run_pass_with_id(&mut emit_wasm_pass, ())
519        .unwrap_or_else(|never| match never {});
520    driver
521        .on_pass_snapshot(&PassSnapshot {
522            kind: PassSnapshotKind::CodegenWasm,
523            name: "codegen.wasm.emit_program",
524            detail: format!(
525                "wat_bytes={} wasm_bytes={} diagnostics={}",
526                wasm_artifacts.wat_source.len(),
527                wasm_artifacts
528                    .wasm_bytes
529                    .as_ref()
530                    .map_or(0, std::vec::Vec::len),
531                wasm_artifacts.diagnostics.len()
532            ),
533            fields: vec![
534                PassSnapshotField {
535                    key: "wat_bytes",
536                    value: PassSnapshotValue::U64(wasm_artifacts.wat_source.len() as u64),
537                },
538                PassSnapshotField {
539                    key: "wasm_bytes",
540                    value: PassSnapshotValue::U64(
541                        wasm_artifacts
542                            .wasm_bytes
543                            .as_ref()
544                            .map_or(0usize, std::vec::Vec::len) as u64,
545                    ),
546                },
547                PassSnapshotField {
548                    key: "diagnostics",
549                    value: PassSnapshotValue::U64(wasm_artifacts.diagnostics.len() as u64),
550                },
551            ],
552        })
553        .map_err(CompileError::Driver)?;
554    driver
555        .on_wat(&wasm_artifacts.wat_source)
556        .map_err(CompileError::Driver)?;
557    if let Some(bytes) = &wasm_artifacts.wasm_bytes {
558        driver.on_wasm(bytes).map_err(CompileError::Driver)?;
559    }
560    driver
561        .on_wasm_diagnostics(&wasm_artifacts.diagnostics)
562        .map_err(CompileError::Driver)?;
563    driver
564        .on_pass_timings(pass_context.pass_timings())
565        .map_err(CompileError::Driver)?;
566    driver
567        .on_provenance_summary(pass_context.provenance().summary())
568        .map_err(CompileError::Driver)?;
569
570    Ok(())
571}
572
573/// Return total local and instruction counts across all MIR functions.
574fn mir_function_totals(mir: &MirProgram) -> (usize, usize) {
575    let mut local_count = 0usize;
576    let mut instr_count = 0usize;
577    for (_, function) in mir.functions.iter() {
578        local_count += function.locals.len();
579        for (_, block) in function.blocks.iter() {
580            instr_count += block.instrs.len();
581        }
582    }
583    (local_count, instr_count)
584}
585
586/// Record provenance chains for all HIR expressions with source spans.
587fn record_hir_provenance_origins<D>(
588    driver: &mut D,
589    pass_context: &mut PassContext,
590    hir: &HirProgram,
591    meta_pass_id: crate::pass::PassId,
592    elab_pass_id: crate::pass::PassId,
593    hir_pass_id: crate::pass::PassId,
594) -> Result<(), D::Error>
595where
596    D: Driver,
597{
598    let mut expr_origins = SecondaryMap::new();
599    for (expr_id, span) in hir.expr_spans.iter() {
600        if let Some(span) = span {
601            let parsed = pass_context.provenance_mut().record_parsed(*span);
602            let expanded = pass_context
603                .provenance_mut()
604                .record_expanded(parsed, meta_pass_id);
605            let elaborated = pass_context
606                .provenance_mut()
607                .record_lowered(expanded, elab_pass_id);
608            let lowered = pass_context
609                .provenance_mut()
610                .record_lowered(elaborated, hir_pass_id);
611            expr_origins[expr_id] = Some(lowered);
612        }
613    }
614    driver.on_hir_origins(&HirOriginTable { expr_origins })
615}
616
617/// Record provenance chains for MIR locals that carry source positions.
618fn record_mir_provenance_origins<D>(
619    driver: &mut D,
620    pass_context: &mut PassContext,
621    mir: &MirProgram,
622    meta_pass_id: crate::pass::PassId,
623    elab_pass_id: crate::pass::PassId,
624    hir_pass_id: crate::pass::PassId,
625    mir_pass_id: crate::pass::PassId,
626) -> Result<(), D::Error>
627where
628    D: Driver,
629{
630    let mut local_origins = SecondaryMap::new();
631    for (func_id, function) in mir.functions.iter() {
632        let mut origins = SecondaryMap::new();
633        for (_, block) in function.blocks.iter() {
634            for instr in &block.instrs {
635                let Some((dst, source_pos)) = mir_instr_source_pos_and_dst(instr) else {
636                    continue;
637                };
638                let Some(span) = source_pos_to_span(source_pos) else {
639                    continue;
640                };
641                let parsed = pass_context.provenance_mut().record_parsed(span);
642                let expanded = pass_context
643                    .provenance_mut()
644                    .record_expanded(parsed, meta_pass_id);
645                let elaborated = pass_context
646                    .provenance_mut()
647                    .record_lowered(expanded, elab_pass_id);
648                let hir_lowered = pass_context
649                    .provenance_mut()
650                    .record_lowered(elaborated, hir_pass_id);
651                let mir_lowered = pass_context
652                    .provenance_mut()
653                    .record_lowered(hir_lowered, mir_pass_id);
654                origins[dst] = Some(mir_lowered);
655            }
656        }
657        local_origins[func_id] = Some(origins);
658    }
659    driver.on_mir_origins(&MirOriginTable { local_origins })
660}
661
662/// Return one MIR destination local and source position when available.
663fn mir_instr_source_pos_and_dst(instr: &MirInstr) -> Option<(MirLocalId, MirSourcePos)> {
664    match instr {
665        MirInstr::MakeArray {
666            dst,
667            source_pos: Some(source_pos),
668            ..
669        }
670        | MirInstr::MakeTuple {
671            dst,
672            source_pos: Some(source_pos),
673            ..
674        }
675        | MirInstr::MakeStruct {
676            dst,
677            source_pos: Some(source_pos),
678            ..
679        }
680        | MirInstr::MakeEnum {
681            dst,
682            source_pos: Some(source_pos),
683            ..
684        }
685        | MirInstr::CallIntrinsic {
686            dst,
687            source_pos: Some(source_pos),
688            ..
689        } => Some((*dst, *source_pos)),
690        _ => None,
691    }
692}
693
694/// Convert MIR source-position metadata into a source span.
695fn source_pos_to_span(source_pos: MirSourcePos) -> Option<SourceSpan> {
696    let line = usize::try_from(source_pos.line).ok()?;
697    let column = usize::try_from(source_pos.column).ok()?;
698    let location = SourceLocation {
699        byte: 0,
700        line,
701        column,
702    };
703    Some(SourceSpan::new(
704        FileId::from_u32(source_pos.file_id),
705        location,
706        location,
707    ))
708}
709
710/// Compile source into browser-facing artifacts.
711///
712/// ```
713/// use std::path::Path;
714///
715/// let source = std::fs::read_to_string("examples/basics.rk").expect("example should exist");
716/// let artifacts = rukalang::driver::compile_for_browser(Path::new("examples/basics.rk"), &source)
717///     .expect("compile should succeed");
718///
719/// assert!(!artifacts.ast_graph.nodes.is_empty());
720/// assert!(artifacts.rust_source.contains("pub fn run_main()"));
721/// ```
722pub fn compile_for_browser(
723    source_file: &Path,
724    source_text: &str,
725) -> Result<BrowserArtifacts, CompileError<Infallible>> {
726    let mut driver = BrowserDriver::default();
727    compile_with_driver_for_browser(source_file, source_text, &mut driver)?;
728
729    Ok(driver.into_artifacts())
730}
731
732#[cfg(test)]
733mod tests;