use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use crate::error::{Error, ErrorKind, Result as BResult};
use crate::parser::AST;
use crate::stream::StringStream;
#[derive(Debug)]
pub enum Format {
Plain,
Ast,
Compiled,
}
#[derive(Debug)]
pub enum FileResult<T> {
Valid(T),
WrongExtension(OsString),
NonExisting,
}
pub fn select_format<'a, T>(
path: &Path,
extensions: &'a [(&'static str, T)],
) -> FileResult<(PathBuf, &'a T)> {
if let Some(extension) = path.extension() {
for (ext, format) in extensions {
if *ext == extension {
return FileResult::Valid((path.to_owned(), format));
}
}
FileResult::WrongExtension(extension.to_owned())
} else {
let mut current_path: Option<(PathBuf, _)> = None;
let mut try_path = path.to_owned();
for (ext, format) in extensions {
try_path.set_extension(ext);
if !try_path.exists() {
continue;
}
if let Some((ref old_path, _)) = current_path {
if matches!(
(
old_path.metadata().and_then(|md| md.modified()),
try_path.metadata().and_then(|md| md.modified()),
),
(Ok(time_old), Ok(time_new)) if time_old < time_new,
) {
current_path = Some((try_path.clone(), format));
}
}
}
if let Some(format) = current_path {
FileResult::Valid(format)
} else {
FileResult::NonExisting
}
}
}
pub trait Buildable: Sized {
const RAW_EXTENSION: &'static str;
const AST_EXTENSION: &'static str;
const COMPILED_EXTENSION: &'static str;
fn build_from_ast(ast: AST) -> BResult<Self>;
fn build_from_compiled(blob: &[u8], path: impl ToOwned<Owned = PathBuf>) -> BResult<Self>;
fn build_from_plain(raw: StringStream) -> BResult<Self>;
fn build_from_blob(blob: &[u8], path: &Path) -> BResult<Self> {
let ast: AST = match select_format(
path,
&[
(Self::COMPILED_EXTENSION, Format::Compiled),
(Self::AST_EXTENSION, Format::Ast),
(Self::RAW_EXTENSION, Format::Plain),
],
) {
FileResult::Valid((path, Format::Compiled)) => {
let result = Self::build_from_compiled(blob, path)?;
return Ok(result);
}
FileResult::Valid((path, Format::Ast)) => {
let string = std::str::from_utf8(blob)
.map_err(|error| Error::with_file(error, path.clone()))?;
serde_json::from_str(string)
.map_err(|error| ErrorKind::IllformedAst { error, path })?
}
FileResult::Valid((actual_path, Format::Plain)) => {
let string = String::from_utf8(blob.to_vec())
.map_err(|error| Error::with_file(error, actual_path.clone()))?;
let stream = StringStream::new(actual_path, string);
let result = Self::build_from_plain(stream)?;
return Ok(result);
}
FileResult::NonExisting => {
return ErrorKind::GrammarNotFound {
path: path.to_owned(),
}
.err();
}
FileResult::WrongExtension(extension) => {
return ErrorKind::UnrecognisedExtension {
extension,
path: path.to_owned(),
}
.err();
}
};
let grammar = Self::build_from_ast(ast)?;
Ok(grammar)
}
fn build_from_path(path: &Path) -> BResult<Self> {
let ast: AST = match select_format(
path,
&[
(Self::COMPILED_EXTENSION, Format::Compiled),
(Self::AST_EXTENSION, Format::Ast),
(Self::RAW_EXTENSION, Format::Plain),
],
) {
FileResult::Valid((actual_path, Format::Compiled)) => {
let mut file = File::open(&actual_path)
.map_err(|err| Error::with_file(err, &actual_path))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|err| Error::with_file(err, &actual_path))?;
let result = Self::build_from_compiled(&buffer, actual_path)?;
return Ok(result);
}
FileResult::Valid((actual_path, Format::Ast)) => {
let file = File::open(&actual_path)
.map_err(|err| Error::with_file(err, actual_path.clone()))?;
serde_json::from_reader(file).map_err(|error| ErrorKind::IllformedAst {
error,
path: actual_path,
})?
}
FileResult::Valid((actual_path, Format::Plain)) => {
let stream = StringStream::from_file(actual_path)?;
let result = Self::build_from_plain(stream)?;
return Ok(result);
}
FileResult::NonExisting => {
return ErrorKind::GrammarNotFound {
path: path.to_owned(),
}
.err();
}
FileResult::WrongExtension(extension) => {
return ErrorKind::UnrecognisedExtension {
extension,
path: path.to_owned(),
}
.err();
}
};
let grammar = Self::build_from_ast(ast)?;
Ok(grammar)
}
}
#[macro_export]
macro_rules! build_system {
(lexer => $lexer_path:literal, parser => $parser_path:literal $(,)?) => {
(|| -> $crate::error::Result<($crate::lexer::Lexer, $crate::parser::earley::EarleyParser)> {
let lexer_source = include_bytes!($lexer_path);
let parser_source = include_bytes!($parser_path);
let lexer =
$crate::lexer::Lexer::build_from_blob(
lexer_source,
::std::path::Path::new($lexer_path),
)?;
let parser =
$crate::parser::earley::EarleyParser::build_from_blob(
parser_source,
::std::path::Path::new($parser_path),
lexer.grammar(),
)?;
Ok((lexer, parser))
})()
};
}