Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/instructions/review/pr-review.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# GitHub Copilot Instructions

You are a senior code reviewer ensuring high standards of code quality

When performing a code review, **respond in Japanese**.
Please focus on the following checklist:

## Checklist

- Code is simple and readable
- Functions and variables are well-named
- No duplicated code
- Proper error handling
- No exposed secrets or API keys
- Input validation implemented
- Good test coverage
- Performance considerations addressed
- Time complexity of algorithms analyzed
- Integrated libraries have compatible licenses (no license conflicts in dependencies)
- No overly large functions (>50 lines)
- No overly large files (>800 lines)
- No deeply nested logic (>4 levels)
- No hardcoded configuration values; configuration is externalized
- No unexplained magic numbers; constants are named and documented
66 changes: 49 additions & 17 deletions crates/regex-core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use thiserror::Error;

use crate::engine::{
compiler::compile,
evaluator::{eval, eval_from_start},
evaluator::{eval, eval_from_starts},
parser::parse,
};

pub(crate) use ast::{Ast, AstAnalysis, analyze_ast};
pub use compiler::CompileError;
pub use evaluator::EvalError;
pub use instruction::Instruction;
Expand Down Expand Up @@ -60,40 +61,57 @@ where
}
}

/// Parse and compile a pattern.
pub fn compile_pattern(pattern: &str) -> Result<Vec<Instruction>, RegexError> {
let ast = parse(pattern)?;
/// Parse, analyze, and compile a pattern.
pub(crate) fn compile_pattern_with_analysis(
pattern: &str,
) -> Result<(Vec<Instruction>, AstAnalysis), RegexError> {
let ast: Ast = parse(pattern)?;
let analysis = analyze_ast(&ast);
let instructions = compile(&ast)?;
Ok(instructions)
Ok((instructions, analysis))
}

/// Parse, extract must literals, and compile a pattern.
#[allow(dead_code)]
pub(crate) fn compile_pattern_with_must_literals(
pattern: &str,
) -> Result<(Vec<Instruction>, Vec<String>), RegexError> {
let (instructions, analysis) = compile_pattern_with_analysis(pattern)?;
Ok((instructions, analysis.must_literals))
}

/// Match an instruction sequence against a line.
pub fn match_line(code: &[Instruction], line: &str) -> Result<bool, RegexError> {
Ok(eval(code, line)?)
}

/// Match an instruction sequence from the start of a line.
pub fn match_line_from_start(code: &[Instruction], line: &str) -> Result<bool, RegexError> {
Ok(eval_from_start(code, line)?)
/// Match an instruction sequence from provided starting character indices.
pub(crate) fn match_line_from_starts(
code: &[Instruction],
line: &str,
starts: &[usize],
) -> Result<bool, RegexError> {
Ok(eval_from_starts(code, line, starts)?)
}

#[cfg(test)]
mod tests {
use crate::engine::{
CompileError, RegexError, compile_pattern, instruction::Instruction, match_line,
match_line_from_start,
CompileError, RegexError, compile_pattern_with_analysis,
compile_pattern_with_must_literals, instruction::Instruction, match_line,
match_line_from_starts,
};

#[test]
fn test_compile_pattern_literal() {
let code = compile_pattern("abc").unwrap();
let (code, _) = compile_pattern_with_must_literals("abc").unwrap();
assert_eq!(code.len(), 4);
assert!(matches!(code.last(), Some(Instruction::Match)));
}

#[test]
fn test_compile_pattern_invalid_backreference() {
let actual = compile_pattern("(a)\\2");
let actual = compile_pattern_with_must_literals("(a)\\2");
assert_eq!(
actual,
Err(RegexError::Compile(CompileError::InvalidBackreference(2)))
Expand All @@ -102,15 +120,29 @@ mod tests {

#[test]
fn test_match_line_backreference() {
let code = compile_pattern("(abc)\\1").unwrap();
let (code, _) = compile_pattern_with_must_literals("(abc)\\1").unwrap();
assert!(match_line(&code, "abcabc").unwrap());
assert!(!match_line(&code, "abcabd").unwrap());
}

#[test]
fn test_match_line_from_start() {
let code = compile_pattern("abc").unwrap();
assert!(match_line_from_start(&code, "abcdef").unwrap());
assert!(!match_line_from_start(&code, "zabc").unwrap());
fn test_compile_pattern_with_must_literals() {
let (_code, must_literals) = compile_pattern_with_must_literals(".*abc.*").unwrap();
assert_eq!(must_literals, vec!["abc".to_string()]);
}

#[test]
fn test_compile_pattern_with_analysis() {
let (_code, analysis) = compile_pattern_with_analysis("(abc|def)").unwrap();
assert!(analysis.must_literals.is_empty());
assert_eq!(analysis.needles, vec!["abc".to_string(), "def".to_string()]);
assert!(!analysis.nullable);
}

#[test]
fn test_match_line_from_starts() {
let (code, _) = compile_pattern_with_must_literals("abc").unwrap();
assert!(!match_line_from_starts(&code, "xabc", &[0]).unwrap());
assert!(match_line_from_starts(&code, "xabc", &[1]).unwrap());
}
}
Loading