use crate::lex::parsing::Document;
use crate::lex::transforms::standard::{TokenStream, CORE_TOKENIZATION, LEXING, STRING_TO_AST};
use crate::lex::transforms::{Transform, TransformError};
use std::fs;
use std::path::Path;
#[derive(Debug, Clone)]
pub enum LoaderError {
IoError(String),
TransformError(TransformError),
}
impl std::fmt::Display for LoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoaderError::IoError(msg) => write!(f, "IO error: {msg}"),
LoaderError::TransformError(err) => write!(f, "Transform error: {err}"),
}
}
}
impl std::error::Error for LoaderError {}
impl From<std::io::Error> for LoaderError {
fn from(err: std::io::Error) -> Self {
LoaderError::IoError(err.to_string())
}
}
impl From<TransformError> for LoaderError {
fn from(err: TransformError) -> Self {
LoaderError::TransformError(err)
}
}
pub struct DocumentLoader {
source: String,
}
impl DocumentLoader {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, LoaderError> {
let source = fs::read_to_string(path)?;
Ok(DocumentLoader { source })
}
pub fn from_string<S: Into<String>>(source: S) -> Self {
DocumentLoader {
source: source.into(),
}
}
pub fn with<O: 'static>(&self, transform: &Transform<String, O>) -> Result<O, LoaderError> {
Ok(transform.run(self.source.clone())?)
}
pub fn parse(&self) -> Result<Document, LoaderError> {
self.with(&STRING_TO_AST)
}
pub fn tokenize(&self) -> Result<TokenStream, LoaderError> {
self.with(&LEXING)
}
pub fn base_tokens(&self) -> Result<TokenStream, LoaderError> {
self.with(&CORE_TOKENIZATION)
}
pub fn source(&self) -> String {
self.source.clone()
}
pub fn source_ref(&self) -> &str {
&self.source
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lex::testing::workspace_path;
use crate::lex::token::Token;
#[test]
fn test_from_string() {
let loader = DocumentLoader::from_string("Hello world\n");
assert_eq!(loader.source(), "Hello world\n");
}
#[test]
fn test_from_path() {
let path =
workspace_path("comms/specs/elements/paragraph.docs/paragraph-01-flat-oneline.lex");
let loader = DocumentLoader::from_path(path).unwrap();
assert!(!loader.source().is_empty());
}
#[test]
fn test_from_path_nonexistent() {
let result = DocumentLoader::from_path("nonexistent.lex");
assert!(result.is_err());
}
#[test]
fn test_parse() {
let loader = DocumentLoader::from_string("Hello world\n");
let doc = loader.parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_parse_with_session() {
let loader = DocumentLoader::from_string("Session:\n Content here\n");
let doc = loader.parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_tokenize() {
let loader = DocumentLoader::from_string("Session:\n Content\n");
let tokens = loader.tokenize().unwrap();
assert!(tokens.iter().any(|(t, _)| matches!(t, Token::Indent(_))));
assert!(tokens.iter().any(|(t, _)| matches!(t, Token::Dedent(_))));
}
#[test]
fn test_base_tokens() {
let loader = DocumentLoader::from_string("Hello world\n");
let tokens = loader.base_tokens().unwrap();
assert!(!tokens.is_empty());
assert!(!tokens.iter().any(|(t, _)| matches!(t, Token::Indent(_))));
}
#[test]
fn test_base_tokens_has_indentation() {
let loader = DocumentLoader::from_string(" Hello\n");
let tokens = loader.base_tokens().unwrap();
assert!(tokens.iter().any(|(t, _)| matches!(t, Token::Indentation)));
}
#[test]
fn test_source() {
let loader = DocumentLoader::from_string("Test content\n");
assert_eq!(loader.source(), "Test content\n");
}
#[test]
fn test_with_custom_transform() {
let loader = DocumentLoader::from_string("Hello\n");
let tokens = loader.with(&CORE_TOKENIZATION).unwrap();
assert!(!tokens.is_empty());
}
#[test]
fn test_loader_is_reusable() {
let loader = DocumentLoader::from_string("Hello\n");
let _tokens = loader.tokenize().unwrap();
let _doc = loader.parse().unwrap();
let _source = loader.source();
}
#[test]
fn test_from_path_integration() {
let path = workspace_path("comms/specs/benchmark/010-kitchensink.lex");
let loader = DocumentLoader::from_path(path).unwrap();
let doc = loader.parse().unwrap();
assert!(!doc.root.children.is_empty());
}
}