use crate::ast::*;
use crate::dialect::Dialect;
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>,
pub notes: Vec<String>,
}
enum Esc {
Char(u32),
Short(Shorthand),
Prop(PropertyRef),
Assert(Assertion),
Quote(Vec<u32>),
}
struct P<'a> {
c: &'a [char],
i: usize,
rust: bool,
base: Flags,
cur: Flags,
depth: u32,
max_depth: u32,
groups: u32,
names: Vec<String>,
notes: Vec<String>,
}
pub(crate) fn parse(c: &[char], d: Dialect, flags: Flags, lim: &ParseLimits) -> Result<Out, PErr> {
let rust = d == Dialect::Rust;
let mut base = flags;
if rust {
base.u = !flags.a;
}
let mut p = P {
c,
i: 0,
rust,
base,
cur: base,
depth: 0,
max_depth: lim.max_depth,
groups: 0,
names: Vec::new(),
notes: Vec::new(),
};
let ast = p.alt()?;
if p.i < c.len() {
return err(ErrorCode::P007, p.i, "unmatched ')'");
}
Ok(Out {
ast,
flags: base,
groups: p.groups,
names: p.names,
notes: p.notes,
})
}
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: &[(&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)]) -> 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 <= 0x10FFFF {
out.push((next, 0x10FFFF));
}
out
}
fn word_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_'
}
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 skip_x(&mut self) {
if !(self.rust && self.cur.x) {
return;
}
while let Some(ch) = self.peek() {
if ch.is_whitespace() {
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, c.u) == (b.i, b.m, b.s, b.u) {
return n;
}
let on = Flags {
i: c.i && !b.i,
m: c.m && !b.m,
s: c.s && !b.s,
u: c.u && !b.u,
..Flags::none()
};
let off = Flags {
i: b.i && !c.i,
m: b.m && !c.m,
s: b.s && !c.s,
u: b.u && !c.u,
..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 spaces = |mut j: usize| {
while self.rust && self.c.get(j).is_some_and(|c| c.is_whitespace()) {
j += 1;
}
j
};
let digits = |j: usize| {
let mut j = spaces(j);
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, spaces(j))
};
let (lo, j) = digits(self.i + 1);
let lo = lo?;
match self.c.get(j) {
Some('}') => Some((lo, Some(lo), j + 1)),
Some(',') => {
let (hi, k) = digits(j + 1);
(self.c.get(k) == Some(&'}')).then_some((lo, hi, k + 1))
}
_ => None,
}
}
fn term(&mut self) -> R<Option<Node>> {
let start = self.i;
let Some(mut atom) = self.atom()? else {
return Ok(None);
};
loop {
self.skip_x();
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))
}
Some('{') => match self.braces() {
Some((lo, hi, end)) => {
self.i = end;
if self.rust {
if hi.is_some_and(|h| lo > h) {
return err(
ErrorCode::P005,
op_at,
"minimum repeat is greater than maximum",
);
}
if lo > u64::from(u32::MAX)
|| hi.is_some_and(|h| h > u64::from(u32::MAX))
{
return err(
ErrorCode::P005,
op_at,
"repetition count out of range",
);
}
} else if lo > 1000
|| hi.is_some_and(|h| h > 1000)
|| hi.is_some_and(|h| lo > h)
{
return err(
ErrorCode::P005,
op_at,
"invalid repeat count (Go allows at most 1000)",
);
}
(lo, hi)
}
None if self.rust => {
return err(
ErrorCode::P005,
op_at,
"repetition quantifier expects a valid decimal",
);
}
None => break,
},
_ => break,
};
let greedy = !self.eat('?');
atom = mk(
NodeKind::Repeat {
node: Box::new(atom),
min,
max,
greedy,
},
start,
self.i,
);
if !self.rust {
self.skip_x();
let nested = match self.peek() {
Some('*' | '+' | '?') => true,
Some('{') => self.braces().is_some(),
_ => false,
};
if nested {
return err(
ErrorCode::P005,
self.i,
"invalid nested repetition operator",
);
}
break;
}
}
Ok(Some(atom))
}
fn atom(&mut self) -> R<Option<Node>> {
let start = self.i;
let Some(ch) = self.peek() else {
return Ok(None);
};
match ch {
'(' => self.group(),
'[' => Ok(Some(self.class()?)),
'.' => {
self.i += 1;
if self.rust && !self.cur.u {
return err(
ErrorCode::P001,
start,
"'.' can match invalid UTF-8 when Unicode mode is off",
);
}
Ok(Some(self.wrap(mk(NodeKind::Any, start, self.i))))
}
'^' | '$' => {
self.i += 1;
let a = if ch == '^' {
Assertion::LineStart
} else {
Assertion::LineEnd
};
Ok(Some(self.wrap(mk(NodeKind::Assert(a), start, self.i))))
}
'*' | '+' | '?' => err(
ErrorCode::P005,
start,
"missing argument to repetition operator",
),
'{' => {
if self.rust || self.braces().is_some() {
return err(
ErrorCode::P005,
start,
"missing argument to repetition operator",
);
}
self.i += 1;
Ok(Some(self.wrap(mk(
NodeKind::Literal('{' as u32),
start,
self.i,
))))
}
'\\' => {
let e = self.escape(false)?;
let kind = match e {
Esc::Char(c) => NodeKind::Literal(c),
Esc::Short(s) => {
if self.rust
&& !self.cur.u
&& matches!(
s,
Shorthand::NotDigit | Shorthand::NotWord | Shorthand::NotSpace
)
{
return err(
ErrorCode::P001,
start,
"negated class can match invalid UTF-8 when Unicode mode is off",
);
}
NodeKind::Class(Class {
negated: false,
items: vec![ClassItem::Shorthand(s)],
})
}
Esc::Prop(p) => NodeKind::Class(Class {
negated: false,
items: vec![ClassItem::Property(p)],
}),
Esc::Assert(a) => NodeKind::Assert(a),
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 => mk(NodeKind::Empty, start, self.i),
1 => lits
.into_iter()
.next()
.unwrap_or_else(|| mk(NodeKind::Empty, start, self.i)),
_ => mk(NodeKind::Concat(lits), start, self.i),
}));
}
};
Ok(Some(self.wrap(mk(kind, start, self.i))))
}
_ => {
self.i += 1;
Ok(Some(self.wrap(mk(
NodeKind::Literal(ch as u32),
start,
self.i,
))))
}
}
}
fn group_body(&mut self, open: usize, saved: Flags) -> R<Node> {
let n = self.alt()?;
if !self.eat(')') {
return err(ErrorCode::P007, open, "unclosed group");
}
self.cur = saved;
Ok(n)
}
fn group(&mut self) -> R<Option<Node>> {
let open = self.i;
self.i += 1;
let saved = self.cur;
if !self.eat('?') {
self.groups += 1;
let idx = self.groups;
let body = self.group_body(open, saved)?;
let kind = NodeKind::Group {
capture: Some(idx),
name: None,
node: Box::new(body),
};
return Ok(Some(mk(kind, open, self.i)));
}
let named = match (self.peek(), self.at(1)) {
(Some('P'), Some('<')) => Some(2),
(Some('<'), Some(c)) if c != '=' && c != '!' => Some(1),
_ => None,
};
if let Some(skip) = named {
self.i += skip;
let name_at = self.i;
let mut name = String::new();
while let Some(c) = self.peek().filter(|&c| c != '>') {
name.push(c);
self.i += 1;
}
if !self.eat('>') {
return err(ErrorCode::P009, name_at, "unterminated group name");
}
let valid = if self.rust {
name.chars()
.next()
.is_some_and(|c| c.is_alphabetic() || c == '_')
&& name.chars().all(|c| c.is_alphanumeric() || c == '_')
} else {
!name.is_empty() && name.chars().all(word_char)
};
if !valid {
return err(ErrorCode::P009, name_at, "invalid group name");
}
if self.rust && self.names.contains(&name) {
return err(ErrorCode::P009, name_at, "duplicate group name");
}
self.groups += 1;
let idx = self.groups;
self.names.push(name.clone());
let body = self.group_body(open, saved)?;
let kind = NodeKind::Group {
capture: Some(idx),
name: Some(name),
node: Box::new(body),
};
return Ok(Some(mk(kind, open, self.i)));
}
if self.eat(':') {
let body = self.group_body(open, saved)?;
return Ok(Some(mk(
NodeKind::Group {
capture: None,
name: None,
node: Box::new(body),
},
open,
self.i,
)));
}
let allowed = if self.rust { "imsuUxR" } else { "imsU" };
let (mut on, mut off) = (Flags::none(), Flags::none());
let (mut neg, mut since_neg, mut any) = (false, 0usize, false);
let mut seen = String::new();
loop {
let Some(c) = self.peek() else {
return err(ErrorCode::P007, open, "unclosed group");
};
match c {
'-' if !neg => {
neg = true;
since_neg = 0;
self.i += 1;
continue;
}
')' | ':' => break,
_ if allowed.contains(c) => {
if self.rust && seen.contains(c) {
return err(ErrorCode::P003, self.i, "duplicate flag");
}
seen.push(c);
let target = if neg { &mut off } else { &mut on };
match c {
'i' => target.i = true,
'm' => target.m = true,
's' => target.s = true,
'u' => target.u = true,
'x' => target.x = true,
'R' => {
return err(
ErrorCode::U007,
self.i,
"CRLF mode (?R) is not yet supported",
)
}
_ => {} }
if neg {
since_neg += 1;
}
any = true;
self.i += 1;
}
_ => {
return err(
ErrorCode::P001,
open,
"unrecognised group syntax after '(?'",
)
}
}
}
if (neg && since_neg == 0) || (self.rust && !any) {
return err(ErrorCode::P003, self.i, "missing flags");
}
if self.rust
&& (on.i && off.i || on.m && off.m || on.s && off.s || on.u && off.u || on.x && off.x)
{
return err(
ErrorCode::P003,
open,
"a flag cannot be both enabled and disabled",
);
}
let next = self.cur.union(on).minus(off);
if self.eat(')') {
self.cur = next;
self.skip_x();
let nothing = matches!(self.peek(), Some('*' | '+' | '?'))
|| (self.peek() == Some('{') && (self.rust || self.braces().is_some()));
if nothing {
return err(
ErrorCode::P005,
self.i,
"missing argument to repetition operator",
);
}
return Ok(None);
}
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,
)))
}
fn hex_run(&mut self, max: usize) -> Option<u32> {
let mut v = 0u32;
for k in 0..max {
v = v.checked_mul(16)?.checked_add(self.at(k)?.to_digit(16)?)?;
}
self.i += max;
Some(v)
}
fn braced_hex(&mut self, at: usize) -> R<u32> {
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(&'}') || v > 0x10FFFF {
return err(ErrorCode::P004, at, "invalid \\x{...} escape");
}
self.i = j + 1;
Ok(v)
}
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,
"trailing backslash at end of expression",
);
};
self.i += 1;
if let Some(s) = shorthand(c) {
return Ok(Esc::Short(s));
}
let surrogate_check = |v: u32, at: usize| -> R<Esc> {
if (0xD800..0xE000).contains(&v) {
return err(
ErrorCode::U007,
at,
"surrogate code points are not supported",
);
}
Ok(Esc::Char(v))
};
match c {
'a' => Ok(Esc::Char(7)),
'f' => Ok(Esc::Char(12)),
'n' => Ok(Esc::Char(10)),
'r' => Ok(Esc::Char(13)),
't' => Ok(Esc::Char(9)),
'v' => Ok(Esc::Char(11)),
'x' => {
let v = if self.peek() == Some('{') {
self.braced_hex(bs)?
} else {
match self.hex_run(2) {
Some(v) => v,
None => {
return err(ErrorCode::P004, bs, "\\x needs two hex digits or {...}")
}
}
};
if self.rust && !self.cur.u && v >= 0x80 {
return err(
ErrorCode::P004,
bs,
"non-ASCII escape while Unicode mode is off",
);
}
surrogate_check(v, bs)
}
'u' | 'U' if self.rust => {
let width = if c == 'u' { 4 } else { 8 };
let v = if self.peek() == Some('{') {
self.braced_hex(bs)?
} else {
match self.hex_run(width) {
Some(v) if v <= 0x10FFFF => v,
_ => return err(ErrorCode::P004, bs, "invalid Unicode escape"),
}
};
surrogate_check(v, bs)
}
'p' | 'P' => self.property(c == 'P', bs),
'A' | 'z' | 'b' | 'B' | '<' | '>' if in_class => {
err(ErrorCode::P004, bs, "assertion escape inside a class")
}
'A' => Ok(Esc::Assert(Assertion::TextStart)),
'z' => Ok(Esc::Assert(Assertion::TextEnd)),
'b' | 'B' => {
if self.rust && c == 'b' && self.peek() == Some('{') {
let mut j = self.i + 1;
let mut name = String::new();
while let Some(&ch) = self.c.get(j) {
if ch == '}' {
break;
}
name.push(ch);
j += 1;
}
if self.c.get(j) != Some(&'}') {
return err(ErrorCode::P004, bs, "unclosed \\b{...}");
}
self.i = j + 1;
return match name.as_str() {
"start" => Ok(Esc::Assert(Assertion::WordStart)),
"end" => Ok(Esc::Assert(Assertion::WordEnd)),
"start-half" | "end-half" => err(
ErrorCode::U007,
bs,
"half word boundaries are not yet supported",
),
_ => err(ErrorCode::P004, bs, "unknown \\b{...} assertion"),
};
}
Ok(Esc::Assert(if c == 'b' {
Assertion::WordBoundary
} else {
Assertion::NotWordBoundary
}))
}
'<' if self.rust => Ok(Esc::Assert(Assertion::WordStart)),
'>' if self.rust => Ok(Esc::Assert(Assertion::WordEnd)),
'Q' if !self.rust && !in_class => {
let mut out = Vec::new();
while let Some(ch) = self.peek() {
if ch == '\\' && self.at(1) == Some('E') {
self.i += 2;
break;
}
if ch == '\\' && self.at(1) == Some('\\') {
out.push('\\' as u32);
self.i += 2;
continue;
}
out.push(ch as u32);
self.i += 1;
}
Ok(Esc::Quote(out))
}
'0'..='9' => {
if self.rust {
return err(
ErrorCode::P004,
bs,
"backreferences and octal escapes are not supported",
);
}
let octal = |d: Option<char>| d.is_some_and(|d| ('0'..='7').contains(&d));
if c != '0' && !(('1'..='7').contains(&c) && octal(self.peek())) {
return err(ErrorCode::P004, bs, "backreferences are not supported");
}
let mut v = c.to_digit(8).unwrap_or(0);
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))
}
_ if c.is_ascii() && !c.is_ascii_alphanumeric() => Ok(Esc::Char(c as u32)),
_ => err(
ErrorCode::P004,
bs,
"invalid or unsupported escape sequence",
),
}
}
fn property(&mut self, negated: bool, bs: usize) -> R<Esc> {
let (name, brace) = 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{...} escape");
}
self.i = j + 1;
(self.c[start..j].iter().collect::<String>(), true)
} else if let Some(c) = self.peek() {
self.i += 1;
(c.to_string(), false)
} else {
return err(ErrorCode::P004, bs, "\\p needs a property name");
};
let mut neg = negated;
let mut body = name.as_str();
if !self.rust && brace {
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 Unicode class or property '{what}'{tail}"),
)
};
let (kind, canon) = if self.rust {
rust_property(body)
} else {
go_property(body)
}
.ok_or_else(|| unknown(body))?;
if self.rust && !self.cur.u {
return err(
ErrorCode::P004,
bs,
"Unicode properties are not allowed when Unicode mode is off",
);
}
Ok(Esc::Prop(PropertyRef {
negated: neg,
kind,
name: canon,
exact: false,
}))
}
fn class_char(&mut self) -> R<ClassPart> {
match self.peek() {
Some('\\') => Ok(match self.escape(true)? {
Esc::Char(c) => ClassPart::Ch(c),
Esc::Short(s) => ClassPart::Item(ClassItem::Shorthand(s)),
Esc::Prop(p) => ClassPart::Item(ClassItem::Property(p)),
Esc::Assert(_) | Esc::Quote(_) => {
return err(ErrorCode::X001, self.i, "unexpected escape in class")
}
}),
Some(c) => {
self.i += 1;
Ok(ClassPart::Ch(c as u32))
}
None => err(ErrorCode::P007, self.i, "unterminated character class"),
}
}
fn posix(&mut self) -> Option<Vec<(u32, u32)>> {
if self.peek() != Some(':') {
return None;
}
let mut j = self.i + 1;
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 None;
}
let name: String = self.c[start..j].iter().collect();
let ranges = POSIX.iter().find(|(n, _)| *n == name)?.1;
self.i = j + 2;
Some(if neg {
complement(ranges)
} else {
ranges.to_vec()
})
}
fn class(&mut self) -> R<Node> {
let open = self.i;
if self.rust {
let expr = self.rust_bracket(open)?;
let simple = match &expr {
ClassExpr::Bracket { negated, inner } => match &**inner {
ClassExpr::Items(items) => Some(Class {
negated: *negated,
items: items.clone(),
}),
_ => None,
},
_ => None,
};
let kind = match simple {
Some(c) => NodeKind::Class(c),
None => NodeKind::ClassSet(expr),
};
return Ok(self.wrap(mk(kind, 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 closing ]");
};
if c == ']' && !first {
self.i += 1;
break;
}
first = false;
if c == '[' && self.at(1) == Some(':') {
self.i += 1;
if let Some(r) = self.posix() {
items.extend(r.into_iter().map(|(a, b)| ClassItem::Range(a, b)));
continue;
}
if self.c[self.i..].iter().position(|&x| x == ']').is_some()
&& self.looks_like_posix()
{
return err(ErrorCode::P006, open, "invalid character class range");
}
self.i -= 1;
}
let at = self.i;
match self.class_char()? {
ClassPart::Item(it) => items.push(it),
ClassPart::Ch(lo) => {
if self.peek() == Some('-') && self.at(1).is_some_and(|n| n != ']') {
self.i += 1;
let hi = match self.class_char()? {
ClassPart::Ch(h) => h,
ClassPart::Item(_) => {
return err(ErrorCode::P006, at, "invalid character class range")
}
};
if hi < lo {
return err(ErrorCode::P006, at, "invalid character class range");
}
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)))
}
fn looks_like_posix(&self) -> bool {
let mut j = self.i + 1;
if self.c.get(j) == Some(&'^') {
j += 1;
}
while self.c.get(j).is_some_and(|c| c.is_ascii_lowercase()) {
j += 1;
}
self.c.get(j) == Some(&':') && self.c.get(j + 1) == Some(&']')
}
fn rust_bracket(&mut self, open: usize) -> R<ClassExpr> {
self.depth += 1;
if self.depth > self.max_depth {
return err(
ErrorCode::L006,
self.i,
"class nesting exceeds the depth limit",
);
}
self.i += 1; let negated = self.eat('^');
let mut result: Option<ClassExpr> = None;
let mut pending_op: Option<SetOp> = None;
let mut operand: Vec<ClassExpr> = Vec::new();
let mut items: Vec<ClassItem> = Vec::new();
let mut first = true;
let flush = |items: &mut Vec<ClassItem>, operand: &mut Vec<ClassExpr>| {
if !items.is_empty() {
operand.push(ClassExpr::Items(std::mem::take(items)));
}
};
let finish = |operand: &mut Vec<ClassExpr>| -> ClassExpr {
match operand.len() {
1 => operand.remove(0),
_ => ClassExpr::Union(std::mem::take(operand)),
}
};
loop {
self.skip_x();
let Some(c) = self.peek() else {
return err(ErrorCode::P007, open, "unclosed character class");
};
if c == ']' && !first {
self.i += 1;
break;
}
first = false;
let two = (c, self.at(1));
let op = match two {
('&', Some('&')) => Some(SetOp::Intersect),
('-', Some('-')) => Some(SetOp::Difference),
('~', Some('~')) => Some(SetOp::SymmetricDifference),
_ => None,
};
if let Some(op) = op {
self.i += 2;
flush(&mut items, &mut operand);
let lhs = finish(&mut operand);
result = Some(match (result.take(), pending_op) {
(Some(acc), Some(p)) => ClassExpr::Op(p, Box::new(acc), Box::new(lhs)),
_ => lhs,
});
pending_op = Some(op);
continue;
}
if c == '[' {
if self.at(1) == Some(':') {
let save = self.i;
self.i += 1;
if let Some(r) = self.posix() {
items.extend(r.into_iter().map(|(a, b)| ClassItem::Range(a, b)));
continue;
}
self.i = save;
}
flush(&mut items, &mut operand);
operand.push(self.rust_bracket(self.i)?);
continue;
}
let at = self.i;
match self.class_char()? {
ClassPart::Item(it) => {
if self.peek() == Some('-') && self.at(1).is_some_and(|n| n != ']' && n != '-')
{
return err(
ErrorCode::P006,
at,
"a class escape cannot be a range endpoint",
);
}
items.push(it)
}
ClassPart::Ch(lo) => {
if self.peek() == Some('-') && self.at(1).is_some_and(|n| n != ']' && n != '-')
{
self.i += 1;
let hi = match self.class_char()? {
ClassPart::Ch(h) => h,
ClassPart::Item(_) => {
return err(
ErrorCode::P006,
at,
"a class escape cannot be a range endpoint",
)
}
};
if hi < lo {
return err(ErrorCode::P006, at, "range end precedes range start");
}
items.push(ClassItem::Range(lo, hi));
} else {
items.push(ClassItem::Range(lo, lo));
}
}
}
}
flush(&mut items, &mut operand);
let last = finish(&mut operand);
let inner = match (result, pending_op) {
(Some(acc), Some(op)) => ClassExpr::Op(op, Box::new(acc), Box::new(last)),
_ => last,
};
self.depth -= 1;
if !self.cur.u && negated {
return err(
ErrorCode::P001,
open,
"a negated class can match invalid UTF-8 when Unicode mode is off",
);
}
let inner = match inner {
ClassExpr::Union(v) if v.len() == 1 => {
v.into_iter().next().unwrap_or(ClassExpr::Items(Vec::new()))
}
other => other,
};
Ok(ClassExpr::Bracket {
negated,
inner: Box::new(inner),
})
}
}
enum ClassPart {
Ch(u32),
Item(ClassItem),
}
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 go_property(name: &str) -> Option<(PropKind, &'static str)> {
let names = uni::all_names();
if let Some(c) = find_loose(
name,
names
.iter()
.filter_map(|&n| uni::gc_canonical(n).map(|c| (n, c))),
) {
return Some((PropKind::GeneralCategory, c));
}
if let Some(c) = find_loose(
name,
names.iter().filter_map(|&n| {
uni::script_canonical(n)
.filter(|&l| l == n && l != "Unknown")
.map(|c| (n, c))
}),
) {
return Some((PropKind::Script, c));
}
["Any", "ASCII", "Assigned"]
.into_iter()
.find(|b| loose(b) == loose(name))
.map(|b| (PropKind::Binary, b))
}
fn rust_property(body: &str) -> Option<(PropKind, &'static str)> {
let names = uni::all_names();
let gc = |v: &str| {
find_loose(
v,
names
.iter()
.filter_map(|&n| uni::gc_canonical(n).map(|c| (n, c))),
)
};
let sc = |v: &str| {
find_loose(
v,
names
.iter()
.filter_map(|&n| uni::script_canonical(n).map(|c| (n, c))),
)
.filter(|&c| c != "Unknown")
};
let bin = |v: &str| {
find_loose(
v,
names
.iter()
.filter_map(|&n| uni::binary_canonical(n).map(|c| (n, c))),
)
};
if let Some((key, value)) = body.split_once(['=', ':']) {
return match loose(key).as_str() {
"gc" | "generalcategory" | "category" => {
gc(value).map(|c| (PropKind::GeneralCategory, c))
}
"sc" | "script" => sc(value).map(|c| (PropKind::Script, c)),
"scx" | "scriptextensions" => sc(value).map(|c| (PropKind::ScriptExtensions, c)),
_ => None,
};
}
if let Some(c) = bin(body) {
return Some((PropKind::Binary, c));
}
if let Some(c) = gc(body) {
return Some((PropKind::GeneralCategory, c));
}
sc(body).map(|c| (PropKind::Script, c))
}