หลาม
เนื่องจากการติดตั้ง 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
มันทำงานอย่างไร:
สำหรับทฤษฎีที่มีรายละเอียดให้อ่านบทนำของทฤษฎีออโตมาตาภาษาและการคำนวณนี้
แนวคิดคือการแปลงนิพจน์ปกติดั้งเดิมเป็น nondeterminist finite automata (NFA) ที่จริงแล้วนิพจน์ทั่วไปของ PCRE นั้นเป็นไวยากรณ์อย่างน้อยบริบทที่เราต้องการออโตมาตาแบบกดลง แต่เราจะ จำกัด ตัวเองไว้ที่เซตย่อยของ PCRE
โดยอัตโนมัติ จำกัด เป็นกราฟกำกับซึ่งโหนดเป็นสถานะขอบเป็นช่วงการเปลี่ยนภาพและแต่ละช่วงการเปลี่ยนภาพมีอินพุตที่ตรงกัน เริ่มแรกคุณเริ่มจากโหนดเริ่มต้นที่กำหนดไว้ล่วงหน้า เมื่อใดก็ตามที่คุณได้รับอินพุตที่ตรงกับช่วงการเปลี่ยนภาพคุณจะเข้าสู่สถานะใหม่ หากคุณไปถึงโหนดเทอร์มินัลจะถูกเรียกว่าอินพุตที่ยอมรับโดยอัตโนมัติ ในกรณีที่เราใส่ฟังก์ชั่นการจับคู่ที่ส่งกลับจริง
พวกเขาถูกเรียกว่า nondeterminist automata เพราะบางครั้งมีการเปลี่ยนการจับคู่มากกว่าที่คุณสามารถทำได้จากสถานะเดียวกัน ในการนำไปใช้ของฉันการเปลี่ยนสถานะทั้งหมดจะต้องตรงกับสิ่งเดียวกันดังนั้นฉันจึงเก็บฟังก์ชันการจับคู่ไว้พร้อมกับสถานะปลายทาง ( states[dest][0]
)
เราเปลี่ยน regexp ของเราให้เป็นออโตมาต้าที่ จำกัด โดยใช้แบบเอกสารสำเร็จรูป Building Block มีโหนดเริ่มต้น ( first
) และโหนดสุดท้าย ( last
) และจับคู่บางอย่างจากข้อความ (สตริงว่างที่เป็นไปได้)
ตัวอย่างที่ง่ายที่สุด ได้แก่
- ไม่มีอะไรที่ตรงกัน:
True
( first == last
)
- จับคู่ตัวละคร:
c == txt[pos]
( first == last
)
- การจับคู่จุดสิ้นสุดของสตริง: pos == len (txt)
(
first == last`)
คุณจะต้องตำแหน่งใหม่ในข้อความที่ตรงกับโทเค็นถัดไป
ตัวอย่างที่ซับซ้อนมากขึ้นคือ (ตัวพิมพ์ใหญ่ยืนสำหรับบล็อก)
การจับคู่ B +:
- สร้างโหนด: u, v (ไม่มีอะไรที่ตรงกัน)
- สร้างการเปลี่ยน: u -> B. ก่อนอื่น B.last -> v, v -> u
- เมื่อคุณไปที่โหนด v คุณได้จับคู่ B แล้วคุณมีสองตัวเลือก: ไปต่อหรือลองจับคู่ B อีกครั้ง
การจับคู่ A | B | C:
- สร้างโหนด: u, v (ไม่มีอะไรที่ตรงกัน)
- สร้างการเปลี่ยน: 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 ')'
หวังว่าการใช้ไวยากรณ์อย่างง่าย (ฉันคิดว่าเป็น LL (1) แต่แก้ไขฉันถ้าฉันผิด) ง่ายกว่าการสร้าง NFA
เมื่อคุณมี NFA แล้วคุณต้องย้อนกลับจนกว่าจะถึงเทอร์มินัลโหนด
รหัสที่มา (หรือที่นี่ ):
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)