폴리글롯에 패스 추가

Copyright (c) 2016-, All Rights Reserved by Kwanghoon Choi
(아래 내용을 자유롭게 이용하되 다른 웹 사이트 등을 통한 배포를 금합니다.)

이번에는 폴리글롯에 원하는 기능을 추가하도록 컴파일 패스를 추가하는 방법을 살펴본다. 지난번에 폴리글롯을 설치하고 JL7 컴파일러를 지정해서 만들었지만 기존의 컴파일 기능을 변경한 것은 아니었다.

 

1. AST의 실체와 확장 가능한 AST 구조

지난번에 작성한 MyPolyglotProject은 Java 1.7 컴파일러이고, 해당 버전으로 작성한 소스 프로그램을 받아 AST로 변환하고 그 다음 컴파일 단계를 진행한다.

  • 예) A.java -> JL7 컴파일 -> AST -> ...

AST는 프로그램 소스를 노드로 표현한 것이다. polyglot.ast.Node 인터페이스를 기반으로 다양한 상속 인터페이스를 두어 각각의 노드를 표현한다. 예를 들어 polyglot.ast.MethodDecl 인터페이스에서 메소드 선언을 표현하는 노드를 다룬다.

(polyglot.ast) NullLit 인터페이스가 표현하는 것은? Java 프로그램에서 사용하는 null 상수를 표현한다. 예를 들어,  if ( x == null ) ... 과 같이 프로그램을 작성하면 If 노드의 조건식을 표현하는 노드에서 NullLit 객체를 가리키도록 구성할 것이다.

참고로 Lit는 Literal을 줄인 것으로 상수를 뜻한다. 예를 들어, 123, "123", null, 1.23이 모두 리터럴이다.

NullLit_c 클래스는 NullLit 인터페이스를 구현한다. If를 If_c에서 구현한다. 이것이 폴리글롯의 노드 인터페이스와 이를 구현한 클래스의 이름을 짓는 방법이다.

노드들의 트리인 AST가 주어졌을때 컴파일러는 대개 무엇을 할까?

  1. AST의 루트 노드에서 시작해서 개별 노드를 차례대로 따라간다.
  2. AST를 변환한다. 예를 들어, for (x : s) ... => ... while ( ... ) ...
  3. 소스 형태로 다시 출력한다.

1,2,3은 모든 노드들에 공통으로 적용할 연산들이다. 폴리글롯은 polyglot.ast.NodeOps 인터페이스를 두어 노드에 적용할 범용 연산들을 모아놓았다.

  1. visit... ()
  2. translate()
  3. prettyPrint ... ()

새로운 연산을 추가하고 싶으면 보통 NodeOps를 상속받은 ...Ops로 끝나는 이름의 인터페이스를 생성한다.

컴파일러 구조가 확장가능하면, 기존의 컴파일러 소스를 한 줄도 고치지 않고 새로운 노드를 추가하거나 기존 노드로 구성된 AST에 새로운 패스를 추가할 수 있다. 예를 들어, Java 1.4를 확장해서 1.5나 1.7을 만들때 기존  AST 노드를 수정하면 안된다!

확장가능한 컴파일러 구조를 갖추기 위해서 폴리글롯은 모든 AST 노드에 확장노드(의 리스트)를 둔다. 즉, 새로운 If를 언어에 넣고 싶다면,

  • 기존 Java 1.4의 If_c 클래스를 그대로 사용하고
  • 적절한 확장노드 클래스를 새로 정의해서
  • If_c 객체로 표현한 노드에 확장노들로써 연결한다.

모든 확장노드의 기반 인터페이스로 polyglot.ast.Ext를 정의하고 Ext_c 클래스를 Node에서 가리키도록 구조화하였다.

기본 노드와 확장 노드들의 관계 (ext, node 필드)

한가지 주의할 점은,

  • 새로운 언어로 확장할때는 당연히 Ext와 Ext_c 구조를 활용하지만
  • 기존 언어를 그대로 사용할때도 이 구조를 활용할 수 있다.

Java 언어를 바꾸지 않더라도 AST에 우리가 하고 싶은 일을 추가할 때도 Ext, Ext_c 구조를 활용해야 하기 때문이다. 각 AST 노드에 analyze() 메소드를 적용하기 위해 NodeOps 인터페이스에 이 메소드 선언을 추가한다고 가정하면 기존 코드를 수정했기 때문에 확장 가능한 컴파일러 구조라 할 수 없다. 이런 경우, 이 새로운 메소드를 지원하는 확장 노드를 만들어 기존 노드의 ext에 연결함으로써 폴리글롯은 확장가능한 컴파일러 구조를 유지한다.

