2011-04-29 2 views
25

현재 PHP로 작성된 PHP 파서를 작성 중입니다. 기존 파서가 my previous question에 없었습니다. parser itself은 꽤 잘 작동합니다.AST를 소스 코드로 다시 컴파일

이제 분명히 자체 분석기가 정적 분석과 별개로 좋지 않습니다. 변환을 AST에 적용한 다음 소스 코드로 다시 컴파일하고 싶습니다. 변형을 적용하는 것은 큰 문제가 아니며, 일반적인 Visitor 패턴은 그렇게해야합니다.

내 문제는 현재 소스로 AST를 컴파일하는 방법입니다.

  1. 가 원래 코드의 서식을 유지하고 만 변경된 노드에 1을 적용 할 몇 가지 미리 정의 된 계획
  2. 를 사용하여 코드를 컴파일 : 내가 보는 두 가지 가능성이 기본적으로있다.

지금 나는 2.에 열중하고 싶다. (당신이 그것에 관한 조언을 얻은다면, 나는 그것을 듣고 싶다).

하지만 실제로 어떤 디자인 패턴을 사용하여 코드를 컴파일 할 수 있는지 잘 모르겠습니다. 이것을 구현하는 가장 쉬운 방법은 모든 노드에 ->compile 메서드를 추가하는 것입니다. 여기에서 볼 수있는 단점은 생성 된 출력 형식을 변경하는 것이 매우 어려울 것이라는 점입니다. 하나는 노드 자체를 변경해야합니다. 따라서 나는 다른 해결책을 찾고있다.

방문객 패턴도이 패턴에 사용할 수 있다고 들었지만 실제로 어떻게 작동하는지 생각할 수는 없습니다. 방문자 패턴을 이해하면 NodeTraverser 일부 노드가 반복적으로 모든 노드에서 반복되고 ->visit 메서드가 Visitor으로 호출됩니다. 이것은 노드 조작에 꽤 유망한 것으로 들리지만, Visitor->visit 메서드는 전달 된 노드를 간단히 변경할 수 있지만 컴파일에 사용하는 방법을 모르겠습니다. 분명한 아이디어는 노드 트리를 잎에서 루트로 반복하고 방문한 노드를 소스 코드로 대체하는 것입니다. 그러나 이것은 어떻게 든 매우 깨끗한 해결책으로 보이지 않습니다.

+0

50 점을 주신 것 같아요. 음, 고마워! –

답변

51

AST를 소스 코드로 다시 변환하는 문제는 일반적으로 "미리 인쇄"라고합니다. 두 가지 미묘한 변형이 있습니다. 가능한 한 원본과 일치하는 텍스트를 재생성합니다 (필자는 이것을 "충실도 인쇄"라고 부름). 멋지게 형식화 된 텍스트를 생성하는 (멋진) 미리 인쇄. 그리고 코더가 재생성 코드에서 작업하는지 (종종 충실도 인쇄가 필요함) 또는 의도 만이 (어떤 사전적인 사전 인쇄가 괜찮은지) 컴파일하는 방법에 따라 문제를 인쇄하는 방법은 입니다.

prettyprinting을 수행하려면 일반적으로 고전적인 파서가 수집하는 것보다 더 많은 정보가 필요합니다. 대부분의 파서 생성기가이 추가 정보 수집을 지원하지 않는다는 사실에 의해 악화됩니다. 나는 이것을 잘 수행 할 수있는 정보를 수집하는 파서를 "리엔지니어링 파서"라고 부른다. 자세한 내용은 아래를 참조하십시오.

prettyprinting이 수행되는 기본적인 방법은 AST (사용자가 지정한대로 방문자 패턴)를 걷고 AST 노드 내용을 기반으로 텍스트를 생성하는 것입니다. 기본 트릭은 : 자식 노드를 왼쪽에서 오른쪽으로 (원래 텍스트의 순서라고 가정) 자식 노드를 호출하여 나타내는 텍스트를 생성하고이 AST 노드 유형에 적절한 추가 텍스트를 산재시킵니다. 당신이 나무를 방문으로이 즉석에서 텍스트를 뱉는 것을

