piton
Tam PCRE'yi uygulamak çok fazla olduğundan, sadece önemli bir alt kümesini uyguladım.
Destekler |.\.\w\W\s+*()
. Normal ifade girişi doğru olmalıdır.
Örnekler:
$ 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
Nasıl çalışır:
Ayrıntılı teori için, Otomata Teorisi, Diller ve Hesaplamaya Giriş bölümünü okuyun .
Fikir, orijinal düzenli ifadeyi belirsiz olmayan bir sonlu otomata (NFA) dönüştürmektir. Aslında, PCRE düzenli ifadeleri, en azından aşağı itmeli otomatlara ihtiyacımız olan bağlamsız gramerlerdir, ancak kendimizi bir PCRE alt kümesiyle sınırlayacağız.
Sonlu otomatalar, düğümlerin durum olduğu, kenarların geçiş olduğu ve her geçişin eşleşen bir girişe sahip olduğu yönlendirilmiş grafiklerdir. Başlangıçta, önceden tanımlanmış bir başlangıç düğümünden başlarsınız. Geçişlerden biriyle eşleşen bir girdi aldığınızda, bu geçişi yeni bir duruma alırsınız. Bir terminal düğümüne ulaştıysanız, buna otomat kabul edilen giriş denir. Bizim durumumuzda girdi true döndüren bir eşleştirme işlevidir.
Bunlara belirsiz olmayan otomata denir, çünkü bazen aynı durumdan alabileceğiniz daha eşleşen geçişler vardır. Benim uygulamada aynı duruma tüm geçiş aynı şeyle eşleşmelidir, bu yüzden hedef durumu ( states[dest][0]
) ile birlikte eşleşen işlevi sakladı .
Normal ifademizi yapı taşlarını kullanarak sonlu bir otomata dönüştürüyoruz. Bir yapı bloğunun bir başlangıç düğümü ( first
) ve bir bitiş düğümü ( last
) vardır ve metinden bir şeyle eşleşir (olası boş dize).
En basit örnekler şunları içerir:
- hiçbir şey eşleşmiyor:
True
( first == last
)
- bir karakter eşleme:
c == txt[pos]
( first == last
)
- dizenin eşleşen sonu: pos == len (txt)
(
ilk == son`)
Ayrıca, bir sonraki belirtecin eşleşeceği metinde yeni konuma da ihtiyacınız olacak.
Daha karmaşık örnekler şunlardır (büyük harfler blokları ifade eder).
eşleşen B +:
- düğüm oluştur: u, v (hiçbir şey eşleşmiyor)
- geçiş oluşturma: u -> B.first, B.last -> v, v -> u
- v düğümüne ulaştığınızda B ile zaten eşleştiniz. O zaman iki seçeneğiniz var: daha ileri gidin veya B'yi tekrar eşleştirmeyi deneyin.
A | B | C ile eşleşen:
- düğüm oluştur: u, v (hiçbir şey eşleşmiyor)
- geçişler oluşturun: u -> A.first, u -> C.first, u -> C.first,
- geçiş oluştur: A-> son -> v, B-> son -> v, C-> son -> v,
- senden herhangi bir bloğa gidebilirsin
Tüm normal ifade operatörleri bu şekilde dönüştürülebilir. Sadece bir deneyin *
.
Son bölüm, çok basit bir dilbilgisi gerektiren normal ifadeyi ayrıştırmaktır:
or: seq ('|' seq)*
seq: empty
seq: atom seq
seq: paran seq
paran: '(' or ')'
Umarım basit bir dilbilgisi uygulamak (bence LL (1), ama yanılıyorsam beni düzelt) bir NFA oluşturmaktan çok daha kolay.
NFA'ya sahip olduğunuzda, terminal düğümüne ulaşıncaya kadar geri izlemeniz gerekir.
Kaynak kodu (veya burada ):
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)