127 lines
3.5 KiB
Rust
127 lines
3.5 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use rottlib::arena::Arena;
|
|
use rottlib::diagnostics::{Diagnostic, Severity};
|
|
use rottlib::lexer::{TokenPosition, TokenSpan, TokenizedFile};
|
|
use rottlib::parser::Parser;
|
|
|
|
mod block_items;
|
|
mod control_flow_expressions;
|
|
mod primary_expressions;
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct ExpectedLabel {
|
|
pub span: TokenSpan,
|
|
pub message: &'static str,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct ExpectedDiagnostic<'a> {
|
|
pub headline: &'static str,
|
|
pub severity: Severity,
|
|
pub code: Option<&'static str>,
|
|
pub primary_label: Option<ExpectedLabel>,
|
|
pub secondary_labels: &'a [ExpectedLabel],
|
|
pub help: Option<&'static str>,
|
|
pub notes: &'a [&'static str],
|
|
}
|
|
|
|
#[track_caller]
|
|
pub(super) fn assert_diagnostic(actual: &Diagnostic, expected: &ExpectedDiagnostic<'_>) {
|
|
assert_eq!(actual.headline(), expected.headline);
|
|
assert_eq!(actual.severity(), expected.severity);
|
|
assert_eq!(actual.code(), expected.code);
|
|
assert_eq!(actual.help(), expected.help);
|
|
|
|
match (actual.primary_label(), expected.primary_label.as_ref()) {
|
|
(None, None) => {}
|
|
(Some(actual), Some(expected)) => {
|
|
assert_eq!(actual.span, expected.span);
|
|
assert_eq!(actual.message, expected.message);
|
|
}
|
|
_ => panic!("primary label mismatch"),
|
|
}
|
|
|
|
let actual_secondary = actual.secondary_labels();
|
|
assert_eq!(actual_secondary.len(), expected.secondary_labels.len());
|
|
|
|
for (actual, expected) in actual_secondary
|
|
.iter()
|
|
.zip(expected.secondary_labels.iter())
|
|
{
|
|
assert_eq!(actual.span, expected.span);
|
|
assert_eq!(actual.message, expected.message);
|
|
}
|
|
|
|
let actual_notes = actual.notes();
|
|
assert_eq!(actual_notes.len(), expected.notes.len());
|
|
|
|
for (actual, expected) in actual_notes.iter().zip(expected.notes.iter()) {
|
|
assert_eq!(actual, expected);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub(super) struct Fixture {
|
|
pub label: &'static str,
|
|
pub source: &'static str,
|
|
}
|
|
|
|
pub(super) type FixtureRun = Vec<Diagnostic>;
|
|
|
|
pub(super) struct FixtureRuns {
|
|
runs: HashMap<&'static str, FixtureRun>,
|
|
}
|
|
|
|
impl FixtureRuns {
|
|
#[track_caller]
|
|
pub fn get(&self, label: &str) -> Option<Vec<Diagnostic>> {
|
|
self.runs.get(label).map(|fixture_run| fixture_run.clone())
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn get_any(&self, label: &str) -> Diagnostic {
|
|
self.runs
|
|
.get(label)
|
|
.map(|fixture_run| fixture_run[0].clone())
|
|
.unwrap()
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn get_by_code(&self, label: &str, code: &str) -> Diagnostic {
|
|
self.runs
|
|
.get(label)
|
|
.unwrap_or_else(|| panic!("no fixture run for `{label}`"))
|
|
.iter()
|
|
.find(|diagnostic| diagnostic.code().as_deref() == Some(code))
|
|
.unwrap_or_else(|| panic!("no `{code}` diagnostic in fixture `{label}`"))
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
fn run_fixture(fixture: &'static Fixture) -> FixtureRun {
|
|
let arena = Arena::new();
|
|
let file = TokenizedFile::tokenize(fixture.source);
|
|
let mut parser = Parser::new(&file, &arena);
|
|
|
|
let _ = parser.parse_expression();
|
|
let diagnostics = parser.diagnostics.clone();
|
|
|
|
for diagnostic in &diagnostics {
|
|
diagnostic.render(&file, fixture.label);
|
|
println!();
|
|
}
|
|
|
|
diagnostics
|
|
}
|
|
|
|
pub(super) fn run_fixtures(fixtures: &'static [Fixture]) -> FixtureRuns {
|
|
let mut runs = HashMap::new();
|
|
|
|
for fixture in fixtures {
|
|
runs.insert(fixture.label, run_fixture(fixture));
|
|
}
|
|
|
|
FixtureRuns { runs }
|
|
}
|