use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use super::forking::GrammarFacts;
use super::operations::{Condition, Statement};
use super::text::{decode_at, fold_case, quote_text};
use crate::grammar::{ByteClassItem, PrecedenceEntry, RuleKind};
pub(super) type Name = Arc<str>;
#[derive(Debug)]
pub(super) enum ClassTest {
Char(u32),
Range(u32, u32),
Property(regex::Regex),
}
impl ClassTest {
fn test(&self, code_point: u32) -> bool {
match self {
Self::Char(value) => code_point == *value,
Self::Range(low, high) => (*low..=*high).contains(&code_point),
Self::Property(pattern) => char::from_u32(code_point)
.is_some_and(|c| pattern.is_match(c.encode_utf8(&mut [0; 4]))),
}
}
}
#[derive(Debug)]
pub(super) enum Matcher {
Literal(Vec<u8>),
Insensitive {
folded: String,
count: usize,
},
CharRange(u32, u32),
CharClass {
negated: bool,
tests: Vec<ClassTest>,
},
ByteClass {
negated: bool,
items: Vec<ByteClassItem>,
},
Any,
}
impl Matcher {
pub(super) fn matches(&self, bytes: &[u8], position: usize, end: usize) -> Option<usize> {
match self {
Self::Literal(literal) => (position + literal.len() <= end
&& bytes[position..position + literal.len()] == literal[..])
.then_some(position + literal.len()),
Self::Insensitive { folded, count } => {
let mut cursor = position;
let mut text = String::new();
for _ in 0..*count {
if cursor >= end {
return None;
}
let (code_point, length) = decode_at(bytes, cursor, end);
text.push(char::from_u32(code_point?)?);
cursor += length;
}
(fold_case(&text) == *folded).then_some(cursor)
}
Self::CharRange(low, high) => {
code_point_match(bytes, position, end, |c| (*low..=*high).contains(&c))
}
Self::CharClass { negated, tests } => code_point_match(bytes, position, end, |c| {
tests.iter().any(|test| test.test(c)) != *negated
}),
Self::ByteClass { negated, items } => {
if position >= end {
return None;
}
let byte = bytes[position];
let contains = items.iter().any(|item| match item {
ByteClassItem::Byte(value) => byte == *value,
ByteClassItem::Range(low, high) => (*low..=*high).contains(&byte),
});
(contains != *negated).then_some(position + 1)
}
Self::Any => (position < end).then(|| position + decode_at(bytes, position, end).1),
}
}
}
fn code_point_match(
bytes: &[u8],
position: usize,
end: usize,
test: impl Fn(u32) -> bool,
) -> Option<usize> {
if position >= end {
return None;
}
let (code_point, length) = decode_at(bytes, position, end);
code_point.filter(|c| test(*c)).map(|_| position + length)
}
pub(super) fn literal_expectation(value: &str) -> Name {
Arc::from(quote_text(value))
}
pub(super) fn insensitive_expectation(value: &str) -> Name {
Arc::from(format!("{}i", quote_text(value)))
}
pub(super) fn range_expectation(start: char, end: char) -> Name {
Arc::from(format!(
"{}..{}",
quote_text(&start.to_string()),
quote_text(&end.to_string())
))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(super) enum Associativity {
Left,
Right,
None,
}
#[derive(Clone, Debug)]
pub(super) struct PrecedenceTag {
pub(super) level: i64,
pub(super) name: Option<Name>,
pub(super) associativity: Associativity,
pub(super) rule: Option<Name>,
}
impl PrecedenceTag {
pub(super) const fn unranked(rule: Option<Name>) -> Self {
Self {
level: 0,
name: None,
associativity: Associativity::None,
rule,
}
}
pub(super) fn same(a: Option<&Self>, b: Option<&Self>) -> bool {
match (a, b) {
(Some(a), Some(b)) => {
a.level == b.level && a.name == b.name && a.associativity == b.associativity
}
(a, b) => a.is_none() && b.is_none(),
}
}
}
pub(super) fn compare_precedence(
a: &PrecedenceTag,
b: &PrecedenceTag,
orders: &[Vec<PrecedenceEntry>],
) -> std::cmp::Ordering {
if a.name.is_none() && b.name.is_none() && (a.level != 0 || b.level != 0) {
return a.level.cmp(&b.level);
}
let matches = |entry: &PrecedenceEntry, tag: &PrecedenceTag| match entry {
PrecedenceEntry::Name(name) => tag.name.as_deref() == Some(name.as_str()),
PrecedenceEntry::Rule(rule) => tag.rule.as_deref() == Some(rule.as_str()),
};
for order in orders {
let (mut left, mut right) = (None, None);
for (position, entry) in order.iter().enumerate() {
if matches(entry, a) {
left = Some(position);
}
if matches(entry, b) {
right = Some(position);
}
if let (Some(left), Some(right)) = (left, right) {
return right.cmp(&left);
}
}
}
std::cmp::Ordering::Equal
}
#[derive(Clone, Debug)]
pub(super) enum Target {
Rule(usize),
External(Name),
}
#[derive(Debug)]
pub(super) enum Expr {
Empty,
Terminal {
matcher: Matcher,
expectation: Name,
expected: Option<usize>,
},
Ref(Target),
Seq(Vec<Self>),
Choice {
ordered: bool,
items: Vec<Self>,
},
Repeat {
item: Box<Self>,
min: usize,
max: Option<usize>,
},
And(Box<Self>),
Not(Box<Self>),
Capture {
label: Option<Name>,
item: Box<Self>,
},
Alias {
name: Name,
item: Box<Self>,
},
Precedence {
level: i64,
name: Option<Name>,
associativity: Associativity,
item: Box<Self>,
},
DynamicPrecedence {
level: i64,
item: Box<Self>,
},
LexicalPrecedence {
level: i64,
item: Box<Self>,
},
Longest(Vec<Self>),
Token(Box<Self>),
ImmediateToken(Box<Self>),
Predicate {
item: Box<Self>,
condition: Box<Condition>,
},
Recover {
item: Box<Self>,
synchronize: Box<Self>,
},
Missing {
item: Box<Self>,
kind: Option<Name>,
literal: bool,
},
Embed {
language: Name,
item: Box<Self>,
},
}
#[derive(Debug)]
pub(super) struct Rule {
pub(super) name: Name,
pub(super) node_kind: Name,
pub(super) kind: RuleKind,
pub(super) expression: Expr,
pub(super) action: Option<Vec<Statement>>,
pub(super) modes: Option<Vec<Name>>,
pub(super) lexical_priority: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum SettlingStep {
Tokens,
Precedence,
Dynamic,
First,
Ambiguity,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Settling {
steps: [SettlingStep; 5],
count: usize,
}
impl Settling {
pub(super) const ALL: Self = Self {
steps: [
SettlingStep::Tokens,
SettlingStep::Precedence,
SettlingStep::Dynamic,
SettlingStep::Ambiguity,
SettlingStep::Ambiguity,
],
count: 4,
};
pub(super) fn new<S: AsRef<str>>(names: &[S]) -> Self {
let mut settling = Self {
steps: [SettlingStep::Ambiguity; 5],
count: 0,
};
for name in names.iter().take(5) {
settling.steps[settling.count] = match name.as_ref() {
"tokens" => SettlingStep::Tokens,
"precedence" => SettlingStep::Precedence,
"dynamic" => SettlingStep::Dynamic,
"first" => SettlingStep::First,
_ => SettlingStep::Ambiguity,
};
settling.count += 1;
}
settling
}
pub(super) fn steps(&self) -> &[SettlingStep] {
&self.steps[..self.count]
}
pub(super) fn has(&self, step: SettlingStep) -> bool {
self.steps().contains(&step)
}
pub(super) fn ambiguity(&self) -> bool {
self.steps().last() == Some(&SettlingStep::Ambiguity)
}
}
impl Default for Settling {
fn default() -> Self {
Self::ALL
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct TokenRank {
pub(super) priority: i64,
pub(super) specificity: u8,
pub(super) order: usize,
}
#[derive(Debug, Default)]
pub(super) struct TokenRanks {
pub(super) kinds: HashMap<Name, TokenRank>,
pub(super) literals: HashMap<Vec<u8>, TokenRank>,
pub(super) immediate: HashSet<Vec<u8>>,
pub(super) expressions: HashMap<usize, TokenRank>,
}
#[derive(Debug)]
pub(super) struct Scanner {
pub(super) operations: Vec<Statement>,
pub(super) consults: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(super) enum InExtra {
Outside,
Flat,
Nesting,
}
#[derive(Debug)]
pub(super) struct Trivia {
pub(super) expression: Expr,
pub(super) kind: Option<Name>,
pub(super) modes: Option<Vec<Name>>,
}
#[derive(Debug)]
pub(super) struct Program {
pub(super) peg: bool,
pub(super) settling: Settling,
pub(super) token_ranks: Option<TokenRanks>,
pub(super) start: Option<String>,
pub(super) rules: Vec<Rule>,
pub(super) rule_index: HashMap<String, usize>,
pub(super) external: HashMap<String, usize>,
pub(super) scanners: Vec<Scanner>,
pub(super) conflicts: HashSet<String>,
pub(super) precedence_orders: Vec<Vec<PrecedenceEntry>>,
pub(super) ranked_silent: HashSet<String>,
pub(super) trivia: Vec<Trivia>,
pub(super) nesting_extras: HashSet<Name>,
pub(super) expected_references: HashMap<String, usize>,
pub(super) grammar: GrammarFacts,
}
#[derive(Debug)]
pub(super) struct Compiled {
pub(super) programs: Vec<Program>,
pub(super) languages: HashMap<String, usize>,
pub(super) main: usize,
}