polyglot.ast에 있는 Java 1.4의 구성 요소를 노드로 표현하는 각종 인터페이스를 기본으로 하고, Java 1.5로 확장하기 위해 polyglot.ext.jl5.JL5Ext를 두었고, Java 1.7로 확장하고자 polyglot.ext.jl7.JL7Ext를 두었다.

 

2. 폴리글롯 컴파일러의 시작점과 확장 구조

앞에서 폴리글롯 컴파일러를 생성했을 때 tool.compiler.java 패키지의 Main 클래스에 시작 메소드 main이 있다. 이 메소드를 호출하면서 컴파일러를 시작한다.

  • ExtensionInfo를 (두번째) 인자로 지정하고 기존의 시작점 polyglot.main.Main 클래스의 start 메소드를 호출한다.
// tool.compiler.java
public class Main
{
 public static void main(String[] args) {
   polyglot.main.Main polyglotMain = new polyglot.main.Main();

   try {
     polyglotMain.start(args, new tool.compiler.java.ExtensionInfo());
   }
   catch (polyglot.main.Main.TerminationException e) {
     System.err.println(e.getMessage());
     System.exit(1);
   }
 }
}

polyglot.main.Main 클래스는 변함없지만 ExtensionInfo를 다르게 지정함으로써 다양한 컴파일러를 구성한다. 기본적으로 newext.sh로 생성한 프로젝트에 포함된 ExtensionInfo를 지정한다. Java 1.7 컴파일러로 구성하기 위해서 polyglot.ext.jl7 패키지의 JL7ExtensionInfo를 가지고 시작한다.

     polyglotMain.start(args, new tool.compiler.java.ExtensionInfo());
     polyglotMain.start(args, new polyglot.ext.jl7.JL7ExtensionInfo());

이렇게 새로운 구문을 포함하는 컴파일러를 만들거나 기존 구문과 동일하지만 새로운 패스를 추가한 컴파일러를 만들려면 ExtensionInfo를 적절히 변경하면 된다.

 

3. 폴리글롯을 확장하는 방법은 새로운 ExtensionInfo를 만드는 것

프로젝트에 기본으로 들어가 있는 것은 polyglot.frontend.JLExtensionInfo를 상속받은 ExtensionInfo 클래스다.

ExtensionInfo 클래스에서 다음의 옵션을 지정한다.

  • 소스 프로그램 확장자
  • 컴파일러 이름
  • 파서: 새로운 구문을 추가할 때 반드시 필요
  • 노드 팩토리와 확장노드 팩토리: ==> 노드 팩토리는 기본 노드를 만들고, 확장노드 팩토리는 관련 확장 노드를 만들어 이 기본 노드에 연결시킨다. 새로운 구문을 추가할 때도 새로운 노드/확장노드 팩토리가 필요하지만 기존 AST 노드에 새로운 일(e.g., NodeOps에 선언되지 않은 analyze())을 할때도 두 팩토리가 필요하다.
  • 타입 시스템
  • 스케쥴러

폴리글롯으로 새로운 컴파일러를 만드는 것은 위의 옵션을 새롭게 구성한 ExtensionInfo를 만드는 것으로 볼 수도 있다. MyPolyglotProject 프로젝트에 생성된 ExtensionInfo  소스 코드를 보면 위의 옵션들 어떻게 지정하는지 구체적으로 살펴볼 수 있다.

  • cf. Java 1.4   polyglot.frontend.JLExtensionInfo
/**
 * Extension information for myproj extension.
 */
public class ExtensionInfo extends polyglot.frontend.JLExtensionInfo {
  static {
     // force Topics to load
     @SuppressWarnings("unused")
     Topics t = new Topics();
  }

// 기존 폴리글롯 소스에서 아래 메소드들을 호출함!!!

 @Override
 public String defaultFileExtension() {
     return "java";   // 확장자
 }

 @Override
 public String compilerName() {
     return "myprojc";   // 컴파일러 이름
 }

 @Override
 public Parser parser(Reader reader, Source source, ErrorQueue eq) {
     Lexer lexer = new Lexer_c(reader, source, eq);
     Grm grm = new Grm(lexer, ts, nf, eq);
     return new CupParser(grm, source, eq);   // 파서
 }

 @Override
 public Set<String> keywords() {
     return new Lexer_c(null).keywords();
 }

