2013-03-01 2 views
3

원시 파서 (parser)를 만들었지 만, 나는이 작업을 pyparsing에서 사용하고 싶습니다.pyparsing에서 노드 및 노드 관계를 어떻게 파싱합니까?

내가 파싱하고 싶은 두 가지 유형의 문자열이 있습니다. 단지 구문 분석 하나는 노드와 추가하면 노드가 다른 노드 내부에 있음을 표시 할 수 있습니다 두 번째 노드의 관계는

verb node1, node2, ... 

verb node1->node2->node3 

당신은 을 인용 할 수있는 하나 개 이상의 노드를 지정할 수 있습니다 를 사용하여 당신은 또한 노드 관계를 표시 할 수있는 ^

verb node1, node2^node3, node4 

를 추가하여 ->, <- 또는 <-> 지시자. 이 거의 대한 파싱에 매핑

node :: word composed of alphas, digits, '_' 
verb :: one of several defined keywords 
binop :: '->' | '<-' | '<->' 
nodeFactor :: node '^' node | node 
nodeExpr :: nodeFactor op nodeFactor 
nodeCommand :: verb nodeExpr [',' nodeExpr]... 

:

verb node1->node2<->node3 

은 다시 당신은 노드의 모습이 형식에 대한 ^

verb node1->node2^node4<->node3 
+0

이미 시도한 내용을 확인하는 것이 도움이됩니다. "원시 파서"를 최소한의 형태로 보여 주시면 거기에서 도울 수 있습니다. – Hooked

+0

