parseExpression() 같은 직접 작성 함수 대신 추가적인 추상화와 간접 호출 계층을 거침
임의의 동적 미리보기도 지원하므로 대안이 여러 개면 하나만 유효하게 남을 때까지 모든 해석을 동시에 시뮬레이션함
충분히 최적화된 ANTLR라도 그래프 순회 인터프리터 구조로는 직접 구현한 재귀 하강 파서보다 빨라지기 어려움
두 가지 파서 구현을 병렬로 실험
AI를 활용하면 직접 구현한 파서를 작성하고 유지하기가 쉬워지지만, Claude에 오류 없는 Rust 파서를 요청하는 것만으로는 충분하지 않았음
Claude는 많은 실수를 만들었음
재작성 가능성 자체를 반복해서 의심함
각 코딩 라운드가 끝날 때마다 작업을 마치려는 경향을 보임
여러 장기 실행 Claude Code 세션에서 두 접근법을 병렬로 시험함
성능 중심 접근법은 재귀 하강 파서와 Pratt 표현식 루프를 사용하고 필요한 곳에만 미리보기와 백트래킹을 추가함
성공 가능성 중심 접근법은 ANTLR의 동작을 최대한 따르되, 범용 그래프 순회 대신 전이를 명시적인 코드로 구현함
두 접근법은 결국 비슷한 수준으로 작동했지만, 이를 확인하기까지 며칠이 걸림
목표는 현실적인 모든 쿼리에서 기존 C++ 파서와 완전히 일치하고, 인위적인 쿼리에서도 가능한 한 가까운 결과를 내는 것이었음
SELECT SELECT FROM FROM WHERE WHERE AND AND처럼 비정상적으로 보이지만 유효한 SQL도 테스트 대상에 포함함
기존 C++ 파서를 기준 구현(오라클) 으로 삼아 다르게 처리되는 SQL을 찾고, 새 파서를 수정한 뒤 다시 비교하는 테스트 주도 방식으로 개발함
속성 기반 테스트로 불일치 생성
기존 파서 개발 과정에서 쌓인 회귀 테스트를 먼저 통과시킨 뒤 새로운 불일치를 체계적으로 생성함
Hypothesis를 이용한 속성 기반 테스트(PBT) 에서 검증 속성을 “새 파서가 기준 구현과 일치한다”로 정의함
입력값은 SQL 쿼리임
Hypothesis가 두 파서의 결과가 일치하지 않는 쿼리를 탐색함
흥미로운 SQL을 생성하기 위해 ANTLR 문법 파일에서 SQL 생성기를 코드 생성하는 도구를 Claude와 함께 만듦
.g4 파일 자체를 읽는 별도의 파서도 작성함
이후 토큰 교환이나 괄호 추가 같은 변형을 생성 단계에 포함함
취약한 수정을 막는 프롬프트 구성
PBT는 새 테스트를 안정적으로 만들었지만, Claude는 한 토큰 미리보기로 특정 사례를 고친 뒤 나중에 두 토큰 미리보기가 필요하다는 사실을 발견하는 식의 취약한 수정을 반복함
컨텍스트 창이 자주 가득 차 압축되면서 실제 문법이나 기준 파서의 동작을 잊은 상태로 수정했을 가능성이 있었음
각 불일치를 고치는 코드를 작성하기 직전에 관련 문법 파일과 C++ 소스 코드를 모두 컨텍스트로 불러오도록 지시해 이 문제를 완화함
CPU와 Claude를 함께 계속 가동
PBT가 백그라운드에서 계속 실행되며 실패 사례를 파일에 기록하고, Claude는 다른 작업이 없을 때 해당 사례를 가져오도록 도구를 구성함
실패 사례는 여러 경로에서 수집함
기존 회귀 테스트
PBT로 생성한 SQL
프로덕션 쿼리 로그에서 가져온 익명화 쿼리
백그라운드 에이전트에 “경계 사례를 정말 깊게 생각하라”고 요청해 만든 사례
병렬로 개발한 두 파서는 회귀 테스트 모음을 공유해 한 세션에서 발견한 실패가 다른 세션에도 즉시 반영됨
Hypothesis는 직접 생성한 사례를 최소 재현으로 축소할 수 있지만 외부 SQL에는 적용할 수 없어, 해당 사례에는 ShrinkRay를 사용함
이후 코드 커버리지 유도 테스트 생성도 추가함
아직 실행되지 않은 SQL 구성 요소를 감지해 해당 구조가 더 자주 생성되도록 편향함
프로덕션 쿼리 집합에서 100% 정확도에 도달하는 데 필수는 아니었지만, 매우 미묘한 사례를 찾는 데 도움을 줌
실패를 찾고 고치는 자동 반복 과정
최종 개발 루프는 다음 단계를 자율적으로 반복함
1. PBT, 실제 쿼리 집합, 회귀 테스트, 경계 사례 탐색으로 새로운 실패를 생성함
2. 실패를 축소해 계속 확장되는 회귀 테스트 목록에 추가함
3. 일반적인 해결책을 우선하면서 문법과 C++ 기준 구현의 처리 방식을 확인함
4. 수정한 뒤 사람이 읽을 수 있는 한 문단 요약을 출력함
5. 전체 회귀 테스트를 실행해 모든 사례가 통과하는지 확인함
6. 같은 과정을 다시 자율 실행함
새 파서가 훨씬 빨랐기 때문에 프로덕션에서 기존 C++ 파서와 함께 섀도 모드로 실행하며 결과 차이를 보고할 수 있었음
프로덕션 검증과 전환
프로덕션 쿼리 로그를 이용한 사전 비교에서는 약 5만 개의 쿼리를 테스트함
섀도 모드에서는 수백만 번의 파싱을 빠르게 실행했으며 기존 파서와의 불일치가 한 건도 발생하지 않음
원래 며칠 동안 섀도 모드를 유지할 계획이었지만 결과가 충분히 강해 몇 시간 만에 프로덕션 트래픽을 새 파서로 전환함
전환 후에는 기존 파서로 다시 비교하는 0.1% 역방향 섀도를 유지함
새 파서는 AST뿐 아니라 소스 위치 정보까지 C++ ANTLR 파서와 동일하게 출력함
노트북 70배, 프로덕션 454배
노트북 벤치마크에서는 기존 파서보다 약 70배 빠른 성능을 기록함
프로덕션 쿼리에서는 평균 454배 빨라짐
프로덕션에서는 파서 캐시에 적중하지 않는 더 긴 SQL을 주로 처리해 노트북보다 성능 차이가 커짐
최종 결과물은 파서 코드 약 16K줄, 도구 약 5K줄, 테스트 수천 줄로 구성됨
현실적인 쿼리에서는 기존 파서와 동등하며, 차이는 인위적으로 구성한 극소수 쿼리에 한정됨
최종 파서 구조와 달라진 개발 방식
최종 파서는 다음 구조로 구성됨
대부분을 예측형 재귀 하강 파서로 구현함
표현식 처리에는 Pratt 파서 코어를 사용함
기본 커서는 LL(2)이며, 특정 지점에서만 입력을 소비하지 않는 제한적 미리보기 탐색으로 범위를 넓힘
반드시 필요한 소수의 판단에서만 순서가 있는 선택 기반의 국소적 추측 백트래킹을 적용함
전체 파서는 2026년 5월 Claude Opus 4.7이 Rust로 작성했으며, 코드를 직접 손으로 작성하지 않았음
문법으로 입력을 생성하고 코드 커버리지로 생성을 유도하는 PBT 구성은 파서 퍼징의 최신 방식에 가까우며, 단순한 “바이브 코딩”과 구별됨
특정 파서 지식이 있는 개발자에게도 수개월 걸릴 수 있는 작업을 며칠 만에 수행함
앞으로는 ANTLR 같은 파서 생성기가 기준 구현을 제공하고, LLM이 PBT와 퍼징으로 동등성을 맞추면서 더 빠른 직접 구현 파서를 작성하는 방식이 일반화될 수 있음