Skip to main content

cratestack_parser/load/
mod.rs

1//! Loading a schema from a file, with the `part`s it lists
2//! (docs/design/multi-file-schemas.md section 14).
3
4mod attribute;
5mod collisions;
6mod diagnostics;
7mod file_io;
8mod graph;
9mod import_rules;
10mod imports;
11mod merge;
12mod part_rules;
13mod parts;
14mod paths;
15mod sources;
16
17use std::path::{Path, PathBuf};
18use std::sync::Arc;
19
20use cratestack_core::Schema;
21
22use crate::diagnostics::SchemaError;
23
24pub use self::diagnostics::load_schema_diagnostics;
25pub use self::sources::{FsSources, OverlaySources, SchemaSources, canonical_schema_path};
26
27/// How to load.
28#[derive(Default, Clone, Copy)]
29pub struct LoadOptions<'a> {
30    /// Given a file that declares `part of`, load the root it names instead
31    /// (`check` and the language server). Without it that is an error (M4).
32    pub follow_part_of: bool,
33    /// Where file text comes from. Default: the disk.
34    pub sources: Option<&'a dyn SchemaSources>,
35}
36
37/// What a loaded file is to the schema.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum FileRole {
40    Root,
41    Part,
42    Import,
43    ImportPart,
44}
45
46/// One file of a loaded schema.
47#[derive(Debug, Clone)]
48pub struct SchemaFile {
49    /// The path as shown in a code frame.
50    pub display: String,
51    /// Relative to the root's directory, as shown in messages.
52    pub relative: String,
53    /// Canonical.
54    pub path: PathBuf,
55    pub source: Arc<str>,
56    /// Offset of this file's first byte in the schema's address space.
57    pub base: usize,
58    pub role: FileRole,
59}
60
61/// A schema and the files it was loaded from, in load order (root first).
62#[derive(Debug, Clone)]
63pub struct LoadedSchema {
64    pub schema: Schema,
65    pub files: Vec<SchemaFile>,
66}
67
68impl LoadedSchema {
69    pub fn root(&self) -> &SchemaFile {
70        &self.files[0]
71    }
72
73    /// The file containing a global offset, and the offset within it.
74    pub fn locate(&self, offset: usize) -> Option<(&SchemaFile, usize)> {
75        self.files
76            .iter()
77            .find(|file| file.base <= offset && offset <= file.base + file.source.len())
78            .map(|file| (file, offset - file.base))
79    }
80
81    pub fn is_single_file(&self) -> bool {
82        self.files.len() == 1
83    }
84}
85
86/// Everything a load found. `files` lists every file a read was tried for,
87/// including when loading stopped early.
88pub struct LoadDiagnostics {
89    pub schema: Option<LoadedSchema>,
90    pub errors: Vec<SchemaError>,
91    pub files: Vec<PathBuf>,
92    /// Each file that was read, by the name its errors carry (`SchemaError::file`)
93    /// and its canonical path, so an error is mapped to its file without
94    /// reading a display string as a path.
95    pub named: Vec<(String, PathBuf)>,
96}
97
98/// Loads and validates, returning the first error. Same checks, in the same
99/// order, as [`load_schema_diagnostics`].
100pub fn load_schema(
101    path: impl AsRef<Path>,
102    options: &LoadOptions<'_>,
103) -> Result<LoadedSchema, SchemaError> {
104    let diagnostics = load_schema_diagnostics(path, options);
105    match diagnostics.schema {
106        Some(loaded) => Ok(loaded),
107        None => Err(diagnostics
108            .errors
109            .into_iter()
110            .next()
111            .expect("a load without a schema reports an error")),
112    }
113}