Skip to content

Implement Thompson NFA-based Regular Expressions#1172

Open
JAi-SATHVIK wants to merge 119 commits into
fortran-lang:masterfrom
JAi-SATHVIK:regex
Open

Implement Thompson NFA-based Regular Expressions#1172
JAi-SATHVIK wants to merge 119 commits into
fortran-lang:masterfrom
JAi-SATHVIK:regex

Conversation

@JAi-SATHVIK

Copy link
Copy Markdown
Contributor

issue #1163

@arjenmarkus

arjenmarkus commented Apr 5, 2026 via email

Copy link
Copy Markdown
Member

@arjenmarkus

arjenmarkus commented Apr 5, 2026 via email

Copy link
Copy Markdown
Member

@arjenmarkus

arjenmarkus commented Apr 5, 2026 via email

Copy link
Copy Markdown
Member

@JAi-SATHVIK

Copy link
Copy Markdown
Contributor Author

Thanks @jalvesz @arjenmarkus ! I’ve updated the code and addressed those issues:

Off-by-one and match lengths: Fixed! abc now correctly returns ms=5, me=7, and aaaab with a*b correctly returns ms=1, me=5.
Leftmost-Longest Priority: The engine follows the standard where the leftmost start always wins first. Because an "A" matches starting at index 1, it is chosen over any subsequent matches elsewhere in the string.

Among all matches starting at that same leftmost position, the engine will strictly select the longest one before concluding.

@JAi-SATHVIK
JAi-SATHVIK requested a review from jalvesz April 5, 2026 19:59
@arjenmarkus

arjenmarkus commented Apr 7, 2026 via email

Copy link
Copy Markdown
Member

@JAi-SATHVIK

Copy link
Copy Markdown
Contributor Author

Thanks for the tests @arjenmarkus

  • Updated tokenize lexer logic to actively evaluate the preceding AST token before assigning repeat quantifiers (*, +, ?).
  • The parser now properly rejects nested or invalid quantifiers that lack a valid operand (e.g a**, a+*, (*a), or |*).
  • Enhanced parenthesis matching logic to correctly identify and throw errors for explicitly empty groups ().
  • Tested for strict compliance using Arjen Markus's Regex test catalog runner script. All edge cases successfully trigger stat = 1 immediately during regcomp.

@JAi-SATHVIK

Copy link
Copy Markdown
Contributor Author

Hi @jvdp1 @jalvesz , there are some ci failures which I have addressed in issue #1178 can you once have a look?

Comment thread src/regex/stdlib_regex.f90 Outdated
Comment thread src/regex/stdlib_regex.f90 Outdated
Comment thread src/regex/stdlib_regex.f90 Outdated
@JAi-SATHVIK

Copy link
Copy Markdown
Contributor Author

Thanks @jalvesz, I’ve updated is_term_ender, is_term_starter, and prec to be elemental. This makes them much more versatile for array-based operations within the module.

@JAi-SATHVIK
JAi-SATHVIK requested a review from jalvesz April 17, 2026 16:19
@jalvesz
jalvesz requested a review from arjenmarkus April 17, 2026 16:38

@jvdp1 jvdp1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you @JAi-SATHVIK . Here are a few comments after a very quick review

Comment thread test/regex/catalogue_regex.f90 Outdated
integer :: mismatches
logical :: matched

open( 10, file = 'catalogue_regex.inp', status = 'old', iostat = ierr )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use newunit instead of a defined unit.

Suggested change
open( 10, file = 'catalogue_regex.inp', status = 'old', iostat = ierr )
open( newunit=un, file = 'catalogue_regex.inp', status = 'old', iostat = ierr )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored to use newunit

Comment thread test/regex/catalogue_regex.f90 Outdated
error stop
endif

open( 20, file = 'catalogue_regex.report' )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
open( 20, file = 'catalogue_regex.report' )
open( newunit=un20, file = 'catalogue_regex.report' )

Comment thread test/regex/catalogue_regex.f90 Outdated
mismatches = 0

do
read( 10, '(a)', iostat = ierr ) line

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
read( 10, '(a)', iostat = ierr ) line
read( un, '(a)', iostat = ierr ) line

! Anchored match
call regcomp(re, "^hello", stat)
call regmatch(re, "hello world", found)
print "(A,L1)", "found = ", found

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
print "(A,L1)", "found = ", found
print "(a,l1)", "found = ", found

Comment thread src/regex/stdlib_regex.f90 Outdated
Comment on lines +34 to +44
integer, parameter :: CHAR_ZERO = iachar('0')
integer, parameter :: CHAR_NINE = iachar('9')
integer, parameter :: CHAR_LOWER_A = iachar('a')
integer, parameter :: CHAR_LOWER_Z = iachar('z')
integer, parameter :: CHAR_UPPER_A = iachar('A')
integer, parameter :: CHAR_UPPER_Z = iachar('Z')
integer, parameter :: CHAR_SPACE = iachar(' ')
integer, parameter :: CHAR_TAB = 9
integer, parameter :: CHAR_LF = 10
integer, parameter :: CHAR_CR = 13
integer, parameter :: CHAR_UNDERSCORE = iachar('_')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some of these might be already defined in stdlib_ascii. Did you check them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update stdlib_regex.f90 to use constants from stdlib_ascii (TAB, LF, CR)

