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//! HIR spec v3 (hir-lang 0.4) to LSB format 2 (bytecode-lang 0.3), in the
40//! **dynamic profile**: every value is an LSB `dyn`. Functions with defaults
41//! (per call or once), named, variadic, and by-reference parameters, each
42//! carrying its LSB parameter list so dynamic calls bind in the VM; locals in
43//! registers, shared variables in LSB references; closures with explicit,
44//! implicit, and recursive captures; generators (keyed `yield`, `yield
45//! from`), `await`, and `spawn`; `if`, the loop primitive with its step,
46//! labeled `break`/`continue`, `return`; `match` by a sequential decision
47//! procedure, raising `NoMatch` when no arm matches; direct, dynamic, host,
48//! and method calls; maps and arrays with PHP value semantics, separation
49//! that copies only what is shared, and key coercion (or shared references);
50//! `let_place` and PHP references into array elements; every OPS v2
51//! operation with its policy (`pow`, `promote`, `shift = saturate`);
52//! union and intersection type tests and casts; `throw` and
53//! `try`/`catch`/`finally` by LSB's canonical lowering, `defer`; globals,
54//! constants, statics, and the module body; named intrinsics through an
55//! [`Intrinsics`] table ([`Intrinsics::mox`] maps every Mox intrinsic to an
56//! inline fast path or a host function). Every instruction maps back to the
57//! HIR node it came from ([`Compiled::origin`]) and to a line-table row.
58//!
59//! Classes (0.4): classes, interfaces, and mixins (PHP traits) compile to LSB
60//! structs with PHP's semantics: single inheritance, constructors,
61//! properties with defaults, instance and static methods, `self::`,
62//! `parent::`, and late-bound `static::`, static properties, constants,
63//! `new`/`instanceof`/`clone` as [`ClassOp`]s of the intrinsic table,
64//! visibility checked where PHP checks it (raised as `TypeError` with PHP's
65//! message), objects thrown and caught by class or interface, and the
66//! destructor's name for the VM ([`Compiled::destructor`]).
67//!
68//! Not yet: declared records and sums, and typed fast paths (0.5); each is a
69//! precise [`CompileError::Unsupported`], as is every class form HIR or LSB
70//! cannot express (multiple inheritance, `impl` blocks).
71//!
72//! ## Guarantees
73//!
74//! - **Loads.** Every module it emits passes `bvm-lang`'s load-time checks:
75//!   registers, windows, constants, branch targets, and handlers in range.
76//! - **Semantics.** Differential tests run random programs through the
77//!   compiler and the VM, and through a reference interpreter written in
78//!   the tests, comparing results, error kinds, and error locations.
79//! - **No recursion over the input.** Generation is an explicit task stack;
80//!   any nesting depth compiles in bounded native stack.
81//! - **Budgeted and deterministic.** Output is bounded by
82//!   [`Options::with_max_instructions`]; the same unit compiles to the same
83//!   bytes.
84//!
85//! ## `no_std`
86//!
87//! The crate needs only `alloc`. The default `std` feature is additive.
88
89#![cfg_attr(not(feature = "std"), no_std)]
90#![cfg_attr(docsrs, feature(doc_cfg))]
91#![forbid(unsafe_code)]
92#![deny(missing_docs)]
93#![deny(unused_must_use)]
94#![deny(unused_results)]
95#![deny(clippy::unwrap_used)]
96#![deny(clippy::expect_used)]
97#![deny(clippy::todo)]
98#![deny(clippy::unimplemented)]
99#![deny(clippy::print_stdout)]
100#![deny(clippy::print_stderr)]
101#![deny(clippy::dbg_macro)]
102
103extern crate alloc;
104
105mod analysis;
106mod class;
107mod codegen;
108mod error;
109mod func;
110mod intrinsics;
111mod members;
112mod options;
113mod output;
114mod unit;
115
116pub use error::{ClassProblem, CompileError, Unsupported};
117pub use intrinsics::{Arity, ClassOp, Fast, IntrinsicDef, Intrinsics, Lowering, LsbOp};
118pub use options::{ArraySemantics, BodyMode, KeyCoercion, LineIndex, Locate, Location, Options};
119pub use output::Compiled;
120
121/// Compiles and runs the `rust` code blocks in `README.md`, `docs/API.md`,
122/// and the current release notes as part of `cargo test`, so the published
123/// examples cannot drift from the API.
124#[cfg(doctest)]
125#[doc = include_str!("../README.md")]
126#[doc = include_str!("../docs/API.md")]
127#[doc = include_str!("../docs/release/v0.4.0.md")]
128pub struct MarkdownDocTests;
129
130use hir_lang::Hir;
131use intern_lang::Lookup;
132
133/// Compiles a resolved unit with the default [`Options`] (the PHP profile).
134///
135/// `names` is the interner the unit's names were interned in; function,
136/// import, property, and debug names are read from it.
137///
138/// # Errors
139///
140/// A [`CompileError`] naming the first form that does not compile, in the
141/// unit's canonical order.
142///
143/// # Examples
144///
145/// ```
146/// use hir_lang::Builder;
147/// use intern_lang::Interner;
148///
149/// let mut b = Builder::new();
150/// let root = b.module(None, &[]);
151/// let hir = b.finish(root)?;
152/// let out = bcgen_lang::compile(&hir, &Interner::new())?;
153/// assert_eq!(out.module().functions().len(), 1); // the entry function
154/// # Ok::<(), Box<dyn std::error::Error>>(())
155/// ```
156pub fn compile<L: Lookup>(hir: &Hir, names: &L) -> Result<Compiled, CompileError> {
157    Compiler::new(Options::new()).compile(hir, names)
158}
159
160/// A configured compiler: [`Options`] and, optionally, a [`Locate`] for the
161/// line tables.
162///
163/// # Examples
164///
165/// ```
166/// use bcgen_lang::{Compiler, LineIndex, Options};
167/// use hir_lang::Builder;
168/// use intern_lang::Interner;
169///
170/// let lines = LineIndex::new("main.mox", 0, "");
171/// let mut b = Builder::new();
172/// let root = b.module(None, &[]);
173/// let hir = b.finish(root)?;
174/// let out = Compiler::new(Options::new())
175///     .with_locator(&lines)
176///     .compile(&hir, &Interner::new())?;
177/// assert!(out.start().is_none());
178/// # Ok::<(), Box<dyn std::error::Error>>(())
179/// ```
180#[derive(Clone)]
181pub struct Compiler<'a> {
182    options: Options,
183    locator: Option<&'a dyn Locate>,
184}
185
186impl core::fmt::Debug for Compiler<'_> {
187    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188        f.debug_struct("Compiler")
189            .field("options", &self.options)
190            .field("locator", &self.locator.is_some())
191            .finish()
192    }
193}
194
195impl Compiler<'static> {
196    /// A compiler with these options and no locator (line-table rows carry
197    /// byte offsets; see [`Locate`]).
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use bcgen_lang::{ArraySemantics, Compiler, Options};
203    ///
204    /// let c = Compiler::new(Options::new().with_arrays(ArraySemantics::Reference));
205    /// assert_eq!(c.options().arrays(), ArraySemantics::Reference);
206    /// ```
207    #[must_use]
208    pub fn new(options: Options) -> Self {
209        Compiler {
210            options,
211            locator: None,
212        }
213    }
214}
215
216impl Compiler<'_> {
217    /// The same compiler, writing line-table rows through `locator`.
218    ///
219    /// # Examples
220    ///
221    /// See [`Compiler`].
222    #[must_use]
223    pub fn with_locator(self, locator: &dyn Locate) -> Compiler<'_> {
224        Compiler {
225            options: self.options,
226            locator: Some(locator),
227        }
228    }
229
230    /// The options.
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// use bcgen_lang::{Compiler, Options};
236    ///
237    /// assert_eq!(Compiler::new(Options::new()).options(), &Options::new());
238    /// ```
239    #[must_use]
240    pub fn options(&self) -> &Options {
241        &self.options
242    }
243
244    /// Compiles a resolved unit.
245    ///
246    /// # Errors
247    ///
248    /// A [`CompileError`] naming the first form that does not compile.
249    ///
250    /// # Examples
251    ///
252    /// See [`Compiler`].
253    pub fn compile<L: Lookup>(&self, hir: &Hir, names: &L) -> Result<Compiled, CompileError> {
254        unit::compile(hir, names, &self.options, self.locator)
255    }
256}