Compare commits
2 commits
dc3f497fb7
...
b356784431
Author | SHA1 | Date | |
---|---|---|---|
b356784431 | |||
e25540d60c |
5 changed files with 427 additions and 5 deletions
|
@ -1,4 +1,25 @@
|
||||||
//! 正規表現エンジン
|
//! 正規表現エンジン
|
||||||
// mod codegen;
|
|
||||||
// mod evaluator;
|
use std::fmt::{self, Display};
|
||||||
mod parser;
|
mod codegen;
|
||||||
|
mod evaluator;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Instruction {
|
||||||
|
Char(char),
|
||||||
|
Match,
|
||||||
|
Jump(usize),
|
||||||
|
Split(usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Instruction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Instruction::Char(c) => write!(f, "char {}", c),
|
||||||
|
Instruction::Match => write!(f, "match"),
|
||||||
|
Instruction::Jump(addr) => write!(f, "jump {:>04}", addr),
|
||||||
|
Instruction::Split(addr1, addr2) => write!(f, "split {:>04}, {:>04}", addr1, addr2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
226
src/engine/codegen.rs
Normal file
226
src/engine/codegen.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
//! ASTからコード生成を行う
|
||||||
|
use super::{parser::AST, Instruction};
|
||||||
|
use crate::helper::safe_add;
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Display},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// コード生成エラーを表す型
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CodeGenError {
|
||||||
|
PCOverFlow,
|
||||||
|
FailStar,
|
||||||
|
FailOr,
|
||||||
|
FailQuestion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CodeGenError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "CodeGenError: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for CodeGenError {}
|
||||||
|
|
||||||
|
/// コード生成器
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct Generator {
|
||||||
|
pc: usize,
|
||||||
|
insts: Vec<Instruction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// コード生成を行う関数
|
||||||
|
pub fn get_code(ast: &AST) -> Result<Vec<Instruction>, CodeGenError> {
|
||||||
|
let mut generator = Generator::default();
|
||||||
|
generator.gen_code(ast)?;
|
||||||
|
Ok(generator.insts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// コード生成器のメソッド定義
|
||||||
|
impl Generator {
|
||||||
|
/// コード生成を行う関数の入り口
|
||||||
|
fn gen_code(&mut self, ast: &AST) -> Result<(), CodeGenError> {
|
||||||
|
self.gen_expr(ast)?;
|
||||||
|
self.inc_pc()?;
|
||||||
|
self.insts.push(Instruction::Match);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ASTをパターン分けしコード生成を行う関数
|
||||||
|
fn gen_expr(&mut self, ast: &AST) -> Result<(), CodeGenError> {
|
||||||
|
match ast {
|
||||||
|
AST::Char(c) => self.gen_char(*c)?,
|
||||||
|
AST::Or(e1, e2) => self.gen_or(e1, e2)?,
|
||||||
|
AST::Plus(e) => self.gen_plus(e)?,
|
||||||
|
AST::Star(e1) => {
|
||||||
|
match &**e1 {
|
||||||
|
// `(a*)*`のように`Star`が二重となっている場合にスタックオーバーフローする問題を回避するため、
|
||||||
|
// このような`(((r*)*)*...*)*`を再帰的に処理して1つの`r*`へと変換する。
|
||||||
|
AST::Star(_) => self.gen_expr(&e1)?,
|
||||||
|
AST::Seq(e2) if e2.len() == 1 => {
|
||||||
|
if let Some(e3 @ AST::Star(_)) = e2.get(0) {
|
||||||
|
self.gen_expr(e3)?
|
||||||
|
} else {
|
||||||
|
self.gen_star(e1)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => self.gen_star(&e)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AST::Question(e) => self.gen_question(e)?,
|
||||||
|
AST::Seq(v) => self.gen_seq(v)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// char命令生成関数
|
||||||
|
fn gen_char(&mut self, c: char) -> Result<(), CodeGenError> {
|
||||||
|
let inst = Instruction::Char(c);
|
||||||
|
self.insts.push(inst);
|
||||||
|
self.inc_pc()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OR演算子のコード生成器。
|
||||||
|
///
|
||||||
|
/// 以下のようなコードを生成。
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// split L1, L2
|
||||||
|
/// L1: e1のコード
|
||||||
|
/// jmp L3
|
||||||
|
/// L2: e2のコード
|
||||||
|
/// L3:
|
||||||
|
/// ```
|
||||||
|
fn gen_or(&mut self, e1: &AST, e2: &AST) -> Result<(), CodeGenError> {
|
||||||
|
// split L1, L2
|
||||||
|
let split_addr = self.pc;
|
||||||
|
self.inc_pc()?;
|
||||||
|
let split = Instruction::Split(self.pc, 0); // L1 = self.pc。L2は仮に0と設定
|
||||||
|
self.insts.push(split);
|
||||||
|
|
||||||
|
// L1: e1のコード
|
||||||
|
self.gen_expr(e1)?;
|
||||||
|
|
||||||
|
// jmp L3
|
||||||
|
let jmp_addr = self.pc;
|
||||||
|
self.insts.push(Instruction::Jump(0)); // L3を仮に0と設定
|
||||||
|
|
||||||
|
// L2の値を設定
|
||||||
|
self.inc_pc()?;
|
||||||
|
if let Some(Instruction::Split(_, l2)) = self.insts.get_mut(split_addr) {
|
||||||
|
*l2 = self.pc;
|
||||||
|
} else {
|
||||||
|
return Err(CodeGenError::FailOr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// L2: e2のコード
|
||||||
|
self.gen_expr(e2)?;
|
||||||
|
|
||||||
|
// L3の値を設定
|
||||||
|
if let Some(Instruction::Jump(l3)) = self.insts.get_mut(jmp_addr) {
|
||||||
|
*l3 = self.pc;
|
||||||
|
} else {
|
||||||
|
return Err(CodeGenError::FailOr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ?限量子のコード生成器。
|
||||||
|
///
|
||||||
|
/// 以下のようなコードを生成
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// split L1, L2
|
||||||
|
/// L1: eのコード
|
||||||
|
/// L2:
|
||||||
|
/// ```
|
||||||
|
fn gen_question(&mut self, e: &AST) -> Result<(), CodeGenError> {
|
||||||
|
// split L1, L2
|
||||||
|
let split_addr = self.pc;
|
||||||
|
self.inc_pc()?;
|
||||||
|
let split = Instruction::Split(self.pc, 0); // self.pcがL1。L2を仮に0と設定
|
||||||
|
self.insts.push(split);
|
||||||
|
|
||||||
|
// L1: eのコード
|
||||||
|
self.gen_expr(e)?;
|
||||||
|
|
||||||
|
// L2の値を設定
|
||||||
|
if let Some(Instruction::Split(_, l2)) = self.insts.get_mut(split_addr) {
|
||||||
|
*l2 = self.pc;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CodeGenError::FailQuestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 以下のようなコードを生成
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// L1: eのコード
|
||||||
|
/// split L1, L2
|
||||||
|
/// L2:
|
||||||
|
/// ```
|
||||||
|
fn gen_plus(&mut self, e: &AST) -> Result<(), CodeGenError> {
|
||||||
|
// L1: eのコード
|
||||||
|
let l1 = self.pc;
|
||||||
|
self.gen_expr(e)?;
|
||||||
|
|
||||||
|
// split L1, L2
|
||||||
|
self.inc_pc()?;
|
||||||
|
let split = Instruction::Split(l1, self.pc); // self.pcがL2
|
||||||
|
self.insts.push(split);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// *限量子のコード生成器。
|
||||||
|
///
|
||||||
|
/// 以下のようなコードを生成
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// L1: split L2, L3
|
||||||
|
/// L2: eのコード
|
||||||
|
/// jump L1
|
||||||
|
/// L3:
|
||||||
|
/// ```
|
||||||
|
fn gen_star(&mut self, e: &AST) -> Result<(), CodeGenError> {
|
||||||
|
// L1: split L2, L3
|
||||||
|
let l1 = self.pc;
|
||||||
|
self.inc_pc()?;
|
||||||
|
let split = Instruction::Split(self.pc, 0); // self.pcがL2。L3を仮に0と設定
|
||||||
|
self.insts.push(split);
|
||||||
|
|
||||||
|
// L2: eのコード
|
||||||
|
self.gen_expr(e)?;
|
||||||
|
|
||||||
|
// jump L1
|
||||||
|
self.inc_pc()?;
|
||||||
|
self.insts.push(Instruction::Jump(l1));
|
||||||
|
|
||||||
|
// L3の値を設定
|
||||||
|
if let Some(Instruction::Split(_, l3)) = self.insts.get_mut(l1) {
|
||||||
|
*l3 = self.pc;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CodeGenError::FailStar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 連続する正規表現のコード生成
|
||||||
|
fn gen_seq(&mut self, exprs: &[AST]) -> Result<(), CodeGenError> {
|
||||||
|
for e in exprs {
|
||||||
|
self.gen_expr(e)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// プログラムカウンタをインクリメント
|
||||||
|
fn inc_pc(&mut self) -> Result<(), CodeGenError> {
|
||||||
|
safe_add(&mut self.pc, &1, || CodeGenError::PCOverFlow)
|
||||||
|
}
|
||||||
|
}
|
151
src/engine/evaluator.rs
Normal file
151
src/engine/evaluator.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
//! 命令列と入力文字列を受け取り、マッチングを行う
|
||||||
|
use super::Instruction;
|
||||||
|
use crate::helper::safe_add;
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Display},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EvalError {
|
||||||
|
PCOverFlow,
|
||||||
|
SPOverFlow,
|
||||||
|
InvalidPC,
|
||||||
|
InvalidContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EvalError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "CodeGenError: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for EvalError {}
|
||||||
|
|
||||||
|
/// 命令列の評価を行う関数。
|
||||||
|
///
|
||||||
|
/// instが命令列となり、その命令列を用いて入力文字列lineにマッチさせる。
|
||||||
|
/// is_depthがtrueの場合に深さ優先探索を、falseの場合に幅優先探索を行う。
|
||||||
|
///
|
||||||
|
/// 実行時エラーが起きた場合はErrを返す。
|
||||||
|
/// マッチ成功時はOk(true)を、失敗時はOk(false)を返す。
|
||||||
|
pub fn eval(inst: &[Instruction], line: &[char], is_depth: bool) -> Result<bool, EvalError> {
|
||||||
|
if is_depth {
|
||||||
|
eval_depth(inst, line, 0, 0)
|
||||||
|
} else {
|
||||||
|
eval_width(inst, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 深さ優先探索で再帰的にマッチングを行う評価器
|
||||||
|
fn eval_depth(
|
||||||
|
inst: &[Instruction],
|
||||||
|
line: &[char],
|
||||||
|
mut pc: usize,
|
||||||
|
mut sp: usize,
|
||||||
|
) -> Result<bool, EvalError> {
|
||||||
|
loop {
|
||||||
|
let next = if let Some(i) = inst.get(pc) {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
return Err(EvalError::InvalidPC);
|
||||||
|
};
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Instruction::Char(c) => {
|
||||||
|
if let Some(sp_c) = line.get(sp) {
|
||||||
|
if c == sp_c {
|
||||||
|
safe_add(&mut pc, &1, || EvalError::PCOverFlow)?;
|
||||||
|
safe_add(&mut sp, &1, || EvalError::SPOverFlow)?;
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::Match => {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Instruction::Jump(addr) => {
|
||||||
|
pc = *addr;
|
||||||
|
}
|
||||||
|
Instruction::Split(addr1, addr2) => {
|
||||||
|
if eval_depth(inst, line, *addr1, sp)? || eval_depth(inst, line, *addr2, sp)? {
|
||||||
|
return Ok(true);
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_ctx(
|
||||||
|
pc: &mut usize,
|
||||||
|
sp: &mut usize,
|
||||||
|
ctx: &mut VecDeque<(usize, usize)>,
|
||||||
|
) -> Result<(), EvalError> {
|
||||||
|
if let Some((p, s)) = ctx.pop_back() {
|
||||||
|
*pc = p;
|
||||||
|
*sp = s;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(EvalError::InvalidContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 幅優先探索で再帰的にマッチングを行う評価器
|
||||||
|
fn eval_width(inst: &[Instruction], line: &[char]) -> Result<bool, EvalError> {
|
||||||
|
let mut ctx = VecDeque::new();
|
||||||
|
let mut pc = 0;
|
||||||
|
let mut sp = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let next = if let Some(i) = inst.get(pc) {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
return Err(EvalError::InvalidPC);
|
||||||
|
};
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Instruction::Char(c) => {
|
||||||
|
if let Some(sp_c) = line.get(sp) {
|
||||||
|
if c == sp_c {
|
||||||
|
safe_add(&mut pc, &1, || EvalError::PCOverFlow)?;
|
||||||
|
safe_add(&mut sp, &1, || EvalError::SPOverFlow)?;
|
||||||
|
} else {
|
||||||
|
if ctx.is_empty() {
|
||||||
|
return Ok(false);
|
||||||
|
} else {
|
||||||
|
pop_ctx(&mut pc, &mut sp, &mut ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ctx.is_empty() {
|
||||||
|
return Ok(false);
|
||||||
|
} else {
|
||||||
|
pop_ctx(&mut pc, &mut sp, &mut ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::Match => {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Instruction::Jump(addr) => {
|
||||||
|
pc = *addr;
|
||||||
|
}
|
||||||
|
Instruction::Split(addr1, addr2) => {
|
||||||
|
pc = *addr1;
|
||||||
|
ctx.push_back((*addr2, sp));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.is_empty() {
|
||||||
|
ctx.push_back((pc, sp));
|
||||||
|
pop_ctx(&mut pc, &mut sp, &mut ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/helper.rs
Normal file
24
src/helper.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
pub trait SafeAdd: Sized {
|
||||||
|
fn safe_add(&self, n: &Self) -> Option<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SafeAdd for usize {
|
||||||
|
fn safe_add(&self, n: &Self) -> Option<Self> {
|
||||||
|
self.checked_add(*n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn safe_add<T, F, E>(dst: &mut T, src: &T, f: F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
T: SafeAdd,
|
||||||
|
F: Fn() -> E,
|
||||||
|
{
|
||||||
|
if let Some(n) = dst.safe_add(src) {
|
||||||
|
*dst = n;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(f())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DynError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
|
@ -10,6 +10,6 @@
|
||||||
//! regex::print(expr); // 正規表現のASTと命令列を表示
|
//! regex::print(expr); // 正規表現のASTと命令列を表示
|
||||||
//! ```
|
//! ```
|
||||||
mod engine;
|
mod engine;
|
||||||
// mod helper;
|
mod helper;
|
||||||
|
|
||||||
// pub use engine::{do_matching, print};
|
// pub use engine::{do_matching, print};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue