From 67c08d09cc1b6e89f3039abd4efe8582c8ca50f0 Mon Sep 17 00:00:00 2001 From: shu-kitamura Date: Sun, 8 Feb 2026 12:39:24 +0900 Subject: [PATCH 1/4] chore: update thiserror crate version --- crates/regex-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/regex-cli/Cargo.toml b/crates/regex-cli/Cargo.toml index a58e11f..36576c8 100644 --- a/crates/regex-cli/Cargo.toml +++ b/crates/regex-cli/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true [dependencies] clap = { version = "4.5.11", features = ["derive"] } regex-core = { path = "../regex-core" } -thiserror = "1.0" +thiserror = "2.0.18" [dev-dependencies] tempfile = "3.0" From 0564c446d4261b0072dfea97d6febb801e187e60 Mon Sep 17 00:00:00 2001 From: shu-kitamura Date: Wed, 11 Feb 2026 20:28:07 +0900 Subject: [PATCH 2/4] refactor: parser.rs - move single_char_class() to test as a helper function. - add parse_single_char() in Parser struct. --- crates/regex-core/src/engine/parser.rs | 41 +++++++++++++++----------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/regex-core/src/engine/parser.rs b/crates/regex-core/src/engine/parser.rs index 36693dd..df6f214 100644 --- a/crates/regex-core/src/engine/parser.rs +++ b/crates/regex-core/src/engine/parser.rs @@ -53,9 +53,9 @@ struct Parser { captures: usize, } -/// Parses `regex` and returns its AST representation. -pub fn parse(regex: &str) -> Result { - let mut parser = Parser::new(regex); +/// Parses `pattern` and returns its AST representation. +pub fn parse(pattern: &str) -> Result { + let mut parser = Parser::new(pattern); let ast = parser.parse_expression()?; if parser.peek().is_some() { return Err(ParseError::UnexpectedChar(parser.peek().unwrap())); @@ -65,9 +65,9 @@ pub fn parse(regex: &str) -> Result { impl Parser { /// Creates a parser from a pattern string. - fn new(regex: &str) -> Self { + fn new(pattern: &str) -> Self { Self { - input: regex.chars().collect(), + input: pattern.chars().collect(), pos: 0, captures: 1, } @@ -205,10 +205,7 @@ impl Parser { Some(ch) if Self::is_special_char(ch) => Err(ParseError::UnexpectedChar(ch)), Some(_) => { let ch = self.next().ok_or(ParseError::UnexpectedEnd)?; - Ok(Ast::CharClass(CharClass::new( - vec![CharRange { start: ch, end: ch }], - false, - ))) + Ok(Self::parse_single_char(ch)) } None => Err(ParseError::UnexpectedEnd), } @@ -287,7 +284,7 @@ impl Parser { } Ast::Backreference(num as usize) } - _ => single_char_class(ch), + _ => Self::parse_single_char(ch), }; Ok(ast) } @@ -369,21 +366,29 @@ impl Parser { false } } -} -/// Builds an `Ast::CharClass` representing exactly one literal character. -fn single_char_class(ch: char) -> Ast { - Ast::CharClass(CharClass::new( - vec![CharRange { start: ch, end: ch }], - false, - )) + /// Builds an `Ast::CharClass` that matches exactly one literal character. + fn parse_single_char(ch: char) -> Ast { + Ast::CharClass(CharClass::new( + vec![CharRange { start: ch, end: ch }], + false, + )) + } } #[cfg(test)] mod tests { - use super::{ParseError, Parser, parse, single_char_class}; + use super::{ParseError, Parser, parse}; use crate::engine::ast::{Ast, CharClass, CharRange, Predicate}; + /// helper to build a single-character class AST + fn single_char_class(ch: char) -> Ast { + Ast::CharClass(CharClass::new( + vec![CharRange { start: ch, end: ch }], + false, + )) + } + #[test] fn test_parse_abc() { let actual = parse("abc").unwrap(); From a7ed72260088ab9af0671e8936f10e36b40551ca Mon Sep 17 00:00:00 2001 From: shu-kitamura Date: Wed, 11 Feb 2026 21:21:57 +0900 Subject: [PATCH 3/4] Delete #[allow(dead_code)] in file top. Only ast::Predicate items are left specially. --- crates/regex-core/src/engine/ast.rs | 13 +------------ crates/regex-core/src/engine/compiler.rs | 1 - crates/regex-core/src/engine/evaluator.rs | 1 - crates/regex-core/src/engine/instruction.rs | 1 - crates/regex-core/src/engine/parser.rs | 1 - 5 files changed, 1 insertion(+), 16 deletions(-) diff --git a/crates/regex-core/src/engine/ast.rs b/crates/regex-core/src/engine/ast.rs index f65b1f8..81ef826 100644 --- a/crates/regex-core/src/engine/ast.rs +++ b/crates/regex-core/src/engine/ast.rs @@ -1,5 +1,4 @@ //! AST definitions for the regex engine. -#![allow(dead_code)] /// Inclusive character range. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -10,17 +9,6 @@ pub struct CharRange { pub end: char, } -impl CharRange { - /// Creates a range when `start <= end`; otherwise returns `None`. - pub fn new(start: char, end: char) -> Option { - if start <= end { - Some(Self { start, end }) - } else { - None - } - } -} - /// Character class. /// /// `ranges` represents inclusive `[start, end]` spans. @@ -41,6 +29,7 @@ impl CharClass { } /// Zero-width assertion kinds. +#[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Predicate { /// Line start assertion (`^`). diff --git a/crates/regex-core/src/engine/compiler.rs b/crates/regex-core/src/engine/compiler.rs index 2f069f1..0ac7339 100644 --- a/crates/regex-core/src/engine/compiler.rs +++ b/crates/regex-core/src/engine/compiler.rs @@ -1,5 +1,4 @@ //! Compile an AST into an instruction sequence (`Instruction`). -#![allow(dead_code)] use thiserror::Error; diff --git a/crates/regex-core/src/engine/evaluator.rs b/crates/regex-core/src/engine/evaluator.rs index 6723058..6b24f0d 100644 --- a/crates/regex-core/src/engine/evaluator.rs +++ b/crates/regex-core/src/engine/evaluator.rs @@ -1,5 +1,4 @@ //! Evaluate an instruction sequence. -#![allow(dead_code)] use std::collections::HashSet; diff --git a/crates/regex-core/src/engine/instruction.rs b/crates/regex-core/src/engine/instruction.rs index f1361eb..19920eb 100644 --- a/crates/regex-core/src/engine/instruction.rs +++ b/crates/regex-core/src/engine/instruction.rs @@ -1,5 +1,4 @@ //! Instruction set used by the compiler and evaluator. -#![allow(dead_code)] use std::fmt::{self, Display}; diff --git a/crates/regex-core/src/engine/parser.rs b/crates/regex-core/src/engine/parser.rs index df6f214..a057bd3 100644 --- a/crates/regex-core/src/engine/parser.rs +++ b/crates/regex-core/src/engine/parser.rs @@ -1,7 +1,6 @@ //! Recursive-descent parser for regex patterns. //! //! The parser converts a pattern string into an `Ast` used by the compiler. -#![allow(dead_code)] use crate::engine::ast::{Ast, CharClass, CharRange, Predicate}; use thiserror::Error; From 1123a4a0587c6fce2b92e1a8b60b49f2b45a595a Mon Sep 17 00:00:00 2001 From: shu-kitamura Date: Thu, 12 Feb 2026 21:10:59 +0900 Subject: [PATCH 4/4] remove helper function in test replace single_char_class() with Parser::parse_single_char(). --- crates/regex-core/src/engine/parser.rs | 106 ++++++++++++------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/crates/regex-core/src/engine/parser.rs b/crates/regex-core/src/engine/parser.rs index a057bd3..6ab9aec 100644 --- a/crates/regex-core/src/engine/parser.rs +++ b/crates/regex-core/src/engine/parser.rs @@ -380,21 +380,13 @@ mod tests { use super::{ParseError, Parser, parse}; use crate::engine::ast::{Ast, CharClass, CharRange, Predicate}; - /// helper to build a single-character class AST - fn single_char_class(ch: char) -> Ast { - Ast::CharClass(CharClass::new( - vec![CharRange { start: ch, end: ch }], - false, - )) - } - #[test] fn test_parse_abc() { let actual = parse("abc").unwrap(); let expect = Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), - single_char_class('c'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), + Parser::parse_single_char('c'), ]); assert_eq!(actual, expect); } @@ -404,10 +396,10 @@ mod tests { let actual = parse("a|b|c").unwrap(); let expect = Ast::Alternate( Box::new(Ast::Alternate( - Box::new(single_char_class('a')), - Box::new(single_char_class('b')), + Box::new(Parser::parse_single_char('a')), + Box::new(Parser::parse_single_char('b')), )), - Box::new(single_char_class('c')), + Box::new(Parser::parse_single_char('c')), ); assert_eq!(actual, expect); } @@ -417,12 +409,12 @@ mod tests { let actual = parse("ab|cd").unwrap(); let expect = Ast::Alternate( Box::new(Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), ])), Box::new(Ast::Concat(vec![ - single_char_class('c'), - single_char_class('d'), + Parser::parse_single_char('c'), + Parser::parse_single_char('d'), ])), ); assert_eq!(actual, expect); @@ -431,11 +423,17 @@ mod tests { #[test] fn test_parse_alternation_empty_side() { let actual = parse("a|").unwrap(); - let expect = Ast::Alternate(Box::new(single_char_class('a')), Box::new(Ast::Empty)); + let expect = Ast::Alternate( + Box::new(Parser::parse_single_char('a')), + Box::new(Ast::Empty), + ); assert_eq!(actual, expect); let actual = parse("|a").unwrap(); - let expect = Ast::Alternate(Box::new(Ast::Empty), Box::new(single_char_class('a'))); + let expect = Ast::Alternate( + Box::new(Ast::Empty), + Box::new(Parser::parse_single_char('a')), + ); assert_eq!(actual, expect); } @@ -444,30 +442,30 @@ mod tests { let actual_star = parse("a*b").unwrap(); let expect_star = Ast::Concat(vec![ Ast::ZeroOrMore { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, }, - single_char_class('b'), + Parser::parse_single_char('b'), ]); assert_eq!(actual_star, expect_star); let actual_plus = parse("a+b").unwrap(); let expect_plus = Ast::Concat(vec![ Ast::OneOrMore { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, }, - single_char_class('b'), + Parser::parse_single_char('b'), ]); assert_eq!(actual_plus, expect_plus); let actual_question = parse("a?b").unwrap(); let expect_question = Ast::Concat(vec![ Ast::ZeroOrOne { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, }, - single_char_class('b'), + Parser::parse_single_char('b'), ]); assert_eq!(actual_question, expect_question); } @@ -478,8 +476,8 @@ mod tests { let expect = Ast::ZeroOrMore { expr: Box::new(Ast::Capture { expr: Box::new(Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), ])), index: 1, }), @@ -491,8 +489,8 @@ mod tests { let expect = Ast::Repeat { expr: Box::new(Ast::Capture { expr: Box::new(Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), ])), index: 1, }), @@ -507,7 +505,7 @@ mod tests { fn test_parse_repeat_forms() { let actual = parse("a{3}").unwrap(); let expect = Ast::Repeat { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, min: 3, max: Some(3), @@ -516,7 +514,7 @@ mod tests { let actual = parse("a{2,}").unwrap(); let expect = Ast::Repeat { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, min: 2, max: None, @@ -525,7 +523,7 @@ mod tests { let actual = parse("a{2,5}").unwrap(); let expect = Ast::Repeat { - expr: Box::new(single_char_class('a')), + expr: Box::new(Parser::parse_single_char('a')), greedy: true, min: 2, max: Some(5), @@ -641,7 +639,7 @@ mod tests { fn test_parse_char_class_concat() { let actual = parse("a[bc]d").unwrap(); let expect = Ast::Concat(vec![ - single_char_class('a'), + Parser::parse_single_char('a'), Ast::CharClass(CharClass::new( vec![ CharRange { @@ -655,7 +653,7 @@ mod tests { ], false, )), - single_char_class('d'), + Parser::parse_single_char('d'), ]); assert_eq!(actual, expect); } @@ -689,17 +687,17 @@ mod tests { let expect = Ast::Concat(vec![ Ast::Capture { expr: Box::new(Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), - single_char_class('c'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), + Parser::parse_single_char('c'), ])), index: 1, }, Ast::Capture { expr: Box::new(Ast::Concat(vec![ - single_char_class('d'), - single_char_class('e'), - single_char_class('f'), + Parser::parse_single_char('d'), + Parser::parse_single_char('e'), + Parser::parse_single_char('f'), ])), index: 2, }, @@ -713,9 +711,9 @@ mod tests { let expect = Ast::Concat(vec![ Ast::Capture { expr: Box::new(Ast::Concat(vec![ - single_char_class('a'), - single_char_class('b'), - single_char_class('c'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), + Parser::parse_single_char('c'), ])), index: 1, }, @@ -729,9 +727,9 @@ mod tests { let actual = parse("^abc$").unwrap(); let expect = Ast::Concat(vec![ Ast::Assertion(Predicate::StartOfLine), - single_char_class('a'), - single_char_class('b'), - single_char_class('c'), + Parser::parse_single_char('a'), + Parser::parse_single_char('b'), + Parser::parse_single_char('c'), Ast::Assertion(Predicate::EndOfLine), ]); assert_eq!(actual, expect); @@ -748,7 +746,7 @@ mod tests { fn test_parse_dot() { let actual = parse("a.c").unwrap(); let expect = Ast::Concat(vec![ - single_char_class('a'), + Parser::parse_single_char('a'), Ast::CharClass(CharClass::new( vec![CharRange { start: '\u{0000}', @@ -756,7 +754,7 @@ mod tests { }], false, )), - single_char_class('c'), + Parser::parse_single_char('c'), ]); assert_eq!(actual, expect); } @@ -771,23 +769,23 @@ mod tests { #[test] fn test_parse_escaped_literals() { let actual = parse("\\*").unwrap(); - let expect = single_char_class('*'); + let expect = Parser::parse_single_char('*'); assert_eq!(actual, expect); let actual = parse("\\\\").unwrap(); - let expect = single_char_class('\\'); + let expect = Parser::parse_single_char('\\'); assert_eq!(actual, expect); let actual = parse("\\+").unwrap(); - let expect = single_char_class('+'); + let expect = Parser::parse_single_char('+'); assert_eq!(actual, expect); let actual = parse("\\?").unwrap(); - let expect = single_char_class('?'); + let expect = Parser::parse_single_char('?'); assert_eq!(actual, expect); let actual = parse("\\a").unwrap(); - let expect = single_char_class('a'); + let expect = Parser::parse_single_char('a'); assert_eq!(actual, expect); }