 @Override
 protected NodeFactory createNodeFactory() {
     return new JavaNodeFactory_c(JavaLang_c.instance, // 노드 팩토리
                  new JavaExtFactory_c()); // 확장 노드 팩토리
 }

 @Override
 protected TypeSystem createTypeSystem() {
     return new JavaTypeSystem_c(); // 타입 시스템
 }

}

위의 소스 코드에서 JavaLang은 만들고자 하는 컴파일러의 입력 프로그램의 언어를 가리킨다. 새로운 구문을 추가해서 확장된 언어를 구성할 수 있고, 또한 기존 구문을 그대로 사용하더라도 새로운 패스를 추가해서 확장 노드를 사용함으로 해서 새로운 언어를 구성할 수 있다. 기본 노드와 여기에 연결된 여러 확장 노드가 있을 때 각 노드 별로 새로운 언어라고 구분하는 것이 폴리글롯의 구조이다. 이때 컴파일러가 다루는 언어에 따라 기본 노드를 선택할지 확잔 노드를 선택할지 구분해야 하고 또한 확장 노드가 여러 개가 매달려 있는 경우 어떤 확장 노드를 선택할지 결정하는 일을  ...Lang 클래스에서 구현한다. 나중에 상세 코드를 살펴본다.

위의 ExtensionInfo 클래스에서는 특별히 지정하지 않았지만 아래의 추상 메소드를 구현해서 스케쥴러를 지정할 수도 있다. 스케쥴러는 컴파일러 패스의 순서를 정한다.

// polyglot.frontend
protected abstract Scheduler createScheduler();

참고로 폴리글롯에 구현되어 있는 컴파일 패스는 다음 그림과 같다. 스케쥴러는 이 중에 어느 위치에 원하는 패스를 추가할지 지정하는 역할을 담당한다. 화살표 머리가 가리키는 박스의 컴파일 패스가 먼저 실행되어야 화살표 꼬리에 붙은 박스의 컴파일 패스를 실행한다.

참고로, 폴리글롯을 실행할 때 옵션을 주면 다양한 정보의 로그를 출력해서 세부 동작을 확인할 수 있다.

  • -report verbose=1
  • ===> 소스 코드 내에 Report.report(1, msg); 라고 작성된 경우 해당 msg를 출력한다.

숫자를 높이면 매우 다양한 로그들을 확인할 수 있는데, 그 중 컴파일러가 갖추고 있는 각 pass/goal이 위 그림의 순서대로 실행 됨을 확인할 수 있다.

 

4. 폴리글롯 기반 컴파일러 패스 추가하기

이 메모에서 추가하고자 하는 컴파일러 패스는 AST 트리를 받아 각 노드를 방문하며 분석하는 일을 하는 것이다.

분석하는 일을 담당할 메소드는 기존 NodeOps 인터페이스에 선언되어 있지 않다. 따라서 새로운 연산을 메소드로 선언하는 ...Ops 인터페이스를 선언해야 한다.

  • tool.compiler.java.ast.EquGenOps (extending NodeOps) 인터페이스
  • ===> 하고 싶은 연산을 메소드로 선언. equGenEnter, equGen  (equGenLeave???)
public interface EquGenOps extends NodeOps {
    EquGenerator equGenEnter(EquGenerator v);
    Node equGen(EquGenerator v);
}

그 다음에 할 일은 위 두 메소드를 적용 가능한 확장 노드를 만드는 것이다. 각 기본 노드에 대해 확장 노드를 만들 수 있고, 이러한 모든 확장 노드의 시조 클래스 역할을 할 ...Ext 클래스를 만든다.

  • tool.compiler.java.ast.EquGenExt (extending Ext_c, EquGenOps)
  • ===> 하고 싶은 연산을 추가하는 모든 확장 노드를 표현하는 시조 클래스
public class EquGenExt extends Ext_c implements EquGenOps {  
          // TODO: Not JL7Ext, but Ext_c to override lang()!!!
   private static final long serialVersionUID = SerialVersionUID.generate();
	
   @Override
   protected Object clone() throws CloneNotSupportedException {
	return super.clone();
   }
	
   public static EquGenExt ext(Node n) {
	Ext e = n.ext();
	while ( e != null && !(e instanceof EquGenExt) ) {
		e = e.ext();
	}
	if ( e == null ) {
	  throw new InternalCompilerError("No EquGen extension object for node"
			+ n + " (" + n.getClass() + ")", n.position());
	}
		
	return (EquGenExt)e;
   }
		
   @Override  // TODO: Must extend Ext_c, not JL7Ext!!!
   public final EquGenLang lang() {
	return EquGenLang_c.instance;
   }
	
