mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.5 KiB
202 lines
5.5 KiB
//! Boa parser input source types. |
|
|
|
use std::{ |
|
fs::File, |
|
io::{self, BufReader, Read}, |
|
path::Path, |
|
}; |
|
|
|
pub use utf16::UTF16Input; |
|
pub use utf8::UTF8Input; |
|
|
|
mod utf16; |
|
mod utf8; |
|
|
|
/// A source of ECMAScript code. |
|
/// |
|
/// [`Source`]s can be created from plain [`str`]s, file [`Path`]s or more generally, any [`Read`] |
|
/// instance. |
|
#[derive(Debug)] |
|
pub struct Source<'path, R> { |
|
pub(crate) reader: R, |
|
pub(crate) path: Option<&'path Path>, |
|
} |
|
|
|
impl<'bytes> Source<'static, UTF8Input<&'bytes [u8]>> { |
|
/// Creates a new `Source` from any type equivalent to a slice of bytes e.g. [`&str`][str], |
|
/// <code>[Vec]<[u8]></code>, <code>[Box]<[\[u8\]][slice]></code> or a plain slice |
|
/// <code>[&\[u8\]][slice]</code>. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ``` |
|
/// # use boa_parser::Source; |
|
/// let code = r#"var array = [5, 4, 3, 2, 1];"#; |
|
/// let source = Source::from_bytes(code); |
|
/// ``` |
|
/// |
|
/// [slice]: std::slice |
|
pub fn from_bytes<T: AsRef<[u8]> + ?Sized>(source: &'bytes T) -> Self { |
|
Self { |
|
reader: UTF8Input::new(source.as_ref()), |
|
path: None, |
|
} |
|
} |
|
} |
|
|
|
impl<'input> Source<'static, UTF16Input<'input>> { |
|
/// Creates a new `Source` from a UTF-16 encoded slice e.g. <code>[&\[u16\]][slice]</code>. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ``` |
|
/// # use boa_parser::Source; |
|
/// let utf16: Vec<u16> = "var array = [5, 4, 3, 2, 1];".encode_utf16().collect(); |
|
/// let source = Source::from_utf16(&utf16); |
|
/// ``` |
|
/// |
|
/// [slice]: std::slice |
|
#[must_use] |
|
pub fn from_utf16(input: &'input [u16]) -> Self { |
|
Self { |
|
reader: UTF16Input::new(input), |
|
path: None, |
|
} |
|
} |
|
} |
|
|
|
impl<'path> Source<'path, UTF8Input<BufReader<File>>> { |
|
/// Creates a new `Source` from a `Path` to a file. |
|
/// |
|
/// # Errors |
|
/// |
|
/// See [`File::open`]. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```no_run |
|
/// # use boa_parser::Source; |
|
/// # use std::{fs::File, path::Path}; |
|
/// # fn main() -> std::io::Result<()> { |
|
/// let path = Path::new("script.js"); |
|
/// let source = Source::from_filepath(path)?; |
|
/// # Ok(()) |
|
/// # } |
|
/// ``` |
|
pub fn from_filepath(source: &'path Path) -> io::Result<Self> { |
|
let reader = File::open(source)?; |
|
Ok(Self { |
|
reader: UTF8Input::new(BufReader::new(reader)), |
|
path: Some(source), |
|
}) |
|
} |
|
} |
|
|
|
impl<'path, R: Read> Source<'path, UTF8Input<R>> { |
|
/// Creates a new `Source` from a [`Read`] instance and an optional [`Path`]. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```no_run |
|
/// # use boa_parser::Source; |
|
/// # use std::{fs::File, io::Read, path::Path}; |
|
/// # fn main() -> std::io::Result<()> { |
|
/// let strictler = r#""use strict""#; |
|
/// |
|
/// let path = Path::new("no_strict.js"); |
|
/// let file = File::open(path)?; |
|
/// let strict = strictler.as_bytes().chain(file); |
|
/// |
|
/// let source = Source::from_reader(strict, Some(path)); |
|
/// # Ok(()) |
|
/// # } |
|
/// ``` |
|
pub fn from_reader(reader: R, path: Option<&'path Path>) -> Self { |
|
Self { |
|
reader: UTF8Input::new(reader), |
|
path, |
|
} |
|
} |
|
} |
|
|
|
impl<'path, R> Source<'path, R> { |
|
/// Returns the path (if any) of this source file. |
|
pub fn path(&self) -> Option<&'path Path> { |
|
self.path |
|
} |
|
} |
|
|
|
/// This trait is used to abstract over the different types of input readers. |
|
pub trait ReadChar { |
|
/// Retrieves the next unicode code point. Returns `None` if the end of the input is reached. |
|
/// |
|
/// # Errors |
|
/// |
|
/// Returns an error if the next input in the input is not a valid unicode code point. |
|
fn next_char(&mut self) -> io::Result<Option<u32>>; |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use std::io::Cursor; |
|
|
|
use super::*; |
|
|
|
#[test] |
|
fn from_bytes() { |
|
let mut source = Source::from_bytes("'Hello' + 'World';"); |
|
|
|
assert!(source.path.is_none()); |
|
|
|
let mut content = String::new(); |
|
while let Some(c) = source.reader.next_char().unwrap() { |
|
content.push(char::from_u32(c).unwrap()); |
|
} |
|
|
|
assert_eq!(content, "'Hello' + 'World';"); |
|
} |
|
|
|
#[test] |
|
fn from_filepath() { |
|
let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")); |
|
let filepath = manifest_path.join("src/parser/tests/test.js"); |
|
let mut source = Source::from_filepath(&filepath).unwrap(); |
|
|
|
assert_eq!(source.path, Some(&*filepath)); |
|
|
|
let mut content = String::new(); |
|
while let Some(c) = source.reader.next_char().unwrap() { |
|
content.push(char::from_u32(c).unwrap()); |
|
} |
|
|
|
assert_eq!(content, "\"Hello\" + \"World\";\n"); |
|
} |
|
|
|
#[test] |
|
fn from_reader() { |
|
// Without path |
|
let mut source = Source::from_reader(Cursor::new("'Hello' + 'World';"), None); |
|
|
|
assert!(source.path.is_none()); |
|
|
|
let mut content = String::new(); |
|
while let Some(c) = source.reader.next_char().unwrap() { |
|
content.push(char::from_u32(c).unwrap()); |
|
} |
|
|
|
assert_eq!(content, "'Hello' + 'World';"); |
|
|
|
// With path |
|
let mut source = |
|
Source::from_reader(Cursor::new("'Hello' + 'World';"), Some("test.js".as_ref())); |
|
|
|
assert_eq!(source.path, Some("test.js".as_ref())); |
|
|
|
let mut content = String::new(); |
|
while let Some(c) = source.reader.next_char().unwrap() { |
|
content.push(char::from_u32(c).unwrap()); |
|
} |
|
|
|
assert_eq!(content, "'Hello' + 'World';"); |
|
} |
|
}
|
|
|