diff --git a/src/engine.rs b/src/engine.rs new file mode 100644 index 0000000..d0e0b30 --- /dev/null +++ b/src/engine.rs @@ -0,0 +1,4 @@ +//! 正規表現エンジン +// mod codegen; +// mod evaluator; +mod parser; \ No newline at end of file diff --git a/src/engine/parser.rs b/src/engine/parser.rs new file mode 100644 index 0000000..76d7ca0 --- /dev/null +++ b/src/engine/parser.rs @@ -0,0 +1,199 @@ +//! 正規表現の式をパースし、抽象構文木に変換 +use std::{ + error::Error, + fmt::{self, Display}, + mem::take, +}; + +/// パースエラーを表すための型 +#[derive(Debug)] +pub enum ParseError { + InvalidEscape(usize, char), // 誤ったエスケープシーケンス + InvalidRightParen(usize), // 左開き括弧無し + NoPrev(usize), // +、|、*、?の前に式がない + NoRightParen, // 右閉じ括弧無し + Empty, // 空のパターン +} + +/// パースエラーを表示するために、Displayトレイトを実装 +impl Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseError::InvalidEscape(pos, c) => { + write!(f, "ParseError: invalid escape: pos = {pos}, char = '{c}'") + } + ParseError::InvalidRightParen(pos) => { + write!(f, "ParseError: invalid right parenthesis: pos = {pos}") + } + ParseError::NoPrev(pos) => { + write!(f, "ParseError: no previous expression: pos = {pos}") + } + ParseError::NoRightParen => { + write!(f, "ParseError: no right parenthesis") + } + ParseError::Empty => write!(f, "ParseError: empty expression"), + } + } +} + +impl Error for ParseError {} // エラー用に、Errorトレイトを実装 + +/// 抽象構文木を表現するための型 +#[derive(Debug)] +pub enum AST { + Char(char), + Plus(Box<AST>), + Star(Box<AST>), + Question(Box<AST>), + Or(Box<AST>, Box<AST>), + Seq(Vec<AST>), +} + +/// parse_plus_star_question関数で利用するための列挙型 +enum PSQ { + Plus, + Star, + Question, +} + +/// 正規表現を抽象構文木に変換 +pub fn parse(expr: &str) -> Result<AST, ParseError> { + // 内部状態を表現するための型 + // Char状態 : 文字列処理中 + // Escape状態 : エスケープシーケンス処理中 + enum ParseState { + Char, + Escape, + } + + let mut seq = Vec::new(); // 現在のSeqのコンテキスト + let mut seq_or = Vec::new(); // 現在のOrのコンテキスト + let mut stack = Vec::new(); // コンテキストのスタック + let mut state = ParseState::Char; // 現在の状態 + + for (i, c) in expr.chars().enumerate() { + match &state { + ParseState::Char => { + match c { + '+' => parse_plus_star_question(&mut seq, PSQ::Plus, i)?, + '*' => parse_plus_star_question(&mut seq, PSQ::Star, i)?, + '?' => parse_plus_star_question(&mut seq, PSQ::Question, i)?, + '(' => { + // 現在のコンテキストをスタックに追加し、 + // 現在のコンテキストを空の状態にする + let prev = take(&mut seq); + let prev_or = take(&mut seq_or); + stack.push((prev, prev_or)); + } + ')' => { + // 現在のコンテキストをスタックからポップ + if let Some((mut prev, prev_or)) = stack.pop() { + // "()"のように式が空の場合はpushしない + if !seq.is_empty() { + seq_or.push(AST::Seq(seq)); + } + + // Orを生成 + if let Some(ast) = fold_or(seq_or) { + prev.push(ast); + } + + // 以前のコンテキストを、現在のコンテキストにする + seq = prev; + seq_or = prev_or; + } else { + // "abc)"のように、開き括弧がないのに閉じ括弧がある場合はエラー + return Err(ParseError::InvalidRightParen(i)); + } + } + '|' => { + if seq.is_empty() { + // "||", "(|abc)"などと、式が空の場合はエラー + return Err(ParseError::NoPrev(i)); + } else { + let prev = take(&mut seq); + seq_or.push(AST::Seq(prev)); + } + } + '\\' => state = ParseState::Escape, + _ => seq.push(AST::Char(c)), + }; + } + ParseState::Escape => { + // エスケープシーケンス処理 + let ast = parse_escape(i, c)?; + seq.push(ast); + state = ParseState::Char; + } + } + } + + // 閉じ括弧が足りない場合はエラー + if !stack.is_empty() { + return Err(ParseError::NoRightParen); + } + + // "()"のように式が空の場合はpushしない + if !seq.is_empty() { + seq_or.push(AST::Seq(seq)); + } + + // Orを生成し、成功した場合はそれを返す + if let Some(ast) = fold_or(seq_or) { + Ok(ast) + } else { + Err(ParseError::Empty) + } +} + +/// +、*、?をASTに変換 +/// +/// 後置記法で、+、*、?の前にパターンがない場合はエラー +/// +/// 例 : *ab、abc|+などはエラー +fn parse_plus_star_question( + seq: &mut Vec<AST>, + ast_type: PSQ, + pos: usize, +) -> Result<(), ParseError> { + if let Some(prev) = seq.pop() { + let ast = match ast_type { + PSQ::Plus => AST::Plus(Box::new(prev)), + PSQ::Star => AST::Star(Box::new(prev)), + PSQ::Question => AST::Question(Box::new(prev)), + }; + seq.push(ast); + Ok(()) + } else { + Err(ParseError::NoPrev(pos)) + } +} + +/// 特殊文字のエスケープ +fn parse_escape(pos: usize, c: char) -> Result<AST, ParseError> { + match c { + '\\' | '(' | ')' | '|' | '+' | '*' | '?' => Ok(AST::Char(c)), + _ => { + let err = ParseError::InvalidEscape(pos, c); + Err(err) + } + } +} + +/// orで結合された複数の式をASTに変換 +/// +/// たとえば、abc|def|ghi は、AST::Or("abc", AST::Or("def", "ghi"))というASTとなる +fn fold_or(mut seq_or: Vec<AST>) -> Option<AST> { + if seq_or.len() > 1 { + // seq_orの要素が複数ある場合は、Orで式を結合 + let mut ast = seq_or.pop().unwrap(); + seq_or.reverse(); + for s in seq_or { + ast = AST::Or(Box::new(s), Box::new(ast)); + } + Some(ast) + } else { + // seq_orの要素が一つのみの場合は、Orではなく、最初の値を返す + seq_or.pop() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..46370bf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +//! # 正規表現エンジン用クレート。 +//! +//! ## 利用例 +//! +//! ``` +//! use regex; +//! let expr = "a(bc)+|c(def)*"; // 正規表現 +//! let line = "cdefdefdef"; // マッチ対象文字列 +//! regex::do_matching(expr, line, true); // 幅優先探索でマッチング +//! regex::print(expr); // 正規表現のASTと命令列を表示 +//! ``` +mod engine; +// mod helper; + +// pub use engine::{do_matching, print}; \ No newline at end of file