implement file provider for working without file systems

This commit is contained in:
Moritz Hölting 2024-06-21 19:22:24 +02:00
parent b428c64f89
commit af544ac79e
6 changed files with 155 additions and 27 deletions

View File

@ -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.")]

100
src/base/file_provider.rs Normal file
View File

@ -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<P: AsRef<Path>>(&self, path: P) -> Result<String, Error>;
}
/// 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<P: AsRef<Path>>(&self, path: P) -> Result<String, Error> {
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<P: AsRef<Path>>(&self, path: P) -> Result<String, Error> {
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<P: AsRef<Path>>(path: P) -> Option<String> {
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);
}
}
}

View File

@ -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;

View File

@ -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<Arc<Self>, Error> {
let source = fs::read_to_string(path).map_err(Error::IoError)?;
pub fn load(path: &Path, provider: &impl FileProvider) -> Result<Arc<Self>, Error> {
let source = provider.read_to_string(path)?;
Ok(Self::new(path.to_path_buf(), source))
}

View File

@ -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<TokenStream> {
pub fn tokenize<F>(file_provider: &F, path: &Path) -> Result<TokenStream>
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<TokenStream> {
/// # Errors
/// - If an error occurs while reading the file.
/// - If an error occurs while parsing the source code.
pub fn parse(path: &Path) -> Result<ProgramFile> {
pub fn parse<F>(file_provider: &F, path: &Path) -> Result<ProgramFile>
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<ProgramFile> {
/// - If an error occurs while parsing the source code.
/// - If an error occurs while transpiling the source code.
#[cfg(feature = "shulkerbox")]
pub fn transpile<P>(script_paths: &[(String, P)]) -> Result<Datapack>
pub fn transpile<F, P>(file_provider: &F, script_paths: &[(String, P)]) -> Result<Datapack>
where
F: FileProvider,
P: AsRef<Path>,
{
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<P>(script_paths: &[(String, P)]) -> Result<VFolder>
pub fn compile<F, P>(file_provider: &F, script_paths: &[(String, P)]) -> Result<VFolder>
where
F: FileProvider,
P: AsRef<Path>,
{
let printer = Printer::new();
public_helpers::compile(&printer, script_paths)
public_helpers::compile(&printer, file_provider, script_paths)
}
struct Printer {

View File

@ -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<TokenStream> {
pub fn tokenize(
printer: &Printer,
file_provider: &impl FileProvider,
path: &Path,
) -> Result<TokenStream> {
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<ProgramFile> {
let tokens = tokenize(printer, path)?;
pub fn parse(
printer: &Printer,
file_provider: &impl FileProvider,
path: &Path,
) -> Result<ProgramFile> {
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<ProgramFile> {
/// Transpiles the source code at the given paths into a shulkerbox [`Datapack`].
#[cfg(feature = "shulkerbox")]
pub fn transpile<P>(printer: &Printer, script_paths: &[(String, P)]) -> Result<Datapack>
pub fn transpile<F, P>(
printer: &Printer,
file_provider: &F,
script_paths: &[(String, P)],
) -> Result<Datapack>
where
F: FileProvider,
P: AsRef<Path>,
{
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<P>(printer: &Printer, script_paths: &[(String, P)]) -> Result<VFolder>
pub fn compile<F, P>(
printer: &Printer,
file_provider: &F,
script_paths: &[(String, P)],
) -> Result<VFolder>
where
F: FileProvider,
P: AsRef<Path>,
{
let datapack = transpile(printer, script_paths)?;
let datapack = transpile(printer, file_provider, script_paths)?;
tracing::info!("Compiling the source code.");