Python
Karena menerapkan PCRE penuh terlalu banyak, saya hanya mengimplementasikan sebagian yang penting saja.
Mendukung |.\.\w\W\s+*()
. Input regexp harus benar.
Contoh:
$ python regexp.py
^\s*(\w+)$
hello
Matches: hello
Group 1 hello
$ python regexp.py
(a*)+
infinite loop
$ python regexp.py
(\w+)@(\w+)(\.com|\.net)
sam@test.net
Matches: sam@test.net
Group 1 sam
Group 2 test
Group 3 .net
Bagaimana itu bekerja:
Untuk teori terperinci, baca Pengantar Teori Automata, Bahasa, dan Komputasi ini .
Idenya adalah untuk mengubah ekspresi reguler asli menjadi automata nondeterminist finite automata (NFA). Sebenarnya, ekspresi reguler PCRE setidaknya adalah tata bahasa bebas konteks yang kami perlukan push-down automata, tetapi kami akan membatasi diri kami untuk subset dari PCRE.
Automatas terbatas adalah grafik terarah di mana node dinyatakan, tepi adalah transisi dan setiap transisi memiliki input yang cocok. Awalnya Anda mulai dari simpul awal, yang telah ditentukan sebelumnya. Setiap kali Anda menerima input yang cocok dengan salah satu transisi Anda mengambil transisi itu ke keadaan baru. Jika Anda mencapai terminal node, itu disebut input diterima automata. Dalam kasus kami, input adalah fungsi yang cocok yang mengembalikan true.
Mereka disebut nondeterminist automata karena kadang-kadang ada lebih banyak transisi yang cocok yang dapat Anda ambil dari keadaan yang sama. Dalam implementasi saya semua transisi ke keadaan yang sama harus cocok dengan hal yang sama, jadi saya menyimpan fungsi yang cocok bersama-sama dengan negara tujuan ( states[dest][0]
).
Kami mengubah regexp kami menjadi automata terbatas menggunakan blok bangunan. Blok penyusun memiliki simpul mulai ( first
) dan simpul akhir ( last
) dan cocok dengan sesuatu dari teks (kemungkinan string kosong).
Contoh paling sederhana termasuk
- tidak ada yang cocok:
True
( first == last
)
- cocok dengan karakter:
c == txt[pos]
( first == last
)
- akhir string yang cocok: pos == len (txt)
(
pertama == terakhir`)
Anda juga akan membutuhkan posisi baru di teks tempat untuk mencocokkan token berikutnya.
Contoh yang lebih rumit adalah (huruf besar berarti blok).
pencocokan B +:
- buat node: u, v (tidak ada yang cocok)
- buat transisi: u -> B.first, B.last -> v, v -> u
- ketika Anda sampai ke simpul v Anda sudah cocok B. Kemudian Anda memiliki dua opsi: melangkah lebih jauh, atau mencoba untuk mencocokkan B lagi.
pencocokan A | B | C:
- buat node: u, v (tidak ada yang cocok)
- buat transisi: u -> A.first, u -> C.first, u -> C.first,
- buat transisi: A-> last -> v, B-> last -> v, C-> last -> v,
- dari kamu kamu bisa pergi ke blok mana saja
Semua operator regexp dapat diubah seperti ini. Coba saja *
.
Bagian terakhir adalah mengurai regexp yang membutuhkan tata bahasa yang sangat sederhana:
or: seq ('|' seq)*
seq: empty
seq: atom seq
seq: paran seq
paran: '(' or ')'
Semoga menerapkan tata bahasa sederhana (saya pikir adalah LL (1), tetapi benar kalau saya salah) jauh lebih mudah daripada membangun NFA.
Setelah Anda memiliki NFA, Anda harus mundur hingga mencapai simpul terminal.
Kode sumber (atau di sini ):
from functools import *
WORDCHAR = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_'
def match_nothing(txt, pos):
return True, pos
def match_character(c, txt, pos):
return pos < len(txt) and txt[pos] == c, pos + 1
def match_space(txt, pos):
return pos < len(txt) and txt[pos].isspace(), pos + 1
def match_word(txt, pos):
return pos < len(txt) and txt[pos] in WORDCHAR, pos + 1
def match_nonword(txt, pos):
return pos < len(txt) and txt[pos] not in WORDCHAR, pos + 1
def match_dot(txt, pos):
return pos < len(txt), pos + 1
def match_start(txt, pos):
return pos == 0, pos
def match_end(txt, pos):
return pos == len(txt), pos
def create_state(states, match=None, last=None, next=None, name=None):
if next is None: next = []
if match is None: match = match_nothing
state = len(states)
states[state] = (match, next, name)
if last is not None:
states[last][1].append(state)
return state
def compile_or(states, last, regexp, pos):
mfirst = create_state(states, last=last, name='or_first')
mlast = create_state(states, name='or_last')
while True:
pos, first, last = compile_seq(states, mfirst, regexp, pos)
states[last][1].append(mlast)
if pos != len(regexp) and regexp[pos] == '|':
pos += 1
else:
assert pos == len(regexp) or regexp[pos] == ')'
break
return pos, mfirst, mlast
def compile_paren(states, last, regexp, pos):
states.setdefault(-2, []) # stores indexes
states.setdefault(-1, []) # stores text
group = len(states[-1])
states[-2].append(None)
states[-1].append(None)
def match_pfirst(txt, pos):
states[-2][group] = pos
return True, pos
def match_plast(txt, pos):
old = states[-2][group]
states[-1][group] = txt[old:pos]
return True, pos
mfirst = create_state(states, match=match_pfirst, last=last, name='paren_first')
mlast = create_state(states, match=match_plast, name='paren_last')
pos, first, last = compile_or(states, mfirst, regexp, pos)
assert regexp[pos] == ')'
states[last][1].append(mlast)
return pos + 1, mfirst, mlast
def compile_seq(states, last, regexp, pos):
first = create_state(states, last=last, name='seq')
last = first
while pos < len(regexp):
p = regexp[pos]
if p == '\\':
pos += 1
p += regexp[pos]
if p in '|)':
break
elif p == '(':
pos, first, last = compile_paren(states, last, regexp, pos + 1)
elif p in '+*':
# first -> u ->...-> last -> v -> t
# v -> first (matches at least once)
# first -> t (skip on *)
# u becomes new first
# first is inserted before u
u = create_state(states)
v = create_state(states, next=[first])
t = create_state(states, last=v)
states[last][1].append(v)
states[u] = states[first]
states[first] = (match_nothing, [[u], [u, t]][p == '*'])
last = t
pos += 1
else: # simple states
if p == '^':
state = create_state(states, match=match_start, last=last, name='begin')
elif p == '$':
state = create_state(states, match=match_end, last=last, name='end')
elif p == '.':
state = create_state(states, match=match_dot, last=last, name='dot')
elif p == '\\.':
state = create_state(states, match=partial(match_character, '.'), last=last, name='dot')
elif p == '\\s':
state = create_state(states, match=match_space, last=last, name='space')
elif p == '\\w':
state = create_state(states, match=match_word, last=last, name='word')
elif p == '\\W':
state = create_state(states, match=match_nonword, last=last, name='nonword')
elif p.isalnum() or p in '_@':
state = create_state(states, match=partial(match_character, p), last=last, name='char_' + p)
else:
assert False
first, last = state, state
pos += 1
return pos, first, last
def compile(regexp):
states = {}
pos, first, last = compile_or(states, create_state(states, name='root'), regexp, 0)
assert pos == len(regexp)
return states, last
def backtrack(states, last, string, start=None):
if start is None:
for i in range(len(string)):
if backtrack(states, last, string, i):
return True
return False
stack = [[0, 0, start]] # state, pos in next, pos in text
while stack:
state = stack[-1][0]
pos = stack[-1][2]
#print 'in state', state, states[state]
if state == last:
print 'Matches: ', string[start:pos]
for i in xrange(len(states[-1])):
print 'Group', i + 1, states[-1][i]
return True
while stack[-1][1] < len(states[state][1]):
nstate = states[state][1][stack[-1][1]]
stack[-1][1] += 1
ok, npos = states[nstate][0](string, pos)
if ok:
stack.append([nstate, 0, npos])
break
else:
pass
#print 'not matched', states[nstate][2]
else:
stack.pop()
return False
# regexp = '(\\w+)@(\\w+)(\\.com|\\.net)'
# string = 'sam@test.net'
regexp = raw_input()
string = raw_input()
states, last = compile(regexp)
backtrack(states, last, string)