cove_sema/stdlib.rs
1//! The standard library: Cove source embedded in the compiler binary.
2//!
3//! `cove_schema::builtins::STANDARD_LIBRARY` names which builtin methods have
4//! moved out of Rust and into Cove, and points at the module each one lives
5//! in. This module is where that module's source
6//! actually is: `crates/cove-sema/std/*.cove`, read into the binary with
7//! `include_str!` so a checked program never depends on a file existing on
8//! disk at some path relative to the running `cove`.
9//!
10//! # This is the precompile boundary
11//!
12//! [`attach`] parses the embedded source into the caller's [`SourceMap`]
13//! every time it is called, exactly as parsing any other module does. That
14//! is deliberate and temporary: nothing about the standard library changes
15//! between runs, so the day it is warm enough to matter, this is the one
16//! function that changes — to answer a cached checked
17//! [`Program`](crate::resolve::Program) or cached IR instead of parsing from
18//! scratch — and every caller stays as it is. Do not build that cache before
19//! there is a measurement asking for it; the point of writing it down here is
20//! that nothing outside this function has to know when it arrives.
21
22use std::path::PathBuf;
23
24use cove_diag::{Diagnostic, SourceMap};
25
26use crate::package::{Module, Unit};
27
28/// One embedded standard-library source file and the module it belongs to.
29struct StdSource {
30 /// Dotted module name, such as `"std.array"`.
31 module: &'static str,
32 /// A path to show in a diagnostic or a stack trace. Never read from
33 /// disk: the text beside it is what is actually parsed.
34 path: &'static str,
35 /// The file's contents, embedded at compile time.
36 text: &'static str,
37}
38
39/// Every file the standard library is made of.
40///
41/// One file per receiver, which is why there are several holding one
42/// function each: `Array` and `Vector` cannot share a body without a bound
43/// the language does not have, so they do not share a file either. A method
44/// migrating out of Rust adds a function to an existing file or a new file
45/// here, and an entry to `cove_schema::builtins::STANDARD_LIBRARY` pointing
46/// at it.
47static SOURCES: &[StdSource] = &[
48 StdSource {
49 module: "std.array",
50 path: "std/array.cove",
51 text: include_str!("../std/array.cove"),
52 },
53 StdSource {
54 module: "std.vector",
55 path: "std/vector.cove",
56 text: include_str!("../std/vector.cove"),
57 },
58 StdSource {
59 module: "std.map",
60 path: "std/map.cove",
61 text: include_str!("../std/map.cove"),
62 },
63 StdSource {
64 module: "std.set",
65 path: "std/set.cove",
66 text: include_str!("../std/set.cove"),
67 },
68 StdSource {
69 module: "std.string",
70 path: "std/string.cove",
71 text: include_str!("../std/string.cove"),
72 },
73 StdSource {
74 module: "std.option",
75 path: "std/option.cove",
76 text: include_str!("../std/option.cove"),
77 },
78 StdSource {
79 module: "std.result",
80 path: "std/result.cove",
81 text: include_str!("../std/result.cove"),
82 },
83 StdSource {
84 module: "std.int",
85 path: "std/int.cove",
86 text: include_str!("../std/int.cove"),
87 },
88 StdSource {
89 module: "std.duration",
90 path: "std/duration.cove",
91 text: include_str!("../std/duration.cove"),
92 },
93];
94
95/// Every module name the standard library declares.
96///
97/// This is `cove_sema::package::load`'s and `Compiler::compile`'s way of
98/// asking "is the standard library here?" without parsing anything: a
99/// package that already has a module by one of these names either loaded it
100/// from `attach` or collides with it, and either way the answer does not
101/// require a parse.
102pub fn module_names() -> &'static [&'static str] {
103 // If a later file adds a second module, dedupe here rather than asking
104 // every caller to.
105 static NAMES: std::sync::OnceLock<Vec<&'static str>> = std::sync::OnceLock::new();
106 NAMES.get_or_init(|| {
107 let mut names: Vec<&'static str> = SOURCES.iter().map(|source| source.module).collect();
108 names.dedup();
109 names
110 })
111}
112
113/// Adds the standard library's sources to `sources` and answers the modules
114/// to put in a package.
115///
116/// This must add to the *caller's* [`SourceMap`] rather than one of its own:
117/// a [`Span`](cove_diag::Span) is an offset into whichever `SourceMap` it was
118/// built against, and a diagnostic built from a span into a different map
119/// than the one rendering it would point at the wrong file entirely. Calling
120/// this is therefore always `attach(&mut sources)` where `sources` is the
121/// same map the rest of the package's units are already in.
122///
123/// Adds the standard library to a package a host is composing.
124///
125/// This is the one call an embedder makes. `cove_sema::package::load` makes
126/// it for a package read off disk; a host that composes its own — because
127/// its sources are embedded, or generated, or come from somewhere that is
128/// not a directory — makes it itself, and this is the whole of that step:
129///
130/// ```no_run
131/// # use std::collections::BTreeMap;
132/// # use cove_diag::SourceMap;
133/// # use cove_sema::package::Module;
134/// # fn f(sources: &mut SourceMap, modules: &mut BTreeMap<String, Module>) -> Result<(), Vec<cove_diag::Diagnostic>> {
135/// cove_sema::stdlib::install(sources, modules)?;
136/// # Ok(())
137/// # }
138/// ```
139///
140/// It is not done inside [`crate::Compiler::compile`], and that is a
141/// decision rather than an omission: what `compile` is given should be a
142/// package that is already whole, dependencies and all, so that what checks
143/// is what the host assembled. `compile` refuses a package missing a module
144/// `cove_schema::builtins::STANDARD_LIBRARY` names — see the diagnostic
145/// `cove::compile::missing_stdlib`, which says to call this.
146pub fn install(
147 sources: &mut SourceMap,
148 modules: &mut std::collections::BTreeMap<String, Module>,
149) -> Result<(), Vec<Diagnostic>> {
150 for (name, module) in attach(sources)? {
151 modules.insert(name, module);
152 }
153 Ok(())
154}
155
156/// See the module doc for what this function is allowed to become without
157/// its callers changing.
158pub fn attach(sources: &mut SourceMap) -> Result<Vec<(String, Module)>, Vec<Diagnostic>> {
159 let mut modules = Vec::with_capacity(SOURCES.len());
160 let mut diagnostics = Vec::new();
161 for source in SOURCES {
162 let path = PathBuf::from(source.path);
163 let file = sources.add(path.clone(), source.text);
164 match cove_syntax::parse_file(sources, file) {
165 Ok(ast) => modules.push((
166 source.module.to_string(),
167 Module {
168 name: source.module.to_string(),
169 dir: path.clone(),
170 units: vec![Unit { file, path, ast }],
171 },
172 )),
173 Err(errs) => diagnostics.extend(errs),
174 }
175 }
176 if diagnostics.is_empty() {
177 Ok(modules)
178 } else {
179 Err(diagnostics)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn attaches_every_module_it_names() {
189 let mut sources = SourceMap::new();
190 let modules = attach(&mut sources).expect("the embedded standard library parses");
191 let mut names: Vec<&str> = modules.iter().map(|(name, _)| name.as_str()).collect();
192 names.sort();
193 let mut expected: Vec<&str> = module_names().to_vec();
194 expected.sort();
195 assert_eq!(names, expected);
196 }
197
198 #[test]
199 fn declares_every_function_the_schema_binds_to_it() {
200 let mut sources = SourceMap::new();
201 let modules = attach(&mut sources).expect("the embedded standard library parses");
202 for binding in cove_schema::builtins::standard_library() {
203 let (_, module) = modules
204 .iter()
205 .find(|(name, _)| name == binding.module)
206 .unwrap_or_else(|| panic!("no embedded module named `{}`", binding.module));
207 let declares = module.units.iter().any(|unit| {
208 unit.ast.items.iter().any(|item| {
209 matches!(
210 &item.kind,
211 cove_syntax::ast::ItemKind::Fn(decl)
212 if decl.name.node == binding.function
213 )
214 })
215 });
216 assert!(
217 declares,
218 "`{}.{}` names `{}.{}`, which that module does not declare",
219 binding.receiver, binding.method, binding.module, binding.function
220 );
221 }
222 }
223}