diff --git a/src/engine.rs b/src/engine.rs index d0e0b30..6cd06f3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,4 +1,25 @@ //! 正規表現エンジン -// mod codegen; -// mod evaluator; -mod parser; \ No newline at end of file + +use std::fmt::{self, Display}; +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), + } + } +} diff --git a/src/engine/codegen.rs b/src/engine/codegen.rs new file mode 100644 index 0000000..6ef088f --- /dev/null +++ b/src/engine/codegen.rs @@ -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) + } +} diff --git a/src/engine/evaluator.rs b/src/engine/evaluator.rs new file mode 100644 index 0000000..9ad5105 --- /dev/null +++ b/src/engine/evaluator.rs @@ -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)?; + } + } +} diff --git a/src/helper.rs b/src/helper.rs new file mode 100644 index 0000000..e88163b --- /dev/null +++ b/src/helper.rs @@ -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>; diff --git a/src/lib.rs b/src/lib.rs index 46370bf..47da32c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,6 @@ //! regex::print(expr); // 正規表現のASTと命令列を表示 //! ``` mod engine; -// mod helper; +mod helper; -// pub use engine::{do_matching, print}; \ No newline at end of file +// pub use engine::{do_matching, print};