use crate::lex::ast::elements::{
Annotation, Definition, List, Paragraph, Session, Table, Verbatim,
};
use crate::lex::ast::Document;
use crate::lex::loader::DocumentLoader;
use crate::lex::parsing::parse_document;
use crate::lex::parsing::ParseError;
use crate::lex::testing::lexplore::specfile_finder;
use std::fs;
pub use specfile_finder::{DocumentType, ElementType};
#[derive(Debug, Clone)]
pub enum ElementSourceError {
FileNotFound(String),
IoError(String),
ParseError(String),
InvalidElement(String),
}
impl std::fmt::Display for ElementSourceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElementSourceError::FileNotFound(msg) => write!(f, "File not found: {msg}"),
ElementSourceError::IoError(msg) => write!(f, "IO error: {msg}"),
ElementSourceError::ParseError(msg) => write!(f, "Parse error: {msg}"),
ElementSourceError::InvalidElement(msg) => write!(f, "Invalid element: {msg}"),
}
}
}
impl std::error::Error for ElementSourceError {}
impl From<std::io::Error> for ElementSourceError {
fn from(err: std::io::Error) -> Self {
ElementSourceError::IoError(err.to_string())
}
}
impl From<ParseError> for ElementSourceError {
fn from(err: ParseError) -> Self {
ElementSourceError::ParseError(err.to_string())
}
}
impl From<specfile_finder::SpecFileError> for ElementSourceError {
fn from(err: specfile_finder::SpecFileError) -> Self {
match err {
specfile_finder::SpecFileError::FileNotFound(msg) => {
ElementSourceError::FileNotFound(msg)
}
specfile_finder::SpecFileError::IoError(msg) => ElementSourceError::IoError(msg),
specfile_finder::SpecFileError::DuplicateNumber(msg) => {
ElementSourceError::IoError(msg)
}
}
}
}
fn load_isolated_element(element_type: ElementType, number: usize) -> Document {
let path = specfile_finder::find_element_file(element_type, number)
.unwrap_or_else(|e| panic!("Failed to find {element_type:?} #{number}: {e}"));
let source = fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path.display(), e));
if matches!(element_type, ElementType::Annotation) {
use crate::lex::testing::parse_without_annotation_attachment;
parse_without_annotation_attachment(&source).unwrap()
} else {
parse_document(&source).unwrap()
}
}
macro_rules! element_shortcuts {
($($name:ident => $variant:ident, $label:literal);* $(;)?) => {
$(
#[doc = concat!("Load a ", $label, " file (returns DocumentLoader for transforms)")]
pub fn $name(number: usize) -> DocumentLoader {
Self::load(ElementType::$variant, number)
}
)*
};
}
macro_rules! document_shortcuts {
($($name:ident => $variant:ident, $label:literal);* $(;)?) => {
$(
#[doc = concat!("Load a ", $label, " document (returns DocumentLoader for transforms)")]
pub fn $name(number: usize) -> DocumentLoader {
Self::load_document(DocumentType::$variant, number)
}
)*
};
}
pub struct Lexplore;
impl Lexplore {
pub fn load(element_type: ElementType, number: usize) -> DocumentLoader {
let path = specfile_finder::find_element_file(element_type, number)
.unwrap_or_else(|e| panic!("Failed to find {element_type:?} #{number}: {e}"));
DocumentLoader::from_path(path)
.unwrap_or_else(|e| panic!("Failed to load {element_type:?} #{number}: {e}"))
}
pub fn load_document(doc_type: DocumentType, number: usize) -> DocumentLoader {
let path = specfile_finder::find_document_file(doc_type, number)
.unwrap_or_else(|e| panic!("Failed to find {doc_type:?} #{number}: {e}"));
DocumentLoader::from_path(path)
.unwrap_or_else(|e| panic!("Failed to load {doc_type:?} #{number}: {e}"))
}
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> DocumentLoader {
DocumentLoader::from_path(path).unwrap_or_else(|e| panic!("Failed to load from path: {e}"))
}
pub fn get_paragraph(number: usize) -> &'static Paragraph {
let doc = Box::leak(Box::new(load_isolated_element(
ElementType::Paragraph,
number,
)));
doc.root.expect_paragraph()
}
pub fn get_list(number: usize) -> &'static List {
let doc = Box::leak(Box::new(load_isolated_element(ElementType::List, number)));
doc.root.expect_list()
}
pub fn get_session(number: usize) -> &'static Session {
let doc = Box::leak(Box::new(load_isolated_element(
ElementType::Session,
number,
)));
doc.root.expect_session()
}
pub fn get_definition(number: usize) -> &'static Definition {
let doc = Box::leak(Box::new(load_isolated_element(
ElementType::Definition,
number,
)));
doc.root.expect_definition()
}
pub fn get_annotation(number: usize) -> &'static Annotation {
let doc = Box::leak(Box::new(load_isolated_element(
ElementType::Annotation,
number,
)));
doc.root.expect_annotation()
}
pub fn get_verbatim(number: usize) -> &'static Verbatim {
let doc = Box::leak(Box::new(load_isolated_element(
ElementType::Verbatim,
number,
)));
doc.root.expect_verbatim()
}
pub fn get_table(number: usize) -> &'static Table {
let doc = Box::leak(Box::new(load_isolated_element(ElementType::Table, number)));
doc.root.expect_table()
}
element_shortcuts! {
paragraph => Paragraph, "paragraph";
list => List, "list";
session => Session, "session";
definition => Definition, "definition";
annotation => Annotation, "annotation";
verbatim => Verbatim, "verbatim";
table => Table, "table";
document => Document, "document";
}
document_shortcuts! {
benchmark => Benchmark, "benchmark";
trifecta => Trifecta, "trifecta";
}
pub fn list_numbers_for(element_type: ElementType) -> Result<Vec<usize>, ElementSourceError> {
Ok(specfile_finder::list_element_numbers(element_type)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lex::ast::traits::Container;
use crate::lex::lexing::Token;
use crate::lex::testing::lexplore::extraction::*;
use crate::lex::testing::workspace_path;
#[test]
fn test_list_numbers_for_paragraphs() {
let numbers = Lexplore::list_numbers_for(ElementType::Paragraph).unwrap();
assert!(!numbers.is_empty());
assert!(numbers.contains(&1));
}
#[test]
fn test_get_paragraph() {
let paragraph = Lexplore::get_paragraph(1);
assert!(paragraph_text_starts_with(paragraph, "This is a simple"));
}
#[test]
fn test_get_list() {
let list = Lexplore::get_list(1);
assert!(!list.items.is_empty());
}
#[test]
fn test_get_session() {
let session = Lexplore::get_session(1);
assert!(!session.label().is_empty());
}
#[test]
fn test_get_definition() {
let definition = Lexplore::get_definition(1);
assert!(!definition.label().is_empty());
}
#[test]
fn test_benchmark_fluent_api() {
let doc = Lexplore::benchmark(10).parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_trifecta_fluent_api() {
let doc = Lexplore::trifecta(0).parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_benchmark_source_only() {
let source = Lexplore::benchmark(10).source();
assert!(!source.is_empty());
}
#[test]
fn test_trifecta_source_only() {
let source = Lexplore::trifecta(0).source();
assert!(!source.is_empty());
}
#[test]
fn test_tokenize_paragraph() {
let tokens = Lexplore::paragraph(1).tokenize().unwrap();
assert!(!tokens.is_empty());
}
#[test]
fn test_tokenize_list() {
let tokens = Lexplore::list(1).tokenize().unwrap();
assert!(
tokens.iter().any(|(t, _)| matches!(t, Token::Dash))
|| tokens.iter().any(|(t, _)| matches!(t, Token::Number(_)))
);
}
#[test]
fn test_tokenize_benchmark() {
let tokens = Lexplore::benchmark(10).tokenize().unwrap();
assert!(!tokens.is_empty());
assert!(tokens.len() > 10);
}
#[test]
fn test_tokenize_trifecta() {
let tokens = Lexplore::trifecta(0).tokenize().unwrap();
assert!(!tokens.is_empty());
assert!(tokens.iter().any(|(t, _)| matches!(t, Token::Text(_))));
}
#[test]
fn test_from_path_parse() {
let path =
workspace_path("comms/specs/elements/paragraph.docs/paragraph-01-flat-oneline.lex");
let doc = Lexplore::from_path(path).parse().unwrap();
let paragraph = doc.root.expect_paragraph();
assert!(!paragraph.text().is_empty());
}
#[test]
fn test_from_path_tokenize() {
let path =
workspace_path("comms/specs/elements/paragraph.docs/paragraph-01-flat-oneline.lex");
let tokens = Lexplore::from_path(path).tokenize().unwrap();
assert!(!tokens.is_empty());
assert!(tokens.iter().any(|(t, _)| matches!(t, Token::Text(_))));
}
#[test]
fn test_from_path_source() {
let path =
workspace_path("comms/specs/elements/paragraph.docs/paragraph-01-flat-oneline.lex");
let source = Lexplore::from_path(path).source();
assert!(!source.is_empty());
}
#[test]
fn test_from_path_with_benchmark() {
let path = workspace_path("comms/specs/benchmark/010-kitchensink.lex");
let doc = Lexplore::from_path(path).parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_from_path_with_trifecta() {
let path = workspace_path("comms/specs/trifecta/000-paragraphs.lex");
let doc = Lexplore::from_path(path).parse().unwrap();
assert!(!doc.root.children.is_empty());
}
#[test]
fn test_get_paragraph_direct() {
let paragraph = Lexplore::get_paragraph(1);
assert!(paragraph_text_starts_with(paragraph, "This is a simple"));
}
#[test]
fn test_get_list_direct() {
let list = Lexplore::get_list(1);
assert!(!list.items.is_empty());
}
#[test]
fn test_get_session_direct() {
let session = Lexplore::get_session(1);
assert!(!session.label().is_empty());
}
#[test]
fn test_get_definition_direct() {
let definition = Lexplore::get_definition(1);
assert!(!definition.label().is_empty());
}
#[test]
fn test_get_annotation_direct() {
let _annotation = Lexplore::get_annotation(1);
}
#[test]
fn test_get_verbatim_direct() {
let _verbatim = Lexplore::get_verbatim(1);
}
}