    // TODO:  Override operation methods for overridden AST operations.
   @Override
   public EquGenerator equGenEnter(EquGenerator v) {
	// By default, return the given visitor.
	return v;
   }

   @Override
   public Node equGen(EquGenerator v) {
	// By default, do nothing.
	return node();
   }
}

EquGenExt 클래스의 ext(Node n) 메소드의 주요 코드는 while 루프다. 기본 노드 n을 받아 이 노드에 연결된 확장 노드들을 차례대로 따라가다 EquGenExt와 호환되는 확장 노드를 만나면 루프를 중지하고 이것을 리턴한다.

EquGenExt 클래스에 equGenEnter와 equGen 메소드의 구현이 있다.

확장 노드의 시조 클래스 EquGenExt를 상속받아 구체적인 확장 노드 클래스를 만들 차례이다. 기본 노드 ClassDecl을 확장하는 것에 관심이 있다고 가정하고, EquGenClassDeclExt 클래스를 만든다.

  • tool.compiler.java.ast.EquGenClassDeclExt (extending EquGenExt)
  • ===> 확장 노드의 예: 기존의 ClassDecl 노드에 하고싶은 연산을 포함하는 포함시킨 확장 노드
  • ===> ClassDecl 노드의 ext 필드가 EquGenClassDeclExt 노드를 궁극적으로 가리키게 됨
  • ===> this.node()는, 확장 노드의 필드 node를 리턴하는 것으로 node는 기본 노드(원래 ClassDecl)을 가리킨다.
public class EquGenClassDeclExt extends EquGenExt {
  private static final long serialVersionUID = SerialVersionUID.generate();

  @Override
  public EquGenerator equGenEnter(EquGenerator v) {
	ClassDecl clzDecl = (ClassDecl)this.node();
	Report.report(0, "Class declaration: " + clzDecl.name());
	return super.equGenEnter(v);
  }

  @Override
  public Node equGen(EquGenerator v) {
	return super.equGen(v);
  }	
}

만일 If 노드에 하고 싶은 연산을 포함시키도록 확장 노드를 구성하려면 EquGenIfExt 클래스를 만들고 If 노드의 ext 필드가 이 클래스의 노드를 가리키도록 만들어야 할 것이다.

Q1. 어떤 과정을 통해서 기존 노드의 ext 필드가 확장 노드를 가리키게 만드는가?

A1. 노드 팩토리와 확장 노드 팩토리가 ext 필드 (그리고 node 필드)를 적절히 셋업하는 역할을 담당한다. 즉, EquGenNodeFactory_c에서 기존 노드(e.g. ClassDecl)를 만들 때 EquGenExtFactory_c 클래스를 이용하여 확장 노드를 만들고, 기존 노드의 ext에 연결.  (이 팩토리 클래스에 대해서는 아래에서 곧 설명함)

Q2. 기존 노드와 확장 노드가 연결된 상태로 있을때 어떻게 적절한 확장 노드를 찾는가? (여러 개의 확장 노드가 리스트를 형성하고 있을 수 있다.)

A2. 찾아가려고 하는 확장 노드와 연관된 ...Lang이 EquGenExt 클래스의 ext 메소드를 통해서 적절히 찾아준다. (EquGenExt.ext(Node n) 메소드의 코드)

 

지금까지 확장 노드의 클래스를 만드는 방법을 살펴보았다. 이제 이 클래스를 사용하여 확장 노드를 만드는 확장 노드 팩토리를 만들어야 한다.

원래 폴리글롯 소스에 확장 노드 클래스가 없는데 이 클래스를 어떻게 사용하는가 고민해야 한다. 기존 소스를 수정하면 쉽지만 확장 가능한 컴파일러 구조를 유지하기 위해서는 다른 방법이 필요하다.

확장 노드는 기본 노드가 존재해서 이 노드를 확장하는 것이다. 따라서 각 기본 노드에 대해 확장 노드를 생성하는 훅(hook) 메소드를 미리 준비해서 이 메소드를 재정의 한다. 기존 폴리글롯 소스에서는 훅 메소드를 부르면 재정의한 메소드가 호출되어 그 안에서 새로 정의한 확장 노드를 만들면 된다!

  • ExtFactory 인터페이스를 구현하는 AbstractExtFactory_c 클래스의 extClassDecl()의 짝꿍 메소드 extClassDeclImpl()를 아래와 같이 구현하면 확장노드가 기존 ClassDecl 노드에 알아서 연결된다!
