अजगर
चूंकि पूर्ण PCRE को लागू करना बहुत अधिक है, इसलिए मैंने केवल एक आवश्यक उपसमुच्चय को लागू किया है।
का समर्थन करता है |.\.\w\W\s+*()
। इनपुट regexp सही होना चाहिए।
उदाहरण:
$ 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
यह काम किस प्रकार करता है:
विस्तृत सिद्धांत के लिए यह परिचय ऑटोमेटा थ्योरी, भाषा और संगणना के लिए पढ़ें ।
यह विचार मूल नियमित अभिव्यक्ति को एक नॉडटर्मिनिस्ट परिमित ऑटोमेटा (एनएफए) में बदलने का है। दरअसल, पीसीआरई के नियमित भाव कम से कम संदर्भ मुक्त व्याकरण हैं, जिनके लिए हमें पुश-डाउन ऑटोमेटा की आवश्यकता है, लेकिन हम खुद को पीसीआरई के सबसेट में सीमित कर देंगे।
परिमित ऑटोमेटा निर्देशित ग्राफ होते हैं जिनमें नोड्स अवस्थाएं होती हैं, किनारे संक्रमण होते हैं और प्रत्येक संक्रमण में एक मिलान इनपुट होता है। प्रारंभ में आप पूर्वनिर्धारित प्रारंभ नोड से शुरू करते हैं। जब भी आपको एक इनपुट प्राप्त होता है जो आपके द्वारा किए गए संक्रमण से मेल खाता है तो आप उस संक्रमण को एक नए राज्य में ले जाते हैं। यदि आप एक टर्मिनल नोड पर पहुंच गए, तो इसे ऑटोमेटा स्वीकृत इनपुट कहा जाता है। हमारे मामले में इनपुट एक मेल खाने वाला फ़ंक्शन है जो सही रिटर्न देता है।
उन्हें नोंडेटर्मिनिस्ट ऑटोमेटा कहा जाता है क्योंकि कभी-कभी अधिक मिलान संक्रमण होते हैं जो आप एक ही राज्य से ले सकते हैं। मेरे कार्यान्वयन में एक ही राज्य में सभी संक्रमण एक ही चीज़ से मेल खाना चाहिए, इसलिए मैंने गंतव्य राज्य ( states[dest][0]
) के साथ मिलान फ़ंक्शन को संग्रहीत किया ।
हम बिल्डिंग ब्लॉक का उपयोग करके अपने रेगेक्सप को एक परिमित ऑटोमेटा में बदल देते हैं। एक बिल्डिंग ब्लॉक में एक स्टार्ट नोड ( first
) और एक एंड नोड ( last
) होता है और टेक्स्ट से कुछ से मेल खाता है (संभव खाली स्ट्रिंग)।
सबसे सरल उदाहरणों में शामिल हैं
- कुछ भी नहीं मिलान:
True
( first == last
)
- एक चरित्र का मिलान:
c == txt[pos]
( first == last
)
- स्ट्रिंग का मिलान अंत: pos == len (txt)
(
पहले == last`)
आपको पाठ में नई स्थिति की भी आवश्यकता होगी जहां अगले टोकन से मिलान करना है।
अधिक जटिल उदाहरण हैं (ब्लॉक के लिए कैपिटल लेटर्स स्टैंड)।
मिलान B +:
- नोड बनाएँ: यू, वी (कुछ भी नहीं मिलान)
- संक्रमण बनाएँ: u -> B.first, B.last -> v, v -> u
- जब आप नोड वी से पहले ही मिल जाते हैं, तब आपके पास बी दो विकल्प होते हैं: आगे जाएं, या फिर बी से मिलान करने का प्रयास करें।
मिलान ए | बी | सी:
- नोड बनाएँ: यू, वी (कुछ भी नहीं मिलान)
- संक्रमण बनाएँ: u -> A.first, u -> C.first, u -> C.first, आदि
- संक्रमण बनाएँ: A-> अंतिम -> v, B-> अंतिम -> v, C-> अंतिम -> v,
- यू से आप किसी भी ब्लॉक में जा सकते हैं
सभी regexp ऑपरेटरों को इस तरह से बदला जा सकता है। बस के लिए एक कोशिश दे *
।
अंतिम भाग regexp को पार्स करना है जिसके लिए एक बहुत ही सरल व्याकरण की आवश्यकता होती है:
or: seq ('|' seq)*
seq: empty
seq: atom seq
seq: paran seq
paran: '(' or ')'
उम्मीद है कि एक साधारण व्याकरण को लागू करना (मुझे लगता है कि एलएल (1) है, लेकिन मुझे गलत होने पर सही करें) एनएफए बनाने की तुलना में बहुत आसान है।
एक बार जब आपके पास एनएफए हो जाता है तो आपको टर्मिनल नोड तक पहुंचने के लिए पीछे हटना पड़ता है।
स्रोत कोड (या यहाँ ):
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)