"...를 위한 컴파일러를 원해"에서 시작된 모든 것
요약
OpenAI Codex를 활용하여 자신만의 프로그래밍 언어인 FNL(Farzan's Neat Language)과 컴파일러를 구축한 과정을 다룹니다. AI가 코드를 구현하는 동안 개발자는 언어 설계와 엔지니어링 결정을 내리는 설계자 역할을 수행하며 협업하는 방식을 보여줍니다.
핵심 포인트
- AI 코딩 도구를 활용한 프로그래밍 언어 및 컴파일러 구축 사례
- AI는 구현을 담당하고 인간은 언어 설계 및 엔지니어링 결정을 내리는 협업 모델
- 컴파일러 구현 자체보다 언어 설계(문자열 처리, 형 변환 등)의 복잡성이 더 높음
- 재귀 하강 파서를 활용한 가독성 높은 코드 생성 전략
AI 코딩 에이전트를 활용한 프로그래밍 언어 구축.
2026년 6월 10일, 컴파일러와 프로그래밍 언어에 대한 오랜 열정, 그리고 AI 코딩 도구가 저를 어디까지 데려다줄 수 있을지에 대한 호기심에 이끌려, 저는 OpenAI Codex를 열고 단 하나의 프롬프트를 작성했습니다.
"Go 언어로 작성된 프로그래밍 언어를 위한 컴파일러를 원합니다..."
나머지 프롬프트는 산술 연산, 변수, 직접 작성한 재귀 하강 파서 (recursive descent parser), print(), 그리고 if/else를 지원하는 아주 작은 프로그래밍 언어로부터 Windows x64 바이너리를 생성하는 컴파일러에 대해 설명했습니다.
AI는 다음과 같이 답했습니다:
"이것을 처음부터 시작하는 작은 Go 컴파일러 프로젝트로 구축하겠습니다: 렉서 (lexer),
재귀 하강 파서 (recursive descent parser), 기본적인 의미론적 검사 (semantic checks),
그리고 Windows x64 실행 파일 출력..."
그것이 **FNL (Farzan's Neat Language)**의 시작이었습니다.
컴파일러 구축은 수년 동안 저를 매료시켜 왔습니다. 저는 책을 읽고, 강연을 보고, 파서 이론 (parser theory)을 실험하며 항상 자신만의 컴파일러를 만들고 싶어 했습니다. 이 프로젝트는 상용 언어를 만드는 것보다는 제가 가장 좋아하는 컴퓨터 과학 분야 중 하나를 실무적으로 탐구하는 것에 더 가까웠습니다.
돌이켜보면, 저는 제가 코딩 프로젝트를 시작하고 있다고 생각했습니다.
하지만 제가 실제로 시작한 것은 프로그래밍 언어 설계 (programming language design)로의 여정이었습니다.
AI가 코드를 작성했고, 저는 언어를 설계했습니다.
처음부터 한 가지 짚고 넘어갈 점이 있습니다. 저는 컴파일러를 직접 작성하지 않았습니다. 모든 줄의 Go 코드는 AI에 의해 생성되었습니다. 그렇다고 해서 제가 수동적인 요청자였다는 뜻은 아닙니다.
프로젝트 전반에 걸쳐 저는 설계자, 언어 설계자, 검토자, 테스터, 그리고 궁극적으로 모든 엔지니어링 결정에 책임을 지는 사람으로서 역할을 수행했습니다. 저는 단순히 AI에게 코드를 생성해 달라고 요청한 것이 아니었습니다.
저는 엔지니어링 결정을 내리고 있었으며, 바로 그 점이 흥미로운 부분이었습니다.
AI는 구현 방안을 제안하고, 제 아이디어에 이의를 제기하며, 대안을 제시하고, 때로는 스스로 가정을 하기도 했습니다. 때로는 제가 그것들을 수용했고, 때로는 거부했습니다. 상당히 자주, 우리는 기능을 구현하는 것보다 그 기능에 대해 논의하는 데 훨씬 더 많은 시간을 보냈습니다.
AI는 컴파일러를 구현했습니다.
저는 언어를 설계하고 그 진화를 지시했습니다.
컴파일러를 직접 만들며 배우기
처음부터 저는 의도적으로 프로젝트의 범위를 제한했습니다.
저는 특히 수작업으로 작성된 재귀 하강 파서 (recursive descent parser)를 요청했는데, 그 이유는 제가 편안하게 읽고 이해할 수 있는 코드를 가진 컴파일러를 원했기 때문입니다. 파서 생성기 (Parser generators)는 강력하지만, 제가 배우고 싶었던 대상은 아니었습니다.
매우 빠르게, 저는 컴파일러를 구현하는 것이 가장 어려운 부분이 아니라는 사실을 깨달았습니다. 언어를 설계하는 것이 가장 어려웠습니다.
문자열 (Strings)은 논의를 시작하기 전까지는 쉬운 기능처럼 들렸습니다.
ASCII, UTF-8, UTF-16, 또는 UTF-32 중 무엇이어야 할까요?
문자열 길이는 바이트 (bytes)를 의미해야 할까요, 아니면 유니코드 코드 포인트 (Unicode code points)를 의미해야 할까요?
비교 연산은 로케일 (locale)을 인식해야 할까요?
그다음은 형 변환 (conversions)이었습니다.
int를 double에 할당하는 것이 자동으로 이루어져야 할까요?
"123"이 정수가 되어야 할까요?
편의성이 예상치 못한 동작 (surprising behaviour)이 되기 전까지 암시적 형 변환 (implicit conversions)은 어디까지 허용되어야 할까요?
이 질문들 중 보편적으로 정답인 것은 없습니다. 모든 결정은 한 측면의 언어를 단순화하는 동시에 다른 측면을 복잡하게 만듭니다.
다행히 FNL은 아직 초기 단계입니다. 저는 아직 하위 호환성 (backward compatibility)을 걱정할 필요가 없으므로, 더 나은 설계를 발견할 때마다 이전의 결정들을 다시 검토할 수 있습니다.
컴파일러는 나의 실험실이 되었다
결국, 저는 고립된 코드 조각들로 컴파일러를 테스트하는 것을 멈추고 FNL로 실제 프로그램을 작성하기 시작했습니다.
이제 저장소에는 Hello World 프로그램, 피보나치 수열 생성기, 제곱근 계산기, π 근사치 계산, 그리고 간단한 숫자 맞추기 게임이 포함되어 있습니다.
이 프로그램들은 단순한 예제 그 이상이 되었습니다. 그것들은 이 언어의 첫 번째 사용자(users)가 되었습니다.
그 프로그램들 중 거의 모든 것이 무언가 누락된 부분을 드러냈습니다.
Fibonacci 수열을 작성하는 동안, break와 exit()가 필요하다는 것을 깨달았습니다.
숫자 맞추기 게임(guessing game)을 작성할 때는 난수 생성기(random number generator)가 없다는 사실을 발견했으며, 이는 나중에 math_random()이 되었습니다.
나중에 실수로 sign 대신 signed라는 이름의 변수를 선언한 적이 있습니다. FNL은 이를 수락했지만, 생성된 C 백엔드(backend)는 실패했습니다. signed가 C의 키워드(keyword)였기 때문입니다. C의 제약 사항이 FNL로 유출되는 대신, 저는 백엔드 이름이 소스 이름으로부터 독립될 수 있도록 심볼 맹글링(symbol mangling)을 도입했습니다.
시간이 흐르면서, 저는 언어를 설계하는 동안에는 그 언어를 진정으로 알지 못했다는 사실을 깨달았습니다. 저는 그 언어로 소프트웨어를 작성하면서 비로소 그 언어를 알게 되었습니다.
"너 속였지."
가장 재미있었던 순간 중 하나는 프로젝트 초기에 찾아왔습니다.
저는 AI에게 Windows 실행 파일(executables)을 생성해 달라고 요청했습니다.
AI는 기계어(machine code)를 직접 생성하는 대신, 컴파일러가 FNL 코드를 C11로 트랜스파일(transpile)한 다음 C 컴파일러를 호출하도록 조용히 결정했습니다.
저의 첫 반응은 단순했습니다.
"너 속였지."
그것에 대해 생각해 본 후, 저는 그것이 합리적인 엔지니어링(engineering) 결정이었다는 것을 깨달았습니다.
이 프로젝트의 흥미로운 점은 명령 인코딩(instruction encoding)이 아니었습니다.
그것은 언어 설계(language design), 어휘 분석(lexing), 구문 분석(parsing), 의미 분석(semantic analysis), 심볼 해결(symbol resolution), 그리고 코드 생성(code generation)이었습니다.
최종 기계어 생성을 성숙한 C 컴파일러들에게 위임함으로써, 저는 제가 이해하고 싶었던 부분들에 집중할 수 있었습니다.
오늘날 FNL은 MSVC, GCC, Clang 백엔드를 지원하며 LLVM IR을 직접 출력할 수도 있습니다.
코드 생성기 그 이상
이 프로젝트를 시작하기 전, 저는 AI가 주로 제가 코드를 작성하는 수고를 덜어줄 것이라고 생각했습니다.
확실히 그랬습니다.
하지만 그것은 이야기의 아주 작은 부분에 불과했습니다.
AI가 저에게 컴파일러 설계(compiler design)를 가르쳐준 것이 아닙니다.
저는 AI와 함께 컴파일러를 구축함으로써 컴파일러 설계를 배웠습니다.
프로젝트 전반에 걸쳐 지식은 중요했습니다.
컴파일러 개념을 이해하지 못했다면, 저는 재귀 하강 파서(recursive descent parser)를 요청하거나, 설계 제안에 의문을 제기하거나, 결함이 있는 트레이드오프(trade-off)를 인식하거나, 언어를 특정 방향으로 이끌 것을 알지 못했을 것입니다.
AI는 구현 속도를 극적으로 가속화했습니다. 하지만 결코 엔지니어링적 판단 (engineering judgement)을 대체하지는 못했습니다.
오히려 이 프로젝트는 AI가 아이디어를 거의 즉각적으로 구현할 수 있게 될 때, 전문 지식(expertise)이 훨씬 더 가치 있어진다는 사실을 저에게 확신시켜 주었습니다.
FNL의 향후 행보
FNL은 여전히 작은 언어입니다.
구축해야 할 것들이 많이 남아 있습니다.
- 함수 (Functions)
- 구조체 (Structs) 및 사용자 정의 타입 (user-defined types)
- 배열 (Arrays)
- 더 풍부한 표준 라이브러리 (standard library)
- 내장 린터 (built-in linter)
- 더 나은 디버깅 지원 (debugging support)
- 파일 입출력 (File I/O)
언젠가는 많은 언어 설계자들이 꿈꾸는 이정표에 도달하기를 희망합니다:
부트스트래핑 (Bootstrapping).
FNL 컴파일러를 FNL 자체로 작성하는 것입니다.
그것이 현실이 되기까지는 아직 갈 길이 멀지만, 이는 프로젝트에 명확한 방향성을 제시해 줍니다.
마치며
제가 이 프로젝트를 시작한 이유는 컴파일러가 항상 저를 매료시켰기 때문입니다.
저는 렉서 (lexers), 파서 (parsers), AST (Abstract Syntax Trees), 의미 분석 (semantic analysis), 그리고 코드 생성 (code generation)에 대해 배우기를 기대했습니다.
하지만 유니코드 (Unicode), 타입 변환 (type conversions), 디버거 설계 (debugger design), 표준 라이브러리 아키텍처 (standard library architecture), 심볼 맹글링 (symbol mangling), 그리고 언어 철학 (language philosophy)에 대해 이토록 많은 시간을 보낼 줄은 예상하지 못했습니다.
돌이켜보면, 가장 큰 교훈은 기술적인 것이 아니었습니다.
그것은 AI가 이해를 대체하는 수단이 아니라, 구현 파트너로서 작동할 때 가장 효과적이라는 사실이었습니다.
컴파일러는 AI에 의해 작성되었을지 모릅니다.
하지만 언어는 한 번에 하나의 엔지니어링 결정(engineering decision)을 통해 형성되었습니다.
만약 여러분이 컴파일러나 언어 설계에 관심이 있다면, 제가 FNL을 만드는 과정에서 즐거웠던 것처럼 여러분도 FNL을 따라가는 과정이 즐겁기를 바랍니다. 소스 코드와 예제 프로그램은 프로젝트의 Git 저장소에서 확인할 수 있으며, 여러분의 의견이나 제안을 듣게 된다면 매우 기쁠 것입니다.
이 프로젝트가 여러분의 호기심을 자극했다면, GitHub의 소스 코드를 탐색해 보셔도 좋습니다. 저장소에는 컴파일러 자체를 포함하여 예제 프로그램, 그리고 언어의 진화 과정을 기록한 설계 노트 등 FNL과 관련된 모든 것이 담겨 있습니다.
examples 폴더에는 FNL 샘플 프로그램들이 포함되어 있으며, PROJECT_NOTES.md는 BNF 및 EBNF 표기법으로 작성된 언어의 참조 문법 (reference grammar)을 포함하여 컴파일러의 기술적인 측면들을 다룹니다. 만약 얼마나 많은 설계 결정들이 어떻게 이루어졌는지에 관심이 있다면, FNL_R_AND_D.md를 확인해 보세요. 이 파일은 프로젝트 전반에 걸쳐 AI와 나눈 대화로부터 생성된 일지 (journal)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기