diff --git a/src/engine.rs b/src/engine.rs index f77b475..6cd06f3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display}; mod codegen; -// mod evaluator; +mod evaluator; mod parser; #[derive(Debug)] 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)?; + } + } +}