Thực hiện PCRE bằng ngôn ngữ của bạn.


13

Lưu ý: Sau khi tự mình thử, tôi sớm nhận ra đây là một lỗi gì. Do đó, tôi đang sửa đổi các quy tắc một chút.

Các chức năng yêu cầu tối thiểu:

  • Lớp nhân vật ( ., \w, \W, vv)
  • Nhân ( +, *?)
  • Nhóm chụp đơn giản

Thách thức của bạn là triển khai PCRE bằng ngôn ngữ của đối tượng bạn chọn theo các điều kiện sau:

  • Bạn không được sử dụng các tiện ích RegEx gốc của ngôn ngữ của mình dưới bất kỳ hình thức nào . Bạn cũng không thể sử dụng thư viện RegEx của bên thứ 3.
  • Mục nhập của bạn sẽ thực hiện càng nhiều thông số PCRE. càng tốt
  • Chương trình của bạn nên chấp nhận làm đầu vào, 2 dòng:

    • biểu thức chính quy
    • đầu vào chuỗi để khớp với
  • Chương trình của bạn sẽ chỉ ra trong đầu ra của nó:

    • Liệu RegEx có khớp với bất kỳ vị trí nào trong chuỗi đầu vào không
    • Kết quả của bất kỳ nhóm bắt giữ nào
  • Người chiến thắng sẽ là mục thực hiện càng nhiều thông số kỹ thuật. càng tốt Trong trường hợp hòa, người chiến thắng sẽ là người sáng tạo nhất, theo đánh giá của tôi.


Chỉnh sửa: để làm rõ một số điều, đây là một số ví dụ về đầu vào và đầu ra dự kiến:


  • Đầu vào:
^ \ s * (\ w +) $
         xin chào
  • Đầu ra:
Trận đấu: có
Nhóm 1: 'xin chào'

  • Đầu vào:
(\ w +) @ (\ w +) (?: \. com | \ .net)
sam@test.net
  • Đầu ra:
Trận đấu: có
Nhóm 1: 'sam'
Nhóm 2: 'kiểm tra'


Đó là một thử thách thực sự thách thức, với số lượng tính năng trong PCRE. Đệ quy, quay lui, tìm kiếm / xác nhận, unicode, các mẫu con có điều kiện, ...
Arnaud Le Blanc

1
Xem tài liệu PCRE ; PERL RE ; Tài liệu PHP PCRE cũng rất tuyệt.
Arnaud Le Blanc

@ user300: Mục tiêu là triển khai càng nhiều càng tốt. Rõ ràng mọi thứ sẽ hơi khó khăn.
Nathan Osman

2
@George: Làm thế nào về việc bạn liệt kê các tính năng bạn muốn và đưa ra một số trường hợp thử nghiệm, chỉ để tất cả chúng ta đều ở trên mặt đất.
Marko Dumic

1
@George: Tôi nghĩ rằng @Marko là sau các tính năng cụ thể, hay đúng hơn là tập hợp con tối thiểu mà bạn muốn mọi người thực hiện trước tiên. Nhìn chung, PCRE thực sự là một thách thức quá khó đối với một cuộc thi viết mã thông thường. Tôi đề nghị thay đổi điều này thành một tập hợp con RE rất nhỏ, cụ thể và đưa ra thách thức để thực hiện.
MtnViewMark

Câu trả lời:


10

Con trăn

Vì việc thực hiện PCRE đầy đủ là quá nhiều nên tôi chỉ triển khai một tập hợp con thiết yếu của nó.

Hỗ trợ |.\.\w\W\s+*(). Regrec đầu vào phải chính xác.

Ví dụ:

$ 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

Làm thế nào nó hoạt động:

Để biết lý thuyết chi tiết, hãy đọc phần Giới thiệu về Lý thuyết, Ngôn ngữ và Tính toán của Automata .

Ý tưởng là để chuyển đổi biểu thức chính quy ban đầu thành một automata hữu hạn không hữu hạn (NFA). Trên thực tế, các biểu thức chính quy của PCRE ít nhất là các ngữ pháp không có ngữ cảnh mà chúng ta cần tự động đẩy xuống, nhưng chúng ta sẽ giới hạn bản thân trong một tập hợp con của PCRE.

Máy tự động hữu hạn là đồ thị có hướng trong đó các nút là trạng thái, các cạnh là các chuyển tiếp và mỗi chuyển đổi có một đầu vào phù hợp. Ban đầu bạn bắt đầu từ một nút bắt đầu, được xác định trước. Bất cứ khi nào bạn nhận được một đầu vào khớp với một trong những chuyển đổi, bạn sẽ chuyển quá trình đó sang trạng thái mới. Nếu bạn đạt đến một nút thiết bị đầu cuối, nó được gọi là đầu vào được chấp nhận automata. Trong trường hợp của chúng tôi đầu vào là một chức năng phù hợp trả về đúng.

Chúng được gọi là automata nondeterminist bởi vì đôi khi có nhiều chuyển tiếp phù hợp hơn mà bạn có thể thực hiện từ cùng một trạng thái. Trong quá trình thực hiện của tôi, tất cả các chuyển đổi sang cùng một trạng thái phải khớp với cùng một thứ, vì vậy tôi đã lưu trữ hàm so khớp cùng với trạng thái đích ( states[dest][0]).

Chúng tôi chuyển đổi biểu thức chính quy thành một automata hữu hạn bằng cách sử dụng các khối xây dựng. Một khối xây dựng có nút bắt đầu ( first) và nút kết thúc ( last) và khớp với nội dung nào đó từ văn bản (chuỗi trống có thể).

Các ví dụ đơn giản nhất bao gồm

  • không có gì phù hợp: True( first == last)
  • khớp với một ký tự: c == txt[pos]( first == last)
  • kết thúc chuỗi kết hợp: pos == len (txt) (trước == last`)

Bạn cũng sẽ cần vị trí mới trong văn bản để khớp mã thông báo tiếp theo.

Ví dụ phức tạp hơn là (chữ in hoa là viết tắt của các khối).

  • khớp B +:

    • tạo các nút: u, v (không khớp với gì)
    • tạo hiệu ứng chuyển tiếp: u -> B.first, B.last -> v, v -> u
    • khi bạn đến nút v bạn đã khớp B. Sau đó, bạn có hai tùy chọn: đi xa hơn hoặc thử khớp lại B.
  • phù hợp với A | B | C:

    • tạo các nút: u, v (không khớp với gì)
    • tạo hiệu ứng chuyển tiếp: u -> A.first, u -> C.first, u -> C.first,
    • tạo hiệu ứng chuyển tiếp: A-> lần cuối -> v, B-> lần cuối -> v, C-> lần cuối -> v,
    • từ bạn có thể đi đến bất kỳ khối nào

Tất cả các toán tử regrec có thể được chuyển đổi như thế này. Chỉ cần thử một lần cho *.

Phần cuối cùng là phân tích cú pháp regrec đòi hỏi một ngữ pháp rất đơn giản:

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

Hy vọng việc thực hiện một ngữ pháp đơn giản (tôi nghĩ là LL (1), nhưng sửa tôi nếu tôi sai) dễ hơn nhiều so với việc xây dựng một NFA.

Khi bạn có NFA, bạn cần quay lại cho đến khi bạn đạt đến nút thiết bị đầu cuối.

Mã nguồn (hoặc tại đây ):

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 ... Tôi đã cố gắng tự làm điều này với PHP và hoàn toàn thất bại.
Nathan Osman

TIL (a+b)+phù hợp abaabaaabaaaab.
Alexandru
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.