모호성을 처리 할 수있는 문법을 설정하는 방법


9

내가 고안 한 Excel과 같은 수식을 구문 분석하는 문법을 만들려고하는데 문자열의 시작 부분에 특수 문자가 다른 소스를 나타냅니다. 예를 들어, $문자열을 나타낼 수 있으므로 " $This is text"는 프로그램에서 문자열 입력으로 처리되고 &함수를 나타낼 &foo()수 있으므로 내부 함수에 대한 호출로 처리 될 수 있습니다 foo.

내가 겪고있는 문제는 문법을 올바르게 구성하는 방법입니다. 예를 들어, 이것은 MWE로 단순화 된 버전입니다.

grammar = r'''start: instruction

?instruction: simple
            | func

STARTSYMBOL: "!"|"#"|"$"|"&"|"~"
SINGLESTR: (LETTER+|DIGIT+|"_"|" ")*
simple: STARTSYMBOL [SINGLESTR] (WORDSEP SINGLESTR)*
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: STARTSYMBOL SINGLESTR "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
parser = lark.Lark(grammar, parser='earley')

그래서,이 문법과 같은 일이 : $This is a string, &foo(), &foo(#arg1), &foo($arg1,,#arg2)그리고 &foo(!w1,w2,w3,,!w4,w5,w6)예상대로 모든 구문 분석됩니다. 그러나 simple터미널 에 더 많은 유연성을 추가 하려면 SINGLESTR편리하지 않은 토큰 정의를 다루어야 합니다.

내가 시도한 것

내가 지나칠 수없는 부분은 괄호 (리터럴 포함)를 포함하는 문자열을 원하면 func현재 상황에서 처리 할 수 ​​없다는 것입니다.

  • 내가 괄호에 추가하면 SINGLESTR, 그때 얻을 Expected STARTSYMBOL그것이 함께 혼합지고 있기 때문에, func정의와는 함수의 인수가 의미가있는, 통과되어야한다고 생각한다.
  • 함수에 대해서만 앰퍼샌드 기호를 예약하고 괄호를 추가하도록 문법을 재정의하면 괄호로 SINGLESTR문자열을 구문 분석 할 수 있지만 구문 분석하려는 모든 함수는을 제공합니다 Expected LPAR.

내 의도는 a로 시작하는 모든 것이 토큰 $으로 구문 분석 된 SINGLESTR다음과 같은 것을 구문 분석 할 수 있다는 것 &foo($first arg (has) parentheses,,$second arg)입니다.

내 솔루션은 현재 문자열에 LEFTPAR 및 RIGHTPAR과 같은 '탈출'단어를 사용하고 트리를 처리 할 때 괄호로 변경하는 도우미 함수를 작성했다는 것입니다. 따라서 $This is a LEFTPARtestRIGHTPAR올바른 트리를 생성하고 처리하면로 변환됩니다 This is a (test).

일반적인 질문을 공식화하려면 : 문법에 특수한 일부 문자가 어떤 상황에서는 일반 문자로 처리되고 다른 경우에는 특수 문자로 처리되도록 문법을 정의 할 수 있습니까?


편집 1

시작 주석을 jbndlr기반으로 개별 모드를 만들기 위해 문법을 수정했습니다.

grammar = r'''start: instruction

?instruction: simple
            | func

SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|")")*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''

이것은 두 번째 테스트 사례에 해당합니다. 모든 simple유형의 문자열 (괄호를 포함 할 수있는 TEXT, MD 또는 DB 토큰)과 비어있는 함수를 구문 분석 할 수 있습니다. 예를 들어, &foo()또는 &foo(&bar())올바르게 구문 분석. 함수 내에서 인수를 넣는 순간 (어떤 유형에 관계없이)을 얻습니다 UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP. 개념 증명으로, 위의 새 문법에서 SINGLESTR 정의에서 괄호를 제거하면 모든 것이 정상적으로 작동하지만 다시 정사각형으로 돌아갑니다.


