CPython

CPython/렉싱과 파싱/ '거의 같음'연산자 구현(1)

25G 2022. 12. 10. 15:23

다양한 방식으로 입력된 텍스트 형태의 파있너 코드는 파싱 단계를 거쳐 컴파일러에서 사용할 수 있는 구조로 변환된다.

이번장에서는 텍스트 형태의소스 코드를 컴파일 가능한 논리적 구조로 파싱하는 방법에 대해 알아볼것이다.

 

Cpython은 코드를 파싱하기 위해 CST와 AST두 가지 구조를 사용한다.

리더 -> 렉서 -> 파서 -> 컴파일러

파싱과정은 다음과 같다.

1. 파서.토크나이저 또는 렉서가 CST를 생성한다.

2. 파서가 CST로부터 AST를 생성한다.

이 두단계는 다른 프로그래밍 언어에서도 흔하기 사용되는 패러다임이다.

 

CST 

CST는 문맥 자유 문법에서 코드를 표현하는 루트와 순서가 있는 트리다.

토크나이저와 파서가 CST를 생성한다. 파서 생성기는 문맥 자유 문법이 가질 수 있는 상태에 대한 결정적 유한 오토마다 파싱 테이블을 생성한다.

CST를 구성하는 모든 심벌은 Grammar/Grammar에서 정의한다. 토큰은 Grammar/Tokens 에서 정의한다.

파이썬 문법에서 await, async같은 예약어나 숫자 형식 또는 리터럴 타입은 name의 값으로 쓸수 없다 예를 들어 함수 이름으로 1을 사용하려고하면 SytaxError가 발생한다.

파이썬에서 symbol과 token 모듈로 컴파일된 심벌과 토큰을 확인할 수 있다.

 

 

중요한 용어들

- AST :파이썬 문법과 문장들에 대한 문맥 있는 트리표현입니다.

- CST: 토큰과 심벌에 대한 문맥 없는 트리 표현 입니다.

- 파스트리: CST의 다른 이름입니다.

- 토큰: 심벌의 종류중 하나입니다 예를 들면 + 같은것

- 파싱: 텍스트를 CST나 AST로 변환하는 과정 입니다.

 

'거의 같음' 비교 연산자 추가하기

참고 docs 

https://turingcompl33t.github.io/Hacking-CPython-Syntax/ 

파이썬에 새로운 분법을 추가하고 CPython을 컴파일 해보자 비교표현식에 ~= 과같은 심벌을 사용하는 거의 같은 연사자를 새 비교 연산자로 추가해 보자. 거의 같음 연산자의 동작 부터 정리 해보자

1. 정수와 부동 소수점을 비교할때 부동 소수점은 정수로 변환해 비교한다.

2. 정수와 정수를 비교할 때는 일반 동등 연산자를 사용한다.

 

EX)

1 ~= 1 

True

1 ~= 1.0

Ture

1 ~= 1.01

Ture

1 ~= 1.9

False

새 연산자를 추가하려면 먼저 CPython 문법을 변경해야한다. 비교 연산자 들은 Grammer/python.gram 파일에 comp_op 심벌로 정의 되 어 있다.

compare_op_bitwise_or_pair 식에 ala_bitwise_or 허용

 

 

그리고 ale_bitwise_or 식을 is_bittwise_or 아래에 추가하자

ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, AlE,a) }

 

위와같이 '~=' 단말 기호를 포함하는 ale_bitwise_or 식을 정의 했다.

_PyPegne_cmpop_expr_pair(p, AlE, a) 함수 호출은 AST에서 '거의 같음' 연산자를 뜯하는 AlE타입 cmpop노드를 가져온다

 

다음으로 Grammar/Tokens에 토큰을 추가하자

ALMOSTEQUAL      '~='

변경한 문법과 토큰을 C 코드에 반영하려면 헤더를 다시 생성해야한다. macOS에서 다음을 실행하자.

 

-   make regen-token regen-pegen

해더를 다시 생성하면 토크나이저도 자동으로 변경된다. Parser/token.c를 열어 PyToken_TwoChars() 함수의 case가 변경된 것을 확인할 수 있다.

헤더에 등록됐다.

 

위 사진처럼 토큰 등록이 완료 됐다면 CPython을 다시 컴파일하고 REPL을 실행해 보면 토크나저이는 새토큰을 처리할 수 있지만 AST는 처리하지 못한다.

cpython 컴파일을 하려하면 다음과 같은 메시지와 함께 실패 합니다.

왜냐하면 토크나이저는 새 토큰을 처리할 수 있지만 AST는 처리하지 못하기 때문입니다.

 

REPL을 실행해서 사용해보면 Pyton/ast.c의 ast_for_comp_op()는 ALMOSTEQUAL을 올바른 비교연산자로 인식할 수 없기때문에 예외를 발생시킨다. Parser/Python.asdl에서 정의하는 Compare 표현식은 좌측 표현식 left, 연산자 목록인 ops, 비교할 표현식 목록인 comparators로 이루어져 있다.

 | Compare(expr left, cmpop* ops, expr* comparators)

 

Compare 정의는 cmpop 열거형을 참조한다.

이 열거형은 비교 연산자로 사용할 수 있는 AST 리프 노드의 목록이다. '거의 같음' 연산자를 비교 연산자로 사용하기 위해 AlE를 추가하자

이제 AST를 다시 생성해서 AST 헤더 파일에 변경된 AST를 반영하지

- make regen-ast

위 명령어로 ast를 반영하면 Python-ast.h 파일에 ALE가 추가된 것을 확인할 수 있습니다.

ASTsms ALMOSTEQUAL 토큰이 비교 연산자 AlE라는 것을 아직 알 수 없다. 토큰을 연산자로 인식할 수 있게 AST C 코드를 수정하자.

 

Python/ast.c의 ast_for_comp_op()로 이동해서 연산자 토큰에 대한 switch문을 찾아보자 해당 switch 문은 _comp_op() 열거형 값중 하나를 반환한다. ALMOSTEQUAL 토큰일 경우 ALE 비교 연산자를 반환하는 case 를 추가한다.

            case ALMOSTEQUAL:
                return AlE;

이제 여기까지 왔으면 make 명령어로 컴파일을 실행합니다.(이제오류안남) 이제 토크나이저와 AST모두 코드를 파싱할 수 있지만 컴파일러는 아직 이 연산자를 실행하는 방법을 모르는 상태다. 

AST로 나타넨 '거의같음'연산자를 확인해 보려면 ast.parse()가 반환하는 값에서 첫번째 연산자를 출력해 보자

AST가 코드를 올바르게 파싱했다면 비교 연산자 ALE타입의 객체가 출력될 것입니다.

 

이제 컴파일러가 동작하는 방시을 알아보고 '거의같음 연산자의 동작을 구현하면 됩니다. 다음장에서 하겠습니다.

'CPython' 카테고리의 다른 글

CPython/ 평가루프  (0) 2023.01.24
CPython/컴파일러 (2) '거의 같음' 연산자 구현하기(2)  (0) 2023.01.08
CPython/컴파일러 (1)  (0) 2023.01.08
CPython 구성과 입력  (2) 2022.12.10
CPython 3.9 환경 설정 (macos)  (0) 2022.12.10