@JAi-SATHVIK
JAi-SATHVIK requested a review from jvdp1 April 19, 2026 11:24
@14NGiestas 14NGiestas added the topic: strings String processing label May 7, 2026
@14NGiestas 14NGiestas linked an issue May 7, 2026 that may be closed by this pull request
@jalvesz
jalvesz requested a review from Copilot July 9, 2026 20:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new experimental stdlib_regex module implementing a pure-Fortran Thompson NFA regex engine, and wires it into the build/test/docs/examples so it can be built and exercised within stdlib.

Changes:

  • Add src/regex/stdlib_regex.f90 implementing regex_type, regcomp, and regmatch with support for basic operators, character classes, and anchors.
  • Add unit tests and CMake wiring for a new test/regex test target, plus example programs under example/regex.
  • Add initial user-facing specification documentation (doc/specs/stdlib_regex.md) and index/build-system integration for the new modular component.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/regex/stdlib_regex.f90 Core regex engine (tokenizer → postfix parser → NFA builder → NFA simulation).
src/regex/CMakeLists.txt Defines the regex library target and links it to stdlib_core.
src/CMakeLists.txt Adds the regex subdirectory to the overall source build.
test/regex/test_regex.f90 Adds testdrive unit tests covering literals, quantifiers, alternation, anchors, dot, and empty patterns.
test/regex/catalogue_regex.f90 Adds a standalone regex catalogue runner (currently placed under test/).
test/regex/CMakeLists.txt Registers the regex test target in the CMake test suite.
test/CMakeLists.txt Adds conditional inclusion of the test/regex subdirectory when STDLIB_REGEX is enabled.
example/regex/example_regex_regcomp.f90 Example: compile a regex and check status.
example/regex/example_regex_regmatch.f90 Example: match/search a regex and report match indices.
example/regex/CMakeLists.txt Registers regex examples in the examples build.
example/CMakeLists.txt Adds conditional inclusion of regex examples when STDLIB_REGEX is enabled.
doc/specs/stdlib_regex.md New module specification and API documentation.
doc/specs/index.md Adds regex to the specs index.
CMakeLists.txt Adds REGEX to modular build option checks (check_modular("REGEX")).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +494 to +517
subroutine regcomp(re, pattern, status)
type(regex_type), intent(out) :: re
character(len=*), intent(in) :: pattern
integer, intent(out), optional :: status

type(token_type), allocatable :: tokens(:)
type(token_type), allocatable :: postfix(:)
integer :: n_tok, n_post, stat

call tokenize(pattern, tokens, n_tok, stat)
if (stat /= 0) then
if (present(status)) status = stat
return
end if

call parse_to_postfix(tokens, n_tok, postfix, n_post, stat)
if (stat /= 0) then
if (present(status)) status = stat
return
end if

call build_nfa(postfix, n_post, re%states, re%n_states, re%start_state, stat)
if (present(status)) status = stat
end subroutine regcomp
Comment on lines +527 to +534
integer :: op, curr_state, top
integer, allocatable :: stack(:)

if (state_idx == 0) return

allocate(stack(max(1, size(states) * 2)))
top = 1
stack(top) = state_idx
Comment on lines +157 to +175
if ( any( keyword == known_keywords ) ) then
allocate( value, mold = line )

k1 = index( line, '"' )
if ( k1 > 0 ) then
k2 = k1 + index( line(k1+1:), '"' )
if ( k2 > 0 ) then
value = line(k1+1:k2-1)
else
write( un20, '(a)' ) 'Error interpreting the input line:'
write( un20, '(2a)' ) ' "', trim(line), '"'
write( un20, '(2a)' ) 'Program stopped'
write( *, '(2a)' ) 'Program stopped - error reading input. Please check'
error stop
endif
endif
else
value = ""
endif
Comment thread doc/specs/stdlib_regex.md
| `*` | Zero or more of preceding element | `ab*c` → `ac` |
| `+` | One or more of preceding element | `ab+c` → `abbc` |
| `?` | Zero or one of preceding element | `colou?r` |
| `\|` | Alternation | `cat\|dog` |
Comment on lines +18 to +22
open( newunit=un, file = 'catalogue_regex.inp', status = 'old', iostat = ierr )
if ( ierr /= 0 ) then
write( *, '(a)' ) 'Could not open the file "catalogue_regex.inp"'
write( *, '(a)' ) 'It should exist - please check'
error stop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: strings String processing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pure Fortran Regular Expression (Regex) Module

6 participants