beck_core/stdlib.rs
1//! The standard library's Beck half, carried inside the compiler.
2//!
3//! [`compiler/lib/README.md`](../../../../lib/README.md) divides the standard library in two: a
4//! host's table or grammar is a primitive in [`crate::prelude`], and composition is a file written
5//! in Beck. The primitive half has always been in the binary. This is the other half, in the binary
6//! for the same reason — so that `import bignum` means the library this compiler was built with,
7//! wherever the program being compiled happens to sit.
8//!
9//! [`adr/0018`](../../../../../docs/adr/0018-the-standard-library-is-carried-in-the-compiler.md) is
10//! the decision and the alternatives it was taken over; [`10`](../../../../../docs/10-decisions.md)
11//! D23 is the language-level rule:
12//!
13//! > `import x` resolves against the root module's own directory first, and against the standard
14//! > library second.
15//!
16//! The directory-first half is what keeps the files here editable: `lib/decimal.beck` imports
17//! `bignum` and gets the file beside it, so working on the library does not mean rebuilding the
18//! compiler to see the change. A program anywhere else gets this copy.
19
20use crate::project::Sources;
21
22/// Every module in `compiler/lib/`, under the name an `import` writes.
23///
24/// The list is here rather than discovered by a `build.rs` walk because a file appearing in the
25/// standard library is an API addition — a new name every program in the language can suddenly
26/// import — and one that should be written down and reviewed rather than picked up. It is not
27/// trusted to be complete: `beck-cli/tests/stdlib.rs` reads the directory and fails if a file there
28/// is missing from here, which is the same gate the directory has always had for its tests.
29pub const MODULES: &[(&str, &str)] = &[
30 ("bignum", include_str!("../../../lib/bignum.beck")),
31 ("collections", include_str!("../../../lib/collections.beck")),
32 ("crypto", include_str!("../../../lib/crypto.beck")),
33 ("dates", include_str!("../../../lib/dates.beck")),
34 ("decimal", include_str!("../../../lib/decimal.beck")),
35 ("documents", include_str!("../../../lib/documents.beck")),
36 ("format", include_str!("../../../lib/format.beck")),
37 ("http", include_str!("../../../lib/http.beck")),
38 ("money", include_str!("../../../lib/money.beck")),
39 ("text", include_str!("../../../lib/text.beck")),
40];
41
42/// The source of a standard-library module, if there is one by that name.
43pub fn source(name: &str) -> Option<&'static str> {
44 MODULES.iter().find(|(n, _)| *n == name).map(|(_, s)| *s)
45}
46
47/// Whether `name` is a standard-library module.
48pub fn has(name: &str) -> bool {
49 source(name).is_some()
50}
51
52/// Every name a program can import without a file beside it.
53pub fn names() -> impl Iterator<Item = &'static str> {
54 MODULES.iter().map(|(n, _)| *n)
55}
56
57/// The module as the project loader wants it.
58///
59/// The path is `<std>/<name>.beck` rather than a filesystem path, because there is no file: a
60/// diagnostic inside a standard-library module has to say where it is, and saying `lib/bignum.beck`
61/// would name a file that need not exist on the machine compiling.
62pub fn sources(name: &str) -> Option<Sources> {
63 source(name).map(|text| Sources {
64 module: Some(text.to_string()),
65 interface: None,
66 path: Some(format!("<std>/{name}.beck")),
67 })
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn the_table_is_sorted_and_has_no_duplicates() {
76 let names: Vec<&str> = names().collect();
77 let mut sorted = names.clone();
78 sorted.sort_unstable();
79 sorted.dedup();
80 assert_eq!(
81 names, sorted,
82 "the standard-library table is not a sorted set"
83 );
84 }
85
86 #[test]
87 fn every_module_carries_its_source() {
88 for (name, text) in MODULES {
89 assert!(!text.trim().is_empty(), "`{name}` is embedded empty");
90 }
91 }
92
93 /// A module a program does not have beside it is not a standard-library module by accident.
94 #[test]
95 fn a_name_that_is_not_in_the_library_resolves_to_nothing() {
96 assert!(source("nowhere").is_none());
97 assert!(!has("prelude"));
98 }
99}