이클립스에 내장된 자바파서 활용하기 #1

이클립스는 JDT라는 자바개발환경을 포함하고 있습니다. 그리고 JDT는 자바개발과 관련하여 다양한 기능을 제공(e.g. Code Formatting)하기 위해 자바파서와 AST를 내장하고 있습니다. 따라서 이 부분을 잘 뜯어서 사용하면 훌륭한(!) 자바 파서를 공짜로 얻는 셈이 되는 것이죠.

앞으로 몇부에 걸쳐 JDT에 내장된 자바파서와 AST를 활용할 수 있는 방법을 설명하려고 합니다. 1부에서는 AST의 구조를 파악하는데 도움이 되는 ASTExplorer를 실행해보도록 하겠습니다. 이 과정에서 개발환경을 설정(e.g. 클래스패스 설정)하는 방법도 함께 다루겠습니다. 질문은 덧글로 남겨주세요.

올초에 빠른 시간안에 자바 코드 읽어 다른 형태의 코드로 변환하는 프로그램을 개발해야 했는데, 여러가지 방법을 찾아 고민하던 중에, JDT에 내장된 자바파서를 활용방안을 다룬 다음 웹문서를 발견하게 되었습니다.

Exploring Eclipse’s ASTParser

이 문서에서 ASTExplorer라는 예제 프로그램을 다운 받을 수 있는데, 이클립스 v3.02를 기준으로 하고 있어 다른 버전의 이클립스에 이 프로젝트를 import 하는 경우, 클래스패스에 추가된 JDT 라이브러리의 경로와 이름이 달라 에러가 발생합니다.

이 문제를 해결하는 가장 쉬운 방법은 클래스패스에 등록된 라이브러리를 지우고, eclipse\plugins에 존재하는 라이브러리를 클래스 패스에 추가하는 것입니다만 불필요한 라이브러리가 많이 추가되겠죠?

제가 사용하는 User Library를 첨부합니다. (이클립스 v3.3.1.1 기준) User Libraries에서 Import 하시면 됩니다.

jk10.userlibraries
첨부한 User Library를 클래스패스에 추가하셔도 JDT 버전이 올라가면서 변경된 부분 때문에 컴파일 에러가 발생할 것 입니다.

ASTMain.java의 다음 2라인의 코드를

return new NameEnvironmentAnswer(unit);
return new NameEnvironmentAnswer(classFileReader);

다음과 같이 수정해 주시면 컴파일 에러가 해결됩니다.

return new NameEnvironmentAnswer(unit, null);
return new NameEnvironmentAnswer(classFileReader, null);

ASTMain.java와 ASTExplorer.java 모두 main 메서드를 가지고 있습니다. ASTMain.java의 코드를 읽어보면 AST를 생성하는 작업을 정의하고 있는데, JDT 버전이 달라서 그런지 Exception이 발생하며 제대로 실행되지 않습니다.

ASTExplorer.java의 경우 실행에 문제가 없습니다. 실행해 보시면 다음과 같은 화면을 보실 수 있습니다.

사용자 삽입 이미지
이 프로그램은 JDT 내장 자바파서가 생성하는 AST 객체의 구조를 한눈에 파악할 수 있도록 화면 왼쪽에 AST의 구조를, 화면 오른쪽에 소스코드를 보여줍니다. AST에 익숙하지 않은 경우에 큰 도움을 주는 프로그램이죠.

AST에 대한 API Reference는 이클립스의 Help Contents에서 찾아 볼 수 있습니다. 웹에서 확인하고 싶으신 경우 다음 URL을 참조하세요.

http://help.eclipse.org/help33/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/core/dom/package-summary.html

1부에서는 간단히(?) JDT에 포함된 파서와 AST를 활용한 ASTExplorer를 실행해 보았습니다. 2부에서는 자바소스코드를 읽어 AST를 얻는 방법에 대하여 설명하겠습니다. (물론 ASTExplorer 소스코드를 읽어보시면 쉽게 이해하실 수 있겠습니다만… ^^;)

Visitor 패턴

Visitor 패턴은 AST(Abstract Syntax Tree)와 같은 자료구조를 순회하면서 필요한 작업을 정의하기에 용이하다. 이를 처음으로 접하게 된 것은 CEC(Comlumbia Esterel Compiler)의 소스코드를 분석할때였다. CEC는 Esterel 소스코드를 파싱하여 이를 XML 형태의 IR(Intermediate Representation)로 저장한다. CEC는 C++ 언어로 프로그램되어 있는데 XML 형태의 IR은 C++의 클래스 구조로 구성된 AST에 대응된다.

Esterel Source Code (Text) – AST (C++ Classes) – IR (XML)


