Skip to main content

lutra_compiler/
lib.rs

1mod bytecoding;
2mod check;
3mod diagnostic;
4mod discover;
5mod format;
6mod intermediate;
7mod parser;
8mod project;
9mod resolver;
10mod sql;
11mod utils;
12
13type Result<T, E = diagnostic::Diagnostic> = core::result::Result<T, E>;
14
15pub mod codespan;
16pub mod error;
17pub mod pr;
18pub mod printer;
19
20pub use bytecoding::compile_program as bytecode_program;
21pub use check::{CheckParams, check, check_overlay};
22pub use codespan::Span;
23pub use discover::{DiscoverParams, discover};
24pub use format::format;
25pub use intermediate::inline;
26pub use lutra_bin::{ir, rr};
27pub use project::{Project, SourceTree};
28
29#[derive(Debug, Clone, Copy)]
30#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
31pub enum ProgramFormat {
32    SqlPg,
33    SqlDuckdb,
34    BytecodeLt,
35}
36
37pub fn compile(
38    project: &Project,
39    program: &str,
40    name_hint: Option<&str>,
41    format: ProgramFormat,
42) -> Result<(rr::Program, rr::ProgramType), error::Error> {
43    // resolve
44    let program_pr = self::check::check_overlay(project, program, name_hint)?;
45
46    // lower
47    let program_ir = intermediate::lowerer::lower_expr(project, &program_pr);
48    tracing::debug!("ir:\n{}\n", lutra_bin::ir::print(&program_ir));
49
50    // intermediate optimizations
51    let program_ir = intermediate::inline(program_ir);
52    tracing::debug!("ir (inlined):\n{}\n", lutra_bin::ir::print(&program_ir));
53    let program_ir = intermediate::layouter::on_program(program_ir);
54
55    // backend (sql or bytecode)
56    let program = match format {
57        ProgramFormat::SqlPg => rr::Program::SqlPostgres(Box::new(sql::compile_ir(
58            &program_ir,
59            sql::Dialect::Postgres,
60        ))),
61        ProgramFormat::SqlDuckdb => {
62            rr::Program::SqlDuckDB(Box::new(sql::compile_ir(&program_ir, sql::Dialect::DuckDB)))
63        }
64        ProgramFormat::BytecodeLt => {
65            rr::Program::BytecodeLt(bytecoding::compile_program(program_ir.clone()))
66        }
67    };
68
69    // construct the program ty
70    let ir_func_ty = program_ir.main.ty.kind.into_function().unwrap();
71    let ty = rr::ProgramType {
72        input: ir_func_ty.params.into_iter().next().unwrap(),
73        output: ir_func_ty.body,
74        defs: program_ir.defs,
75    };
76
77    Ok((program, ty))
78}
79
80pub fn project_to_types(project: &Project) -> ir::Module {
81    let module = intermediate::lowerer::lower_type_defs(project);
82
83    intermediate::layouter::on_root_module(module)
84}
85
86/// Internal implementation detail, do not use.
87// Only exposed because I don't want to maintain a separate lexer for IR parser.
88pub mod _lexer {
89    pub use crate::diagnostic::Diagnostic;
90    pub use crate::parser::lexer::{Token, TokenKind};
91
92    pub use crate::parser::lexer::lex_source_recovery as lex;
93}
94
95#[track_caller]
96pub fn _test_compile_ty(ty_source: &str) -> ir::Ty {
97    let source = format!("type t: {ty_source}");
98
99    let source = SourceTree::single("".into(), source);
100    let project = check(source, CheckParams {}).unwrap_or_else(|e| panic!("{e}"));
101
102    let module = project_to_types(&project);
103
104    let item = module.decls.into_iter().next().unwrap();
105    assert_eq!(item.name, "t");
106    let ir::Decl::Type(mut ty) = item.decl else {
107        panic!()
108    };
109
110    ty.name = None;
111    ty
112}
113
114pub fn _test_compile_main(source: &str) -> Result<ir::Program, error::Error> {
115    let source = SourceTree::single("".into(), source.to_string());
116    let project = check(source, CheckParams {})?;
117
118    let main = check_overlay(&project, "main", None)?;
119    let program = intermediate::lowerer::lower_expr(&project, &main);
120    Ok(intermediate::layouter::on_program(program))
121}