Lobsters

Parsing Expression Grammar vs. regexes: Building Org parser in Lisp that exports to HTML (via SXML)

PEG와 정규식 비교: SXML로 HTML을 만드는 Lisp 기반 Org 파서

Guile Scheme의 PEG 문법으로 Org 문서를 AST로 파싱한 뒤 SXML을 거쳐 HTML로 렌더링하는 라이브러리 OrgWebAlchemy를 소개합니다. 중첩 목록과 인라인 마크업처럼 정규식만으로 다루기 까다로운 문법을 처리하며, 아직 Org 전체 기능을 지원하지는 않습니다.

AI 요약

OrgWebAlchemy는 Emacs 없이 Org 문서를 HTML로 내보내려는 목적으로 만든 Guile Scheme 라이브러리입니다. Org 문서를 Parsing Expression Grammar(PEG)로 파싱해 추상 구문 트리(AST)를 만들고, 이를 SXML로 변환한 뒤 HTML로 렌더링합니다. 간단한 예로 This is ~test~ code.를 입력하면 This is <code>test</code> code. 형태로 출력합니다.

정규식 대신 PEG를 쓰는 이유

제작자는 처음에 정규식으로 Org 문서를 파싱했지만, 문법이 복잡해지면서 한계를 느꼈다고 설명합니다. 제목과 문단, 목록은 각각 간단해 보여도 중첩 목록, 순서가 있는 목록과 없는 목록, 들여쓰기 단계, 인라인 마크업, 설명이 붙은 링크, 코드·예제·인용 블록, 표, 이스케이프 등을 함께 처리해야 합니다. 특정 블록 안에서는 입력을 멈춰야 하는 위치도 정확히 판단해야 합니다.

정규식을 계속 덧붙이면 “이것과 일치하되 저것이 뒤따르면 제외하고, 특정 블록 안에서는 예외로 하고, 줄바꿈은 소비하지 않되 이전 줄이 목록 항목이면 다시 허용하는” 식의 조건이 생깁니다. 글쓴이는 이쯤 되면 언어 문법보다 파서 버그의 역사를 설명하는 데 가까워진다고 말합니다. 이에 Guile의 (ice-9 peg) 모듈로 문법을 정의합니다. element 규칙에는 빈 줄, 제목, 구분선, 표, 여러 블록과 목록, 문단 등을 대안으로 나열합니다. 문법 자체가 지원하는 Org 요소를 설명하는 문서처럼 읽히는 점도 장점으로 꼽습니다.

Guile에서는 PEG를 S-expression으로 표현할 수 있으며, 전통적인 문법 표기법도 쓸 수 있습니다. 글쓴이는 Lisp 문법 안에서 파서를 정의하는 방식을 선호합니다. 본문에서는 AI를 PEG 이해와 디버깅에 활용했다고 밝히면서도, 구현은 직접 진행했으며 단위 테스트와 수동 검증, AST 출력 확인을 거쳐 다듬었다고 설명합니다.

파싱 결과와 렌더링

처리 단계는 Org 문서에서 PEG 파싱을 거쳐 AST를 만든 다음, SXML을 HTML로 렌더링하는 구조입니다. 파서가 문법을 해석하는 부분과 출력 형식을 만드는 부분을 분리해 두었습니다. 그래서 현재 HTML 출력 외에 Markdown 등 다른 형식으로 내보내는 확장도 염두에 둘 수 있습니다.

예를 들어 - name :: Josep 같은 설명 목록 항목은 키와 내용으로 나뉜 AST 형태로 표현합니다. 중첩 목록은 파싱 단계에서 먼저 평평한 목록 항목의 연속으로 읽고, AST 처리 단계에서 들여쓰기를 바탕으로 계층 구조를 구성합니다. 렌더러는 그 결과를 중첩된 <ul>과 <li>로 출력합니다. 다만 서로 다른 종류의 목록을 중첩해 섞는 경우에는 아직 작은 문제가 남아 있다고 글쓴이는 덧붙입니다.

HTML 표현에는 SXML을 씁니다. 마크업을 Lisp 데이터 구조로 나타내면 트리를 조립하기 편하다는 설명입니다. 출력 HTML은 Guile parameter로 사용자화할 수 있습니다. 대부분은 클래스 목록을 바꾸는 방식이며, 제목 단계별 클래스는 함수로 지정할 수 있습니다. 예를 들어 1단계 제목에는 text-4xl과 font-bold, 2단계에는 text-2xl과 font-semibold를 적용하고 나머지에는 text-base를 설정할 수 있습니다.

