귀하의 언어로 PCRE를 구현하십시오.


13

참고 : 이것을 직접 시도한 후 곧 이것이 무슨 실수인지 깨달았습니다. 따라서 규칙을 약간 수정하고 있습니다.

필요한 최소 기능 :

  • 문자 클래스 ( ., \w, \W등)
  • 승수 ( +, *?)
  • 간단한 캡처 그룹

다음 조건에 따라 선택한 언어로 PCRE 를 구현 해야합니다.

  • 귀하 귀하의 언어 고유의 RegEx 기능을 어떤 방식으로도 사용할 수 없습니다 . 타사 RegEx 라이브러리도 사용할 수 없습니다.
  • 출품작은 많은 PCRE 사양을 구현해야합니다. 가능한 한.
  • 프로그램은 입력으로 2 줄을 허용해야합니다.

    • 정규식
    • 일치하는 문자열 입력
  • 프로그램은 출력에 표시해야합니다.

    • RegEx가 입력 문자열의 어디에서나 일치하는지 여부
    • 캡처 그룹의 결과
  • 승자는 스펙을 최대한 구현하는 항목이됩니다. 가능한 한. 동점 인 경우, 본인이 판단한 바에 따라 우승자가 가장 창의적입니다.


편집 : 몇 가지를 명확히하기 위해 입력 및 예상 출력의 예는 다음과 같습니다.


  • 입력:
^ \ s * (\ w +) $
         여보세요
  • 산출:
일치 : 예
그룹 1 : 'hello'

  • 입력:
(\ w +) @ (\ w +) (? : \. com | \ .net)
sam@test.net
  • 산출:
일치 : 예
그룹 1 : '샘'
그룹 2 : '테스트'


PCRE의 기능이 많기 때문에 이는 매우 어려운 과제입니다. 재귀, 역 추적, 미리보기 / 어설 션, 유니 코드, 조건부 하위 패턴, ...
Arnaud Le Blanc

1
PCRE 문서를 참조하십시오 . PERL RE ; PHP PCRE 문서도 훌륭합니다.
Arnaud Le Blanc

@ user300 : 목표는 가능한 한 많이 구현하는 것입니다. 분명히 모든 것이 조금 어려울 것입니다.
Nathan Osman

2
@George : 원하는 기능을 나열하고 테스트 사례를 제공하는 것은 어떻습니까?
Marko Dumic

1
@George : @Marko는 특정 기능을 따르거나 사람들이 먼저 구현하기를 원하는 최소한의 하위 집합이라고 생각합니다. 전체적으로 PCRE는 실제 코딩 경쟁에는 너무 어려운 과제입니다. 나는 이것을 매우 작고 구체적인 RE 서브셋으로 변경하고 구현하기가 어렵다는 것을 제안한다.
MtnViewMark

답변:


10

파이썬

전체 PCRE를 구현하는 것이 너무 많기 때문에 필수 서브 세트 만 구현했습니다.

지원합니다 |.\.\w\W\s+*(). 입력 정규식이 정확해야합니다.

예 :

$ 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

작동 방식 :

자세한 이론 은 Automata 이론, 언어 및 계산 소개를 읽으십시오 .

아이디어는 원래 정규 표현식을 NFA (Non-determinist finite automata)로 변환하는 것입니다. 실제로 PCRE 정규식은 푸시-다운 오토마타가 필요한 컨텍스트 프리 문법이지만, PCRE의 하위 집합으로 제한됩니다.

유한 오토마타는 노드가 상태이고 에지가 전환이며 각 전환에 일치하는 입력이있는 직접 그래프입니다. 처음에는 사전 정의 된 시작 노드에서 시작합니다. 전환 중 하나와 일치하는 입력을받을 때마다 해당 전환을 새로운 상태로 가져갑니다. 터미널 노드에 도달하면이를 자동 입력 허용 입력이라고합니다. 우리의 경우 입력은 true를 반환하는 매칭 함수입니다.

때로는 같은 상태에서 취할 수있는 더 많은 일치하는 전환이 있기 때문에 비 결정적 오토마타라고합니다. 구현에서 동일한 상태로의 모든 전환은 동일한 것과 일치해야하므로 일치하는 함수를 대상 상태 ( states[dest][0]) 와 함께 저장했습니다 .

빌딩 블록을 사용하여 정규 표현식을 유한 오토마타로 변환합니다. 빌딩 블록에는 시작 노드 ( first)와 끝 노드 ( last)가 있으며 텍스트의 항목과 일치합니다 (빈 문자열 가능).

가장 간단한 예는 다음과 같습니다.

  • 아무것도 일치하지 않음 : True( first == last)
  • 문자와 일치 : c == txt[pos]( first == last)
  • 일치하는 문자열 끝 : pos == len (txt) (first == last`)

또한 다음 토큰과 일치하는 텍스트의 새로운 위치가 필요합니다.

더 복잡한 예는 다음과 같습니다 (대문자는 블록을 나타냄).

  • 일치하는 B + :

    • 노드 만들기 : u, v (아무것도 일치하지 않음)
    • 전환 만들기 : u-> B.first, B.last-> v, v-> u
    • 노드 v에 도착하면 이미 B와 일치합니다. 그런 다음 두 가지 옵션이 있습니다.
  • 일치하는 A | B | C :

    • 노드 만들기 : u, v (아무것도 일치하지 않음)
    • 전환 만들기 : u-> A.first, u-> C.first, u-> C.first,
    • 전환 만들기 : A-> 마지막-> v, B-> 마지막-> v, C-> 마지막-> v,
    • 당신은 어떤 블록 으로든 갈 수 있습니다

모든 정규 표현식 연산자는 다음과 같이 변환 될 수 있습니다. 시도해보십시오 *.

마지막 부분은 매우 간단한 문법이 필요한 정규 표현식을 구문 분석하는 것입니다.

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

희망적으로 간단한 문법을 ​​구현하는 것이 NFA를 만드는 것보다 훨씬 쉽습니다.

NFA가 있으면 터미널 노드에 도달 할 때까지 역 추적해야합니다.

소스 코드 (또는 here ) :

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 와우 ... PHP로 직접 시도했지만 완전히 실패했습니다.
Nathan Osman

TIL이 (a+b)+일치 abaabaaabaaaab합니다.
Alexandru
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.