Implementa PCRE nella tua lingua.


13

Nota: dopo aver provato questo da solo, mi sono presto reso conto di quale errore fosse. Pertanto, sto modificando un po 'le regole.

La funzionalità minima richiesta:

  • Classi di personaggi ( ., \w, \W, etc.)
  • Moltiplicatori ( +, *, e ?)
  • Gruppi di acquisizione semplici

La tua sfida è implementare PCRE nella lingua che preferisci, alle seguenti condizioni:

  • Non puoi in alcun modo utilizzare le strutture RegEx native della tua lingua . Non è possibile utilizzare nemmeno una libreria RegEx di terze parti.
  • La tua voce dovrebbe implementare la maggior parte delle specifiche PCRE. il più possibile.
  • Il tuo programma dovrebbe accettare come input, 2 righe:

    • l'espressione regolare
    • l'input della stringa da abbinare
  • Il tuo programma dovrebbe indicare nel suo output:

    • Se RegEx corrisponde in qualsiasi punto della stringa di input
    • I risultati di tutti i gruppi di acquisizione
  • Il vincitore sarà la voce che implementa la maggior parte delle specifiche. il più possibile. In caso di pareggio, il vincitore sarà la voce più creativa, come giudicato da me.


Modifica: per chiarire alcune cose, ecco alcuni esempi di input e output previsti:


  • Ingresso:
^ \ S * (\ w +) $
         Ciao
  • Produzione:
Partite: si
Gruppo 1: "ciao"

  • Ingresso:
(\ W +) @ (\ w +) (:?. \ Com | \ .net)
sam@test.net
  • Produzione:
Partite: si
Gruppo 1: "sam"
Gruppo 2: "test"


Questa è una sfida davvero impegnativa, data la quantità di funzionalità di PCRE. Ricorsione, backtracking, lookahead / asserzioni, unicode, modelli secondari condizionali, ...
Arnaud Le Blanc,

1
Vedi i documenti PCRE ; PERL RE ; Anche i documenti PHP PCRE sono fantastici.
Arnaud Le Blanc,

@ user300: l'obiettivo è implementare il più possibile. Ovviamente tutto sarebbe un po 'troppo difficile.
Nathan Osman,

2
@George: Che ne dici di elencare le funzionalità che desideri e di fornire alcuni casi di test, solo per essere tutti su un terreno uniforme.
Marko Dumic,

1
@George: penso che @Marko cercasse funzionalità specifiche, o meglio, il sottoinsieme minimo che vorresti che le persone implementassero per prime. Nel complesso, tuttavia, PCRE è davvero una sfida troppo difficile per una competizione di programmazione casuale. Suggerisco di cambiarlo in un sottoinsieme RE molto piccolo e specifico e fare la sfida da implementare.
MtnViewMark,

Risposte:


10

Pitone

Poiché l'implementazione di PCRE completo è troppo, ne ho implementato solo un sottoinsieme essenziale.

Supporti |.\.\w\W\s+*(). Input regexp deve essere corretto.

Esempi:

$ 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

Come funziona:

Per una teoria dettagliata leggi questo introduzione alla teoria, alle lingue e al calcolo degli automi .

L'idea è di convertire l'espressione regolare originale in automi finiti non deterministici (NFA). In realtà, le espressioni regolari di PCRE sono almeno grammatiche libere dal contesto per le quali abbiamo bisogno di automi push-down, ma ci limiteremo a un sottoinsieme di PCRE.

Gli automatismi finiti sono grafici diretti in cui i nodi sono stati, i bordi sono transizioni e ogni transizione ha un input corrispondente. Inizialmente si parte da un nodo iniziale, predefinito. Ogni volta che ricevi un input che corrisponde a una delle transizioni, prendi quella transizione verso un nuovo stato. Se hai raggiunto un nodo terminale, viene chiamato input accettato dagli automi. Nel nostro caso l'input è una funzione corrispondente che restituisce true.

Sono chiamati automi non deterministici perché a volte ci sono più transizioni corrispondenti che puoi prendere dallo stesso stato. Nella mia implementazione tutte le transizioni allo stesso stato devono corrispondere alla stessa cosa, quindi ho memorizzato la funzione di corrispondenza insieme allo stato di destinazione (states[dest][0] ).

Trasformiamo la nostra regexp in automi finiti usando blocchi predefiniti. Un blocco predefinito ha un nodo iniziale (first ) e un nodo finale ( last) e corrisponde a qualcosa del testo (possibile stringa vuota).

Gli esempi più semplici includono

  • niente corrispondente: True ( first == last)
  • abbinando un personaggio: c == txt[pos] ( first == last)
  • fine stringa corrispondente: pos == len (txt)( first == last`)

Sarà inoltre necessaria la nuova posizione nel testo in cui corrispondere al token successivo.

Esempi più complicati sono (le lettere maiuscole indicano i blocchi).

  • B + corrispondente:

    • creare nodi: u, v (niente corrispondente)
    • creare transizioni: u -> B.first, B.last -> v, v -> u
    • quando arrivi al nodo v hai già abbinato B. Quindi hai due opzioni: andare oltre o provare a abbinare di nuovo B.
  • corrispondenza A | B | C:

    • creare nodi: u, v (niente corrispondente)
    • creare transizioni: u -> A.first, u -> C.first, u -> C.first,
    • creare transizioni: A-> last -> v, B-> last -> v, C-> last -> v,
    • da te puoi andare in qualsiasi blocco

Tutti gli operatori regexp possono essere trasformati in questo modo. Provaci e basta *.

L'ultima parte è analizzare la regexp che richiede una grammatica molto semplice:

 or: seq ('|' seq)*
 seq: empty
 seq: atom seq
 seq: paran seq
 paran: '(' or ')'

Spero che implementare una grammatica semplice (penso che sia LL (1), ma correggermi se sbaglio) è molto più facile che costruire un NFA.

Una volta che hai l'NFA, devi tornare indietro fino a raggiungere il nodo terminale.

Codice sorgente (o qui ):

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 Wow ... Ho provato a farlo da solo con PHP e ho fallito completamente.
Nathan Osman,

(a+b)+Partite TIL abaabaaabaaaab.
Alexandru,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.