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, std_source};
22pub use codespan::Span;
23pub use discover::{DiscoverParams, discover};
24pub use format::format;
25pub use parser::parse_path;
26pub use project::{Project, SourceTree, SymbolInfo};
27
28pub use lutra_bin::{ir, rr};
29
30/// The representation kind of a compiled Lutra program.
31#[derive(Debug, Clone, Copy)]
32#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
33pub enum ProgramRepr {
34    SqlPg,
35    SqlDuckdb,
36    BytecodeLt,
37}
38
39/// The representation kind of a compiled Lutra program.
40#[derive(Clone)]
41#[cfg_attr(feature = "clap", derive(clap::Parser))]
42pub struct CompileParams {
43    /// Program source code.
44    ///
45    /// Usually this is a path to an expression in the project, but it can be
46    /// any lutra expression.
47    #[cfg_attr(feature = "clap", clap(long, default_value = "main"))]
48    program: String,
49
50    /// The name of the program that should be displayed in diagnostics.
51    /// For example, when called by cli, this would be `"--program"`.
52    #[cfg_attr(feature = "clap", clap(skip))]
53    program_name_hint: Option<String>,
54
55    /// Target program representation.
56    #[cfg_attr(feature = "clap", clap(long, default_value = "bytecode-lt"))]
57    repr: ProgramRepr,
58
59    /// Externals that are allowed to be used by the program.
60    ///
61    /// Contains fully-qualified paths to the external functions or modules.
62    /// When a module is referenced, all contained external functions are allowed.
63    #[cfg_attr(feature = "clap", clap(skip))]
64    externals: Vec<std::borrow::Cow<'static, str>>,
65}
66
67pub fn compile(
68    project: &Project,
69    params: &CompileParams,
70) -> Result<(rr::Program, rr::ProgramType), error::Error> {
71    // resolve
72    let program_pr = self::check::check_overlay(
73        project,
74        &params.program,
75        params.program_name_hint.as_deref(),
76    )?;
77
78    // lower
79    let program_ir = intermediate::lowerer::lower_expr(project, &program_pr);
80    tracing::debug!("ir:\n{}\n", lutra_bin::ir::print_no_color(&program_ir));
81
82    let program_ir = intermediate::validate_externals(program_ir, &params.externals)?;
83
84    // intermediate optimizations
85    let program_ir = intermediate::inline(program_ir);
86    tracing::debug!(
87        "ir (inlined):\n{}\n",
88        lutra_bin::ir::print_no_color(&program_ir)
89    );
90    let program_ir = intermediate::layouter::on_program(program_ir);
91
92    // backend (sql or bytecode)
93    let program = match params.repr {
94        ProgramRepr::SqlPg => rr::Program::SqlPostgres(Box::new(sql::compile_ir(
95            &program_ir,
96            sql::Dialect::Postgres,
97        ))),
98        ProgramRepr::SqlDuckdb => {
99            rr::Program::SqlDuckDb(Box::new(sql::compile_ir(&program_ir, sql::Dialect::DuckDB)))
100        }
101        ProgramRepr::BytecodeLt => {
102            rr::Program::BytecodeLt(bytecoding::compile_program(program_ir.clone()))
103        }
104    };
105
106    // construct the program ty
107    let ir_func_ty = program_ir.main.ty.kind.into_function().unwrap();
108    let ty = rr::ProgramType {
109        input: ir_func_ty.params.into_iter().next().unwrap(),
110        output: ir_func_ty.body,
111        defs: program_ir.defs,
112    };
113
114    Ok((program, ty))
115}
116
117pub fn project_to_types(project: &Project) -> ir::Module {
118    let module = intermediate::lowerer::lower_type_defs(project);
119
120    intermediate::layouter::on_root_module(module)
121}
122
123/// Internal implementation detail, do not use.
124// Only exposed because I don't want to maintain a separate lexer for IR parser.
125#[doc(hidden)]
126pub mod _lexer {
127    pub use crate::diagnostic::Diagnostic;
128    pub use crate::parser::{Token, TokenKind, lex_source_recovery as lex};
129}
130
131/// Internal implementation detail, do not use.
132// Exposed for benchmarks.
133#[doc(hidden)]
134pub mod _bench {
135    pub use crate::diagnostic::Diagnostic;
136    pub use crate::parser::{parse_expr, parse_source};
137}
138
139#[track_caller]
140pub fn _test_compile_ty(ty_source: &str) -> (ir::Ty, Vec<ir::TyDef>) {
141    let source = SourceTree::empty();
142    let project = check(source, Default::default()).unwrap_or_else(|e| panic!("{e}"));
143
144    let program = format!("func (x: {ty_source}) -> x");
145    let program = check_overlay(&project, &program, None).unwrap();
146    let program = intermediate::lowerer::lower_expr(&project, &program);
147    let program = intermediate::layouter::on_program(program);
148    (program.get_input_ty().clone(), program.defs)
149}
150
151#[doc(hidden)]
152pub fn _test_compile_main(source: &str) -> Result<ir::Program, error::Error> {
153    let source = SourceTree::single("".into(), source.to_string());
154    let project = check(source, Default::default())?;
155    _test_compile_main_in(&project)
156}
157
158#[doc(hidden)]
159pub fn _test_compile_main_in(project: &Project) -> Result<ir::Program, error::Error> {
160    let main = check_overlay(project, "main", None)?;
161    let program = intermediate::lowerer::lower_expr(project, &main);
162    Ok(intermediate::layouter::on_program(program))
163}
164
165#[doc(hidden)]
166pub fn _test_inline(program: ir::Program) -> ir::Program {
167    intermediate::inline(program)
168}
169
170#[doc(hidden)]
171pub fn _test_layout(program: ir::Program) -> ir::Program {
172    intermediate::layouter::on_program(program)
173}
174
175impl std::fmt::Display for ProgramRepr {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        match self {
178            Self::SqlPg => write!(f, "sql-pg"),
179            Self::SqlDuckdb => write!(f, "sql-duckdb"),
180            Self::BytecodeLt => write!(f, "bytecode-lt"),
181        }
182    }
183}
184
185impl ProgramRepr {
186    fn get_implicit_externals(&self) -> Vec<std::borrow::Cow<'static, str>> {
187        match self {
188            ProgramRepr::SqlPg => vec!["std::sql".into()],
189            ProgramRepr::SqlDuckdb => vec!["std::sql".into(), "std::fs".into()],
190            ProgramRepr::BytecodeLt => vec![],
191        }
192    }
193
194    /// The tag string used in `get_externals()` to identify this repr.
195    pub fn tag(&self) -> &'static str {
196        match self {
197            ProgramRepr::SqlPg => "repr:sql-pg",
198            ProgramRepr::SqlDuckdb => "repr:sql-duckdb",
199            ProgramRepr::BytecodeLt => "repr:bytecode-lt",
200        }
201    }
202
203    /// Extract the program repr from a list of externals.
204    ///
205    /// Looks for a `repr:` prefixed tag. Returns `None` if no repr tag is found.
206    pub fn from_externals<S: AsRef<str>>(externals: &[S]) -> Option<Self> {
207        for ext in externals {
208            match ext.as_ref() {
209                "repr:sql-pg" | "repr:sql-postgres" => return Some(ProgramRepr::SqlPg),
210                "repr:sql-duckdb" => return Some(ProgramRepr::SqlDuckdb),
211                "repr:bytecode-lt" => return Some(ProgramRepr::BytecodeLt),
212                _ => {}
213            }
214        }
215        None
216    }
217}
218
219impl CompileParams {
220    pub fn new(program: impl Into<String>, repr: ProgramRepr) -> Self {
221        Self {
222            program: program.into(),
223            program_name_hint: None,
224            repr,
225            externals: repr.get_implicit_externals(),
226        }
227    }
228
229    /// Create compile params from a program name and runner-provided externals.
230    ///
231    /// The externals list should contain a `repr:` tag (e.g. `repr:sql-pg`)
232    /// that determines the compilation target.
233    pub fn from_externals<S: AsRef<str>>(
234        program: impl Into<String>,
235        externals: &[S],
236    ) -> Result<Self, String> {
237        let repr = ProgramRepr::from_externals(externals).ok_or_else(|| {
238            "runner did not provide a program repr tag in get_externals()".to_string()
239        })?;
240        let externals: Vec<std::borrow::Cow<'static, str>> = externals
241            .iter()
242            .map(|s| std::borrow::Cow::Owned(s.as_ref().to_string()))
243            .collect();
244        Ok(Self {
245            program: program.into(),
246            program_name_hint: None,
247            repr,
248            externals,
249        })
250    }
251
252    pub fn with_program_name_hint(mut self, hint: impl Into<String>) -> Self {
253        self.program_name_hint = Some(hint.into());
254        self
255    }
256
257    pub fn with_externals<I, T>(mut self, externals: I) -> Self
258    where
259        I: IntoIterator<Item = T>,
260        T: Into<std::borrow::Cow<'static, str>>,
261    {
262        self.externals
263            .extend(externals.into_iter().map(|x| x.into()));
264        self
265    }
266
267    pub fn with_external(mut self, external: impl Into<std::borrow::Cow<'static, str>>) -> Self {
268        self.externals.push(external.into());
269        self
270    }
271
272    pub fn repr(&self) -> ProgramRepr {
273        self.repr
274    }
275}