use crate::ast::*;
use crate::error::ErrorCode;
use crate::flags::Flags;
use crate::parser::{err, PErr, ParseLimits, R};
use rexact_unicode as uni;
pub(crate) struct Out {
pub ast: Node,
pub flags: Flags,
pub groups: u32,
pub names: Vec<String>,
}
enum Esc {
Char(u32),
Short(Shorthand),
Class(Class),
Prop(PropertyRef),
Assert(Assertion),
Quote(Vec<u32>),
Nothing,
}
struct P<'a> {
c: &'a [char],
i: usize,
utf: bool,
base: Flags,
cur: Flags,
depth: u32,
max_depth: u32,
groups: u32,
total_groups: u32,
names: Vec<String>,
dup_names: bool,
}
pub(crate) fn parse(c: &[char], flags: Flags, lim: &ParseLimits) -> Result<Out, PErr> {
let total_groups = scan_groups(c);
let mut p = P {
c,
i: 0,
utf: flags.u,
base: flags,
cur: flags,
depth: 0,
max_depth: lim.max_depth,
groups: 0,
total_groups,
names: Vec::new(),
dup_names: false,
};
let ast = p.alt()?;
if p.i < c.len() {
return err(ErrorCode::P007, p.i, "unmatched ')'");
}
Ok(Out {
ast,
flags,
groups: p.groups,
names: p.names,
})
}
fn scan_groups(c: &[char]) -> u32 {
let (mut n, mut i, mut in_class, mut quote) = (0u32, 0usize, false, false);
while i < c.len() {
let ch = c[i];
if quote {
if ch == '\\' && c.get(i + 1) == Some(&'E') {
quote = false;
i += 2;
} else {
i += 1;
}
continue;
}
match ch {
'\\' => {
if c.get(i + 1) == Some(&'Q') {
quote = true;
}
i += 2;
continue;
}
'[' if !in_class => {
in_class = true;
if c.get(i + 1) == Some(&'^') {
i += 1;
}
if c.get(i + 1) == Some(&']') {
i += 1;
}
}
']' if in_class => in_class = false,
'(' if !in_class => {
let next = c.get(i + 1).copied();
let named = next == Some('?')
&& (c.get(i + 2) == Some(&'\'')
|| (c.get(i + 2) == Some(&'<')
&& !matches!(c.get(i + 3), Some('=') | Some('!')))
|| (c.get(i + 2) == Some(&'P') && c.get(i + 3) == Some(&'<')));
if (next != Some('?') && next != Some('*')) || named {
n += 1;
}
}
_ => {}
}
i += 1;
}
n
}
fn mk(kind: NodeKind, start: usize, end: usize) -> Node {
Node {
kind,
span: Span { start, end },
}
}
fn shorthand(c: char) -> Option<Shorthand> {
Some(match c {
'd' => Shorthand::Digit,
'D' => Shorthand::NotDigit,
'w' => Shorthand::Word,
'W' => Shorthand::NotWord,
's' => Shorthand::Space,
'S' => Shorthand::NotSpace,
_ => return None,
})
}
const POSIX_ASCII: &[(&str, &[(u32, u32)])] = &[
("alnum", &[(0x30, 0x39), (0x41, 0x5A), (0x61, 0x7A)]),
("alpha", &[(0x41, 0x5A), (0x61, 0x7A)]),
("ascii", &[(0, 0x7F)]),
("blank", &[(9, 9), (32, 32)]),
("cntrl", &[(0, 0x1F), (0x7F, 0x7F)]),
("digit", &[(0x30, 0x39)]),
("graph", &[(0x21, 0x7E)]),
("lower", &[(0x61, 0x7A)]),
("print", &[(0x20, 0x7E)]),
(
"punct",
&[(0x21, 0x2F), (0x3A, 0x40), (0x5B, 0x60), (0x7B, 0x7E)],
),
("space", &[(9, 13), (32, 32)]),
("upper", &[(0x41, 0x5A)]),
(
"word",
&[(0x30, 0x39), (0x41, 0x5A), (0x5F, 0x5F), (0x61, 0x7A)],
),
("xdigit", &[(0x30, 0x39), (0x41, 0x46), (0x61, 0x66)]),
];
fn complement(r: &[(u32, u32)], max: u32) -> Vec<(u32, u32)> {
let mut out = Vec::new();
let mut next = 0u32;
for &(lo, hi) in r {
if lo > next {
out.push((next, lo - 1));
}
next = hi + 1;
}
if next <= max {
out.push((next, max));
}
out
}
fn name_char(c: char, first: bool) -> bool {
c == '_' || c.is_alphabetic() || (!first && c.is_alphanumeric())
}
impl P<'_> {
fn peek(&self) -> Option<char> {
self.c.get(self.i).copied()
}
fn at(&self, n: usize) -> Option<char> {
self.c.get(self.i + n).copied()
}
fn eat(&mut self, ch: char) -> bool {
if self.peek() == Some(ch) {
self.i += 1;
true
} else {
false
}
}
fn max_cp(&self) -> u32 {
if self.utf {
0x10FFFF
} else {
0xFF
}
}
fn skip_x(&mut self) {
if !self.cur.x {
return;
}
while let Some(ch) = self.peek() {
if matches!(ch, ' ' | '\t' | '\n' | '\x0b' | '\x0c' | '\r') {
self.i += 1;
} else if ch == '#' {
while let Some(ch) = self.peek() {
self.i += 1;
if ch == '\n' {
break;
}
}
} else {
break;
}
}
}
fn wrap(&self, n: Node) -> Node {
let (c, b) = (self.cur, self.base);
if (c.i, c.m, c.s) == (b.i, b.m, b.s) {
return n;
}
let on = Flags {
i: c.i && !b.i,
m: c.m && !b.m,
s: c.s && !b.s,
..Flags::none()
};
let off = Flags {
i: b.i && !c.i,
m: b.m && !c.m,
s: b.s && !c.s,
..Flags::none()
};
let span = n.span;
mk(
NodeKind::FlagGroup {
delta: FlagDelta { on, off },
node: Box::new(n),
},
span.start,
span.end,
)
}
fn alt(&mut self) -> R<Node> {
self.depth += 1;
if self.depth > self.max_depth {
return err(
ErrorCode::L006,
self.i,
"group nesting exceeds the depth limit",
);
}
let start = self.i;
let mut branches = vec![self.concat()?];
while self.eat('|') {
branches.push(self.concat()?);
}
self.depth -= 1;
Ok(match branches.len() {
1 => branches.remove(0),
_ => mk(NodeKind::Alt(branches), start, self.i),
})
}
fn concat(&mut self) -> R<Node> {
let start = self.i;
let mut items = Vec::new();
loop {
self.skip_x();
if matches!(self.peek(), None | Some('|') | Some(')')) {
break;
}
if let Some(n) = self.term()? {
items.push(n);
}
}
Ok(match items.len() {
0 => mk(NodeKind::Empty, start, self.i),
1 => items.remove(0),
_ => mk(NodeKind::Concat(items), start, self.i),
})
}
fn braces(&self) -> Option<(u64, Option<u64>, usize)> {
let digits = |mut j: usize| {
let mut v: Option<u64> = None;
while let Some(d) = self.c.get(j).and_then(|c| c.to_digit(10)) {
v = Some(
v.unwrap_or(0)
.saturating_mul(10)
.saturating_add(u64::from(d)),
);
j += 1;
}
(v, j)
};
let (lo, j) = digits(self.i + 1);
match (lo, self.c.get(j)) {
(Some(lo), Some('}')) => Some((lo, Some(lo), j + 1)),
(lo, Some(',')) => {
let (hi, k) = digits(j + 1);
if lo.is_none() && hi.is_none() {
return None;
}
(self.c.get(k) == Some(&'}')).then_some((lo.unwrap_or(0), hi, k + 1))
}
_ => None,
}
}
fn quantifier_ahead(&self) -> bool {
match self.peek() {
Some('*' | '+' | '?') => true,
Some('{') => self.braces().is_some(),
_ => false,
}
}
fn term(&mut self) -> R<Option<Node>> {
let start = self.i;
let (atom, repeatable) = self.atom()?;
self.skip_x();
let Some(atom) = atom else {
if self.quantifier_ahead() {
return err(
ErrorCode::P005,
self.i,
"quantifier does not follow a repeatable item",
);
}
return Ok(None);
};
if !self.quantifier_ahead() {
return Ok(Some(atom));
}
if !repeatable {
return err(
ErrorCode::P005,
self.i,
"quantifier does not follow a repeatable item",
);
}
let op_at = self.i;
let (min, max) = match self.peek() {
Some('*') => {
self.i += 1;
(0, None)
}
Some('+') => {
self.i += 1;
(1, None)
}
Some('?') => {
self.i += 1;
(0, Some(1))
}
_ => {
let (lo, hi, end) = self.braces().unwrap_or((0, None, self.i));
self.i = end;
if lo > 65535 || hi.is_some_and(|h| h > 65535) {
return err(ErrorCode::P005, op_at, "number too big in {} quantifier");
}
if hi.is_some_and(|h| lo > h) {
return err(
ErrorCode::P005,
op_at,
"numbers out of order in {} quantifier",
);
}
(lo, hi)
}
};
let greedy = !self.eat('?');
if greedy && self.peek() == Some('+') {
return err(
ErrorCode::U004,
self.i,
"possessive quantifier changes matching semantics",
);
}
self.skip_x();
if self.quantifier_ahead() {
return err(
ErrorCode::P005,
self.i,
"quantifier does not follow a repeatable item",
);
}
Ok(Some(mk(
NodeKind::Repeat {
node: Box::new(atom),
min,
max,
greedy,
},
start,
self.i,
)))
}
fn atom(&mut self) -> R<(Option<Node>, bool)> {
let start = self.i;
let Some(ch) = self.peek() else {
return Ok((None, false));
};
match ch {
'(' => self.group(),
'[' => Ok((Some(self.class()?), true)),
'.' => {
self.i += 1;
Ok((Some(self.wrap(mk(NodeKind::Any, start, self.i))), true))
}
'^' | '$' => {
self.i += 1;
let a = if ch == '^' {
Assertion::LineStart
} else {
Assertion::LineEnd
};
Ok((
Some(self.wrap(mk(NodeKind::Assert(a), start, self.i))),
false,
))
}
'*' | '+' | '?' => err(
ErrorCode::P005,
start,
"quantifier does not follow a repeatable item",
),
'{' if self.braces().is_some() => err(
ErrorCode::P005,
start,
"quantifier does not follow a repeatable item",
),
'\\' => {
let e = self.escape(false)?;
let (kind, rep) = match e {
Esc::Char(c) => (NodeKind::Literal(c), true),
Esc::Short(s) => (
NodeKind::Class(Class {
negated: false,
items: vec![ClassItem::Shorthand(s)],
}),
true,
),
Esc::Class(c) => (NodeKind::Class(c), true),
Esc::Prop(p) => (
NodeKind::Class(Class {
negated: false,
items: vec![ClassItem::Property(p)],
}),
true,
),
Esc::Assert(a) => (NodeKind::Assert(a), false),
Esc::Nothing => return Ok((None, true)),
Esc::Quote(cs) => {
let lits: Vec<Node> = cs
.iter()
.map(|&c| self.wrap(mk(NodeKind::Literal(c), start, self.i)))
.collect();
return Ok((
Some(match lits.len() {
0 => return Ok((None, true)),
1 => lits
.into_iter()
.next()
.unwrap_or_else(|| mk(NodeKind::Empty, start, self.i)),
_ => mk(NodeKind::Concat(lits), start, self.i),
}),
true,
));
}
};
Ok((Some(self.wrap(mk(kind, start, self.i))), rep))
}
_ => {
self.i += 1;
Ok((
Some(self.wrap(mk(NodeKind::Literal(ch as u32), start, self.i))),
true,
))
}
}
}
fn group_body(&mut self, open: usize, saved: Flags) -> R<Node> {
let n = self.alt()?;
if !self.eat(')') {
return err(ErrorCode::P007, open, "missing closing parenthesis");
}
self.cur = saved;
Ok(n)
}
fn named(&mut self, open: usize, end: char, saved: Flags) -> R<(Option<Node>, bool)> {
let at = self.i;
let mut name = String::new();
while let Some(c) = self.peek().filter(|&c| c != end) {
name.push(c);
self.i += 1;
}
if !self.eat(end) {
return err(
ErrorCode::P009,
at,
"syntax error in subpattern name (missing terminator)",
);
}
let ok = name.chars().next().is_some_and(|c| name_char(c, true))
&& name.chars().all(|c| name_char(c, false));
if !ok {
return err(ErrorCode::P009, at, "invalid subpattern name");
}
if self.names.contains(&name) && !self.dup_names {
return err(
ErrorCode::P009,
at,
"two named subpatterns have the same name",
);
}
self.groups += 1;
let idx = self.groups;
self.names.push(name.clone());
let body = self.group_body(open, saved)?;
Ok((
Some(mk(
NodeKind::Group {
capture: Some(idx),
name: Some(name),
node: Box::new(body),
},
open,
self.i,
)),
true,
))
}
fn group(&mut self) -> R<(Option<Node>, bool)> {
let open = self.i;
self.i += 1;
let saved = self.cur;
if self.peek() == Some('*') {
return err(
ErrorCode::U007,
open,
"backtracking control verbs and alphabetic assertions are not supported",
);
}
if !self.eat('?') {
self.groups += 1;
let idx = self.groups;
let body = self.group_body(open, saved)?;
return Ok((
Some(mk(
NodeKind::Group {
capture: Some(idx),
name: None,
node: Box::new(body),
},
open,
self.i,
)),
true,
));
}
let c = self
.peek()
.ok_or_else(|| PErr::new(ErrorCode::P007, open, "unclosed group"))?;
match c {
':' => {
self.i += 1;
let body = self.group_body(open, saved)?;
Ok((
Some(mk(
NodeKind::Group {
capture: None,
name: None,
node: Box::new(body),
},
open,
self.i,
)),
true,
))
}
'=' | '!' => {
self.i += 1;
let body = self.group_body(open, saved)?;
Ok((
Some(mk(
NodeKind::Look {
ahead: true,
negate: c == '!',
node: Box::new(body),
},
open,
self.i,
)),
true,
))
}
'<' if matches!(self.at(1), Some('=') | Some('!')) => {
let negate = self.at(1) == Some('!');
self.i += 2;
let body = self.group_body(open, saved)?;
Ok((
Some(mk(
NodeKind::Look {
ahead: false,
negate,
node: Box::new(body),
},
open,
self.i,
)),
true,
))
}
'<' => {
if self.at(1) == Some('*') {
return err(
ErrorCode::U007,
open,
"non-atomic lookbehind is not supported",
);
}
self.i += 1;
self.named(open, '>', saved)
}
'\'' => {
self.i += 1;
self.named(open, '\'', saved)
}
'P' => match self.at(1) {
Some('<') => {
self.i += 2;
self.named(open, '>', saved)
}
Some('=') => err(ErrorCode::U001, open, "named backreference (?P=name)"),
Some('>') => err(ErrorCode::U002, open, "subroutine call (?P>name)"),
_ => err(ErrorCode::P001, open, "unrecognized character after (?P"),
},
'>' => err(
ErrorCode::U004,
open,
"atomic group changes matching semantics",
),
'|' => err(ErrorCode::U007, open, "branch reset group is not supported"),
'*' => err(
ErrorCode::U007,
open,
"non-atomic lookaround is not supported",
),
'(' => err(ErrorCode::U003, open, "conditional group"),
'C' => err(ErrorCode::U006, open, "callout"),
'R' | '&' | '+' | '0'..='9' => {
err(ErrorCode::U002, open, "recursion or subroutine call")
}
'-' if self.at(1).is_some_and(|d| d.is_ascii_digit()) => {
err(ErrorCode::U002, open, "relative subroutine call")
}
'#' => {
while let Some(ch) = self.peek() {
self.i += 1;
if ch == ')' {
return Ok((None, false));
}
}
err(ErrorCode::P007, open, "missing ) after (?# comment")
}
_ => self.flags_group(open, saved),
}
}
fn flags_group(&mut self, open: usize, saved: Flags) -> R<(Option<Node>, bool)> {
let (mut on, mut off) = (Flags::none(), Flags::none());
let mut neg = false;
let mut reset = false;
if self.eat('^') {
reset = true;
}
loop {
let Some(c) = self.peek() else {
return err(ErrorCode::P007, open, "unclosed group");
};
match c {
')' | ':' => break,
'-' if !neg && !reset => {
neg = true;
self.i += 1;
}
'i' | 'm' | 's' | 'x' => {
let t = if neg { &mut off } else { &mut on };
match c {
'i' => t.i = true,
'm' => t.m = true,
's' => t.s = true,
_ => t.x = true,
}
self.i += 1;
}
'a' => {
return err(
ErrorCode::U007,
open,
"ASCII option settings (?a...) are not yet supported",
)
}
'n' | 'U' | 'J' => {
if c == 'J' && !neg {
self.dup_names = true;
}
self.i += 1;
}
_ => {
return err(
ErrorCode::P001,
open,
"unrecognized character after (? or (?-",
)
}
}
}
let base = if reset {
Flags {
i: false,
m: false,
s: false,
x: false,
..self.cur
}
} else {
self.cur
};
let next = base.union(on).minus(off);
if self.eat(')') {
self.cur = next;
return Ok((None, false));
}
self.i += 1; self.cur = next;
let body = self.group_body(open, saved)?;
Ok((
Some(mk(
NodeKind::Group {
capture: None,
name: None,
node: Box::new(body),
},
open,
self.i,
)),
true,
))
}
fn hex_digits(&mut self, max: usize) -> u32 {
let mut v = 0u32;
for _ in 0..max {
match self.peek().and_then(|c| c.to_digit(16)) {
Some(d) => {
v = v * 16 + d;
self.i += 1;
}
None => break,
}
}
v
}
fn check_cp(&self, v: u32, at: usize) -> R<u32> {
if v > self.max_cp() {
return err(
ErrorCode::P004,
at,
"character code point value is too large",
);
}
if self.utf && (0xD800..0xE000).contains(&v) {
return err(
ErrorCode::P004,
at,
"disallowed Unicode code point (surrogate)",
);
}
Ok(v)
}
fn horizontal(&self) -> Vec<(u32, u32)> {
if self.utf {
vec![
(9, 9),
(32, 32),
(0xA0, 0xA0),
(0x1680, 0x1680),
(0x180E, 0x180E),
(0x2000, 0x200A),
(0x202F, 0x202F),
(0x205F, 0x205F),
(0x3000, 0x3000),
]
} else {
vec![(9, 9), (32, 32), (0xA0, 0xA0)]
}
}
fn vertical(&self) -> Vec<(u32, u32)> {
if self.utf {
vec![(10, 13), (0x85, 0x85), (0x2028, 0x2029)]
} else {
vec![(10, 13), (0x85, 0x85)]
}
}
fn escape(&mut self, in_class: bool) -> R<Esc> {
let bs = self.i;
self.i += 1;
let Some(c) = self.peek() else {
return err(ErrorCode::P004, bs, "\\ at end of pattern");
};
self.i += 1;
if let Some(s) = shorthand(c) {
return Ok(Esc::Short(s));
}
let class_of = |r: Vec<(u32, u32)>, negated: bool| {
Esc::Class(Class {
negated,
items: r.into_iter().map(|(a, b)| ClassItem::Range(a, b)).collect(),
})
};
match c {
'a' => Ok(Esc::Char(7)),
'e' => Ok(Esc::Char(27)),
'f' => Ok(Esc::Char(12)),
'n' => Ok(Esc::Char(10)),
'r' => Ok(Esc::Char(13)),
't' => Ok(Esc::Char(9)),
'h' => Ok(class_of(self.horizontal(), false)),
'H' => Ok(class_of(self.horizontal(), true)),
'v' => Ok(class_of(self.vertical(), false)),
'V' => Ok(class_of(self.vertical(), true)),
'N' if self.peek() == Some('{') => {
err(ErrorCode::U007, bs, "\\N{...} is not supported")
}
'N' if !in_class => Ok(class_of(vec![(10, 10)], true)),
'R' if !in_class => err(ErrorCode::U004, bs, "\\R is an atomic group"),
'K' | 'G' | 'X' | 'C' if !in_class => {
err(ErrorCode::U007, bs, "this escape is not supported")
}
'x' => {
let v = if self.peek() == Some('{') {
let mut j = self.i + 1;
let mut v: u32 = 0;
let mut n = 0;
while let Some(d) = self.c.get(j).and_then(|c| c.to_digit(16)) {
v = v.saturating_mul(16).saturating_add(d);
j += 1;
n += 1;
}
if n == 0 || self.c.get(j) != Some(&'}') {
return err(
ErrorCode::P004,
bs,
"non-hex character in \\x{} (closing brace missing?)",
);
}
self.i = j + 1;
v
} else {
self.hex_digits(2)
};
Ok(Esc::Char(self.check_cp(v, bs)?))
}
'o' if self.peek() == Some('{') => {
let mut j = self.i + 1;
let mut v: u32 = 0;
let mut n = 0;
while let Some(d) = self.c.get(j).and_then(|c| c.to_digit(8)) {
v = v.saturating_mul(8).saturating_add(d);
j += 1;
n += 1;
}
if n == 0 || self.c.get(j) != Some(&'}') {
return err(ErrorCode::P004, bs, "non-octal character in \\o{}");
}
self.i = j + 1;
Ok(Esc::Char(self.check_cp(v, bs)?))
}
'c' => match self.peek() {
Some(x) if x.is_ascii() && (' '..='~').contains(&x) => {
self.i += 1;
Ok(Esc::Char((x.to_ascii_uppercase() as u32) ^ 0x40))
}
_ => err(
ErrorCode::P004,
bs,
"\\c must be followed by a printable ASCII character",
),
},
'p' | 'P' => self.property(c == 'P', bs),
'Q' => {
let mut out = Vec::new();
while let Some(ch) = self.peek() {
if ch == '\\' && self.at(1) == Some('E') {
self.i += 2;
break;
}
out.push(ch as u32);
self.i += 1;
}
Ok(Esc::Quote(out))
}
'E' => Ok(Esc::Nothing),
'g' | 'k' => {
match self.peek() {
Some('<' | '\'') if c == 'g' => err(ErrorCode::U002, bs, "subroutine call"),
Some('<' | '\'' | '{') if c == 'k' => {
err(ErrorCode::U001, bs, "named backreference")
}
Some('{') | Some('-') | Some('+') | Some('0'..='9') => {
err(ErrorCode::U001, bs, "backreference")
}
_ => err(ErrorCode::P004, bs, "a numbered reference must not be zero"),
}
}
'0' => {
let mut v = 0u32;
for _ in 0..2 {
if let Some(d) = self.peek().and_then(|d| d.to_digit(8)) {
v = v * 8 + d;
self.i += 1;
} else {
break;
}
}
Ok(Esc::Char(v))
}
'1'..='9' => {
let (mut n, mut j) = (0u64, self.i - 1);
while let Some(d) = self.c.get(j).and_then(|c| c.to_digit(10)) {
n = n.saturating_mul(10).saturating_add(u64::from(d));
j += 1;
}
if in_class {
if let Some(d) = c.to_digit(8) {
let mut v = d;
for _ in 0..2 {
if let Some(d) = self.peek().and_then(|d| d.to_digit(8)) {
v = v * 8 + d;
self.i += 1;
} else {
break;
}
}
return Ok(Esc::Char(v));
}
return err(ErrorCode::P004, bs, "invalid escape in class");
}
if n < 10 || n <= u64::from(self.total_groups) {
return if n <= u64::from(self.total_groups) {
err(ErrorCode::U001, bs, "backreference")
} else {
err(ErrorCode::P004, bs, "reference to non-existent subpattern")
};
}
let mut v = c.to_digit(8).ok_or_else(|| {
PErr::new(ErrorCode::P004, bs, "reference to non-existent subpattern")
})?;
for _ in 0..2 {
if let Some(d) = self.peek().and_then(|d| d.to_digit(8)) {
v = v * 8 + d;
self.i += 1;
} else {
break;
}
}
Ok(Esc::Char(self.check_cp(v, bs)?))
}
'A' if !in_class => Ok(Esc::Assert(Assertion::TextStart)),
'z' if !in_class => Ok(Esc::Assert(Assertion::TextEnd)),
'Z' if !in_class => Ok(Esc::Assert(Assertion::TextEndOptionalNewline)),
'b' if in_class => Ok(Esc::Char(8)),
'b' => Ok(Esc::Assert(Assertion::WordBoundary)),
'B' if !in_class => Ok(Esc::Assert(Assertion::NotWordBoundary)),
_ if c.is_ascii() && !c.is_ascii_alphanumeric() => Ok(Esc::Char(c as u32)),
_ if !c.is_ascii() => Ok(Esc::Char(c as u32)),
_ => err(ErrorCode::P004, bs, "unrecognized character follows \\"),
}
}
fn property(&mut self, negated: bool, bs: usize) -> R<Esc> {
let name = if self.peek() == Some('{') {
let start = self.i + 1;
let mut j = start;
while self.c.get(j).is_some_and(|&c| c != '}') {
j += 1;
}
if self.c.get(j) != Some(&'}') {
return err(ErrorCode::P004, bs, "malformed \\p or \\P sequence");
}
self.i = j + 1;
self.c[start..j].iter().collect::<String>()
} else if let Some(c) = self.peek() {
self.i += 1;
c.to_string()
} else {
return err(ErrorCode::P004, bs, "malformed \\p or \\P sequence");
};
let mut neg = negated;
let mut body = name.as_str();
if let Some(rest) = body.strip_prefix('^') {
neg = !neg;
body = rest;
}
let unknown = |what: &str| {
let hint = uni::suggest(what);
let tail = if hint.is_empty() {
String::new()
} else {
format!("; did you mean: {}", hint.join(", "))
};
PErr::new(
ErrorCode::P008,
bs,
&format!("unknown property '{what}'{tail}"),
)
};
let (kind, canon) = pcre_property(body).ok_or_else(|| unknown(body))?;
Ok(Esc::Prop(PropertyRef {
negated: neg,
kind,
name: canon,
exact: false,
}))
}
fn class_atom(&mut self) -> R<ClassPart> {
match self.peek() {
Some('\\') => match self.escape(true)? {
Esc::Char(c) => Ok(ClassPart::Ch(c)),
Esc::Short(s) => Ok(ClassPart::Item(vec![ClassItem::Shorthand(s)])),
Esc::Prop(p) => Ok(ClassPart::Item(vec![ClassItem::Property(p)])),
Esc::Class(c) if !c.negated => Ok(ClassPart::Item(c.items)),
Esc::Class(c) => {
let r: Vec<(u32, u32)> = c
.items
.iter()
.filter_map(|i| {
if let ClassItem::Range(a, b) = i {
Some((*a, *b))
} else {
None
}
})
.collect();
Ok(ClassPart::Item(
complement(&r, self.max_cp())
.into_iter()
.map(|(a, b)| ClassItem::Range(a, b))
.collect(),
))
}
Esc::Quote(cs) => Ok(ClassPart::Quote(cs)),
Esc::Nothing => Ok(ClassPart::Quote(Vec::new())),
Esc::Assert(_) => err(ErrorCode::X001, self.i, "assertion inside class"),
},
Some(c) => {
self.i += 1;
Ok(ClassPart::Ch(c as u32))
}
None => err(
ErrorCode::P007,
self.i,
"missing terminating ] for character class",
),
}
}
fn posix(&mut self, open: usize) -> R<Option<Vec<ClassItem>>> {
if self.at(1) != Some(':') {
return Ok(None);
}
let mut j = self.i + 2;
let neg = self.c.get(j) == Some(&'^');
if neg {
j += 1;
}
let start = j;
while self.c.get(j).is_some_and(|c| c.is_ascii_lowercase()) {
j += 1;
}
if self.c.get(j) != Some(&':') || self.c.get(j + 1) != Some(&']') {
return Ok(None);
}
let name: String = self.c[start..j].iter().collect();
let Some(&(_, ascii)) = POSIX_ASCII.iter().find(|(n, _)| *n == name) else {
return err(ErrorCode::P006, open, "unknown POSIX class name");
};
self.i = j + 2;
let ucp = self.cur.u;
let mut items: Vec<ClassItem> = Vec::new();
let prop = |kind, name: &'static str| {
ClassItem::Property(PropertyRef {
negated: false,
kind,
name,
exact: true,
})
};
if ucp {
match name.as_str() {
"alpha" => items.push(prop(PropKind::GeneralCategory, "L")),
"lower" => items.push(prop(PropKind::GeneralCategory, "Ll")),
"upper" => items.push(prop(PropKind::GeneralCategory, "Lu")),
"alnum" => {
items.push(prop(PropKind::GeneralCategory, "L"));
items.push(prop(PropKind::GeneralCategory, "N"));
}
"digit" => items.push(prop(PropKind::GeneralCategory, "Nd")),
"space" => items.push(ClassItem::Shorthand(Shorthand::Space)),
"word" => items.push(ClassItem::Shorthand(Shorthand::Word)),
"blank" => items.extend(
self.horizontal()
.into_iter()
.map(|(a, b)| ClassItem::Range(a, b)),
),
"cntrl" => items.push(prop(PropKind::GeneralCategory, "Cc")),
"ascii" | "xdigit" => {
items.extend(ascii.iter().map(|&(a, b)| ClassItem::Range(a, b)))
}
_ => {
return err(
ErrorCode::U007,
open,
"this POSIX class is not yet supported in UCP mode",
)
}
}
if neg {
return err(
ErrorCode::U007,
open,
"negated POSIX classes are not yet supported in UCP mode",
);
}
} else if neg {
items.extend(
complement(ascii, self.max_cp())
.into_iter()
.map(|(a, b)| ClassItem::Range(a, b)),
);
} else {
items.extend(ascii.iter().map(|&(a, b)| ClassItem::Range(a, b)));
}
Ok(Some(items))
}
fn class(&mut self) -> R<Node> {
let open = self.i;
self.i += 1;
let negated = self.eat('^');
let mut items: Vec<ClassItem> = Vec::new();
let mut first = true;
loop {
let Some(c) = self.peek() else {
return err(
ErrorCode::P007,
open,
"missing terminating ] for character class",
);
};
if c == ']' && !first {
self.i += 1;
break;
}
first = false;
if c == '[' {
if let Some(it) = self.posix(open)? {
items.extend(it);
continue;
}
}
let at = self.i;
match self.class_atom()? {
ClassPart::Item(it) => {
if self.peek() == Some('-') && self.at(1).is_some_and(|n| n != ']') {
return err(ErrorCode::P006, at, "invalid range in character class");
}
items.extend(it)
}
ClassPart::Quote(cs) => {
items.extend(cs.into_iter().map(|c| ClassItem::Range(c, c)))
}
ClassPart::Ch(lo) => {
if self.peek() == Some('-') && self.at(1).is_some_and(|n| n != ']') {
self.i += 1;
let hi = match self.class_atom()? {
ClassPart::Ch(h) => h,
_ => {
return err(ErrorCode::P006, at, "invalid range in character class")
}
};
if hi < lo {
return err(
ErrorCode::P006,
at,
"range out of order in character class",
);
}
items.push(ClassItem::Range(lo, hi));
} else {
items.push(ClassItem::Range(lo, lo));
}
}
}
}
Ok(self.wrap(mk(NodeKind::Class(Class { negated, items }), open, self.i)))
}
}
enum ClassPart {
Ch(u32),
Item(Vec<ClassItem>),
Quote(Vec<u32>),
}
fn loose(s: &str) -> String {
s.chars()
.filter(|c| !matches!(c, '_' | '-' | ' '))
.flat_map(char::to_lowercase)
.collect()
}
fn find_loose<T: Copy>(
name: &str,
candidates: impl Iterator<Item = (&'static str, T)>,
) -> Option<T> {
let want = loose(name);
candidates
.into_iter()
.find(|(n, _)| loose(n) == want)
.map(|(_, v)| v)
}
fn pcre_property(body: &str) -> Option<(PropKind, &'static str)> {
let names = uni::all_names();
if let Some((key, value)) = body.split_once(['=', ':']) {
let sc = find_loose(
value,
names
.iter()
.filter_map(|&n| uni::script_canonical(n).map(|c| (n, c))),
)?;
return match loose(key).as_str() {
"script" | "sc" => Some((PropKind::Script, sc)),
"scriptextensions" | "scx" => Some((PropKind::ScriptExtensions, sc)),
_ => None,
};
}
if body == "L&" {
return Some((PropKind::GeneralCategory, "LC"));
}
if let Some(c) = find_loose(
body,
names
.iter()
.filter_map(|&n| uni::gc_canonical(n).filter(|&c| c == n).map(|c| (n, c))),
) {
return Some((PropKind::GeneralCategory, c));
}
if let Some(c) = find_loose(
body,
names
.iter()
.filter_map(|&n| uni::script_canonical(n).map(|c| (n, c))),
) {
return Some((PropKind::Script, c));
}
find_loose(
body,
names.iter().filter_map(|&n| {
uni::binary_canonical(n)
.filter(|&c| c != "Assigned")
.map(|c| (n, c))
}),
)
.map(|c| (PropKind::Binary, c))
}