compose_library/
world.rs

1use crate::Library;
2use crate::diag::FileResult;
3use compose_codespan_reporting::files::Files;
4use compose_syntax::{FileId, Source, Span};
5use std::io::{Read, Write};
6use std::ops::Range;
7
8/**
9Defines how Compose interacts with the outside world.
10
11[`World`] is an abstraction layer between the Compose runtime and its external environment.
12
13It is responsible for:
14
15- Loading source files and defining the program entrypoint.
16- Providing access to a standard library.
17- Provide I/O primitives for stdout.
18
19This trait allows Compose to be embedded in different environments
20(e.g. CLI tools, editors, tests, or sandboxes) without hard-coding
21filesystem or I/O behaviour.
22*/
23pub trait World {
24    /// Returns the [`FileId`] of the entrypoint of the program.
25    fn entry_point(&self) -> FileId;
26
27    /// Returns the Source identified by the given [`FileId`]
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if the source cannot be located or loaded.
32    fn source(&self, file_id: FileId) -> FileResult<Source>;
33
34    /// Returns a reference to the standard library available to the program.
35    fn library(&self) -> &Library;
36
37    /// Provides write access to the program's output stream.
38    ///
39    /// The provided closure is given exclusive access to a writer
40    /// for the duration of the call.
41    fn write(
42        &self,
43        f: &mut dyn FnMut(&mut dyn Write) -> std::io::Result<()>,
44    ) -> std::io::Result<()>;
45
46    /// Provides read access to the program's input stream.
47    ///
48    /// The provided closure is given exclusive access to the reader
49    /// for the duration of the call.
50    fn read(&self, f: &mut dyn FnMut(&mut dyn Read) -> std::io::Result<()>) -> std::io::Result<()>;
51
52    /// Provides read and write access to the programs input and output stream.
53    ///
54    /// The provided closure is given exclusive access to the reader and writer
55    /// for the duration of the call.
56    fn with_io(
57        &self,
58        f: &mut dyn FnMut(&mut dyn Read, &mut dyn Write) -> std::io::Result<()>,
59    ) -> std::io::Result<()> {
60        self.read(&mut |r| self.write(&mut |w| f(r, w)))
61    }
62
63    /// Returns a human-readable name for the given file identifier.
64    ///
65    /// By default, this is derived from the file's path.
66    fn name(&self, id: FileId) -> String {
67        id.path().0.display().to_string()
68    }
69
70    /// Attempts to retrieve the source associated with the given span.
71    ///
72    /// This is primarily used for diagnostics and error reporting.
73    fn related_source(&self, span: Span) -> Option<Source> {
74        self.source(span.id()?).ok()
75    }
76}
77
78pub struct SyntaxContext<'a> {
79    pub world: &'a dyn World,
80}
81
82impl<'a> Files<'a> for &dyn World {
83    type FileId = FileId;
84    type Name = String;
85    type Source = Source;
86
87    fn name(
88        &'a self,
89        id: Self::FileId,
90    ) -> Result<Self::Name, compose_codespan_reporting::files::Error> {
91        Ok(World::name(*self, id))
92    }
93
94    fn source(
95        &'a self,
96        id: Self::FileId,
97    ) -> Result<Self::Source, compose_codespan_reporting::files::Error> {
98        World::source(*self, id).map_err(|_| compose_codespan_reporting::files::Error::FileMissing)
99    }
100
101    fn line_index(
102        &'a self,
103        id: Self::FileId,
104        byte_index: usize,
105    ) -> Result<usize, compose_codespan_reporting::files::Error> {
106        let source = Files::source(self, id)?;
107        Ok(source
108            .line_starts()
109            .binary_search(&byte_index)
110            .unwrap_or_else(|next_line| next_line - 1))
111    }
112
113    fn line_range(
114        &'a self,
115        id: Self::FileId,
116        line_index: usize,
117    ) -> Result<Range<usize>, compose_codespan_reporting::files::Error> {
118        let source = Files::source(self, id)?;
119        let start = source.line_starts().get(line_index).copied().ok_or(
120            compose_codespan_reporting::files::Error::LineTooLarge {
121                given: line_index,
122                max: source.line_starts().len(),
123            },
124        )?;
125
126        let end = source
127            .line_starts()
128            .get(line_index + 1)
129            .copied()
130            .unwrap_or(source.text().len());
131
132        Ok(start..end)
133    }
134}