900===생활/개발자 TIL
인터프리터 구축하기 - 나만의 프로그래밍 언어 만들기 🚀
블로글러
2025. 6. 7. 23:29
안녕하세요! 혹시 파이썬이나 자바스크립트 코드를 실행할 때, "어떻게 컴퓨터가 내가 쓴 코드를 이해하고 실행하는 거지?" 라고 궁금해하신 적 있나요? 🤔
오늘은 여러분과 함께 인터프리터를 만드는 여정을 떠나볼 거예요! 마치 레고 블록을 조립하듯이, 하나씩 차근차근 만들어보면서 프로그래밍 언어가 어떻게 동작하는지 알아보겠습니다.
등장 배경
초기 컴퓨터는 0과 1로만 이루어진 기계어로만 프로그래밍할 수 있었어요. 상상해보세요... "안녕하세요"를 출력하는데 01001000 01100101... 이런 식으로 써야 했다니! 😱
그래서 사람들은 더 쉬운 방법을 찾기 시작했고:
- 1950년대: 어셈블리 언어 등장 (조금 더 읽기 쉬운 기호들)
- 1960년대: BASIC 같은 고급 언어와 함께 첫 인터프리터 등장
- 현재: Python, JavaScript, Ruby 등 수많은 인터프리터 언어들이 활발히 사용됨
인터프리터가 해결하는 문제들:
- 인간 친화적 문법: "print('Hello')" 같은 자연스러운 문법으로 프로그래밍 가능
- 즉시 실행: 컴파일 과정 없이 바로 실행해서 결과 확인 가능
- 플랫폼 독립성: 인터프리터만 있으면 어디서든 같은 코드 실행 가능
핵심 원리
인터프리터는 크게 4단계로 동작해요. 마치 요리 레시피를 따라가는 것처럼요! 👨🍳
1️⃣ 렉서(Lexer) - 재료 준비하기
입력: "x = 5 + 3"
출력: [IDENTIFIER(x), EQUALS, NUMBER(5), PLUS, NUMBER(3)]
2️⃣ 파서(Parser) - 레시피 구조 파악하기
토큰들을 받아서 문법 구조를 트리로 만들어요:
=
/ \
x +
/ \
5 3
3️⃣ AST(추상 구문 트리) - 요리 순서 정리하기
파서가 만든 트리를 더 간단하고 실행하기 좋은 형태로 변환:
AssignmentNode
├── Variable: x
└── BinaryOp: +
├── Number: 5
└── Number: 3
4️⃣ 평가기(Evaluator) - 실제로 요리하기!
AST를 순회하면서 실제로 계산을 수행해요.
단계 구성 요소 역할 비유
1 | 렉서(Lexer) | 텍스트를 토큰으로 분해 | 문장을 단어로 나누기 |
2 | 파서(Parser) | 토큰을 문법에 맞게 구조화 | 단어들로 문장 구조 만들기 |
3 | AST 생성 | 실행 가능한 트리 구조로 변환 | 문장의 의미 파악하기 |
4 | 평가기(Evaluator) | AST를 순회하며 실행 | 의미대로 행동하기 |
실제 구현 예시 🛠️
간단한 계산기 인터프리터를 만들어볼까요?
// 1. 렉서 - 문자열을 토큰으로 변환
function tokenize(input) {
const tokens = [];
let current = 0;
while (current < input.length) {
// 숫자 처리
if (/[0-9]/.test(input[current])) {
let num = '';
while (/[0-9]/.test(input[current])) {
num += input[current];
current++;
}
tokens.push({ type: 'NUMBER', value: parseInt(num) });
}
// 연산자 처리
else if (input[current] === '+') {
tokens.push({ type: 'PLUS' });
current++;
}
// 공백 무시
else if (input[current] === ' ') {
current++;
}
}
return tokens;
}
// 2. 파서 - 토큰을 AST로 변환
function parse(tokens) {
let current = 0;
function parseExpression() {
let left = {
type: 'Number',
value: tokens[current++].value
};
if (tokens[current] && tokens[current].type === 'PLUS') {
current++; // '+' 건너뛰기
return {
type: 'BinaryOp',
operator: '+',
left: left,
right: parseExpression()
};
}
return left;
}
return parseExpression();
}
// 3. 평가기 - AST를 실행
function evaluate(node) {
if (node.type === 'Number') {
return node.value;
}
if (node.type === 'BinaryOp') {
const left = evaluate(node.left);
const right = evaluate(node.right);
if (node.operator === '+') {
return left + right;
}
}
}
// 사용 예시
const input = "5 + 3 + 2";
const tokens = tokenize(input);
const ast = parse(tokens);
const result = evaluate(ast);
console.log(result); // 10
주의사항 및 팁 💡
⚠️ 이것만은 주의하세요!
- 에러 처리를 꼼꼼히
- 잘못된 문법이 들어왔을 때 친절한 에러 메시지 표시
- 예: "Syntax Error: Expected ')' but found ';' at line 5"
- 메모리 관리
- AST가 너무 깊어지지 않도록 주의
- 순환 참조가 생기지 않도록 설계
- 성능 고려
- Tree-walking 인터프리터는 간단하지만 느림
- 성능이 중요하다면 바이트코드 컴파일 고려
💡 꿀팁
- 작은 언어부터 시작하세요! 사칙연산만 되는 계산기도 훌륭한 시작입니다
- 테스트를 많이 작성하세요. 각 단계별로 유닛 테스트를 만들면 디버깅이 쉬워져요
- 기존 인터프리터 코드를 읽어보세요. 특히 "Crafting Interpreters" 책이 정말 좋아요!
마치며
지금까지 인터프리터를 만드는 과정에 대해 알아보았습니다. 처음에는 복잡해 보일 수 있지만, 하나씩 단계를 나누어 접근하면 충분히 만들 수 있어요!
여러분도 한번 간단한 계산기부터 시작해서 자신만의 프로그래밍 언어를 만들어보는 건 어떨까요? 🌟
혹시 더 궁금한 점이 있거나 막히는 부분이 있다면 언제든 물어보세요!
참고 자료 🔖
#인터프리터 #프로그래밍언어 #컴파일러 #렉서 #파서 #AST
728x90
반응형