use std::usize;
use regex;
use Expr;
use Result;
use Error;
use LookAround;
use LookAround::*;
use analyze::Analysis;
use vm::{Insn,Prog};
struct VMBuilder {
prog: Vec<Insn>,
n_saves: usize,
}
impl VMBuilder {
fn new(max_group: usize) -> VMBuilder {
VMBuilder {
prog: Vec::new(),
n_saves: max_group * 2,
}
}
fn build(self) -> Prog {
Prog::new(self.prog, self.n_saves)
}
fn newsave(&mut self) -> usize {
let result = self.n_saves;
self.n_saves += 1;
result
}
fn pc(&self) -> usize {
self.prog.len()
}
fn add(&mut self, insn: Insn) {
self.prog.push(insn);
}
fn set_jmp_target(&mut self, jmp_pc: usize, target: usize) {
match self.prog[jmp_pc] {
Insn::Jmp(ref mut next) => *next = target,
_ => panic!("mutating instruction other than Jmp")
}
}
fn set_split_target(&mut self, jmp_pc: usize, target: usize, second: bool) {
match self.prog[jmp_pc] {
Insn::Split(_, ref mut y) if second => *y = target,
Insn::Split(ref mut x, _) => *x = target,
_ => panic!("mutating instruction other than Split")
}
}
fn set_repeat_target(&mut self, jmp_pc: usize, target: usize) {
match self.prog[jmp_pc] {
Insn::RepeatGr { ref mut next, .. } |
Insn::RepeatNg { ref mut next, .. } |
Insn::RepeatEpsilonGr { ref mut next, .. } |
Insn::RepeatEpsilonNg { ref mut next, .. } => *next = target,
_ => panic!("mutating instruction other than Repeat")
}
}
}
struct Compiler<'a> {
a: &'a Analysis<'a>,
b: VMBuilder,
}
impl<'a> Compiler<'a> {
fn visit(&mut self, ix: usize, hard: bool) -> Result<()> {
let info = &self.a.infos[ix];
if !hard && !info.hard {
return self.compile_delegates(&[ix]);
}
match *info.expr {
Expr::Empty => (),
Expr::Literal{ ref val, casei } => {
if !casei {
try!(self.compile_delegates(&[ix]));
} else {
self.b.add(Insn::Lit(val.clone()));
}
}
Expr::Any { newline: true } => {
self.b.add(Insn::Any);
}
Expr::Any { newline: false } => {
self.b.add(Insn::AnyNoNL);
}
Expr::Concat(_) => {
try!(self.compile_concat(ix, hard));
}
Expr::Alt(_) => {
let mut jmps = Vec::new();
let mut last_pc = usize::MAX;
let mut ix = ix + 1;
loop {
let next = self.a.infos[ix].next_sibling;
let pc = self.b.pc();
if next != 0 {
self.b.add(Insn::Split(pc + 1, usize::MAX));
}
if last_pc != usize::MAX {
self.b.set_split_target(last_pc, pc, true);
}
last_pc = pc;
try!(self.visit(ix, hard));
if next != 0 {
let pc = self.b.pc();
jmps.push(pc);
self.b.add(Insn::Jmp(0));
ix = next;
} else {
break;
}
}
let pc = self.b.pc();
for jmp_pc in jmps {
self.b.set_jmp_target(jmp_pc, pc);
}
}
Expr::Group(_) => {
let group = self.a.infos[ix].start_group;
self.b.add(Insn::Save(group * 2));
try!(self.visit(ix + 1, hard));
self.b.add(Insn::Save(group * 2 + 1));
}
Expr::Repeat { lo, hi, greedy, .. } => {
try!(self.compile_repeat(lo, hi, greedy, ix, hard));
}
Expr::LookAround(_, la) if la == LookAhead || la == LookBehind => {
let save = self.b.newsave();
self.b.add(Insn::Save(save));
try!(self.compile_lookaround(ix + 1, la));
self.b.add(Insn::Restore(save));
}
Expr::LookAround(_, la) => { let pc = self.b.pc();
self.b.add(Insn::Split(pc + 1, usize::MAX));
try!(self.compile_lookaround(ix + 1, la));
self.b.add(Insn::DoubleFail);
let next_pc = self.b.pc();
self.b.set_split_target(pc, next_pc, true);
}
Expr::Backref(group) => {
self.b.add(Insn::Backref(group * 2));
}
Expr::Delegate { .. } | Expr::StartText | Expr::EndText
| Expr::StartLine | Expr::EndLine => {
try!(self.compile_delegates(&[ix]));
}
}
Ok(())
}
fn compile_concat(&mut self, ix: usize, hard: bool) -> Result<()> {
let mut children = Vec::new();
let mut child_ix = ix + 1;
loop {
children.push(child_ix);
child_ix = self.a.infos[child_ix].next_sibling;
if child_ix == 0 {
break;
}
}
let mut prefix_end = 0;
for &child_ix in &children {
let info = &self.a.infos[child_ix];
if !info.const_size || info.hard {
break;
}
prefix_end += 1;
}
let mut suffix_begin = children.len();
if !hard {
for &child_ix in children[prefix_end..].iter().rev() {
if self.a.infos[child_ix].hard {
break;
}
suffix_begin -= 1;
}
}
try!(self.compile_delegates(&children[..prefix_end]));
if prefix_end < suffix_begin {
for &child_ix in &children[prefix_end..suffix_begin - 1] {
try!(self.visit(child_ix, true));
}
try!(self.visit(children[suffix_begin - 1], hard));
}
self.compile_delegates(&children[suffix_begin..])
}
fn compile_repeat(&mut self, lo: usize, hi: usize, greedy: bool,
ix: usize, hard: bool) -> Result<()> {
if lo == 0 && hi == 1 {
let pc = self.b.pc();
self.b.add(Insn::Split(pc + 1, pc + 1));
try!(self.visit(ix + 1, hard));
let next_pc = self.b.pc();
self.b.set_split_target(pc, next_pc, greedy);
return Ok(());
}
let hard = hard | self.a.infos[ix].hard;
if hi == usize::MAX && self.a.infos[ix + 1].min_size == 0 {
let repeat = self.b.newsave();
let check = self.b.newsave();
self.b.add(Insn::Save0(repeat));
let pc = self.b.pc();
if greedy {
self.b.add(Insn::RepeatEpsilonGr{ lo: lo, next: usize::MAX,
repeat: repeat, check: check });
} else {
self.b.add(Insn::RepeatEpsilonNg{ lo: lo, next: usize::MAX,
repeat: repeat, check: check });
}
try!(self.visit(ix + 1, hard));
self.b.add(Insn::Jmp(pc));
let next_pc = self.b.pc();
self.b.set_repeat_target(pc, next_pc);
} else if lo == 0 && hi == usize::MAX {
let pc = self.b.pc();
self.b.add(Insn::Split(pc + 1, pc + 1));
try!(self.visit(ix + 1, hard));
self.b.add(Insn::Jmp(pc));
let next_pc = self.b.pc();
self.b.set_split_target(pc, next_pc, greedy);
} else if lo == 1 && hi == usize::MAX {
let pc = self.b.pc();
try!(self.visit(ix + 1, hard));
let next = self.b.pc() + 1;
let (x, y) = if greedy { (pc, next) } else { (next, pc) };
self.b.add(Insn::Split(x, y));
} else {
let repeat = self.b.newsave();
self.b.add(Insn::Save0(repeat));
let pc = self.b.pc();
if greedy {
self.b.add(Insn::RepeatGr{ lo: lo, hi: hi, next: usize::MAX,
repeat: repeat });
} else {
self.b.add(Insn::RepeatNg{ lo: lo, hi: hi, next: usize::MAX,
repeat: repeat });
}
try!(self.visit(ix + 1, hard));
self.b.add(Insn::Jmp(pc));
let next_pc = self.b.pc();
self.b.set_repeat_target(pc, next_pc);
}
Ok(())
}
fn compile_lookaround(&mut self, ix: usize, la: LookAround) -> Result<()> {
if la == LookBehind || la == LookBehindNeg {
let child_info = &self.a.infos[ix];
if !child_info.const_size {
return Err(Error::LookBehindNotConst);
}
self.b.add(Insn::GoBack(child_info.min_size));
}
self.visit(ix, false)
}
fn compile_delegates(&mut self, ixs: &[usize]) -> Result<()> {
if ixs.is_empty() {
return Ok(());
}
if ixs.iter().all(|&ix| self.a.is_literal(ix)) {
let mut val = String::new();
for &ix in ixs {
self.a.push_literal(ix, &mut val);
}
self.b.add(Insn::Lit(val));
return Ok(());
}
let mut annotated = String::new();
annotated.push('^');
let mut min_size = 0;
let mut const_size = true;
let mut looks_left = false;
for &ix in ixs {
let info = &self.a.infos[ix];
let expr = info.expr;
looks_left |= info.looks_left && min_size == 0;
min_size += info.min_size;
const_size &= info.const_size;
expr.to_str(&mut annotated, 0);
}
let start_group = self.a.infos[ixs[0]].start_group;
let end_group = self.a.infos[ixs[ixs.len() - 1]].end_group;
self.make_delegate(&annotated, min_size, const_size, looks_left,
start_group, end_group)
}
fn make_delegate(&mut self, inner_re: &str,
min_size: usize, const_size: bool, looks_left: bool,
start_group: usize, end_group: usize) -> Result<()> {
let compiled = try!(compile_inner(inner_re));
if looks_left {
let inner1 = ["^(?s:.)(?:", &inner_re[1..], ")"].concat();
let compiled1 = try!(compile_inner(&inner1));
self.b.add(Insn::Delegate {
inner: Box::new(compiled),
inner1: Some(Box::new(compiled1)),
start_group: start_group,
end_group: end_group,
});
} else if const_size && start_group == end_group {
let size = min_size;
self.b.add(Insn::DelegateSized(Box::new(compiled), size));
} else {
self.b.add(Insn::Delegate {
inner: Box::new(compiled),
inner1: None,
start_group: start_group,
end_group: end_group,
});
}
Ok(())
}
}
pub fn compile_inner(inner_re: &str) -> Result<regex::Regex> {
regex::Regex::new(inner_re).map_err(Error::InnerError)
}
pub fn compile(analysis: &Analysis) -> Result<Prog> {
let mut c = Compiler {
a: analysis,
b: VMBuilder::new(analysis.group_ix),
};
try!(c.visit(0, false));
c.b.add(Insn::End);
Ok(c.b.build())
}