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());
+    }
 }