Skip to main content

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}