PrettyPrintBlock: 
    Print("{"}; PrintNewline(); 
    Call PrettyPrint(Node.children[1]); // prints out statements in block 
    Print("}"); PrintNewline(); 
    return; 


PrettyPrintStatements: 
    do i=1,number_of_children 
     Call PrettyPrint(Node.children[i]); Print(";"); PrintNewline(); // print one statement 
    endo 
    return; 

참고 : 문장 블록을 prettyprint하기 위해 다음과 같은 psuedocode가있을 수 있습니다.당신이 관리하는 데 필요한 세부 사항 중 한 곳입니다

:

  • 리터럴을 나타내는 AST 노드를 들어, 당신은 리터럴 값을 다시 생성해야합니다. 대답이 정확하기를 원한다면 보이는 것보다 어렵습니다. 정밀도를 잃지 않고 부동 소수점 수를 인쇄하면 로트이 보이기보다 어렵습니다 (과학자는 Pi의 값을 손상시킬 때 그것을 싫어합니다). 문자열 리터럴의 경우 따옴표 및 문자열 리터럴 내용을 다시 생성해야합니다. 이스케이프해야하는 문자의 이스케이프 시퀀스를 다시 생성하려면주의해야합니다. PHP 이중 인용 문자열 리터럴은 AST에서 단일 토큰으로 표현되지 않기 때문에 조금 더 어려울 수 있습니다. (우리의 PHP Front End (리엔지니어링 파서/prettyprinter)는 "문자"문자열 내부에 적용되는 변환을 가능하게 문자열 조각을 연결하는 식으로 본질적를 나타냅니다

  • 간격이 :. 일부 언어가 중요한 위치에 공백을 필요로하는가. 토큰 ABC17 42는 ABC1742로 인쇄하지 않는 것이 더 좋지만 토큰 (ABC17)은 (ABC17)로 인쇄하는 것이 좋습니다.이 문제를 해결하는 한 가지 방법은 공백이있는 곳이면 어디든지 공백을 넣는 것입니다. 결과 : 너무 많은 공백. 결과를 컴파일하는 경우에만 문제가되지 않습니다.

  • 줄 바꿈 : 기술적으로 임의의 공백을 허용하는 언어는 한 줄의 텍스트로 재생성 될 수 있습니다. f 결과를 컴파일하려고합니다. 때로는 이 생성 된 코드를 보면 불가능 해집니다. 따라서 주요 언어 요소 (명령문, 블록, 메서드, 클래스 등)를 나타내는 AST 노드에 대해 줄 바꿈을 도입하는 방법이 필요합니다. 이것은 대개 어렵지 않습니다. 그러한 구조를 나타내는 노드를 방문 할 때, 구조체를 출력하고 개행을 추가하십시오.

  • 사용자가 결과를 받아 들일 수 있도록하려면 일반적으로 저장하지 않을 원본 텍스트의 일부 속성을 유지해야합니다. 리터럴의 경우 기수를 다시 생성해야 할 수 있습니다 리터럴의; 16 진수 리터럴로 숫자를 입력 한 코더는 정확하게 똑같은 것을 의미하더라도 10 진수를 다시 생성하면 만족스럽지 않습니다. 마찬가지로 문자열에는 "원래"따옴표가 있어야합니다. 대부분의 언어는 "또는"을 문자열 인용 문자로 사용하고 사람들은 원래 사용했던 것을 원합니다. PHP는 문제를 인용하고 문자열 리터럴에서 이스케이프해야하는 문자를 결정합니다. 일부 언어는 대소 문자를 허용합니다 (또는 약어), 대문자와 소문자의 변수 이름이 같은 변수를 의미하며 원래의 저자는 일반적으로 원래의 대소 문자를 원합니다. PHP는 다른 유형의 식별자 (예 : "$")에서 재미있는 문자를 사용하지만, (리터럴 문자열의 $ 변수를 참조하십시오.) 사람들은 종종 원래의 레이아웃 형식을 원하며,이를 위해서는 구체적인 토큰에 대한 열 번호 정보를 저장해야하며 해당 열의 사용시기에 대한 사전 인쇄 규칙이 있어야합니다 - 미리 인쇄 된 텍스트를 위치시킬 수있는 데이터, 가능한 경우 같은 열에서 미리 작성된 행이 해당 열을 채우는 경우 수행 할 작업

  • Comments : 대부분의 표준 파서 (젠드 분석기를 사용하여 구현 한 파서를 포함하여)는 주석을 완전히 버립니다. 다시 말하지만, 사람들은 이것을 싫어하며 주석을 잃어 버리는 사전 인쇄 된 응답을 거부합니다. 일부 prettyprinter가 원본 텍스트 을 사용하여 코드를 재생성하는 주된 이유입니다. 다른 하나는 열 번호 정보를 캡처하지 않은 경우 충실도 인쇄를 위해 원래 코드 레이아웃을 복사하는 것입니다. IMHO, 올바른 트릭은 AST에서 주석을 캡처하여 AST 변환이 주석을 검사/생성 할 수 있도록하지만 모두가 자신의 설계를 선택합니다.

이 모든 "추가"정보는 훌륭한 재구성 파서가 수집합니다. 기존의 파서는 일반적으로 그 중 하나를 수집하지 않기 때문에 허용되는 AST를 인쇄하기가 어렵습니다.

보다 원칙적인 접근 방식은 원본 서식 파일을 최대한 일치시키기 위해 텍스트를 재생성하는 것이 목적 인 정교한 인쇄에서 멋진 서식 지정을위한 프리 형식 인쇄를 구분합니다. 터미널 수준에서 충실도 인쇄를 원한다는 것은 분명합니다. 귀하의 목적에 따라 멋진 서식 또는 충실도 인쇄로 예쁘게 인쇄 할 수 있습니다. 우리가 사용하는 전략은 AST가 변경되지 않았을 때 충실도 인쇄로 기본 설정하고, AST가있는 곳에서 사전 인쇄 (종종 변경 기계에 열 번호 나 숫자 기수 등에 관한 정보가 없기 때문에)입니다. 변환은 새로 생성 된 AST 노드에 "충실도 데이터가 없습니다"라고 표시합니다.

prettyprinting에 대한 조직화 된 접근 방식은 사실상 모든 텍스트 기반 프로그래밍 언어가 직사각형 문자 블록 블록으로 멋지게 렌더링된다는 것을 이해하는 것입니다. (Knuth의 TeX 문서 생성기도이 아이디어를 가지고 있습니다.) 재생성 된 코드 (예 : 터미널 토큰에 대해 직접 생성 된 프리미티브 상자)를 나타내는 텍스트 상자가있는 경우 연산자를 사용하여 해당 상자를 구성 할 수 있습니다. 수평 컴포지션 (다른 상자의 오른쪽 상자에 스택), 수직 (서로의 상단에 스택 상자, 인쇄 줄 바꿈을 대체이 효과), 들여 쓰기 (공백의 상자 가로 조성은) 등 그럼 당신은 텍스트 상자를 구축하고 구성하여 prettyprinter을 구성 할 수 있습니다

PrettyPrintBlock: 
    Box1=PrimitiveBox("{"); Box2=PrimitiveBox("}"); 
    ChildBox=PrettyPrint(Node.children[1]); // gets box for statements in block 
    ResultBox=VerticalBox(Box1,Indent(3,ChildBox),Box2); 
    return ResultBox; 

PrettyPrintStatements: 
    ResultBox=EmptyBox(); 
    do i=1,number_of_children 
     ResultBox=VerticalBox(ResultBox,HorizontalBox(PrettyPrint(Node.children[i]); PrimitiveBox(";") 
    endo 
    return; 

실제 값은 임의의 노드가 임의의 중간 텍스트를 사용하여 임의의 순서로 자식에 의해 생성 된 텍스트 상자를 작성할 수 있다는 것입니다. 이 방법으로 거대한 텍스트 블록을 재배치 할 수 있습니다 (클래스의 메서드를 메서드 이름 순서로 상상해보십시오). 마주 치지 않는 텍스트는 출력되지 않습니다. 루트에 도달했을 때만, 또는 모든 자식 박스가 올바르게 생성 된 것으로 알려진 AST 노드가 있습니다.

우리의 DMS Software Reengineering Toolkit은이 방법을 사용하여 구문 분석 할 수있는 모든 언어 (PHP, Java, C# 등 포함)를 미리 미리 인쇄합니다. 대신 방문자를 통해 AST 노드에있는 상자 계산을 부착, 우리는 .... (수평 박스에 대한 도메인 - 특정 텍스트 박스 표기

  • V를

    • H (...)를 상자 계산을 첨부) 우리가 간결 문법 (파서)와 prettyprinter ("반 파서")을 (를) 이용하실 수 있도록 수직 상자
    • I (...) 들여 박스) 문법 규칙에 직접

    에 대한 한 곳. 사전 지정자 상자 규칙은 DMS에 의해 방문자로 자동 컴파일됩니다. prettyprinter 기계는 주석이 어떻게 작용 하는지를 이해할만큼 똑똑해야하며, 솔직히 말해서 약간 비현실적이지만 한 번만하면됩니다. DMS 예 :

    block = '{' statements '}' ; -- grammar rule to recognize block of statements 
    <<PrettyPrinter>>: { V('{',I(statements),'}'); }; 
    

    당신이이 문법 규칙과 prettyPrinting를 규칙이 결합되는 방법을 보여주는 Wirth's Oberon programming language PrettyPrinter을 위해 수행하는 방법의 더 큰 예를 볼 수 있습니다. PHP 프론트 엔드는 이처럼 보이지만 훨씬 더 커졌습니다.

    prettyPrinting를 할 수있는 더 복잡한 방법 (수단, 트리를 걸어 나무에 참가하였습니다 위해 텍스트 또는 다른 데이터 구조를 구축) 특별한 텍스트 상자에 텍스트 상자를 생성하는 구문 지시 번역기를 구축하는 것입니다 AST.텍스트 상자 AST는 다른 트리 워크에서 미리 인쇄하지만, 그 작업은 기본적으로 간단합니다. 텍스트 상자를 인쇄하십시오. 이 기술 문서 참조 : Pretty-printing for software reengineering

    추가 점 : 물론이 모든 기계를 직접 만들 수 있습니다. 하지만 파서 생성기 (파서 생성기를 만드는 많은 작업과 흥미로운 방식으로 목표에 기여하지 않는 작업)를 사용하는 것과 같은 이유는 오프사이어 방식을 선택하는 것과 같은 이유입니다. 선반 prettyprinter 생성기. 주변에 많은 파서 생성기가 있습니다. 많은 사전 태엽 장치 생성기. [DMS는 둘 다 내장 된 몇 안되는 사람 중 하나입니다.]

  • +0

    그 꽤 광범위한 설명을 주셔서 감사합니다, 나는 다음 며칠 동안 그 팁을 시도 할 것입니다. 추신 : Simlpe 언어 예제 링크를 통해 404. – NikiC

    +0

    @nikic : 처음 제출 한 내용이 잘못되었지만 문제를 해결했습니다. 다시 시도하십시오. –

    +1

    좋아, 도와 줘서 고마워. Ira : 예쁜 프린터를 구현할 수 있었다. (많은 엣지 케이스 버그를 제거하는 데는 상당한 시간이 걸렸다.) 공백이나 주석 정보는 유지되지 않습니다. 나는 그것을 구현하는 것이 어려울 것이라고 생각했다. github에서 결과 패키지를 찾을 수 있습니다 : https://github.com/nikic/PHP-Parser :) 다시 한번 감사드립니다! – NikiC

    관련 문제