PCRE'yi kendi dilinizde uygulayın.


13

Not: Bunu kendim denedikten sonra kısa bir süre sonra bunun ne bir hata olduğunu anladım. Bundan dolayı kuralları biraz değiştiriyorum.

Gereken minimum işlevsellik:

  • Karakter sınıfları ( ., \w, \Wvs.)
  • Çarpanlar ( +, *ve ?)
  • Basit yakalama grupları

Sorun, PCRE'yi aşağıdaki koşullara bağlı olarak seçtiğiniz dilde uygulamaktır :

  • Sen olmayabilir de, dilinizin yerli RegEx imkanları kullanmak hiçbir şekilde . Üçüncü taraf RegEx kitaplığını da kullanamazsınız.
  • Girişiniz PCRE spesifikasyonlarının çoğunu uygulamalıdır. olabildiğince.
  • Programınız girdi olarak kabul edilmelidir, 2 satır:

    • düzenli ifade
    • eşleşecek dize girdisi
  • Programınız çıktısında şunları belirtmelidir:

    • RegEx'in giriş dizesinde herhangi bir yerde eşleşip eşleşmediği
    • Herhangi bir yakalama grubunun sonuçları
  • Kazanan, özelliklerin çoğunu uygulayan giriş olacaktır. olabildiğince. Beraberlik durumunda kazanan, benim tarafımdan değerlendirildiği üzere en yaratıcı giriş olacaktır.


Düzenleme: birkaç şeyi açıklığa kavuşturmak için, bazı girdi ve beklenen çıktı örnekleri:


  • Giriş:
^ \ S * (\ w +) $
         Merhaba
  • Çıktı:
Eşleşmeler: evet
Grup 1: 'merhaba'

  • Giriş:
(\ W +) @ (\ w +) (?:. \ Com | \ .net)
sam@test.net
  • Çıktı:
Eşleşmeler: evet
Grup 1: 'sam'
Grup 2: 'test'


PCRE'deki özelliklerin miktarı göz önüne alındığında, bu gerçekten zor bir iştir. Özyineleme, geri izleme, ileri / iddialar, unicode, koşullu alt şablonlar, ...
Arnaud Le Blanc

1
Bkz. PCRE belgeleri ; PERL RE ; PHP PCRE belgeleri de harika.
Arnaud Le Blanc

@ user300: Amaç mümkün olduğunca uygulamaktır. Açıkçası her şey biraz fazla zor olurdu.
Nathan Osman

2
@George: İstediğiniz özellikleri listelemeye ve bazı test senaryoları vermeye ne dersiniz?
Marko Dumic

1
@George: Bence @Marko belirli özelliklerin peşindeydi, daha doğrusu, ilk önce insanların uygulamak isteyeceği asgari alt kümeydi. Bununla birlikte, genel olarak, PCRE, rahat bir kodlama yarışması için gerçekten çok zor. Bunu çok küçük, spesifik bir RE alt kümesine değiştirmenizi ve uygulamayı zorlaştırmanızı öneririm.
MtnViewMark

Yanıtlar:


10

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)

1
+1 Vay be ... Bunu PHP ile kendim yapmaya çalıştım ve tamamen başarısız oldum.
Nathan Osman

TIL (a+b)+eşleşir abaabaaabaaaab.
Alexandru
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.