// EquGenExtFactory_c 클래스
   protected Ext extClassDeclImpl() { // 오버라이딩하고
      return new EquGenClassDeclExt();
   }
  • extClassDecl과 extClassDeclImpl 메소드를 훅(hook) 메소드라 부른다.
  • AbstractExtFactory_c.extClassDeclImpl()을 오버라이딩해서 new EquGenClassDeclExt()를 리턴하도록
  • 여기까지 완료하면 기존 ClassDecl에 이 확장 노드가 연결된다!!!

 

확장 노드 팩토리에 관한 인터페이스와 클래스는 다음과 같다.

  • tool.compiler.java.ast.EquGenExtFactory 인터페이스
  • tool.compiler.java.ast.EquGenExtFactory_c  클래스 (extClassDeclImpl 메소드 재정의)
  • tool.compiler.java.ast.EquGenAbstractExtFactory_c 클래스 (JL7AbstractExtFactory_c 클래스를 상속받고 EquGenExtFactory 인터페이스를 구현)
public interface EquGenExtFactory extends JL7ExtFactory {
    // TODO: Declare any factory methods for new extension nodes.

}
public final class EquGenExtFactory_c extends EquGenAbstractExtFactory_c {
	
	public EquGenExtFactory_c() {
		super();
	}

	public EquGenExtFactory_c(ExtFactory nextExtFactory) {
		super(nextExtFactory);
	}
	
	@Override
	protected Ext extNodeImpl() {
		return new EquGenExt();
	}
	

    // TODO: Override factory methods for new extension nodes in the current
    // extension.
	
	@Override
	protected Ext extClassDeclImpl() {
		return new EquGenClassDeclExt();
	}
}
package tool.compiler.java.ast;

import polyglot.ast.ExtFactory;
import polyglot.ext.jl7.ast.JL7AbstractExtFactory_c;

public abstract class EquGenAbstractExtFactory_c extends
	JL7AbstractExtFactory_c implements EquGenExtFactory {
	
	public EquGenAbstractExtFactory_c() {
		super();
	}

	public EquGenAbstractExtFactory_c(ExtFactory nextExtFactory) {
		super(nextExtFactory);
	}
	
    // TODO: Implement factory methods for new extension nodes in future
    // extensions.  This entails calling the factory method for extension's
    // AST superclass.
}

 

훅 메소드는 각 언어의 기본 노드를 확장하는 노드를 만들기 위해 준비되었다. Java 1.4에서 정의한 노드에 대한 ExtFactory 인터페이스, Java 1.5 노드에 대한 JL5ExtFactory 인터페이스, Java 1.7 노드에 대한 JL7ExtFactory 인터페이스에 선언된 훅 메소드 목록은 다음과 같다.

1) polyglot.ast.ExtFactory 인터페이스 [ AbstractExtFactory_c 클래스 ]

public interface ExtFactory extends Iterable {

    /**
     * The next extFactory in the chain. 
     */
    ExtFactory nextExtFactory();

    //////////////////////////////////////////////////////////////////
    // Factory Methods
    //////////////////////////////////////////////////////////////////

    Ext extId();
    Ext extAmbAssign();
    Ext extAmbExpr();
    Ext extAmbPrefix();
    Ext extAmbQualifierNode();
    Ext extAmbReceiver();
    Ext extAmbTypeNode();
    Ext extArrayAccess();
    Ext extArrayInit();
    Ext extArrayTypeNode();
    Ext extAssert();
    Ext extAssign();
    Ext extLocalAssign();
    Ext extFieldAssign();
    Ext extArrayAccessAssign();
    Ext extBinary();
    Ext extBlock();
    Ext extBooleanLit();
    Ext extBranch();
    Ext extCall();
    Ext extCanonicalTypeNode();
    Ext extCase();
    Ext extCast();
    Ext extCatch();
    Ext extCharLit();
    Ext extClassBody();
    Ext extClassDecl();
    Ext extClassLit();
    Ext extClassMember();
    Ext extCodeDecl();
    Ext extCompoundStmt();
    Ext extConditional();
    Ext extConstructorCall();
    Ext extConstructorDecl();
    Ext extDo();
    Ext extEmpty();
    Ext extEval();
    Ext extExpr();
    Ext extField();
    Ext extFieldDecl();
    Ext extFloatLit();
    Ext extFor();
    Ext extFormal();
    Ext extIf();
    Ext extImport();
    Ext extInitializer();
    Ext extInstanceof();
    Ext extIntLit();
    Ext extLabeled();
    Ext extLit();
    Ext extLocal();
    Ext extLocalClassDecl();
    Ext extLocalDecl();
    Ext extLoop();
    Ext extMethodDecl();
    Ext extNewArray();
    Ext extNode();
    Ext extNodeList();
    Ext extNew();
    Ext extNullLit();
    Ext extNumLit();
    Ext extPackageNode();
    Ext extProcedureDecl();
    Ext extReturn();
    Ext extSourceCollection();
    Ext extSourceFile();
    Ext extSpecial();
    Ext extStmt();
    Ext extStringLit();
    Ext extSwitchBlock();
    Ext extSwitchElement();
    Ext extSwitch();
    Ext extSynchronized();
    Ext extTerm();
    Ext extThrow();
    Ext extTry();
    Ext extTypeNode();
    Ext extUnary();
    Ext extWhile();
}


