본문 바로가기

Compiler

Python Virtual machine(PVM)-2

From Parse Tree To Abstract Syntax Tree

컴파일 프로세스의 다음 단계는 파이썬 파스 트리를 추상 구문 트리(AST)로 변환하는 것이다. 추상 구문 트리는 파이썬 구문의 세부 사항과 독립적인 코드의 표현이다. 예를 들어 파스트리는 구문 구조이기 때문에 그림 3.0에서 보여지는 것처럼 콜론 노드를 포함한다.하지만 AST는 리스트 3.4에서 보여지는 것처럼 구문 구조를 포함하지 않을 것이다. 

다양한 파이썬에 대한 AST 노드의 정의는 파일 Parser/Python.asdl에서 발견할 수 있다. AST에 대부분의 정의는 IF 상태문이나 속성 lookup과 같은 특정한 소스 구조를 따른다. 파이썬 인터프리터의 ast 모듈은 파이썬 AST를 다루기 위한 능력을 제공한다. codegen과 같은 Tool은 파이썬에 AST 표현과 파이썬 소스 코드에 대응하는 결과를 가질 수 있다. CPython 구현에서 AST 노드는 Include/Python-ast.h에 정의된 C 구조에 의해 표현된다. 이 구조는 사실 파이썬 코드에 의해 생성되었다. Parser/asdl_c.py 모듈은 AST asdl 정의로부터 이 파일을 생성된다. 예를 들어 상태문 노드의 정의의 cross-section은 리스트 3.5에서 보여진다.

리스트 3.5에 union 타입은 타입을 표현하는데 사용되는 C 타입이다. 여기서 타입은 union에 나열된 타입의 하나를 취할 수 있다. Python/ast.c에  PyAST_FromNode 함수는 주어진 파스 트리로부터 AST의 생성을 다룬다. 생성된 AST는 바이트코드의 생성으로 진행될 것이다.

 

Building The Symbol Table

생성된 AST와 함께, 프로세스의 다음 단계는 심볼테이블의 생성이다. 심볼테이블은 제안된 이름과 같이 코드블럭 이내의 이름과 사용되는 네임의 컨텍스트의 집합이다. 심볼테이블 구성의 프로세스는 코드블럭 내에서 포함하는 이름 분석과 해당 이름에 올바르게 범위 지정이 포함한다. 심볼테이블 생성의 복잡함이 논의되기 전에 파이썬에서 이름과 바인딩을 살펴보는 것이 좋다.

 

일련의 함수 호출 run_mod -> PyAST_CompileObject -> PySymtable_BuildObject는 심볼테이블 구성의 과정을 야기한다.

PySymtable_BuildObject function에 2개의 인수는 이전에 생성된 AST와 모듈의 파일 이름이다. 심볼테이블을 구성하는 알고리즘은 두 부분으로 나뉜다. 프로세스의 첫 파트 동안 PySymtable_BuildObject에 인수를 전달하는 AST의 각 노드는 AST 이내에 사용되는 심볼의 집합을 구성하기 위해 방문된다. 이 과정의 매우 단순한 설명은 리스트 3.6에서 주어지고 심볼테이블 구성에 사용되는 데이터 구조가 논의될 때, 이에 사용되는 항목은 더 명백할 것이다. 

 

AST에 알고리즘의 첫 번째 경로 후에, 심볼테이블 엔트리는 모듈 내에서 사용되는 모든 이름을 포함한다. 하지만 이름에 대한 문맥적인 정보는 가지지 않는다. 예를 들어 주어진 변수가 global, local 혹은 free 변수로 주어진다면 인터프리터는 이것을 말해줄 수 없다. e Parser/symtable.c에 symtable_analyze 함수에 호출은 심볼테이블 생성의 두 번째 구문을 시작한다. 이 알고리즘의 구문은 처음 pass로 부터 모여진 심볼에 범위(local, global 혹은 free)를 할당한다. e Parser/symtable.c에 주석은 꽤 유익하고 테이블 구성 프로세스의 두 번째 단계에 대한 통찰력을 제하기 위해 아래에 설명되어 있다.

 

심볼 테이블은 각 이름의 스코프를 결정하기 위해 두 번의 패스가 필요하다. 첫 번째 패스에서는 symtable_visit_* 함수를 통해 AST에서 원시 사실을 수집하고, 두 번째 패스에서는 pass 1에서 생성된 PySTEntryObjects를 통해 이러한 사실들을 분석한다.

두 번째 패스 중에 함수가 진입되면, 부모는 자식들에게 보이는 모든 이름 바인딩의 집합을 전달한다. 이러한 바인딩은 비지역 변수가 자유 변수인지 또는 암시적 전역 변수인지를 결정하는 데 사용된다. 명시적으로 nonlocal로 선언된 이름은 이 보이는 이름들의 집합에 존재해야 한다. 만약 존재하지 않으면, 구문 오류가 발생한다. 지역 분석을 수행한 후에는 업데이트된 이름 바인딩 집합을 사용하여 각 자식 블록을 분석한다.

전역 변수에는 명시적 전역 변수와 암시적 전역 변수 두 가지 종류가 있다. 명시적 전역 변수는 global 문으로 선언된다. 암시적 전역 변수는 컴파일러가 둘러싸고 있는 함수 스코프에서 바인딩을 찾지 못한 free 변수다. 암시적 전역 변수는 전역 변수이거나 내장 변수일 수 있다. Python의 모듈과 클래스 블록은 이러한 이름들을 처리하기 위해 xxx_NAME OPCODE를 사용하여 약간 이상한 의미를 구현한다. 이러한 블록에서는 해당 이름이 할당되기 전까지는 전역으로 처리되며, 할당 후에는 지역으로 처리된다.

자식들은 free 변수 집합을 업데이트합니다. 자식에 의해 free 변수 집합에 로컬 변수가 추가되면 해당 변수는 cell로 표시된다. 정의되는 함수 객체는 함수의 프레임보다 오래 지속될 수 있는 변수에 대한 런타임 스토리지를 제공해야 한다. Cell 변수는 analyze 함수가 부모로 반환되기 전에 free 집합에서 제거된다.

주석은 이러한 과정을 명확한 언어로 설명하려고 노력하지만, "parent passes the set of all name bindings visible to its children"과 같은 혼란스러운 측면이 있습니다. 여기서 어떤 부모와 어떤 자식들을 의미하는지를 이해하려면 심볼 테이블 생성 프로세스에서 사용되는 데이터 구조를 살펴봐야 합니다.

'Compiler' 카테고리의 다른 글

CPython의 새로운 PEG 파서  (1) 2023.07.27
Python Virtual machine(PVM)-1  (0) 2023.07.20
Parsing expression grammar  (0) 2023.07.18
Dead-Code Elimination  (0) 2023.06.18
Dominance Frontiers  (0) 2023.06.16