use std::fmt;
use pomsky_syntax::Span;
use crate::diagnose::{CompileError, CompileErrorKind, UnsupportedError};
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PomskyFeatures {
bits: u16,
}
impl fmt::Debug for PomskyFeatures {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PomskyFeatures")
.field("grapheme", &self.supports(Self::GRAPHEME))
.field("numbered_groups", &self.supports(Self::NUMBERED_GROUPS))
.field("named_groups", &self.supports(Self::NAMED_GROUPS))
.field("atomic_groups", &self.supports(Self::ATOMIC_GROUPS))
.field("references", &self.supports(Self::REFERENCES))
.field("lazy_mode", &self.supports(Self::LAZY_MODE))
.field("ascii_mode", &self.supports(Self::ASCII_MODE))
.field("ranges", &self.supports(Self::RANGES))
.field("variables", &self.supports(Self::VARIABLES))
.field("lookahead", &self.supports(Self::LOOKAHEAD))
.field("lookbehind", &self.supports(Self::LOOKBEHIND))
.field("boundaries", &self.supports(Self::BOUNDARIES))
.field("regexes", &self.supports(Self::REGEXES))
.field("dot", &self.supports(Self::DOT))
.field("recursion", &self.supports(Self::RECURSION))
.field("intersection", &self.supports(Self::INTERSECTION))
.finish()
}
}
impl Default for PomskyFeatures {
fn default() -> Self {
Self {
bits: Self::GRAPHEME
| Self::NUMBERED_GROUPS
| Self::NAMED_GROUPS
| Self::REFERENCES
| Self::LAZY_MODE
| Self::ASCII_MODE
| Self::RANGES
| Self::VARIABLES
| Self::LOOKAHEAD
| Self::LOOKBEHIND
| Self::BOUNDARIES
| Self::ATOMIC_GROUPS
| Self::REGEXES
| Self::DOT
| Self::RECURSION
| Self::INTERSECTION,
}
}
}
impl PomskyFeatures {
pub(crate) const GRAPHEME: u16 = 1 << 0;
pub(crate) const NUMBERED_GROUPS: u16 = 1 << 1;
pub(crate) const NAMED_GROUPS: u16 = 1 << 2;
pub(crate) const REFERENCES: u16 = 1 << 3;
pub(crate) const LAZY_MODE: u16 = 1 << 4;
pub(crate) const ASCII_MODE: u16 = 1 << 5;
pub(crate) const RANGES: u16 = 1 << 6;
pub(crate) const VARIABLES: u16 = 1 << 7;
pub(crate) const LOOKAHEAD: u16 = 1 << 8;
pub(crate) const LOOKBEHIND: u16 = 1 << 9;
pub(crate) const BOUNDARIES: u16 = 1 << 10;
pub(crate) const ATOMIC_GROUPS: u16 = 1 << 11;
pub(crate) const REGEXES: u16 = 1 << 12;
pub(crate) const DOT: u16 = 1 << 13;
pub(crate) const RECURSION: u16 = 1 << 14;
pub(crate) const INTERSECTION: u16 = 1 << 15;
#[must_use]
pub fn new() -> Self {
PomskyFeatures { bits: 0 }
}
fn set_bit(&mut self, bit: u16, support: bool) {
if support {
self.bits |= bit;
} else {
self.bits &= bit ^ 0xFF_FF_u16;
}
}
fn supports(self, bit: u16) -> bool {
(self.bits & bit) != 0
}
pub(super) fn require(self, feature: u16, span: Span) -> Result<(), CompileError> {
if self.supports(feature) {
Ok(())
} else {
Err(CompileErrorKind::UnsupportedPomskySyntax(match feature {
Self::GRAPHEME => UnsupportedError::Grapheme,
Self::NUMBERED_GROUPS => UnsupportedError::NumberedGroups,
Self::NAMED_GROUPS => UnsupportedError::NamedGroups,
Self::REFERENCES => UnsupportedError::References,
Self::LAZY_MODE => UnsupportedError::LazyMode,
Self::ASCII_MODE => UnsupportedError::AsciiMode,
Self::RANGES => UnsupportedError::Ranges,
Self::VARIABLES => UnsupportedError::Variables,
Self::LOOKAHEAD => UnsupportedError::Lookahead,
Self::LOOKBEHIND => UnsupportedError::Lookbehind,
Self::BOUNDARIES => UnsupportedError::Boundaries,
Self::ATOMIC_GROUPS => UnsupportedError::AtomicGroups,
Self::REGEXES => UnsupportedError::Regexes,
Self::DOT => UnsupportedError::Dot,
Self::RECURSION => UnsupportedError::Recursion,
Self::INTERSECTION => UnsupportedError::Intersection,
_ => panic!("Unknown feature `0x{feature:0x}`"),
})
.at(span))
}
}
pub fn grapheme(&mut self, support: bool) -> Self {
self.set_bit(Self::GRAPHEME, support);
*self
}
pub fn numbered_groups(&mut self, support: bool) -> Self {
self.set_bit(Self::NUMBERED_GROUPS, support);
*self
}
pub fn named_groups(&mut self, support: bool) -> Self {
self.set_bit(Self::NAMED_GROUPS, support);
*self
}
pub fn atomic_groups(&mut self, support: bool) -> Self {
self.set_bit(Self::ATOMIC_GROUPS, support);
*self
}
pub fn references(&mut self, support: bool) -> Self {
self.set_bit(Self::REFERENCES, support);
*self
}
pub fn lazy_mode(&mut self, support: bool) -> Self {
self.set_bit(Self::LAZY_MODE, support);
*self
}
pub fn ascii_mode(&mut self, support: bool) -> Self {
self.set_bit(Self::ASCII_MODE, support);
*self
}
pub fn ranges(&mut self, support: bool) -> Self {
self.set_bit(Self::RANGES, support);
*self
}
pub fn variables(&mut self, support: bool) -> Self {
self.set_bit(Self::VARIABLES, support);
*self
}
pub fn lookahead(&mut self, support: bool) -> Self {
self.set_bit(Self::LOOKAHEAD, support);
*self
}
pub fn lookbehind(&mut self, support: bool) -> Self {
self.set_bit(Self::LOOKBEHIND, support);
*self
}
pub fn boundaries(&mut self, support: bool) -> Self {
self.set_bit(Self::BOUNDARIES, support);
*self
}
pub fn regexes(&mut self, support: bool) -> Self {
self.set_bit(Self::REGEXES, support);
*self
}
pub fn dot(&mut self, support: bool) -> Self {
self.set_bit(Self::DOT, support);
*self
}
pub fn recursion(&mut self, support: bool) -> Self {
self.set_bit(Self::RECURSION, support);
*self
}
pub fn intersection(&mut self, support: bool) -> Self {
self.set_bit(Self::INTERSECTION, support);
*self
}
}
#[test]
fn test_toggles() {
let features = PomskyFeatures::new()
.grapheme(true)
.numbered_groups(true)
.named_groups(true)
.atomic_groups(true)
.references(true)
.lazy_mode(true)
.ascii_mode(true)
.ranges(true)
.variables(true)
.lookahead(true)
.lookbehind(true)
.boundaries(true)
.regexes(true)
.dot(true)
.recursion(true)
.intersection(true);
assert_eq!(features.bits, PomskyFeatures::default().bits);
}