loalang 0.1.15

Loa is a general-purpose, purely immutable, object-oriented programming language.
Documentation
use crate::optimization::Optimizable;
use crate::*;
use serde::Deserialize;

extern crate serde_yaml;
extern crate simple_logging;

#[derive(Deserialize)]
struct FixtureConfig {
    main_class: Option<String>,
    expected: FixtureExpectations,
}

#[derive(Deserialize)]
struct FixtureExpectations {
    success: bool,
    stdout: Vec<String>,
}

#[test]
fn fixtures() {
    simple_logging::log_to_stderr(LevelFilter::Debug);

    let mut failures = vec![];

    for entry in glob::glob("src/__fixtures__/*").unwrap() {
        let entry = entry.unwrap();

        let fixture_name = entry.file_name().and_then(std::ffi::OsStr::to_str).unwrap();
        eprintln!("\n{} ===============================", fixture_name);
        if fixture_name.starts_with("_") {
            eprintln!("Skipping");
            continue;
        }

        eprintln!("Parsing.");

        let mut fixture_config_path = entry.clone();
        fixture_config_path.push("fixture.yml");

        let fixture_config: FixtureConfig =
            serde_yaml::from_reader(std::fs::File::open(fixture_config_path).unwrap()).unwrap();

        let mut source_files_path = entry.clone();
        source_files_path.push("**");
        source_files_path.push("*.loa");

        let mut diagnostics = vec![];
        let mut test_comments = vec![];

        let mut sources = Source::files(source_files_path.to_str().unwrap()).unwrap();
        sources.extend(Source::stdlib().unwrap());
        if let Some(ref main_class) = fixture_config.main_class {
            sources.push(Source::main(main_class));
        }

        eprintln!("Analyzing.");

        let mut analysis: semantics::Analysis = sources
            .into_iter()
            .map(syntax::Parser::new)
            .map(syntax::Parser::parse_with_test_comments)
            .map(|(t, d, c)| {
                diagnostics.extend(d);
                test_comments.extend(c);
                (t.source.uri.clone(), t)
            })
            .into();

        diagnostics.extend(analysis.check().clone());

        let actual_success = !Diagnostic::failed(&diagnostics);

        'expected_comment: for comment in test_comments.iter() {
            for diagnostic in diagnostics.iter() {
                if matches(comment, diagnostic) {
                    continue 'expected_comment;
                }
            }
            failures.push(format!(
                "Expected diagnostic: {:?} @ {}",
                comment.lexeme(),
                comment.span
            ));
        }
        'actual_diagnostic: for diagnostic in diagnostics {
            for comment in test_comments.iter() {
                if matches(comment, &diagnostic) {
                    continue 'actual_diagnostic;
                }
            }
            failures.push(format!("Unexpected diagnostic: {:#?}", diagnostic));
        }

        if !actual_success {
            continue;
        }

        if let Some(_) = fixture_config.main_class {
            eprintln!("Generating.");

            let mut generator = generation::Generator::new(&mut analysis);
            let mut assembly = generator.generate_all().unwrap();

            eprintln!("Optimizing.");
            assembly.optimize();

            eprintln!("Running.");
            eprintln!("{:?}", assembly);

            let mut vm = vm::VM::new();
            let result = vm.eval_pop::<()>(assembly.clone().into()).unwrap();

            let actual_stdout = format!("{}\n", result);
            let expected_stdout: String = fixture_config
                .expected
                .stdout
                .into_iter()
                .map(|s| format!("{}\n", s))
                .collect();

            if actual_stdout != expected_stdout {
                failures.push(format!(
                    "{}:\nExpected output: {}\n  Actual output: {}",
                    fixture_name, expected_stdout, actual_stdout
                ));
            }
        }

        if fixture_config.expected.success != actual_success {
            failures.push(format!(
                "Expected {} to {}",
                fixture_name,
                if fixture_config.expected.success {
                    "be successful"
                } else {
                    "fail"
                }
            ));
        }
    }

    assert!(failures.is_empty(), "\n\n{}", failures.join("\n\n"));
}

fn matches(comment: &syntax::Token, diagnostic: &Diagnostic) -> bool {
    let d_span = diagnostic.span();
    (
        comment.span.start.line,
        comment.span.start.uri.clone(),
        &comment.lexeme()[4..],
    ) == (
        d_span.start.line,
        d_span.start.uri.clone(),
        diagnostic.to_string().as_str(),
    )
}