뒤에 오는 것을 식별하는 문자가 STARTSYMBOL있고 명확해야 할 경우 구분 기호와 괄호를 추가합니다. 나는 여기서 모호성을 보지 못한다. 여전히 STARTSYMBOL구별 할 수 있도록 목록을 개별 항목으로 분할해야 합니다.
jbndlr

나는 곧 답변을 게시하고 며칠 동안 노력하고 있습니다.
iliar

나는 대답을 제공했다. 바운티가 만료 될 때까지 2 시간 밖에 걸리지 않지만 다음 24 시간의 유예 기간에 바운티를 수동으로 수여 할 수 있습니다. 대답이 좋지 않으면 곧 알려 주시면 해결하겠습니다.
iliar

답변:


3
import lark
grammar = r'''start: instruction

?instruction: simple
            | func

MIDTEXTRPAR: /\)+(?!(\)|,,|$))/
SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|MIDTEXTRPAR)*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''

parser = lark.Lark(grammar, parser='earley')
parser.parse("&foo($first arg (has) parentheses,,$second arg)")

산출:

Tree(start, [Tree(func, [Token(FUNCNAME, 'foo'), Tree(simple, [Token(TEXT, '$first arg (has) parentheses')]), Token(ARGSEP, ',,'), Tree(simple, [Token(TEXT, '$second arg')])])])

나는 그것이 당신이 찾고있는 것이기를 바랍니다.

그들은 며칠 동안 미쳤다. 나는 잠복을 시도하고 실패했다. 나는 또한 시도 persimonious하고 pyparsing. 이 모든 다른 파서는 모두 함수의 일부인 올바른 괄호를 사용하는 '인수'토큰과 동일한 문제가 있었으며 결국 함수의 괄호가 닫히지 않아 실패했습니다.

요령은 "특별하지 않은"올바른 괄호를 어떻게 정의하는지 알아내는 것이 었습니다. MIDTEXTRPAR위 코드에서 정규 표현식을 참조하십시오 . 인수 분리 또는 문자열 끝이 아닌 오른쪽 괄호로 정의했습니다. 나는 (?!...)뒤에 오는 ...것이 아니라 문자를 소비하지 않는 경우에만 일치 하는 정규 표현식 확장 을 사용하여 그렇게했습니다 . 운 좋게도이 특수 정규 표현식 확장 내에서 문자열 끝을 일치시킬 수도 있습니다.

편집하다:

위에서 언급 한 메소드는)로 끝나는 인수가없는 경우에만 작동합니다. 왜냐하면 MIDTEXTRPAR 정규 표현식은이를 포착하지 못하기 때문에 처리 할 인수가 더 있지만 함수의 끝이라고 생각할 것입니다. 또한 ... asdf), ...와 같은 모호성이있을 수 있습니다. 인수 내에서 함수 선언의 끝이거나 인수 내에서 'text-like') 일 수 있으며 함수 선언이 계속됩니다.

이 문제는 귀하의 질문에 설명 한 내용이 종달새와 같은 파서가 존재 하는 문맥이없는 문법 ( https://en.wikipedia.org/wiki/Context-free_grammar ) 이 아니라는 사실과 관련이 있습니다. 대신 상황에 맞는 문법입니다 ( https://en.wikipedia.org/wiki/Context-sensitive_grammar ).

문맥에 민감한 문법이되는 이유는 파서가 함수 안에 중첩되어 있다는 사실을 '기억'하고 얼마나 많은 중첩 수준이 있는지, 문법의 문법 안에서 어떤 방식 으로든이 메모리를 사용할 수 있기 때문입니다.

EDIT2 :

또한 상황에 따라 달라지고 문제를 해결하는 것처럼 보이지만 중첩 된 함수 수에 기하 급수적으로 시간이 복잡한 다음 구문 분석기를 살펴보십시오. 가능한 함수 장벽을 찾을 때까지 가능한 모든 함수 장벽을 구문 분석하려고합니다. 문맥에 맞지 않기 때문에 지수 적으로 복잡해야한다고 생각합니다.


_funcPrefix = '&'
_debug = False

class ParseException(Exception):
    pass

def GetRecursive(c):
    if isinstance(c,ParserBase):
        return c.GetRecursive()
    else:
        return c

class ParserBase:
    def __str__(self):
        return type(self).__name__ + ": [" + ','.join(str(x) for x in self.contents) +"]"
    def GetRecursive(self):
        return (type(self).__name__,[GetRecursive(c) for c in self.contents])

class Simple(ParserBase):
    def __init__(self,s):
        self.contents = [s]

class MD(Simple):
    pass

class DB(ParserBase):
    def __init__(self,s):
        self.contents = s.split(',')

class Func(ParserBase):
    def __init__(self,s):
        if s[-1] != ')':
            raise ParseException("Can't find right parenthesis: '%s'" % s)
        lparInd = s.find('(')
        if lparInd < 0:
            raise ParseException("Can't find left parenthesis: '%s'" % s)
        self.contents = [s[:lparInd]]
        argsStr = s[(lparInd+1):-1]
        args = list(argsStr.split(',,'))
        i = 0
        while i<len(args):
            a = args[i]
            if a[0] != _funcPrefix:
                self.contents.append(Parse(a))
                i += 1
            else:
                j = i+1
                while j<=len(args):
                    nestedFunc = ',,'.join(args[i:j])
                    if _debug:
                        print(nestedFunc)
                    try:
                        self.contents.append(Parse(nestedFunc))
                        break
                    except ParseException as PE:
                        if _debug:
                            print(PE)
                        j += 1
                if j>len(args):
                    raise ParseException("Can't parse nested function: '%s'" % (',,'.join(args[i:])))
                i = j

def Parse(arg):
    if arg[0] not in _starterSymbols:
        raise ParseException("Bad prefix: " + arg[0])
    return _starterSymbols[arg[0]](arg[1:])

_starterSymbols = {_funcPrefix:Func,'$':Simple,'!':DB,'#':MD}

P = Parse("&foo($first arg (has)) parentheses,,&f($asdf,,&nested2($23423))),,&second(!arg,wer))")
print(P)

import pprint
pprint.pprint(P.GetRecursive())

1
감사합니다. 의도 한대로 작동합니다! 어떤 식 으로든 괄호를 벗어날 필요가 없으므로 현상금을 수여하십시오. 당신은 여분의 마일을 갔다 그리고 그것은 보여줍니다! 여전히 괄호로 끝나는 '텍스트'인수의 가장 중요한 경우가 있지만, 나는 그와 함께 살아야합니다. 또한 모호성을 명확하게 설명했으며 조금 더 테스트해야하지만 내 목적으로는 이것이 잘 작동한다고 생각합니다. 상황에 맞는 문법에 대한 자세한 정보를 제공해 주셔서 감사합니다. 정말 감사!
Dima1982

감사합니다!
iliar December

@ Dima1982 편집 내용을 살펴보면 기하 급수적으로 복잡한 비용으로 문제를 해결할 수있는 파서를 만들었습니다. 또한 나는 그것에 대해 생각했고 문제가 실용적인 가치가 있다면 괄호를 피하는 것이 가장 간단한 해결책 일 수 있습니다. 또는 함수 인수 목록의 끝을 구분하는 것과 같이 함수 괄호를 다른 것으로 만드는 것 &.
iliar December

1

문제는 함수의 인수가 괄호로 묶여 있고 인수 중 하나에 괄호가 포함될 수 있다는 것입니다.
가능한 해결책 중 하나는 문자열의 일부일 때 백 스페이스 \ before (또는)를 사용하는 것입니다.

  SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"\("|"\)")*

문자열 상수가 큰 따옴표로 묶인 문자열 상수의 일부로 큰 따옴표 ( ")를 포함하기 위해 C에서 사용하는 비슷한 솔루션입니다.

  example_string1='&f(!g\()'
  example_string2='&f(#g)'
  print(parser.parse(example_string1).pretty())
  print(parser.parse(example_string2).pretty())

출력

   start
     func
       f
       simple   !g\(

   start
     func
      f
      simple    #g

"("및 ")"를 LEFTPAR 및 RIGHTPAR로 바꾸는 OP의 자체 솔루션과 거의 동일하다고 생각합니다.
iliar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.