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 ("json", include_str!("../../../lib/json.beck")),
39 ("money", include_str!("../../../lib/money.beck")),
40 ("text", include_str!("../../../lib/text.beck")),
41];
42
43/// The source of a standard-library module, if there is one by that name.
44pub fn source(name: &str) -> Option<&'static str> {
45 MODULES.iter().find(|(n, _)| *n == name).map(|(_, s)| *s)
46}
47
48/// Whether `name` is a standard-library module.
49pub fn has(name: &str) -> bool {
50 source(name).is_some()
51}
52
53/// Every name a program can import without a file beside it.
54pub fn names() -> impl Iterator<Item = &'static str> {
55 MODULES.iter().map(|(n, _)| *n)
56}
57
58/// The module as the project loader wants it.
59///
60/// The path is `<std>/<name>.beck` rather than a filesystem path, because there is no file: a
61/// diagnostic inside a standard-library module has to say where it is, and saying `lib/bignum.beck`
62/// would name a file that need not exist on the machine compiling.
63pub fn sources(name: &str) -> Option<Sources> {
64 source(name).map(|text| Sources {
65 module: Some(text.to_string()),
66 interface: None,
67 path: Some(format!("<std>/{name}.beck")),
68 })
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn the_table_is_sorted_and_has_no_duplicates() {
77 let names: Vec<&str> = names().collect();
78 let mut sorted = names.clone();
79 sorted.sort_unstable();
80 sorted.dedup();
81 assert_eq!(
82 names, sorted,
83 "the standard-library table is not a sorted set"
84 );
85 }
86
87 #[test]
88 fn every_module_carries_its_source() {
89 for (name, text) in MODULES {
90 assert!(!text.trim().is_empty(), "`{name}` is embedded empty");
91 }
92 }
93
94 /// A module a program does not have beside it is not a standard-library module by accident.
95 #[test]
96 fn a_name_that_is_not_in_the_library_resolves_to_nothing() {
97 assert!(source("nowhere").is_none());
98 assert!(!has("prelude"));
99 }
100}