update: finalize

This commit is contained in:
Akira Tempaku 2025-02-18 21:41:17 +09:00
parent b356784431
commit b20e3d3603
Signed by: paku
GPG key ID: 5B4E8402BCC50607
3 changed files with 161 additions and 5 deletions

View file

@ -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)?)
}

View file

@ -12,4 +12,4 @@
mod engine;
mod helper;
// pub use engine::{do_matching, print};
pub use engine::{do_matching, print};

View file

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