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**.

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instruction to "respond in Japanese" seems unusual for a codebase that uses English throughout. This should be removed unless there's a specific reason for requiring Japanese responses in code reviews.

Copilot uses AI. Check for mistakes.
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
Comment on lines +1 to +24

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire file appears to be unrelated to the PR's stated purpose of improving prefilter functionality. The PR description mentions analyzing AST for must literals, nullable, and needles, but doesn't mention adding Copilot review instructions. Consider removing this file from this PR or explaining its relevance in the PR description.

Copilot uses AI. Check for mistakes.
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