배경은 이쯤에서 정리하고 본론으로 들어가보자. Esterel 언어는 하드웨어 언어인 Verilog로 컴파일되기도 하고 소프트웨어 언어인 C로 컴파일 되기도 하는 신기한(?)언어인데, Esterel 소스코드로 부터 C언어 코드가 나오기까지 IR을 XML 포맷으로 유지하며 여러단계의 독립적인 프로그램을 거치게 된다. 이때마다 XML 파일을 읽어 AST를 구축한 후 Visitor 패턴을 이용해 AST를 순회하며 필요한 작업을 수행한다. 물론 그러한 작업에 의해 AST가 수정되면 수정된 상태의 AST가 다시 XML형태로 저장된다.

Esterel Source Code – Parser – Expander – Dismantler – GRC synthesizer – C generator – C Source code


필자가 하고 있는 일은 동시설계 개발환경에서 Esterel 언어를 기반으로 임베디드 시스템을 정의하는 방법론을 제시하고 그 것으로 부터 임베디드 시스템 구현에 필요한 여러가지 인터페이스를 자동으로 생성하는 것이다. 임베디드 시스템을 XML 형태의 언어로 정의하도록 하고 이를 파싱하여 저장하는 나름의(?) AST를 자바 클래스로 구축하였다. 인터페이스 생성등 다양한 작업이 AST를 기반으로 이루어질 것 이며, 여기서는 AST에 저장되어 있는 정보를 다시 XML로 출력하는 프로그램을 예로 들어 Visitor 패턴을 설명하려고 한다.  

모든 AST의 클래스들은 다음과 같은 AbstractSpec 추상클래스를 상속한다. 여기에는 Visitor 패턴을 위한 메서드인 welcome 추상 메서드가 정의되어 있다.

package kr.ac.kaist.vicode.spec;
public abstract class AbstractSpec {
  public abstract void welcome(Visitor v);
}
그리고 당연히 모든 AST 노드에 해당하는 클래스들은 welcome 메서드를 정의해야 하는데 그 내용은 모두 다음과 같이 동일하다. 굳이 설명하자면 파라메터로 넘어온 visitor를 이용해서 자기 자신에 해당하는 visit 메서드를 호출하게 하는 것 이다.

public void welcome(Visitor v)
{
    v.visit(this);
}

다음으로 해야할 일은 Visitor 클래스를 생성하는 것이다. Visitor 패턴을 이용하는 클래스는 이 클래스를 상속받아서 visit 메서드를 구현하기만 하면 된다. 즉 파라메터로 입력받은 AST 노드에 해당하는 작업을 visit 메서드에 정의하면 된다.

package kr.ac.kaist.vicode.spec;

public abstract class Visitor {
  public abstract void visit(Spec n);
  public abstract void visit(Communication n);
  public abstract void visit(Api n);
  public abstract void visit(Rule n);
  public abstract void visit(DataFunc n);
  public abstract void visit(SignalFunc n);
  public abstract void visit(SignalDecl n);
  public abstract void visit(State n);
  public abstract void visit(Transition n);
  public abstract void visit(Set n);
}

Visitor 패턴을 사용하기 위한 준비작업은 모두 끝이 났다. 지금부터는 AST에 저장되어 있는 정보를 Visitor 패턴을 이용하여 AST를 순회하면서 XML형태로 출력하는 예제를 살펴본다. Spec2Xml 클래스는 Vistor 클래스를 상속하여 Visitor 패턴을 구현하고 있다. 각 visit 매서드에서 하는 일은 크게 두가지다. 하나는 해당 노드에 해당하는 작업을 수행하는 것이고, 나머지 하나는 자식 노드를 방문하게 하는 일인데 이과정을 위해서 단순히 각 AST 노드의 welcome 메서드를 호출하는 print 메서드를 정의하였다.

package kr.ac.kaist.vicode.spec.util;

import java.util.*;
import kr.ac.kaist.vicode.spec.*;

public class Spec2Xml extends Visitor {
  public void print(AbstractSpec n) {
       n.welcome(this);
  }

  public void visit(Spec n) {
       output(“<spec>”);
       print(n.getCommunication());
       print(n.getApi());
       print(n.getRule());
       output(“</spec>”);
  }

Vistor 패턴의 시작은 간단하다. AST의 최상위 노드에 대해서 print 메서드를 호출하면 자신의 자식노드에 대해서 visit 메서드를 호출하게 되고 이러한 일련의 과정을 통해 경우에 따라서 모든 노드를 방문하며 특정 작업을 수행할 수 있다. 위의 예제에서 Spec은 AST의 최상위 노드에 해당하며 이는 3가지 자식노드 (communication, api, rule)을 가지고 있으며 각각에 대해서 visit 메서드가 호출되도록 한다.  

지금까지 살펴본 것 처럼 Visitor 패턴은 AST와 유사한 자료구조를 빠짐없이 탐색하며 특정작업을 수행하기에 적합한 방법론을 제공한다. 특히 컴파일러나 인터프리터와 같이 AST를 사용하는 프로그램에서 잘 활용하면 유지보수가 용이하고 코드가 깔끔한 코드를 작성하는데 큰 도움이 될 것 같다.