skullbrain-parser 0.1.0

Parses skullbrain tokens into a concrete syntax tree (CST).
Documentation
use std::{fmt::Write, path::Path};

use cstree::{build::NodeCache, syntax::SyntaxNode};
use insta::assert_snapshot;
use skullbrain_diagnostics::{
    Cache, CharSet, Emitter, FileCache, LocalizableLanguage, ReportConfig, Source, StringWriter,
};

use crate::{state::ConcreteSyntaxTreeBuilder, CstreeBuilder, ParserState, SyntaxKind};

//type TestDiagnostics<'a> = RawDiagnostics<CustomCache<'a>, Vec<u8>>;

/// Creates and runs a parsing test based on files matching a given glob pattern.
/// # Arguments
///
/// * `glob`: A string representing the glob pattern used to find input files
///   within the `inputs/` directory. For example, `"*.txt"` would match all
///   files ending with `.txt`.
pub(crate) fn create_parsing_test(
    glob: &str,
    parse: impl Fn(&mut ParserState<&mut Emitter<StringWriter>, CstreeBuilder<&str>>) + Clone,
) {
    insta::glob!(&format!("inputs/{glob}"), |source| {
        actual_test_impl(source, parse.clone())
    })
}

fn actual_test_impl(
    source: &Path,
    parse: impl Fn(&mut ParserState<&mut Emitter<StringWriter>, CstreeBuilder<&str>>),
) {
    let mut cache = NodeCache::new();
    let input = std::fs::read_to_string(source).expect("Failed reading path");
    let builder = CstreeBuilder::new(&mut cache, input.as_str());
    let source_file = {
        let file_name = source.file_name().expect("failed to get file name");
        Path::new(file_name)
    };

    let mut diagnostics = {
        let report_config = ReportConfig::new()
            .with_char_set(CharSet::Ascii)
            .with_color(false);

        let writer = StringWriter::default();
        let cache = FileCache::default();
        Emitter::new(LocalizableLanguage::LangEn, cache, report_config, writer)
    };

    let mut state = ParserState::new(source_file, input.as_str(), &mut diagnostics, builder);
    parse(&mut state);

    let green = state.finish();
    let resolved_node: SyntaxNode<SyntaxKind> = SyntaxNode::new_root(green);

    let mut display_target = String::new();
    resolved_node
        .write_debug(cache.interner(), &mut display_target, true)
        .expect("Failed writing to display");

    let _ = display_target.write_char('\n');
    let _ = display_target.write_char('\n');
    let _ = display_target.write_str(diagnostics.writer().as_str());

    insta::with_settings!({ info => &input }, {
        assert_snapshot!(display_target);
    });
}

pub(crate) struct CustomCache<'a>(Source<&'a str>);

impl<'a> Cache<Path> for CustomCache<'a> {
    type Storage = &'a str;
    fn fetch(&mut self, _id: &Path) -> Result<&Source<Self::Storage>, impl std::fmt::Debug> {
        Ok::<_, ()>(&self.0)
    }

    fn display<'l>(&self, id: &'l Path) -> Option<impl std::fmt::Display + 'l> {
        Some(id.display())
    }
}