implement file provider for working without file systems
This commit is contained in:
parent
b428c64f89
commit
af544ac79e
|
@ -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.")]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
28
src/lib.rs
28
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<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 {
|
||||
|
|
|
@ -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.");
|
||||
|
||||
|
|
Loading…
Reference in New Issue