use crate::dialect::Dialect;
use crate::error::ErrorCode;
#[derive(Debug, Clone, Copy)]
pub struct MatrixRow {
pub id: &'static str,
pub status: [&'static str; 5],
pub code: Option<ErrorCode>,
}
const fn row(id: &'static str, status: [&'static str; 5], code: Option<ErrorCode>) -> MatrixRow {
MatrixRow { id, status, code }
}
const SSSSS: [&str; 5] = ["S", "S", "S", "S", "S"];
const RSV: &str = "S in recent versions";
pub const MATRIX: &[MatrixRow] = &[
row("literals-escapes", SSSSS, None),
row("any", SSSSS, None),
row("concat-alt", SSSSS, None),
row("classes", SSSSS, None),
row("class-escapes", SSSSS, None),
row("posix-classes", ["–", "–", "S", "S", "S"], None),
row("class-set-ops", ["–", "–", "–", "S", "–"], None),
row(
"unicode-properties",
["R", "–", "S", "S", "S"],
Some(ErrorCode::U007),
),
row("astral-escapes", ["R", "S", "S", "S", "S"], None),
row("quantifiers", SSSSS, None),
row("counted-quantifiers", SSSSS, None),
row(
"possessive",
["–", RSV, "–", "–", "U"],
Some(ErrorCode::U004),
),
row("groups", SSSSS, None),
row("named-groups", SSSSS, None),
row("inline-flag-groups", ["–", "S", "S", "S", "S"], None),
row("atomic", ["–", RSV, "–", "–", "U"], Some(ErrorCode::U004)),
row(
"conditional",
["–", "U", "–", "–", "U"],
Some(ErrorCode::U003),
),
row(
"branch-reset",
["–", "–", "–", "–", "U"],
Some(ErrorCode::U007),
),
row(
"recursion",
["–", "–", "–", "–", "U"],
Some(ErrorCode::U002),
),
row("callouts", ["–", "–", "–", "–", "U"], Some(ErrorCode::U006)),
row("anchors", SSSSS, None),
row("text-anchors", ["–", "S", "S", "S", "S"], None),
row("word-boundary", ["R", "R", "R", "R", "R"], None),
row(
"lookahead",
["R", "R", "–", "–", "R"],
Some(ErrorCode::U005),
),
row(
"lookbehind",
["R", "R", "–", "–", "R"],
Some(ErrorCode::U005),
),
row(
"backreferences",
["U", "U", "–", "–", "U"],
Some(ErrorCode::U001),
),
row("flag-i", SSSSS, None),
row("flag-m", SSSSS, None),
row("flag-s", SSSSS, None),
row(
"flag-u",
["S", "implicit", "implicit", "implicit", "S"],
None,
),
row("flag-x", ["–", "S", "–", "S", "S"], None),
row("flag-a", ["–", "S", "–", "S", "–"], None),
row(
"flags-gyd-v",
["R", "–", "–", "–", "–"],
Some(ErrorCode::U007),
),
];
pub const REJECTION_SAMPLES: &[(Dialect, &str, ErrorCode)] = &[
(Dialect::Ecma, r"(a)\1", ErrorCode::U001),
(Dialect::Ecma, r"(?<n>a)\k<n>", ErrorCode::U001),
(Dialect::Python, r"(a)\1", ErrorCode::U001),
(Dialect::Python, r"(?P<n>a)(?P=n)", ErrorCode::U001),
(Dialect::Python, "(?>a+)b", ErrorCode::U004),
(Dialect::Python, "a*+", ErrorCode::U004),
(Dialect::Python, "(a)(?(1)b|c)", ErrorCode::U003),
];
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use crate::flags::Flags;
use crate::parser::Pattern;
#[test]
fn rows_match_documentation_status_cells() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../docs/13-syntax-support-matrix.md"
);
let doc = std::fs::read_to_string(path).expect("docs/13 readable");
let mut documented: Vec<Vec<String>> = Vec::new();
for line in doc.lines() {
let line = line.replace("\\|", "\u{1}");
let cells: Vec<String> = line.split('|').map(|c| c.trim().to_string()).collect();
if cells.len() == 9
&& !cells[1].is_empty()
&& cells[1] != "Element"
&& cells[1] != "Flag"
&& !cells[1].starts_with("---")
{
documented.push(cells[2..7].to_vec());
}
}
let data: Vec<Vec<String>> = MATRIX
.iter()
.map(|r| r.status.iter().map(|s| s.to_string()).collect())
.collect();
assert_eq!(documented, data);
}
#[test]
fn ids_are_unique() {
let mut ids: Vec<_> = MATRIX.iter().map(|r| r.id).collect();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), MATRIX.len());
}
#[test]
fn every_rejection_sample_yields_its_code() {
for (d, text, code) in REJECTION_SAMPLES {
let e = Pattern::parse(text, *d, Flags::none()).expect_err(text);
assert_eq!(e.kind, *code, "{} / {text}", d.name());
}
}
}