2) polyglot.ext.jl5.ast.JL5ExtFactory 인터페이스 [ JL5ExtFactory_c ]

public interface JL5ExtFactory extends ExtFactory {

    Ext extAmbTypeInstantiation();
    Ext extAmbWildCard();
    Ext extEnumDecl();
    Ext extExtendedFor();
    Ext extEnumConstantDecl();
    Ext extEnumConstant();
    Ext extParamTypeNode();
    Ext extAnnotationElemDecl();
    Ext extNormalAnnotationElem();
    Ext extMarkerAnnotationElem();
    Ext extSingleElementAnnotationElem();
    Ext extElementValuePair();
    Ext extElementValueArrayInit();
}

3) polyglot.ext.jl7.ast.JL7ExtFactory 인터페이스 [ JL7ExtFactory_c ]

public interface JL7ExtFactory extends JL5ExtFactory {

    Ext extAmbDiamondTypeNode();
    Ext extAmbUnionType();
    Ext extMultiCatch();
    Ext extResource();
    Ext extTryWithResources();
}

확장 노드 팩토리를 만들어보았고 이제 노드 팩토리를 만들 차례이다. 그런데 우리 목적은 새로운 구문을 추가하는 것이 아니기 때문에 노드 팩토리는 Java 1.7용을 그대로 활용한다.

  •  tool.compiler.java.ast.EquGenNodeFactory 인터페이스 (JL7NodeFactory를 그대로 상속 받고 아무것도 추가하지 않음)
package tool.compiler.java.ast;

import polyglot.ext.jl7.ast.JL7NodeFactory;

public interface EquGenNodeFactory extends JL7NodeFactory {
    // TODO: Declare any factory methods for new AST nodes.

}

  •  tool.compiler.java.ast.EquGenNodeFactory_c 클래스(JL7NodeFactory_c를 그대로 물려받고, EquGenNodeFactory 인터페이스를 구현 - 실제로는 구현할 것이 없음)
package tool.compiler.java.ast;

import polyglot.ext.jl7.ast.JL7NodeFactory_c;

public class EquGenNodeFactory_c extends JL7NodeFactory_c implements
		EquGenNodeFactory {

	public EquGenNodeFactory_c(EquGenLang lang, EquGenExtFactory extFactory) {
		super(lang, extFactory);
	}

	@Override 
	public EquGenExtFactory extFactory() {
		return (EquGenExtFactory) super.extFactory();
	}
	

    // TODO:  Implement factory methods for new AST nodes.
    // TODO:  Override factory methods for overridden AST nodes.
    // TODO:  Override factory methods for AST nodes with new extension nodes.
}

노드 팩토리와 확장 노드 팩토리를 정의 했다면 이를 조합해서 노드 팩토리를 생성하는 코드를 다음과 같이 작성할 수 있다.

  • tool.compiler.java.ExtensionInfo의 createNodeFactory() 메소드 구현
 protected NodeFactory createNodeFactory() {
	return new EquGenNodeFactory_c(EquGenLang_c.instance,
			new EquGenExtFactory_c(
                             new JL7ExtFactory_c(
                                  new JL5ExtFactory_c())));
	}
  • tool.compiler.java.ast.EquGenLang 인터페이스 (EquGenOps 인터페이스에 선언한 메소드와 동일한 메소드를 추가 선언. J7Lang을 확장.)
package tool.compiler.java.ast;

import polyglot.ast.Node;
import polyglot.ext.jl7.ast.J7Lang;
import tool.compiler.java.visit.EquGenerator;