지원 범위와 프로젝트 상태

현재 지원하는 요소는 제목, 문단, 중첩 수준이 있는 순서·비순서·설명 목록, 기울임꼴·굵은 글씨·인라인 코드, 설명이 있거나 없는 링크, 가로 구분선, 표, #+begin_src, #+begin_example, #+begin_quote 블록입니다. 인용 블록과 링크는 내부 내용을 다시 파싱합니다. #+begin_export html 블록은 Org 인라인 파서를 거치지 않고 신뢰된 리터럴 출력으로 내보냅니다.

OrgWebAlchemy는 Org의 모든 기능을 구현한 파서가 아닙니다. 글쓴이는 Org가 규모가 큰 소프트웨어라는 점을 언급하며, 현재는 주요 구성 요소 일부를 지원한다고 선을 긋습니다. 프로젝트는 버전 1.0이 어느 정도 안정된 상태이며, 저장소에 테스트 스위트도 있습니다. GNU LGPL v3 이상 라이선스를 적용했고, GNU Guix용 guile-orgwebalchemy 패키징을 준비하고 있습니다. 글쓴이는 문법, AST 설계, 파서 구조와 아직 처리하지 못한 Org 요소에 관한 피드백을 요청합니다.

Lobsters 반응

  • @e3bc54b2 — 훌륭한 작업입니다! 사실상 Org의 대체 구현이며, Org 명세가 완전하고 정확한지 확인하는 데도 쓸모가 있을 것 같습니다. 다른 구현도 몇 개 있습니다. Organics는 JavaScript를 쓰고, 하나 더 있는데 이름이 기억나지 않습니다. PEG는 이해하기 훨씬 쉽습니다. 계속 지켜보겠습니다. 흥미로운 프로젝트입니다.
    • @tonyarkles — Pandoc에도 Haskell로 구현한 기능이 있는 것 같습니다. Org용 PEG가 생기는 건 정말 반갑습니다. 저도 오래전에 Elixir로 파서와 왕복 로더, AST, 작성기까지 4분의 3 정도 만들었는데, 공식 명세가 없어 꽤 힘들었습니다. 이 프로젝트 같은 PEG 문법이나 BNF처럼 기준이 되는 무언가가 있으면 좋겠습니다. 명세 해석과 실제 호환 동작이 일치하는지 확인하려고 Org의 Elisp 구현도 자주 참고했습니다. 쉽지 않았습니다.
    • @jjba23 — 그런 작업에 뛰어들다니 꽤 용감하시네요! Pandoc도 Org 지원이 괜찮고 구현을 참고할 만합니다. Org는 과소평가받는다고 생각합니다. 여러 기능이 좋고 문법도 합리적이라 다른 형식보다 더 널리 쓰여야 합니다. 물론 오랜 기간 이어진 유산도 많습니다.
    • @tonyarkles — 인정하기 싫지만 저는 전반적으로 Org에서 다른 도구로 옮겼습니다. 모바일에서 쓸 만한 방법을 찾지 못했기 때문입니다. 현장 업무가 많았고, 노트북보다 iPad가 편했습니다. 휴대전화에서 갑자기 기록할 일이 생겼을 때 Org에 넣을 좋은 방법도 없었습니다. Org가 그립기는 하지만, 제가 보기에는 모바일 환경이 그다지 나아지지 않았습니다. Todoist는 아내와 할 일 목록을 공유하는 기능도 훨씬 잘 지원합니다. 언젠가 돌아갈지도 모르지만, 현장 업무를 하지 않게 되거나 훌륭한 모바일 Org 앱이 나오기 전까지는 어려울 것 같습니다.
    • @jjba23 — 충분히 이해합니다!
    • @jjba23 — 댓글 감사합니다. 지금은 내보내기 백엔드가 하나뿐인 대체 Org 구현이지만, 다른 형식으로 렌더링할 가능성도 생겼습니다. PEG가 자기 설명적이고 우아한 방식으로 구현된 점이 특히 마음에 듭니다. 앞으로 호환성 높은 구현에 더 가까워지길 바랍니다.

원문: jointhefreeworld.org / 번역·요약: Trawling