Skip to main content

obeli_sk_boa_parser/source/
mod.rs

1//! Boa parser input source types.
2
3use std::{
4    fs::File,
5    io::{self, BufReader, Read},
6    path::Path,
7};
8
9pub use utf8::UTF8Input;
10pub use utf16::UTF16Input;
11
12mod utf16;
13mod utf8;
14
15/// A source of ECMAScript code.
16///
17/// [`Source`]s can be created from plain [`str`]s, file [`Path`]s or more generally, any [`Read`]
18/// instance.
19#[derive(Debug)]
20pub struct Source<'path, R> {
21    pub(crate) reader: R,
22    pub(crate) path: Option<&'path Path>,
23}
24
25impl<'bytes> Source<'static, UTF8Input<&'bytes [u8]>> {
26    /// Creates a new `Source` from any type equivalent to a slice of bytes e.g. [`&str`][str],
27    /// <code>[Vec]<[u8]></code>, <code>[Box]<[\[u8\]][slice]></code> or a plain slice
28    /// <code>[&\[u8\]][slice]</code>.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// # use boa_parser::Source;
34    /// let code = r#"var array = [5, 4, 3, 2, 1];"#;
35    /// let source = Source::from_bytes(code);
36    /// ```
37    ///
38    /// [slice]: std::slice
39    pub fn from_bytes<T: AsRef<[u8]> + ?Sized>(source: &'bytes T) -> Self {
40        Self {
41            reader: UTF8Input::new(source.as_ref()),
42            path: None,
43        }
44    }
45}
46
47impl<'input> Source<'static, UTF16Input<'input>> {
48    /// Creates a new `Source` from a UTF-16 encoded slice e.g. <code>[&\[u16\]][slice]</code>.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// # use boa_parser::Source;
54    /// let utf16: Vec<u16> =
55    ///     "var array = [5, 4, 3, 2, 1];".encode_utf16().collect();
56    /// let source = Source::from_utf16(&utf16);
57    /// ```
58    ///
59    /// [slice]: std::slice
60    #[must_use]
61    pub fn from_utf16(input: &'input [u16]) -> Self {
62        Self {
63            reader: UTF16Input::new(input),
64            path: None,
65        }
66    }
67}
68
69impl<'path> Source<'path, UTF8Input<BufReader<File>>> {
70    /// Creates a new `Source` from a `Path` to a file.
71    ///
72    /// # Errors
73    ///
74    /// See [`File::open`].
75    ///
76    /// # Examples
77    ///
78    /// ```no_run
79    /// # use boa_parser::Source;
80    /// # use std::{fs::File, path::Path};
81    /// # fn main() -> std::io::Result<()> {
82    /// let path = Path::new("script.js");
83    /// let source = Source::from_filepath(path)?;
84    /// # Ok(())
85    /// # }
86    /// ```
87    pub fn from_filepath(source: &'path Path) -> io::Result<Self> {
88        let reader = File::open(source)?;
89        Ok(Self {
90            reader: UTF8Input::new(BufReader::new(reader)),
91            path: Some(source),
92        })
93    }
94}
95
96impl<'path, R: Read> Source<'path, UTF8Input<R>> {
97    /// Creates a new `Source` from a [`Read`] instance and an optional [`Path`].
98    ///
99    /// # Examples
100    ///
101    /// ```no_run
102    /// # use boa_parser::Source;
103    /// # use std::{fs::File, io::Read, path::Path};
104    /// # fn main() -> std::io::Result<()> {
105    /// let strictler = r#""use strict""#;
106    ///
107    /// let path = Path::new("no_strict.js");
108    /// let file = File::open(path)?;
109    /// let strict = strictler.as_bytes().chain(file);
110    ///
111    /// let source = Source::from_reader(strict, Some(path));
112    /// #    Ok(())
113    /// # }
114    /// ```
115    pub fn from_reader(reader: R, path: Option<&'path Path>) -> Self {
116        Self {
117            reader: UTF8Input::new(reader),
118            path,
119        }
120    }
121}
122
123impl<'path, R> Source<'path, R> {
124    /// Sets the path of this [`Source`].
125    pub fn with_path(self, new_path: &Path) -> Source<'_, R> {
126        Source {
127            reader: self.reader,
128            path: Some(new_path),
129        }
130    }
131
132    /// Returns the path (if any) of this source file.
133    pub fn path(&self) -> Option<&'path Path> {
134        self.path
135    }
136}
137
138/// This trait is used to abstract over the different types of input readers.
139pub trait ReadChar {
140    /// Retrieves the next unicode code point. Returns `None` if the end of the input is reached.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if the next input in the input is not a valid unicode code point.
145    fn next_char(&mut self) -> io::Result<Option<u32>>;
146}
147
148#[cfg(test)]
149mod tests {
150    use std::io::Cursor;
151
152    use super::*;
153
154    #[test]
155    fn from_bytes() {
156        let mut source = Source::from_bytes("'Hello' + 'World';");
157
158        assert!(source.path.is_none());
159
160        let mut content = String::new();
161        while let Some(c) = source.reader.next_char().unwrap() {
162            content.push(char::from_u32(c).unwrap());
163        }
164
165        assert_eq!(content, "'Hello' + 'World';");
166    }
167
168    #[test]
169    fn from_filepath() {
170        let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR"));
171        let filepath = manifest_path.join("src/parser/tests/test.js");
172        let mut source = Source::from_filepath(&filepath).unwrap();
173
174        assert_eq!(source.path, Some(&*filepath));
175
176        let mut content = String::new();
177        while let Some(c) = source.reader.next_char().unwrap() {
178            content.push(char::from_u32(c).unwrap());
179        }
180
181        assert_eq!(content, "\"Hello\" + \"World\";\n");
182    }
183
184    #[test]
185    fn from_reader() {
186        // Without path
187        let mut source = Source::from_reader(Cursor::new("'Hello' + 'World';"), None);
188
189        assert!(source.path.is_none());
190
191        let mut content = String::new();
192        while let Some(c) = source.reader.next_char().unwrap() {
193            content.push(char::from_u32(c).unwrap());
194        }
195
196        assert_eq!(content, "'Hello' + 'World';");
197
198        // With path
199        let mut source =
200            Source::from_reader(Cursor::new("'Hello' + 'World';"), Some("test.js".as_ref()));
201
202        assert_eq!(source.path, Some("test.js".as_ref()));
203
204        let mut content = String::new();
205        while let Some(c) = source.reader.next_char().unwrap() {
206            content.push(char::from_u32(c).unwrap());
207        }
208
209        assert_eq!(content, "'Hello' + 'World';");
210    }
211}