[기술] 컴파일러[프론트엔드] 한 걸음씩 - 두번째

 


인사


오늘은 컴파일단계에 프론트엔드에서 Lexial analysis에 대해서 제가 공부한 내용을 상세하게 소개해 드리겠습니다. 


Frontend - Lexical Analysis 요약


프론트엔드의 첫 번째 단계인 **Lexical Analysis(어휘 분석)**는 유저의 소스 코드를 해석하여 **토큰 스트림(Token Stream)**을 생성하는 과정입니다. 이 단계에서는:

  1. 소스 코드를 한 글자씩 순차적으로 탐색하여 문자 리스트를 만듭니다.

  2. 이 리스트에서 주석, 공백(whitespace) 등의 불필요한 요소를 제거합니다.

  3. 정제된 문자 리스트를 오토마타 이론에 기반해 분석하여 의미 있는 토큰(token) 단위로 분리하고, 이를 토큰 스트림으로 구성합니다.

이 과정을 통해 소스 코드는 파서가 이해할 수 있는 형태로 전처리됩니다.



탐색과 제거


Lexical Analysis 단계에서는 소스 코드를 문자 단위로 탐색하고, 불필요한 요소들을 제거하여 분석 가능한 형태로 가공합니다.


1. 주석(Comment) 제거

프로그래밍 언어마다 주석을 표시하는 방식이 다릅니다:

  • JavaScript: //, /* */

  • C언어: //, /* */

  • Python: #

Lexical Analysis 단계에서는 이러한 주석 문법을 인식하고, 해당 주석 내용은 토큰화 대상에서 제외됩니다.

2. 공백(Whitespace) 제거

  • 코드 작성 중 포함되는 공백 문자들(' ', '\n', '\t' 등)은 컴파일 시 직접적인 의미를 가지지 않으므로 제거 대상입니다.

  • 이는 코드 구조를 단순화하고, 의미 있는 토큰만 추출하기 위함입니다.

3. 예시: JavaScript 코드 처리

다음 JavaScript 코드를 예로 들어보겠습니다:

// 1 + 2 * 3을 계산해주는 함수
const calculus = () => {return 1 + 2 * 3};

문자 단위로 분해하면 다음과 같이 변환됩니다:

[

  '\n', '/', '/', ' ', '1', ' ', '+', ' ', '2', ' ', '*', ' ', '3', '을', ' ', '계', '산', '해',

  '주', '는', ' ', '함', '수', '\n', 'c', 'o', 'n', 's', 't', ' ', 'c', 'a', 'l', 'c', 'u',

  'l', 'u', 's', ' ', '=', ' ', '(', ')', ' ', '=', '>', ' ', '{', 'r', 'e', 't', 'u', 'r',

  'n', ' ', '1', ' ', '+', ' ', '2', ' ', '*', ' ', '3', '}', ';', '\n'

]

주석 제거 + 공백 제거 이후 남는 요소는 다음과 같습니다:

[

  'c', 'o', 'n', 's', 't', 'c',

  'a', 'l', 'c', 'u', 'l', 'u',

  's', '=', '(', ')', '=', '>',

  '{', 'r', 'e', 't', 'u', 'r',

  'n', '1', '+', '2', '*', '3',

  '}', ';'

]

이처럼 불필요한 문자와 주석이 제거된 문자열은 이후 오토마타 기반 분석 과정을 통해 의미 있는 토큰 스트림으로 변환됩니다.


분석 


생성된 문자들은 오토마타 이론(automata theory)을 이용해 토큰 스트림으로 변환됩니다. 이때 주의할 점은 각 문장을 가능한 한 최장으로 연결하여 인식해야 한다는 것입니다.


여기서 오토마타 이론에 대해 간단히 소개드리면, 오토마타는 입력 문자열을 읽으면서 상태를 전이하는 추상적인 기계를 의미합니다. 다시 말해, 상태(state), 입력(input), 출력(output)의 시퀀스를 조사하고 분석하는 이론입니다. 이는 컴퓨터 과학의 핵심 이론 중 하나로, 다양한 형태의 계산 모델을 정의합니다.

