Skip to main content

bcgen_lang/
lib.rs

1//! # bcgen_lang
2//!
3//! Compiles HIR (the representation every LexerSketch language lowers into)
4//! to LSB, the bytecode `bvm-lang` executes.
5//!
6//! ## The lazy path
7//!
8//! Build or lower a resolved unit, [`compile`] it, and hand the module to a
9//! VM:
10//!
11//! ```
12//! use hir_lang::{Builder, Name, OpKind};
13//! use intern_lang::Interner;
14//!
15//! // fn add(a, b) { a + b }
16//! let mut names = Interner::new();
17//! let mut b = Builder::new();
18//! let (pa, a) = b.local_param(Name::new(names.intern("a")));
19//! let (pb, bb) = b.local_param(Name::new(names.intern("b")));
20//! let (ua, ub) = (b.use_binder(a), b.use_binder(bb));
21//! let sum = b.op(OpKind::Add, &[ua, ub]);
22//! let body = b.block(&[], Some(sum));
23//! let add = b.func(Name::new(names.intern("add")), &[pa, pb], body);
24//! let root = b.module(None, &[add]);
25//! let hir = b.finish(root)?;
26//!
27//! let out = bcgen_lang::compile(&hir, &names)?;
28//! let module = out.module();
29//! assert!(module.function(out.function(add).unwrap()).is_some());
30//! assert!(!bytecode_lang::encode(module).is_empty());
31//! # Ok::<(), Box<dyn std::error::Error>>(())
32//! ```
33//!
34//! Running it (with `bvm-lang`): `Program::load(out.into_module(), &host)`,
35//! then `vm.run(func, &[Value::Int(2), Value::Int(3)])` returns `Int(5)`.
36//!
37//! ## What it compiles
38//!
39//! The **dynamic profile**: every value is an LSB `dyn`. Functions with
40//! defaults (per call or once), named, variadic, and by-reference
41//! parameters; locals in registers, shared variables in cells; closures with
42//! explicit, implicit, and recursive captures; `if`, the loop primitive with
43//! its step, labeled `break`/`continue`, `return`; `match` by a sequential
44//! decision procedure; direct, dynamic, host, and method calls; maps and
45//! arrays with PHP value semantics and key coercion (or shared references);
46//! every OPS operation with its policy, `promote` included; `throw` and
47//! `try`/`catch`/`finally` by LSB's canonical lowering, `defer`; globals,
48//! constants, statics, and the module body. Every instruction maps back to
49//! the HIR node it came from ([`Compiled::origin`]) and to a line-table row.
50//!
51//! Not yet: coroutines, classes, declared records and sums, and typed fast
52//! paths (0.5); each is a precise [`CompileError::Unsupported`].
53//!
54//! ## Guarantees
55//!
56//! - **Loads.** Every module it emits passes `bvm-lang`'s load-time checks:
57//!   registers, windows, constants, branch targets, and handlers in range.
58//! - **Semantics.** Differential tests run random programs through the
59//!   compiler and the VM, and through a reference interpreter written in
60//!   the tests, comparing results, error kinds, and error locations.
61//! - **No recursion over the input.** Generation is an explicit task stack;
62//!   any nesting depth compiles in bounded native stack.
63//! - **Budgeted and deterministic.** Output is bounded by
64//!   [`Options::with_max_instructions`]; the same unit compiles to the same
65//!   bytes.
66//!
67//! ## `no_std`
68//!
69//! The crate needs only `alloc`. The default `std` feature is additive.
70
71#![cfg_attr(not(feature = "std"), no_std)]
72#![cfg_attr(docsrs, feature(doc_cfg))]
73#![forbid(unsafe_code)]
74#![deny(missing_docs)]
75#![deny(unused_must_use)]
76#![deny(unused_results)]
77#![deny(clippy::unwrap_used)]
78#![deny(clippy::expect_used)]
79#![deny(clippy::todo)]
80#![deny(clippy::unimplemented)]
81#![deny(clippy::print_stdout)]
82#![deny(clippy::print_stderr)]
83#![deny(clippy::dbg_macro)]
84
85extern crate alloc;
86
87mod analysis;
88mod codegen;
89mod error;
90mod func;
91mod options;
92mod output;
93mod unit;
94
95pub use error::{CompileError, Unsupported};
96pub use options::{ArraySemantics, BodyMode, KeyCoercion, LineIndex, Locate, Location, Options};
97pub use output::Compiled;
98
99/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md`
100/// as part of `cargo test`, so the published examples cannot drift from the
101/// API.
102#[cfg(doctest)]
103#[doc = include_str!("../README.md")]
104#[doc = include_str!("../docs/API.md")]
105pub struct MarkdownDocTests;
106
107use hir_lang::Hir;
108use intern_lang::Lookup;
109
110/// Compiles a resolved unit with the default [`Options`] (the PHP profile).
111///
112/// `names` is the interner the unit's names were interned in; function,
113/// import, property, and debug names are read from it.
114///
115/// # Errors
116///
117/// A [`CompileError`] naming the first form that does not compile, in the
118/// unit's canonical order.
119///
120/// # Examples
121///
122/// ```
123/// use hir_lang::Builder;
124/// use intern_lang::Interner;
125///
126/// let mut b = Builder::new();
127/// let root = b.module(None, &[]);
128/// let hir = b.finish(root)?;
129/// let out = bcgen_lang::compile(&hir, &Interner::new())?;
130/// assert_eq!(out.module().functions().len(), 1); // the entry function
131/// # Ok::<(), Box<dyn std::error::Error>>(())
132/// ```
133pub fn compile<L: Lookup>(hir: &Hir, names: &L) -> Result<Compiled, CompileError> {
134    Compiler::new(Options::new()).compile(hir, names)
135}
136
137/// A configured compiler: [`Options`] and, optionally, a [`Locate`] for the
138/// line tables.
139///
140/// # Examples
141///
142/// ```
143/// use bcgen_lang::{Compiler, LineIndex, Options};
144/// use hir_lang::Builder;
145/// use intern_lang::Interner;
146///
147/// let lines = LineIndex::new("main.mox", 0, "");
148/// let mut b = Builder::new();
149/// let root = b.module(None, &[]);
150/// let hir = b.finish(root)?;
151/// let out = Compiler::new(Options::new())
152///     .with_locator(&lines)
153///     .compile(&hir, &Interner::new())?;
154/// assert!(out.start().is_none());
155/// # Ok::<(), Box<dyn std::error::Error>>(())
156/// ```
157#[derive(Clone)]
158pub struct Compiler<'a> {
159    options: Options,
160    locator: Option<&'a dyn Locate>,
161}
162
163impl core::fmt::Debug for Compiler<'_> {
164    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165        f.debug_struct("Compiler")
166            .field("options", &self.options)
167            .field("locator", &self.locator.is_some())
168            .finish()
169    }
170}
171
172impl Compiler<'static> {
173    /// A compiler with these options and no locator (line-table rows carry
174    /// byte offsets; see [`Locate`]).
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use bcgen_lang::{ArraySemantics, Compiler, Options};
180    ///
181    /// let c = Compiler::new(Options::new().with_arrays(ArraySemantics::Reference));
182    /// assert_eq!(c.options().arrays(), ArraySemantics::Reference);
183    /// ```
184    #[must_use]
185    pub fn new(options: Options) -> Self {
186        Compiler {
187            options,
188            locator: None,
189        }
190    }
191}
192
193impl Compiler<'_> {
194    /// The same compiler, writing line-table rows through `locator`.
195    ///
196    /// # Examples
197    ///
198    /// See [`Compiler`].
199    #[must_use]
200    pub fn with_locator(self, locator: &dyn Locate) -> Compiler<'_> {
201        Compiler {
202            options: self.options,
203            locator: Some(locator),
204        }
205    }
206
207    /// The options.
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// use bcgen_lang::{Compiler, Options};
213    ///
214    /// assert_eq!(Compiler::new(Options::new()).options(), &Options::new());
215    /// ```
216    #[must_use]
217    pub fn options(&self) -> &Options {
218        &self.options
219    }
220
221    /// Compiles a resolved unit.
222    ///
223    /// # Errors
224    ///
225    /// A [`CompileError`] naming the first form that does not compile.
226    ///
227    /// # Examples
228    ///
229    /// See [`Compiler`].
230    pub fn compile<L: Lookup>(&self, hir: &Hir, names: &L) -> Result<Compiled, CompileError> {
231        unit::compile(hir, names, &self.options, self.locator)
232    }
233}