컴파일러에 대한 내용을 읽다보면 구문분석(파서)의 결과의 예시로 AST라는 것이 있다.
AST는 무엇일까?
추상 구문 트리
위와 같은 트리의 형태를 추상 구문 트리라 부른다. (Abstract Syntax Tree)
각 언어로 프로그래밍 된 소스 코드는 추상적으로 바뀌게 되고 코드의 구조는 위와 같이 노드로 바뀌게 된다.
이러한 노드들이 모여 만들어진 자료구조가 AST다.
const test = 'this is test';
function testFunction(string) {
console.log(string)
}
testFunction(test)
위와 같은 코드가 있다고 할 때 AST는 어떻게 생겼을까?
위의 사이트에서 각 파서에 맞는 출력물을 확인할 수 있다.
위의 사이트를 통해 가장 첫 줄에 관한 정보를 가져오면 아래와 같다.
{
"type": "VariableDeclaration",
"start": 0,
"end": 28,
"loc": {
"start": {
"line": 1,
"column": 0,
"index": 0
},
"end": {
"line": 1,
"column": 28,
"index": 28
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 27,
"loc": {
"start": {
"line": 1,
"column": 6,
"index": 6
},
"end": {
"line": 1,
"column": 27,
"index": 27
}
},
"id": {
"type": "Identifier",
"start": 6,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 6,
"index": 6
},
"end": {
"line": 1,
"column": 10,
"index": 10
},
"identifierName": "test"
},
"name": "test"
},
"init": {
"type": "StringLiteral",
"start": 13,
"end": 27,
"loc": {
"start": {
"line": 1,
"column": 13,
"index": 13
},
"end": {
"line": 1,
"column": 27,
"index": 27
}
},
"extra": {
"rawValue": "this is test",
"raw": "'this is test'"
},
"value": "this is test"
}
}
],
"kind": "const"
},
조금 살펴보면 아래와 같이 해석할 수 있다.
- VariableDeclaration = 변수를 선언
- Identifier = 해당 내용으로 식별 (변수의 이름)
- identifierName = 이 이름을 갖는 변수
- StringLiteral = 문자열 타입
- value = 값 (this is test)
- kind = 종류 (const)
결론적으로 "test라는 이름의 변수를 const로 만들어서 문자열 타입의 this is test라는 값을 넣겠다"라는 내용이 된다.
함수 호출부에 대해서도 함께 보면 아래와 같다.
- ExpressionStatement = 표현문
- CallExpression = 호출
- callee: { type: "Identifier" } = 함수를 호출한 callee의 타입은 식별자
- identifierName = 식별자 이름 (testFunction)
- arguments = 인자 (test)
마찬가지로 해석해보면 " testFunction라는 식별자로 함수를 호출하는데 인자로는 test라는 식별자를 사용한다"라는 내용이 된다.
AST는 이와 같이 소스 코드를 추상화된 트리 구조로 나타낸 것이라 할 수 있다.
응용
여기까지만 본다면 AST가 어떤 것인지는 알 수 있다.
컴파일러가 이런 자료 구조를 사용한다는 것은 이해하는데 이것을 알면 어디 써먹을 곳이 있을까?
생각보다 AST는 다양한 방법으로 응용할 수 있다.
아래 내용 부터는 개인의 생각이 포함되어 있을 수 있습니다.
eslint
Core Concepts - ESLint - Pluggable JavaScript Linter
가장 대표적으로 AST를 활용하는 라이브러는 eslint가 있다.
eslint는 파서를 이용해 소스 코드를 AST로 변환하고 이를 바탕으로 정적 분석을 수행해준다.
(내부적으로는 espree라는 파서 라이브러를 활용하고 있다고 한다)
그렇다면 eslint가 소스 코드에 AST를 활용하는 이유는 무엇일지 한 번 생각해보자.
위에서 봤던 AST의 구조를 살펴보면 각 구문에 대해 해당 구문의 타입, 위치, 식별자 등을 토큰화 해서 트리로 나타낸 것이다.
아래의 코드를 보자.
const test = 'this is test';
// const test = 'this is test';
두 코드의 내용은 같다.
하지만, AST로 변환하면 서로 다른 객체가 된다.
(변수 선언문의 경우 위의 토큰 참고)
"comments": [
{
"type": "CommentLine",
"value": " const test = 'this is test';",
"start": 29,
"end": 60,
"loc": {
"start": {
"line": 2,
"column": 0,
"index": 29
},
"end": {
"line": 2,
"column": 31,
"index": 60
}
}
}
]
개발자가 작성한 내용은 같지만 하나는 VariableDeclaration으로 변환되고 하나는 CommentLine으로 변환되었다.
이는 AST로 변환되며 각 코드가 어떤 역할을 하는지 명확해졌다는 뜻이 된다.
만약, eslint가 단순히 파일의 내용을 텍스트로 가져와서 문자열을 확인한다거나 추상화 되지 않은 문자들을 가지고 분석을 했다면 위의 차이를 구분하지 못할 것이다.
test라는 변수를 사용할 수 없다는 eslint 규칙이 있다고 가정했을 때 일반적인 문자열 확인 방법으로는 대응하기 어려울 것이다.
여기서 AST를 활용하게 된다면 소스 코드를 토큰으로 나눈 뒤 추상화된 트리를 만들게 될 것이고 소스 코드에서 각 문자열이 어떤 역할을 하는지 알 수 있게 된다.
babel
@babel/parser · Babel (babeljs.io)
자주 사용하는 라이브러리 중 하나인 babel도 AST를 활용한다.
babel이 포함된 리액트 프로젝트를 하는 경우를 생각해보자.
리액트를 열심히 JSX로 만들어도 브라우저는 이해하지 못한다.
결국 이 JSX를 트랜스파일링 해야 하는데 정규식 등의 방법을 활용한다면 위의 문제와 같은 문제가 발생할 것이다.
여기서 AST를 활용한다면 JSX 코드를 AST로 만들고 각 노드가 어떤 역할을 하는지 파악할 수 있다.
이를 브라우저에서 실행할 수 있는 JS로 변환한다면 브라우저에서 화면을 볼 수 있게 될 것이다.
(실제로 이렇게 작동하는지는 못찾았지만)
babel을 사용하는 주된 역할은 트랜스파일링이다.
ES6와 같이 비교적 최신 문법의 JS를 여러 브라우저에서 범용성있게 실행시키기 위해서는 다른 버전의 JS로 변환해야 한다.
위의 방식을 응용한다면 ES6 문법의 JS를 AST로 변환한 뒤, 각 노드의 타입을 확인하여 적절한 버전의 JS 문법으로 교체한다면 소스 코드의 틀을 유지한 채 문법의 버전만 바꿀 수 있을 것이다.
결론
AST는 컴파일러에서 사용하는 자료 구조중 하나다.
이러한 AST를 응용한다면 소스 코드와 관련된 다양한 것을 할 수 있을 것이다.
[참고자료]
1) AST를 이용한 코드 변환 - Fundamental Frontend (wikidocs.net)
Find and fix problems in your JavaScript code - ESLint - Pluggable JavaScript Linter
'개발 > 개념' 카테고리의 다른 글
[Node.js] Node.js와 V8 (with. ECMAScript) (0) | 2024.10.12 |
---|---|
선언형 프로그래밍과 명령형 프로그래밍 (feat. React 18 Concurrent Mode) (0) | 2024.05.04 |
WebRTC란 무엇이며 어떤 과정을 갖는가? (0) | 2023.11.12 |
Vite는 왜 빠를까? (0) | 2023.10.21 |
[객체지향] SOLID 예제(5) - 인터페이스 분리의 원칙(ISP) (0) | 2023.10.03 |