public interface EquGenLang extends J7Lang {
    // TODO: Declare any dispatch methods for new AST operations
	EquGenerator equGenEnter(Node n, EquGenerator v);
	Node equGen(Node n, EquGenerator v);

}
  • tool.compiler.java.ast.EquGenLang_c 클래스 (EquGenOps 인터페이스에서 정의한 메소드를 구현.)
  • ===> 각 메소드의 구현은
  • ===> 주어진 노드에서 lang 메소드를 통해 해당 확장 노드를 선택해서
  • ===> 선택한 확장 노드의 동일한 메소드를 호출하는 것
package tool.compiler.java.ast;

import polyglot.ast.*;
import polyglot.ext.jl7.ast.J7Lang_c;
import polyglot.util.InternalCompilerError;
import tool.compiler.java.visit.EquGenerator;

public class EquGenLang_c extends J7Lang_c implements EquGenLang {
	public static final EquGenLang_c instance = new EquGenLang_c();
	
	public static EquGenLang lang(NodeOps n) {
		while ( n != null ) {
			Lang lang = n.lang();
			if ( lang instanceof EquGenLang) return (EquGenLang)lang;
			if ( n instanceof Ext ) 
				n = ((Ext)n).pred();
			else return null;
		}
		throw new InternalCompilerError("Impossible to reach");
	}
	
	protected EquGenLang_c() {
	}
	
	
    // TODO:  Implement dispatch methods for new AST operations.
    // TODO:  Override *Ops methods for AST nodes with new extension nodes.
	
	protected static EquGenExt equgenExt(Node n) {
		return EquGenExt.ext(n);
	}

	@Override
	protected NodeOps NodeOps(Node n) {
		return equgenExt(n);  // TODO: equgenExt???
	}
	
	protected EquGenOps EquGenOps(Node n) {
		return equgenExt(n);
	}

	@Override
	public EquGenerator equGenEnter(Node n, EquGenerator v) {
		return EquGenOps(n).equGenEnter(v);
	}
	
	@Override
	public Node equGen(Node n, EquGenerator v) {
		return EquGenOps(n).equGen(v);
	}
}

 

지금까지 설명은 ExtensionInfo (tool.compiler.java)의 createNodeFactory에서 노드 팩토리를 만들기 위해서 필요한 모든 구성을 설명하였다.

  • 확장 노드
  • 확장 노드 팩토리
  • 노드 팩토리

실제로 EquGenClassDeclExt 클래스로 표현한 확장 노드에 우리가 하고 싶은 일이 메소드로 구현해놓았고, 폴리글롯 컴파일러가 이 메소드를 호출하는 방법 또는 과정을 이제 설명한다.

폴리글롯 컴파일러에 패스를 추가하는 작업부터 시작한다. ExtensionInfo (tool.compiler.java)의 스케쥴러에서 패스를 지정한다.

  •  ExtensionInfo (tool.compiler.java)의 createScheduler 메소드에서 EquGenScheduler 클래스 객체를 스케쥴러로 만든다.
public Scheduler createScheduler() {
	return new EquGenScheduler(this);
}
  • tool.compiler.java.EquGenScheduler
  • ===> TypeChecked 패스(Goal) 다음에
  • ===> ReachabilityChecked 패스(Goal) 앞에 배치
  • ===> 우리 패스(Goal)은 VisitorGoal로 만들되
  • ===> EquGenerator (비지터 Visitor)를 지정한다. 이 비지터가 주어진 AST의 모든 노드와 확장 노드를 차례대로 방문하면서 추가해놓은 메소드들을 실행한다. (EquGenClassDeclExt의 메소드를 실행한다!)
public class EquGenScheduler extends JL7Scheduler {
	
  public EquGenScheduler(JL7ExtensionInfo extInfo) { 
     // tool.compiler.java.ExtensionInfo??
	super(extInfo);
  }

  public Goal EquGenerated(Job job) {
	ExtensionInfo extInfo = job.extensionInfo();
	TypeSystem ts = extInfo.typeSystem();
	NodeFactory nf = extInfo.nodeFactory();
	Goal g = new VisitorGoal(job, new EquGenerator(job,ts,nf));
	try {
	  g.addPrerequisiteGoal(TypeChecked(job), this); // TODO: TypeChecked???
	}
	catch(CyclicDependencyException e) {
		throw new InternalCompilerError(e);
	}
	return internGoal(g);
  }

	
  // Add a pass before ReachabilityChecked (after TypeChecked)
  @Override
  public Goal ReachabilityChecked(Job job) {
	Goal goal = super.ReachabilityChecked(job);
	try {
		goal.addPrerequisiteGoal(EquGenerated(job), this);
	}
	catch(CyclicDependencyException e) {
		throw new InternalCompilerError(e);
	}
	return internGoal(goal);
   }	
}

