파서 조합기 - 작은 파서를 조합해 구조화된 데이터를 만드는 방법
요약
본 글은 Python의 re 모듈 내장 스캐너를 활용하여 정규식 기반 토큰 분석기를 구현하는 방법을 설명합니다. 이 도구는 단순한 findall과 달리 일치 구간의 연속성을 보장하며, 입력 전체 처리 여부 검증 및 오류 위치 기록이 가능해 구조화된 데이터 파싱에 유용합니다.
핵심 포인트
- re.Scanner를 사용하면 토큰별 패턴 매칭과 연속성 보장이 가능하다.
- 단순 findall과 달리 일치하지 않는 문자를 무시하지 않고 순회하며 처리한다.
- 스캐너의 remainder 확인을 통해 입력 전체가 성공적으로 파싱되었는지 검증할 수 있다.
Python에서 정규식 하나로 처리하기에는 크고 본격적인 파서를 쓰기에는 작은 문제라면 내장 re.Scanner 를 추천함. re 모듈에 있는 정규식 기반 토큰 분석기로, 재귀 파싱은 지원하지 않으며 어째서인지 공식 문서에는 전혀 나오지 않음.
토큰 종류별 패턴과 일치한 문자열을 처리할 함수를 지정하면 처리된 토큰 목록을 돌려줌. 캡처 그룹을 쓴 단순한 re.findall은 일치하지 않는 문자를 무시하지만, 이 도구는 한 번 순회하면서 일치 구간의 연속성을 보장함. 실행 중인 스캐너도 참조할 수 있어 오류 보고용 위치를 기록할 수 있음.
예를 들어 숫자는 COUNT 정수로, 단어는 끝의 s를 제거한 ANIMAL 문자열로 바꾸고 구두점과 공백은 건너뛸 수 있음. scanner.scan()이 반환한 remainder가 비었는지 확인하면 입력 전체를 처리했는지도 검증 가능함.
참고: https://stackoverflow.com/a/693818/252218, https://en.wikipedia.org/wiki/Lexical_analysis#Tokenization.
애니메이션 덕분에 동작을 직관적으로 이해하기 좋음. 다만 파서 조합기는 별도 문법 파일에서 파서를 생성하는 라이브러리와 비교하면 결과를 변환하거나 감싼 구조를 풀어내는 코드가 많아 가독성이 떨어진다고 봄.
이를 개선하려고 Rust 라이브러리를 개발 중임. capture와 bind 라는 조합기를 추가해 bind로 파서 결과를 저장하고 capture로 저장된 값을 사용해 최종 결과를 구성함. 이렇게 하면 변환·해체 코드를 없애고 문법과 결과 구성을 분리할 수 있음. 피드백을 받고 싶음. https://github.com/ArneCode/marser.
이 방식의 이점이 잘 보이지 않음. 같은 주사위 예제를 Winnow로 구현해도 감싸거나 풀어내는 코드가 그리 많지 않음. digit1.try_map(str::parse)로 숫자를 읽고, separated_pair(number, 'd', number)의 결과를 (count, sides)로 받아 Roll { count, sides }를 반환하면 됨. "3d6"을 Roll { count: 3, sides: 6 }으로 파싱하는 테스트도 간단함.
파서 조합기를 쓰면서 구체적으로 어떤 변환·해체 코드를 작성해야 했는지 궁금함.
왜 단일 페이지로 만들 수 없는지 모르겠음. 슬라이드 형식이라 새 내용을 읽다가 앞의 장을 다시 참고하기 어렵고 검색도 할 수 없음. 이상한 미적 이유로 디지털 매체에 없는 종이 매체의 제약을 굳이 되살리는 것 아닌가?
처음에는 계속 읽기가 꺼려졌지만 끝까지 읽기를 잘했다고 느낌. 다만 다음으로 넘어가는 방법이 불분명하고, 스크롤만 하면 될 일을 스크롤과 페이지 넘기기 사이에서 오가야 하는 게 불편함. 좋은 콘텐츠를 사용성이 가리고 있음.
불편하다는 건 이해하지만 나는 좋았음. 구간마다 분리되어 있어 더 읽기 편하게 느껴짐. 같은 콘텐츠를 양쪽 형식으로 보여줄 수 있으니 슬라이드와 단일 페이지 전환을 지원하면 이상적일 듯함.
사이트와 주제는 멋지지만 작은 화면에서도 테스트해 보면 좋겠음. 지금 MacBook Air로 보고 있는데 오른쪽 위는 여백이 많고, 정작 스크롤해서 읽는 본문은 화면의 3분의 1 정도만 차지함. 왼쪽 영역도 또 3분의 1가량을 차지함.
새로운 UI를 시도하는 건 존중하지만, 생각을 전달하는 것이 목표라면 본문에 더 많은 화면 공간을 할애해 주면 좋겠음. 특정 글을 읽으러 들어온 만큼 적어도 그 글을 다 읽기 전까지는 다른 글에 관심이 없음.
YouTube 영상과 블로그 글 사이의 형식으로, 조금씩 나누어 소비할 수 있는 콘텐츠를 만들고 싶었음. 시각 자료가 대부분을 전달하게 하려 했지만 파서 조합기는 결국 텍스트가 많아졌음. UI를 좀 더 압축하고 테스트용으로 더 작은 휴대전화를 구해 보겠음.
첫 번째 주소는 호스트명일 수도 있으므로 IP 주소로 단정해 파싱하지 않는 편이 맞을 듯함. 사소해 보이지만 여러 종류의 구문 중 하나가 올 수 있을 때 어떻게 처리하는지 보여줄 기회가 됨.
본문 영역이 너무 좁아 읽기도 다소 어려움. 장이 짧은데도 여러 페이지에서 스크롤해야 했음.
같은 저자의 색상 연재도 훌륭함. 설명을 읽고 처음으로 이해된 내용들이 있었고, 연결해 둔 YouTube 채널도 깊이 파고들 만함. https://t3.at/blog/2026-08-21-color-1-rgb-cube/, https://m.youtube.com/@ooqui.
후속 주제를 제안할 수 있다면 DCI-P3·Adobe RGB와 sRGB의 관계를 더 알고 싶음. Linux/Wayland 애플리케이션이 색 공간 S의 색 C로 픽셀을 지정했을 때, 해당 색 공간을 완전히 지원하거나 지원하지 못하는 색 보정된 화면에 표시되기까지의 처리 과정도 궁금함.
타입 시스템 연재의 마지막 글에는 몇 가지 오류가 있는 듯함. 타입 이론이나 논리학 전문가는 아니지만, 커리–하워드 대응은 직관주의 논리나 구성적 타입 이론에만 한정되지 않으며 타입 시스템의 표현력도 얼마든지 확장할 수 있음.
비구성적 증명은 선택 공리를 가정해야만 가능한 것이 아님. 핵심은 배중률과 귀류법임. https://t3.at/blog/2025-12-24-type-systems-iceberg/.
오늘 첫 화면에 오를 줄은 몰랐는데, 한동안 청소하지 않은 집에 손님들이 들어오는 기분임. 색상 연재를 시작한 계기도 Ooqui였음. 개발자라면 색상과 지각을 조금은 알겠지만, 이 채널은 훨씬 깊이 들어가며 영상 보기 전에는 들어보지 못한 독특한 주제를 많이 다룸.
지금은 우유에 관한 연재를 작업 중이지만 색상에도 다룰 내용이 많아 돌아올 예정임. 색 공간과 색역은 특별히 신경 써서 다루겠음.
시작 타입으로 type Parser = (input: string) => [T, string] 은 적절하지 않음. 문자열을 받으면 반드시 T를 반환한다는 뜻이지만, 파서는 T를 반환할 수도 있고 실패할 수도 있음.
글은 ‘부분 파서’를 조합해 다른 파서를 만든다고 설명하지만, 조합한다고 별개의 종류가 되는 것은 아님. 부분 파서를 조합하면 부분 파서가 되고, 완전한 파서라는 것이 있다면 그것들을 조합한 결과도 완전한 파서가 됨.
오히려 여기서 부분 함수라는 의미는 잘 들어맞음. 모든 입력에 대해 정의된 함수가 아니어서 어떤 입력에는 T를 반환하고, 다른 입력에는 예외를 던질지도 모르기 때문임.
TypeScript로 작업하므로 부분 파서는 언제든 예외를 던질 수 있음. parsimmon도 그렇게 하는 것으로 알고 있음. 이런 기술적 세부 사항에는 너무 깊이 들어가지 않기로 했음.
‘부분’이 실패할 수 있다는 뜻이라면 조합 결과가 꼭 부분 파서인 것은 아님. 합리적인 파서 조합기 의미론에서 elem이 실패할 때 입력을 소비하지 않는다면 optional(elem)과 repeat(elem)은 전함수가 됨. 다만 repeat(elem)에서 elem이 빈 문자열과 일치하는 것은 보통 프로그래머의 오류로 간주함.
조합기는 함수이므로 항상 무언가를 반환함. 아무것도 반환하고 싶지 않다면 T는 어떤 타입이든 될 수 있으니 단위 타입(unit) 처럼 값이 하나뿐인 타입을 반환하면 됨.
좋은 안내서지만 패킷 메타데이터를 임의의 키–값 쌍으로 파싱할 기회를 놓친 듯함. 64 bytes from 1.1.1.1: icmp_seq=3 ttl=52 time=47.1 ms에서 콜론 뒤는 모두 키–값 데이터이므로, 범용 pKeyVal() 파서를 넣었다면 이 접근법의 우아함이 더 잘 드러났을 것임.
AI 자동 생성 콘텐츠
본 콘텐츠는 RSS: GeekNews (한국어)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기