컴파일러에서는 주로 유한 상태 기계인 FA(Finite Automata)를 사용하며, 특히 NFA(비결정적 유한 오토마타)를 기반으로 로직을 정의한 뒤, 이를 DFA(결정적 유한 오토마타)로 변환하여 실제 분석에 적용합니다. 일부 고급 분석에서는 mDFA(최적화된 DFA)도 사용되지만, 여기서는 DFA까지를 다룹니다.

아래 그림은 유튜브 채널 Neso Academy 에서 자료를 가져왔습니다. 





예를 들어, ia라는 입력은 상태 1을 거쳐 상태 3으로 전이되어 IDENTIFIER로 인식되고, if라는 입력은 상태 1에서 상태 2로 전이되어 KEYWORD로 분류됩니다. 이처럼 오토마타를 통해 문자열을 최장 매칭 기준으로 인식하여, 정확한 토큰으로 분류할 수 있습니다.

이렇게 만들어진 토큰 스트림은 주로 Map 구조로 표현되는데, 이는 Map이 리스트, 힙, 트리에 비해 삽입, 검색, 삭제 등의 연산에서 효율적이기 때문입니다. 토큰 스트림에는 토큰의 이름(name), 타입(type), 크기(size), 차원(dimension), 문자열 길이(string length) 등 다양한 정보가 담기며, 이러한 정보는 다음 단계인 구문 분석(Syntax Analysis) 에서 사용됩니다.


오류


Lexical Analysis 단계에서도 다양한 오류를 검출할 수 있으며, 이러한 오류들은 컴파일 에러라고 부릅니다. 우리가 일반적으로 코드를 작성한 후에 마주치는 오류 중 대부분은 런타임 에러이지만, 코드를 실행하기 전 컴파일러가 소스코드를 분석하는 과정에서 발생하는 오류는 바로 이 단계에서 포착됩니다. 이제 Lexical Analysis 단계에서 검출할 수 있는 대표적인 오류들을 소개하겠습니다. 


1. 스캐닝 단계


소스 코드를 문자 단위로 탐색하며 토큰으로 나눕니다. 이 과정에서 언어에서 허용하지 않는 문자(예: ¥, @ 등)가 포함되었거나, 주석이 닫히지 않았을 경우(/**만 쓰고 */가 없음), 문자열이 끝나지 않은 경우("Hello처럼 닫히는 따옴표가 없음)에는 스캐너가 이를 인식하지 못하고 에러를 발생시킵니다.

예를 들어 다음과 같은 코드에서:

const num = "0xzz;

컴파일러는 다음과 같은 에러를 출력합니다:

SyntaxError: Invalid or unexpected token


2. 분석 단계 


상태 전이 모델인 **DFA(결정적 유한 오토마타)**를 통해, 앞서 분리한 문자들을 실제 의미 있는 토큰으로 변환합니다. 이 과정에서 DFA 규칙에 어긋나는 입력이 들어오면 오류를 반환하게 됩니다. 예를 들어, JavaScript에서 16진수는 0x 다음에 09, af의 범위로만 구성되어야 하는데, 아래와 같은 코드처럼 유효하지 않은 문자가 포함되면:

const a = 0xZZ;

역시 다음과 같은 컴파일 오류가 발생합니다:

SyntaxError: Invalid or unexpected token

이처럼 Lexical Analysis 단계에서는 문법에 맞지 않는 문자나 형식들을 사전에 걸러내며, 실행 이전 단계에서 많은 오류를 조기에 발견할 수 있도록 도와줍니다.


예고


다음 포스팅에서는 프론트엔드 단계 중 **Syntax Analysis(구문 분석)**에 대해 소개해드릴 예정입니다.
긴 글 읽어주셔서 감사합니다.


개발자 김동완

개발, 비전공, 백엔드, 풀스택, 프로그래밍

댓글 쓰기

다음 이전