verb + oneormore (node ​​+ optional (.... – Marinus

+0

)와 같은 표준 문법 구조를 사용하여 첫 번째 구문 분석 문제를 해결했습니다. 두 번째 문제는 실제로 쌍을 추출하기 때문에 매우 흥미 롭습니다. a-> b-> c가 구문 분석 할 때 그룹에 이전에 파싱 된 노드를 포함하려고합니다. – Marinus

답변

2

개념적 BNF를 사용하여 다른 내부에 있음을 표시 할 수 있습니다 단계를위한 단계 :

다음 부분은 pyparsing의 infixNotation 메서드 (이전의 operatorPrecedence)를 사용하여 가장 쉽게 구현됩니다. infixNotation을 사용하면 작업 계층을 정의 할 수 있으며 계층 구조에 정의 된 우선 순위에 따라 구문 분석 된 출력을 그룹화합니다. 나는 귀하의 '^' "내부"연산자는 이진수 '->', 등 연산자 전에 평가되어야한다고 가정합니다. infixNotation은 또한 괄호 안에 중첩을 허용하지만 절대적으로 필요한 것으로 보여주는 예제는 없습니다. infixNotation은 기본 피연산자 유형을 지정하고 그 뒤에 연산자를 표시하는 3 개 튜플 목록, 단항 연산자, 2 진 또는 3 진 연산자에 대한 값 1, 2 또는 3 및 왼쪽 또는 오른쪽에 대한 상수 opAssoc.LEFT 또는 RIGHT을 지정하여 정의합니다 연산자의 연관성 :

nodeExpr = infixNotation(nodeRef, 
    [ 
    ('^', 2, opAssoc.LEFT), 
    (binop, 2, opAssoc.LEFT), 
    ]) 

마지막으로 우리는 일종의 명령으로 해석 한 전체 표현식을 정의합니다. 쉼표로 구분 된 노드 표현식 목록은 nodeExpr + ZeroOrMore(Suppress(',') + nodeExpr)으로 직접 구현 될 수 있습니다 (구문 분석 된 출력에서 ​​쉼표를 표시하지 않습니다. 구문 분석시 유용하지만 이후에는 건너 뛰어도됩니다).

nodeCommand = verb('verb') + delimitedList(nodeExpr)('nodes') 

이름 '동사'와 각각의 표현에 분석 결과가 그 이름에 관련 지을 수 '노드'원인, 그것을 만들 것입니다 :하지만이 그렇게 자주, 대한 파싱이 방법 delimitedList을 제공 온다 파싱이 완료된 후에 파싱 된 데이터로 작업하는 것이 더 쉽습니다.지금

파서 테스트하려면 중첩리스트로 해석 토큰 아웃

tests = """\ 
    GO node1,node2 
    TURN node1->node2->node3 
    GO node1,node2^node3,node4 
    FOLLOW node1->node2<->node3 
    GO node5,node1->node2^node4<->node3,node6 
    """.splitlines() 
for test in tests: 
    test = test.strip() 
    if not test: 
     continue 
    print (test) 
    try: 
     result = nodeCommand.parseString(test, parseAll=True) 
     print (result.dump()) 
    except ParseException as pe: 
     print ("Failed:", test) 
     print (pe) 

dump()있어서 인쇄를 각 결과 이름을 나열하고 그 부착 값 :

GO node1,node2 
['GO', 'node1', 'node2'] 
- nodes: ['node1', 'node2'] 
- verb: GO 
TURN node1->node2->node3 
['TURN', ['node1', '->', 'node2', '->', 'node3']] 
- nodes: [['node1', '->', 'node2', '->', 'node3']] 
- verb: TURN 
GO node1,node2^node3,node4 
['GO', 'node1', ['node2', '^', 'node3'], 'node4'] 
- nodes: ['node1', ['node2', '^', 'node3'], 'node4'] 
- verb: GO 
FOLLOW node1->node2<->node3 
['FOLLOW', ['node1', '->', 'node2', '<->', 'node3']] 
- nodes: [['node1', '->', 'node2', '<->', 'node3']] 
- verb: FOLLOW 
GO node5,node1->node2^node4<->node3,node6 
['GO', 'node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6'] 
- nodes: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6'] 
- verb: GO 

이때 명령을 구문 분석 한 다음 verb을 기반으로 해당 동사를 수행하는 적절한 방법으로 발송하십시오.

그러나이 논리를 파이썬 개체를 사용하여 캡처하는 것이 도움이된다는 것을 알았습니다. 추상적 인 방법 doCommand의 다양한 동사 기능을 구현 명령의 간단한 클래스 계층 구조를 정의

# base class 
class Command(object): 
    def __init__(self, tokens): 
     self.cmd = tokens.verb 
     self.nodeExprs = tokens.nodes 

    def doCommand(self): 
     """ 
     Execute command logic, using self.cmd and self.nodeExprs. 
     To be overridden in sub classes. 
     """ 
     print (self.cmd, '::', self.nodeExprs.asList()) 

# these should implement doCommand, but not needed for this example 
class GoCommand(Command): pass 
class TurnCommand(Command): pass 
class FollowCommand(Command): pass 

적절한 명령 클래스의 인스턴스로 구문 분석 결과를 변환합니다이 방식 :

verbClassMap = { 
    'GO' : GoCommand, 
    'TURN' : TurnCommand, 
    'FOLLOW' : FollowCommand, 
    } 
def tokensToCommand(tokens): 
    cls = verbClassMap[tokens.verb] 
    return cls(tokens) 

그러나 파싱이 완료되면 구문 분석기의 구문 분석기 콜백으로이를 빌드하여 구문 분석이 완료되면 문자열과 하위 목록뿐만 아니라 doCommand 메서드를 호출하여 "실행"할 준비가 된 개체를 얻을 수 있습니다. 이렇게하려면, 단지 전체 nodeCommand 식에 구문 분석 작업으로 tokensToCommand 첨부 : 우리가 실제로 서브 클래스에 doCommand을 구현하지 않았기 때문에

for test in tests: 
    test = test.strip() 
    if not test: 
     continue 
    try: 
     result = nodeCommand.parseString(test, parseAll=True) 
     result[0].doCommand() 
    except ParseException as pe: 
     print ("Failed:", test) 
     print (pe) 

:

nodeCommand.setParseAction(tokensToCommand) 

이제 우리는 약간 우리의 테스트 코드를 수정

GO :: ['node1', 'node2'] 
TURN :: [['node1', '->', 'node2', '->', 'node3']] 
GO :: ['node1', ['node2', '^', 'node3'], 'node4'] 
FOLLOW :: [['node1', '->', 'node2', '<->', 'node3']] 
GO :: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6'] 

: 우리가 얻을 수있는 모든 단지 구문 분석 동사와 노드 목록을 반향하는 기본 기본 클래스의 행동입니다 (이 코드는 Python 3에서 실행되었고, 2.0.0으로 짂행되었다. 또한 1.5.7을 대한 파싱, 파이썬이 실행됩니다.)

편집

에서 [A, 연산, B, 연산 구조 조정 구문 분석 작업을 사용하여 체인 표현 a op b op c[a,op,b], [b, op, c]로 얻으려면, c]는 pairwise 표현식으로 분석됩니다. infixNotation 메서드를 사용하면 연산자 계층 구조의 수준에 첨부 할 구문 분석 동작을 정의 할 수 있습니다.

def expandChainedExpr(tokens): 
    ret = ParseResults([]) 
    tokeniter = iter(tokens[0]) 
    lastexpr = next(tokeniter) 
    for op,nextexpr in zip(tokeniter,tokeniter): 
     ret += ParseResults([[lastexpr, op, nextexpr]]) 
     lastexpr = nextexpr 
    return ret 

이 원래 체인 결과를 대체하기 위해 완전히 새로운 ParseResults를 구축 : 같은

체인화 된 표현의 결과를 구조 조정하는 방법이 보인다. 각 lastexpr op nextexpr이 자신의 하위 그룹으로 저장되는 방식에 유의하십시오. nextexprlastexpr으로 복사 된 다음 다음 op-nextexpr 쌍을 얻기 위해 반복됩니다.

는, 파서에이 포매터를 부착 infixNotation에 해당 계층의 수준의 제 4 요소로 추가하려면 : 이제

nodeExpr = infixNotation(nodeRef, 
    [ 
    ('^', 2, opAssoc.LEFT), 
    (binop, 2, opAssoc.LEFT, expandChainedExpr), 
    ]) 

의 출력 :

FOLLOW node1->node2<->node3 

로 확장됩니다

('FOLLOW', '::', [['node1', '->', 'node2'], ['node2', '<->', 'node3']]) 
+0

먼저 꺼내기 (a, b, '->') , 폴, 대답 주셔서 고마워 .Pyparsing 정말 대단해요! 당신은 내 질문에 대한 대부분의 답변했습니다. 나는 하나의 걸출한 문제가 있습니다. 만약 당신이 다음과 같은 구문 분석 이벤트가 node1 -> node2 <-> node3 당신이 이상적으로 끝내고 싶습니다. ('node1', '->', node2), ('node2', '<->', 'node3')]와 같이 목록을 다음과 같이 나열하면됩니다. 출력을 재분석 할 수는 있지만, pyparsing은 이전 노드에서 '되돌아 와서'다음 구문 분석 된 그룹을 생성하는 데 사용합니다. – Marinus

+0

편집 내용보기 대답에 추가됩니다. 필자는 파서 정의 솔루션을 갖고 있지 않지만 파싱 된 토큰을 'a b b op c'에서 'a op b'및 'b op c'로 재구성하는 구문 분석 작업을 사용합니다. – PaulMcG