From af544ac79eea4498ef4563acfb7e8dd14ec5c84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:22:24 +0200 Subject: [PATCH] implement file provider for working without file systems --- src/base/error.rs | 6 +-- src/base/file_provider.rs | 100 ++++++++++++++++++++++++++++++++++++++ src/base/mod.rs | 3 ++ src/base/source_file.rs | 7 ++- src/lib.rs | 28 +++++++---- src/public_helpers.rs | 38 +++++++++++---- 6 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 src/base/file_provider.rs diff --git a/src/base/error.rs b/src/base/error.rs index 160855e..5575dce 100644 --- a/src/base/error.rs +++ b/src/base/error.rs @@ -1,11 +1,9 @@ -use std::io; - /// An error that occurred during compilation. #[allow(missing_docs)] #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("An error occurred while reading the file.")] - IoError(#[from] io::Error), + #[error("An error occurred while working with Input/Output.")] + IoError(String), #[error("An error occured while tokenizing the source code.")] TokenizeError(#[from] crate::lexical::token::TokenizeError), #[error("An error occurred while parsing the source code.")] diff --git a/src/base/file_provider.rs b/src/base/file_provider.rs new file mode 100644 index 0000000..a62e3bc --- /dev/null +++ b/src/base/file_provider.rs @@ -0,0 +1,100 @@ +use std::path::{Path, PathBuf}; + +use super::Error; + +/// A trait for providing file contents. +pub trait FileProvider { + /// Reads the contents of the file at the given path. + /// + /// # Errors + /// - If an error occurs while reading the file. + /// - If the file does not exist. + fn read_to_string>(&self, path: P) -> Result; +} + +/// Provides file contents from the file system. +#[derive(Debug, Clone)] +pub struct FsProvider { + /// The root directory to base paths off of. + root: PathBuf, +} + +impl Default for FsProvider { + fn default() -> Self { + Self { + root: PathBuf::from("."), + } + } +} + +impl FileProvider for FsProvider { + fn read_to_string>(&self, path: P) -> Result { + let full_path = self.root.join(path); + std::fs::read_to_string(full_path).map_err(|err| Error::IoError(err.to_string())) + } +} + +#[cfg(feature = "shulkerbox")] +mod vfs { + use super::{Error, FileProvider, Path}; + use shulkerbox::virtual_fs::{VFile, VFolder}; + + impl FileProvider for VFolder { + fn read_to_string>(&self, path: P) -> Result { + normalize_path_str(path).map_or_else( + || Err(Error::IoError("Invalid path".to_string())), + |path| { + self.get_file(&path) + .ok_or_else(|| Error::IoError("File not found".to_string())) + .and_then(|file| match file { + VFile::Text(text) => Ok(text.to_owned()), + VFile::Binary(bin) => String::from_utf8(bin.clone()) + .map_err(|err| Error::IoError(err.to_string())), + }) + }, + ) + } + } + + fn normalize_path_str>(path: P) -> Option { + let mut err = false; + let res = path + .as_ref() + .to_str()? + .split('/') + .fold(Vec::new(), |mut acc, el| match el { + "." | "" => acc, + ".." => { + let popped = acc.pop(); + if popped.is_none() { + err = true; + } + acc + } + _ => { + acc.push(el); + acc + } + }) + .join("/"); + + if err { + None + } else { + Some(res) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_normalize_path() { + assert_eq!(normalize_path_str("a/b/c"), Some("a/b/c".to_string())); + assert_eq!(normalize_path_str("a/b/../c"), Some("a/c".to_string())); + assert_eq!(normalize_path_str("./a/b/c"), Some("a/b/c".to_string())); + assert_eq!(normalize_path_str("../a/b/c"), None); + } + } +} diff --git a/src/base/mod.rs b/src/base/mod.rs index a01328e..c6e5a00 100644 --- a/src/base/mod.rs +++ b/src/base/mod.rs @@ -9,4 +9,7 @@ pub use error::{Error, Result}; mod diagnostic; pub use diagnostic::{DummyHandler, Handler}; +mod file_provider; +pub use file_provider::{FileProvider, FsProvider}; + pub mod log; diff --git a/src/base/source_file.rs b/src/base/source_file.rs index eaf7207..162d8a0 100644 --- a/src/base/source_file.rs +++ b/src/base/source_file.rs @@ -3,7 +3,6 @@ use std::{ cmp::Ordering, fmt::Debug, - fs, iter::{Iterator, Peekable}, ops::Range, path::{Path, PathBuf}, @@ -13,7 +12,7 @@ use std::{ use getset::{CopyGetters, Getters}; -use super::Error; +use super::{file_provider::FileProvider, Error}; /// Represents a source file that contains the source code. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -84,8 +83,8 @@ impl SourceFile { /// /// # Errors /// - [`Error::IoError`]: Error occurred when reading the file contents. - pub fn load(path: &Path) -> Result, Error> { - let source = fs::read_to_string(path).map_err(Error::IoError)?; + pub fn load(path: &Path, provider: &impl FileProvider) -> Result, Error> { + let source = provider.read_to_string(path)?; Ok(Self::new(path.to_path_buf(), source)) } diff --git a/src/lib.rs b/src/lib.rs index 9177dab..9ad6230 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ mod public_helpers; use std::{cell::Cell, fmt::Display, path::Path}; -use base::{Handler, Result}; +use base::{FileProvider, Handler, Result}; use syntax::syntax_tree::program::ProgramFile; #[cfg(feature = "shulkerbox")] @@ -31,14 +31,19 @@ use shulkerbox::{datapack::Datapack, virtual_fs::VFolder}; use crate::lexical::token_stream::TokenStream; +const DEFAULT_PACK_FORMAT: u8 = 48; + /// Converts the given source code to tokens. /// /// # Errors /// - If an error occurs while reading the file. -pub fn tokenize(path: &Path) -> Result { +pub fn tokenize(file_provider: &F, path: &Path) -> Result +where + F: FileProvider, +{ let printer = Printer::new(); - public_helpers::tokenize(&printer, path) + public_helpers::tokenize(&printer, file_provider, path) } /// Parses the given source code. @@ -46,10 +51,13 @@ pub fn tokenize(path: &Path) -> Result { /// # Errors /// - If an error occurs while reading the file. /// - If an error occurs while parsing the source code. -pub fn parse(path: &Path) -> Result { +pub fn parse(file_provider: &F, path: &Path) -> Result +where + F: FileProvider, +{ let printer = Printer::new(); - public_helpers::parse(&printer, path) + public_helpers::parse(&printer, file_provider, path) } /// Transpiles the given source code into a shulkerbox [`Datapack`]. @@ -62,13 +70,14 @@ pub fn parse(path: &Path) -> Result { /// - If an error occurs while parsing the source code. /// - If an error occurs while transpiling the source code. #[cfg(feature = "shulkerbox")] -pub fn transpile