EquGenScheduelr 클래스의 코드를 보면 우리가 하고 싶은 일을 TypeChecked 다음 ReachabilityChecke 이전에 EquGenerator를 지정한 goal을 둔다.

EquGenerator는 ContextVistor라는 일종의 비지터로 모든 AST노드를 따라가면서 일할때 유용

여기까지 작업을 하면 폴리글롯 컴파일러가 TypeChecked pass 다음에 우리의 EquGenerator 비지터를 사용하는 pass를 실행한다!!!!

 

우리가 추가한 비지터 EquGenerator를 마지막으로 살펴본다.

  •  tool.compiler.java.visit.EquGenerator (extending polyglot.visit.ContextVisitor)
package tool.compiler.java.visit;


public class EquGenerator extends ContextVisitor {

	public EquGenerator(Job job, TypeSystem ts, NodeFactory nf) {
		super(job, ts, nf);
	}
	
	@Override
	public EquGenLang lang() {
		return (EquGenLang) super.lang();
	}

	@Override
	public NodeVisitor begin() {
		Report.report(1, "EquGenerator: begin()");
		NodeVisitor nv = super.begin();
		return nv;
	}

	@Override
	public void finish() {
		Report.report(1, "EquGenerator: finish()");
		super.finish();
	}

	@Override
	protected NodeVisitor enterCall(Node n) throws SemanticException {

		return lang().equGenEnter(n, this); // 추가한 메소드 호출 
	}

	@Override
	protected Node leaveCall(Node old, Node n, NodeVisitor v)
			throws SemanticException {

		return lang().equGen(n,  this); // 추가한 메소드 호출
	}

	@Override
	public TypeSystem typeSystem() {
		return super.typeSystem();
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}	
}

 

다시 한번 반복하자면, ExtensionInfo의 스케쥴러에서 생성하는 createScheudler()를 보면 EquGenScheduler 클래스의 객체를 만든다.

EquGenScheduelr 클래스의 코드를 보면 우리가 하고 싶은 일을 TypeChecked 다음 ReachabilityChecke 이전에 EquGenerator를 지정한 goal을 둔다.

EquGenerator는 ContextVistor라는 일종의 비지터로 모든 AST노드를 따라가면서 일할때 유용

여기까지 작업을 하면 폴리글롯 컴파일러가 TypeChecked pass 다음에 우리의 EquGenerator 비지터를 사용하는 pass를 실행한다!!!!

EquGenerator는 다음과 같이 동작한다.

  • EquGenerator의 전체 AST의 시작점은 begin()이고, 마지막은 finish()
  • 각 노드를 들어갈때마다 비지터의 enterCall(), leaveCall()을 호출한다.

각 확장 노드에서 할일은 확장 노드의 소스 코드에서 직접 구현한다. (예: ClassDecl에서 하고 싶은 일은 EquGenClassDeclExt 클래스의 코드에서 정의한다. 세부 코드는 위의 클래스 정의 참조)

EquGenerator (extending ContextVisitor)의 enterCall과 leaveCall 메소드에서 새로 추가한 메소드를 부르는 것 부터 시작한다. (enterCall과 leaveCall은 AST의 모든 노드에 대해 호출되는 메소드이다.)

1. Visitor에서 Language dispatcher로

  • EquGenerator.enterCall에서 EquGenLang.equGenEnter() 메소드를 호출
  • EquGenerator.leaveCall에서 EquGenLang.equGen() 메소드를 호출

EquGenLang/EquGenLang_c에서 동일한 메소드(equGenEnter, equGen)를 추가 해두었었다.

2. Language dispatcher는 적절한 Extension Node를 찾고

  • EquGenLang_c.equGenEnter에서 EquGenOps(n).equGenEnter() 메소드를 호출 (EquGenOps(n)은 기본 노드 n의 EquGenExt 확장노드를 찾는다!!!!)
  • EquGenLang_c.equGen에서 EquGenOps(n).equGen() 메소드를 호출

3. 개별 노드의 확장 노드의 메소드를 호출

  • EquGenOps를 구현한 EquGenExt를 상속 받은 EquGenClassDeclExt.equGenEnter와 EquGenClassDeclExt.equGen 메소드를 호출

 

5.마무리하며 ...

폴리글롯 컴파일러 구조에 맞추어 관심있는 노드를 분석하는 컴파일 패스를 작성하는 방법을 설명했다. 이제부터 본격적으로 소스 분석기를 위한 패스를 구현하는 구체적인 작업을 진행한다...

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *