From b20e3d360350f0478f10edaab738ea5352980490 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Tue, 18 Feb 2025 21:41:17 +0900 Subject: [PATCH] update: finalize --- src/engine.rs | 61 +++++++++++++++++++++++++++++- src/lib.rs | 2 +- src/main.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 6cd06f3..54d3822 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,10 +1,12 @@ //! 正規表現エンジン - -use std::fmt::{self, Display}; mod codegen; mod evaluator; mod parser; +use crate::helper::DynError; +use std::fmt::{self, Display}; + +/// 命令列 #[derive(Debug)] pub enum Instruction { Char(char), @@ -23,3 +25,58 @@ impl Display for Instruction { } } } + +/// 正規表現をパースしてコード生成し、 +/// ASTと命令列を標準出力に表示。 +/// +/// # 利用例 +/// +/// ``` +/// use regex; +/// regex::print("abc|(de|cd)+"); +/// ``` +/// +/// # 返り値 +/// +/// 入力された正規表現にエラーがあったり、内部的な実装エラーがある場合はErrを返す。 +pub fn print(expr: &str) -> Result<(), DynError> { + println!("expr: {expr}"); + let ast = parser::parse(expr)?; + println!("AST: {:?}", ast); + + println!(); + println!("code:"); + let code = codegen::get_code(&ast)?; + for (n, c) in code.iter().enumerate() { + println!("{:>04}: {c}", n); + } + + Ok(()) +} + +/// 正規表現と文字列をマッチング。 +/// +/// # 利用例 +/// +/// ``` +/// use regex; +/// regex::do_matching("abc|(de|cd)+", "decddede", true); +/// ``` +/// +/// # 引数 +/// +/// exprに正規表現、lineにマッチ対象とする文字列を与える。 +/// is_depthがtrueの場合は深さ優先探索を、falseの場合は幅優先探索を利用。 +/// +/// # 返り値 +/// +/// エラーなく実行でき、かつマッチングに**成功**した場合はOk(true)を返し、 +/// エラーなく実行でき、かつマッチングに**失敗**した場合はOk(false)を返す。 +/// +/// 入力された正規表現にエラーがあったり、内部的な実装エラーがある場合はErrを返す。 +pub fn do_matching(expr: &str, line: &str, is_depth: bool) -> Result<bool, DynError> { + let ast = parser::parse(expr)?; + let code = codegen::get_code(&ast)?; + let line = line.chars().collect::<Vec<char>>(); + Ok(evaluator::eval(&code, &line, is_depth)?) +} diff --git a/src/lib.rs b/src/lib.rs index 47da32c..b39f73c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,4 +12,4 @@ mod engine; mod helper; -// pub use engine::{do_matching, print}; +pub use engine::{do_matching, print}; diff --git a/src/main.rs b/src/main.rs index e7a11a9..4fe2503 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,102 @@ -fn main() { - println!("Hello, world!"); +mod engine; +mod helper; + +use helper::DynError; +use std::{ + env, + fs::File, + io::{BufRead, BufReader}, +}; + +fn main() -> Result<(), DynError> { + let args: Vec<String> = env::args().collect(); + if args.len() <= 2 { + eprintln!("usage: {} regex file", args[0]); + return Err("invalid arguments".into()); + } else { + match_file(&args[1], &args[2])?; + } + + Ok(()) +} + +/// ファイルをオープンし、行ごとにマッチングを行う。 +/// +/// マッチングはそれぞれの行頭から1文字ずつずらして行い、 +/// いずれかにマッチした場合に、その行がマッチしたものとみなす。 +/// +/// たとえば、abcdという文字列があった場合、以下の順にマッチが行われ、 +/// このいずれかにマッチした場合、与えられた正規表現にマッチする行と判定する。 +/// +/// - abcd +/// - bcd +/// - cd +/// - d +fn match_file(expr: &str, file: &str) -> Result<(), DynError> { + let f = File::open(file)?; + let reader = BufReader::new(f); + + engine::print(expr)?; + println!(); + + for line in reader.lines() { + let line = line?; + for (i, _) in line.char_indices() { + if engine::do_matching(expr, &line[i..], true)? { + println!("{line}"); + break; + } + } + } + + Ok(()) +} + +// 単体テスト。プライベート関数もテスト可能 +#[cfg(test)] +mod tests { + use crate::{ + engine::do_matching, + helper::{safe_add, SafeAdd}, + }; + + #[test] + fn test_safe_add() { + let n: usize = 10; + assert_eq!(Some(30), n.safe_add(&20)); + + let n: usize = !0; // 2^64 - 1 (64 bits CPU) + assert_eq!(None, n.safe_add(&1)); + + let mut n: usize = 10; + assert!(safe_add(&mut n, &20, || ()).is_ok()); + + let mut n: usize = !0; + assert!(safe_add(&mut n, &1, || ()).is_err()); + } + + #[test] + fn test_matching() { + // パースエラー + assert!(do_matching("+b", "bbb", true).is_err()); + assert!(do_matching("*b", "bbb", true).is_err()); + assert!(do_matching("|b", "bbb", true).is_err()); + assert!(do_matching("?b", "bbb", true).is_err()); + + // パース成功、マッチ成功 + assert!(do_matching("abc|def", "def", true).unwrap()); + assert!(do_matching("(abc)*", "abcabc", true).unwrap()); + assert!(do_matching("(ab|cd)+", "abcdcd", true).unwrap()); + assert!(do_matching("abc?", "ab", true).unwrap()); + assert!(do_matching("((((a*)*)*)*)", "aaaaaaaaa", true).unwrap()); + assert!(do_matching("(a*)*b", "aaaaaaaaab", true).unwrap()); + assert!(do_matching("(a*)*b", "b", true).unwrap()); + assert!(do_matching("a**b", "aaaaaaaaab", true).unwrap()); + assert!(do_matching("a**b", "b", true).unwrap()); + + // パース成功、マッチ失敗 + assert!(!do_matching("abc|def", "efa", true).unwrap()); + assert!(!do_matching("(ab|cd)+", "", true).unwrap()); + assert!(!do_matching("abc?", "acb", true).unwrap()); + } }