(script_paths: &[(String, P)]) -> Result +pub fn transpile(file_provider: &F, script_paths: &[(String, P)]) -> Result where + F: FileProvider, P: AsRef, { let printer = Printer::new(); - public_helpers::transpile(&printer, script_paths) + public_helpers::transpile(&printer, file_provider, script_paths) } /// Compiles the given source code. @@ -81,13 +90,14 @@ where /// - If an error occurs while parsing the source code. /// - If an error occurs while transpiling the source code. #[cfg(feature = "shulkerbox")] -pub fn compile

(script_paths: &[(String, P)]) -> Result +pub fn compile(file_provider: &F, script_paths: &[(String, P)]) -> Result where + F: FileProvider, P: AsRef, { let printer = Printer::new(); - public_helpers::compile(&printer, script_paths) + public_helpers::compile(&printer, file_provider, script_paths) } struct Printer { diff --git a/src/public_helpers.rs b/src/public_helpers.rs index 5ef2df7..594291f 100644 --- a/src/public_helpers.rs +++ b/src/public_helpers.rs @@ -1,7 +1,7 @@ use std::path::Path; use crate::{ - base::{source_file::SourceFile, Error, Result}, + base::{source_file::SourceFile, Error, FileProvider, Result}, lexical::token_stream::TokenStream, syntax::{parser::Parser, syntax_tree::program::ProgramFile}, Printer, @@ -14,17 +14,25 @@ use crate::transpile::transpiler::Transpiler; use shulkerbox::{datapack::Datapack, util::compile::CompileOptions, virtual_fs::VFolder}; /// Tokenizes the source code at the given path. -pub fn tokenize(printer: &Printer, path: &Path) -> Result { +pub fn tokenize( + printer: &Printer, + file_provider: &impl FileProvider, + path: &Path, +) -> Result { tracing::info!("Tokenizing the source code at path: {}", path.display()); - let source_file = SourceFile::load(path)?; + let source_file = SourceFile::load(path, file_provider)?; Ok(TokenStream::tokenize(&source_file, printer)) } /// Parses the source code at the given path. -pub fn parse(printer: &Printer, path: &Path) -> Result { - let tokens = tokenize(printer, path)?; +pub fn parse( + printer: &Printer, + file_provider: &impl FileProvider, + path: &Path, +) -> Result { + let tokens = tokenize(printer, file_provider, path)?; if printer.has_printed() { return Err(Error::Other( @@ -50,14 +58,19 @@ pub fn parse(printer: &Printer, path: &Path) -> Result { /// Transpiles the source code at the given paths into a shulkerbox [`Datapack`]. #[cfg(feature = "shulkerbox")] -pub fn transpile

(printer: &Printer, script_paths: &[(String, P)]) -> Result +pub fn transpile( + printer: &Printer, + file_provider: &F, + script_paths: &[(String, P)], +) -> Result where + F: FileProvider, P: AsRef, { let programs = script_paths .iter() .map(|(program_identifier, path)| { - let program = parse(printer, path.as_ref())?; + let program = parse(printer, file_provider, path.as_ref())?; Ok((program_identifier, program)) }) @@ -73,7 +86,7 @@ where tracing::info!("Transpiling the source code."); - let mut transpiler = Transpiler::new(27); + let mut transpiler = Transpiler::new(crate::DEFAULT_PACK_FORMAT); transpiler.transpile(&programs, printer)?; let datapack = transpiler.into_datapack(); @@ -88,11 +101,16 @@ where /// Compiles the source code at the given paths. #[cfg(feature = "shulkerbox")] -pub fn compile

(printer: &Printer, script_paths: &[(String, P)]) -> Result +pub fn compile( + printer: &Printer, + file_provider: &F, + script_paths: &[(String, P)], +) -> Result where + F: FileProvider, P: AsRef, { - let datapack = transpile(printer, script_paths)?; + let datapack = transpile(printer, file_provider, script_paths)?; tracing::info!("Compiling the source code.");