extern crate alloc;
use alloc::vec::Vec;
use super::ast::CharClass;
use super::opcode::{Inst, Program};
use super::syntax::Options;
#[derive(Clone, Debug)]
pub struct ReqLit {
pub bytes: Vec<u8>,
pub min_dist: u32,
pub max_dist: Option<u32>,
pub run_class: Option<u16>,
}
#[derive(Clone, Copy)]
struct Dist {
min: u32,
max: Option<u32>,
}
impl Dist {
fn zero() -> Self {
Self {
min: 0,
max: Some(0),
}
}
fn add(self, min: u32, max: Option<u32>) -> Self {
Self {
min: self.min.saturating_add(min),
max: match (self.max, max) {
(Some(a), Some(b)) => Some(a.saturating_add(b)),
_ => None,
},
}
}
}
fn class_admits_byte(prog: &Program, class_pc: usize, b: u8) -> bool {
match prog.class_plans.get(class_pc).copied().flatten() {
Some(plan) => {
if b >= 0x80 {
return true;
}
plan.hit(u32::from(b))
}
None => true,
}
}
fn class_of(prog: &Program, pc: usize) -> Option<&CharClass> {
match prog.insts.get(pc) {
Some(Inst::Class { class }) => Some(class),
_ => None,
}
}
pub fn required_literal(prog: &Program, ascii_ok: bool, options: Options) -> Option<ReqLit> {
if !ascii_ok {
return None;
}
let mut cur = options;
let mut opt_stack: Vec<Options> = Vec::new();
let mut best: Option<ReqLit> = None;
let mut dist = Dist::zero();
let mut pc = 0usize;
let mut fuel = 4096u32;
let mut pending_run: Option<u16> = None;
loop {
if fuel == 0 {
break;
}
fuel -= 1;
let inst = match prog.insts.get(pc) {
Some(i) => i,
None => break,
};
match inst {
Inst::Nop
| Inst::Save(_)
| Inst::Keep
| Inst::Assert(_)
| Inst::Callout { .. } => pc += 1,
Inst::PushOptions(set, clear) => {
opt_stack.push(cur);
cur = cur.union(*set).difference(*clear);
pc += 1;
}
Inst::PopOptions => {
if let Some(o) = opt_stack.pop() {
cur = o;
}
pc += 1;
}
Inst::Jump(j) => {
let j = *j as usize;
if j <= pc {
break;
}
pc = j;
}
Inst::Atomic { body, .. } => pc = *body as usize,
Inst::Look {
body,
after,
behind,
negative,
} => {
if !*behind && !*negative && !cur.contains(Options::IGNORECASE) {
match prog.insts.get(*body as usize) {
Some(Inst::Char(c)) if *c <= 0x7F => {
consider(&mut best, &[*c as u8], dist, pending_run, prog);
}
Some(Inst::Literal(v)) if v.iter().all(|c| *c <= 0x7F) => {
let bytes: Vec<u8> = v.iter().map(|c| *c as u8).collect();
consider(&mut best, &bytes, dist, pending_run, prog);
}
_ => {}
}
}
pc = *after as usize;
}
Inst::Char(c) => {
if *c > 0x7F {
break;
}
let bytes = alloc::vec![*c as u8];
if !cur.contains(Options::IGNORECASE) {
consider(&mut best, &bytes, dist, pending_run, prog);
}
dist = dist.add(1, Some(1));
pending_run = None;
pc += 1;
}
Inst::Literal(v) => {
if v.iter().any(|c| *c > 0x7F) {
break;
}
let bytes: Vec<u8> = v.iter().map(|c| *c as u8).collect();
let n = bytes.len() as u32;
if !cur.contains(Options::IGNORECASE) {
consider(&mut best, &bytes, dist, pending_run, prog);
}
dist = dist.add(n, Some(n));
pending_run = None;
pc += 1;
}
Inst::Class { .. } | Inst::Any { .. } | Inst::SuperAny | Inst::TextSegment => {
dist = dist.add(1, None);
pending_run = None;
pc += 1;
}
Inst::GeneralNewline => {
dist = dist.add(1, None);
pending_run = None;
pc += 1;
}
Inst::Repeat {
body,
after,
min,
max,
..
} => {
let (body, after) = (*body as usize, *after as usize);
let (rmin, rmax) = (*min, *max);
let unit = match prog.insts.get(body) {
Some(Inst::Char(_)) | Some(Inst::Class { .. }) => Some(1u32),
Some(Inst::Literal(v)) => Some(v.len() as u32),
_ => None,
};
match unit {
Some(w) => {
dist = dist.add(
rmin.saturating_mul(w),
rmax.map(|m| m.saturating_mul(w)),
);
}
None => {
dist = dist.add(rmin, None);
}
}
pending_run = if rmax.is_none() && rmin >= 1 && matches!(prog.insts.get(body), Some(Inst::Class { .. })) {
Some(body as u16)
} else {
None
};
pc = after;
}
_ => break,
}
}
best
}
fn consider(
best: &mut Option<ReqLit>,
bytes: &[u8],
dist: Dist,
pending_run: Option<u16>,
prog: &Program,
) {
if bytes.is_empty() {
return;
}
let run_class = pending_run.filter(|pc| !class_admits_byte(prog, *pc as usize, bytes[0]));
if dist.max.is_none() && run_class.is_none() {
return;
}
let cand = ReqLit {
bytes: bytes.to_vec(),
min_dist: dist.min,
max_dist: dist.max,
run_class,
};
let better = match best {
None => true,
Some(b) => {
(cand.bytes.len(), cand.run_class.is_some(), cand.max_dist.is_some())
> (b.bytes.len(), b.run_class.is_some(), b.max_dist.is_some())
}
};
if better {
*best = Some(cand);
}
}
#[inline]
pub fn find_bytes(hay: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || needle.len() > hay.len() {
return None;
}
let first = needle[0];
if needle.len() == 1 {
return hay.iter().position(|&b| b == first);
}
let last = hay.len() - needle.len();
let mut i = 0usize;
while i <= last {
match hay[i..=last].iter().position(|&b| b == first) {
Some(off) => {
let p = i + off;
if &hay[p..p + needle.len()] == needle {
return Some(p);
}
i = p + 1;
}
None => return None,
}
}
None
}
pub fn class_run_start(
prog: &Program,
class_pc: usize,
hay: &[u8],
floor: usize,
at: usize,
) -> usize {
if class_of(prog, class_pc).is_none() {
return floor;
}
let mut s = at;
while s > floor {
let b = hay[s - 1];
if !class_admits_byte(prog, class_pc, b) {
break;
}
s -= 1;
}
s
}