LLM에서 마크다운이 널리 쓰이게 되면서 안 보고 싶어도 볼 수 밖에 없게 된 흔한 꼬라지로 그림에서 보는 것처럼 마크다운 강조 표시(**)가 그대로 노출되어 버리는 광경이 있다. 이 문제는 CommonMark의 고질적인 문제로, 한 10년 전쯤에 보고한 적도 있는데 지금까지 어떤 해결책도 제시되지 않은 채로 방치되어 있다.
문제의 상세는 이러하다. CommonMark는 마크다운을 표준화하는 과정에서 파싱의 복잡도를 제한하기 위해 연속된 구분자(delimiter run)라는 개념을 넣었는데, 연속된 구분자는 어느 방향에 있느냐에 따라서 왼편(left-flanking)과 오른편(right-flanking)이라는 속성을 가질 수 있다(왼편이자 오른편일 수도 있고, 둘 다 아닐 수도 있다). 이 규칙에 따르면 **는 왼편의 연속된 구분자로부터 시작해서 오른편의 연속된 구분자로 끝나야만 한다. 여기서 중요한 건 왼편인지 오른편인지를 판단하는 데 외부 맥락이 전혀 안 들어가고 주변의 몇 글자만 보고 바로 결정된다는 것인데, 이를테면 왼편의 연속된 구분자는 **<보통 글자> 꼴이거나 <공백>**<기호> 또는 <기호>**<기호> 꼴이어야 한다. ("보통 글자"란 공백이나 기호가 아닌 글자를 가리킨다.) 첫번째 꼴은 아무래도 **마크다운**은 같이 낱말 안에 끼어 들어가 있는 연속된 구분자를 허용하기 위한 것이고, 두번째/세번째 꼴은 이 **"마크다운"** 형식은 같이 기호 앞에 붙어 있는 연속된 구분자를 제한적으로 허용하기 위한 것이라 해석할 수 있겠다. 오른편도 방향만 다르고 똑같은 규칙을 가지는데, 이 규칙으로 **마크다운(Markdown)**은을 해석해 보면 뒷쪽 **의 앞에는 기호가 들어 있으므로 뒤에는 공백이나 기호가 나와야 하지만 보통 글자가 나왔으므로 오른편이 아니라고 해석되어 강조의 끝으로 처리되지 않는 것이다.
CommonMark 명세에서도 설명되어 있지만, 이 규칙의 원 의도는 **이런 **식으로** 중첩되어** 강조된 문법을 허용하기 위한 것이다. 강조를 한답시고 **이런 ** 식으로 공백을 강조 문법 안쪽에 끼워 넣는 일이 일반적으로는 없으므로, 이런 상황에서 공백에 인접한 강조 문법은 항상 특정 방향에만 올 수 있다고 선언하는 것으로 모호함을 해소하는 것이다. 허나 CJK 환경에서는 공백이 아예 없거나 공백이 있어도 한국어처럼 낱말 안에서 기호를 쓰는 경우가 드물지 않기 때문에, 이런 식으로 어느 연속된 구분자가 왼편인지 오른편인지 추론하는 데 한계가 있다는 것이다. 단순히 <보통 문자>**<기호>도 왼편으로 해석하는 식으로 해서 **마크다운(Markdown)**은 같은 걸 허용한다 하더라도, このような**[状況](...)**は 이런 상황은 어쩔 것인가? 내가 느끼기에는 중첩되어 강조된 문법의 효용은 제한적인 반면 이로 인해 생기는 CJK 환경에서의 불편함은 명확하다. 그리고 LLM은 CommonMark의 설계 의도 따위는 고려하지 않고 실제 사람들이 사용할 법한 식으로 마크다운을 쓰기 때문에, 사람들이 막연하게 가지고만 있던 이런 불편함이 그대로 표면화되어 버린 것이고 말이다.
![* 21. Ba5# - 백이 룩과 퀸을 희생한 후, 퀸 대신 **비숍(Ba5)**이 결정적인 체크메이트를 성공시킵니다. 흑 킹이 탈출할 곳이 없으며, 백의 기물로 막을 수도 없습니다. [강조 처리된 "비숍(Ba5)" 앞뒤에 마크다운의 강조 표시 "**"가 그대로 노출되어 있다.]](https://media.hackers.pub/note-media/17646c5d-3f9d-472b-9d56-dd34006ad291.webp)
![> 다음 C 프로그램이 하는 일을 알려 줘. [코드 생략]
이 C 프로그램은 **난독화(obfuscated)**된 코드로, 입력받은 **시간 또는 단위가 포함된 문자열**을 해석하여 총합을 계산한 뒤, 그 결과를 **16진수(Hexadecimal)**로 출력하는 프로그램입니다.
구체적인 작동 방식은 다음과 같습니다:
1. **입력 처리**:
* `getchar()`를 통해 표준 입력(stdin)에서 문자를 하나씩 읽어들입니다.
* `|32`를 통해 대문자를 소문자로 변환하여 처리합니다.
2. **문자 매핑 (해싱)**:
* `"ynwtsflrabg"`라는 문자열은 입력받은 문자가 특정 단위(예: year, week, second 등)인지 확인하기 위한 인덱스 문자열입니다.
* 입력된 문자가 이 문자열에 포함되면 변수 `b`에 특정 해시 값을 누적합니다.
3. **단위 변환 및 계산**:
* `"1+DIY/.K430x9G(kC["`라는 복잡한 문자열은 **룩업 테이블(Lookup Table)** 역할을 합니다.
* 내부 루프에서 이 테이블과 비트 연산(`&255^b`)을 통해 현재 입력된 단위가 무엇인지(예: 'y'가 연도인지, 's'가 초인지) 판별합니다.
* `u=1ll<<6177%n--*4` 부분은 각 단위에 맞는 **가중치(Multiplier)**를 계산합니다. `<<` 비트 시프트 연산을 사용하는 것으로 보아, 특정 단위마다 2의 제곱수 형태의 배수를 적용하거나, 혹은 시간을 초 단위(또는 나노초 단위)로 변환하는 계수를 생성합니다.
4. **결과 누적**:
* 변수 `m`에 파싱된 수치와 단위 가중치를 곱하여 누적합니다.
5. **출력**:
* 최종적으로 계산된 값 `m`을 `printf("%llx\n", m)`을 통해 **16진수(long long hex)** 형태로 출력합니다.
**요약하자면:**
이 코드는 "1y(1년)", "2w(2주)", "30s(30초)"와 같은 형식의 텍스트를 입력받아, 이를 초(second)나 특정 기본 단위로 환산한 총합을 16진수로 보여주는 **단위 변환 계산기**입니다.](https://media.hackers.pub/note-media/53eb3036-9463-495d-bb5c-a04de12a7f21.webp)



![Angel의 2025-08-01 현재 스크린샷. 좌측에 New Session, Settings 버튼 및 세션 목록이 있다. 오른쪽에는 주황색 시스템 프롬프트, 초기 사용자 입력, 생각 거품(12개 중 7번째가 선택되어 보여지고 있음), 에이전트 출력, 그리고 후속 사용자 입력까지가 보이며, 그 하단에는 파일 첨부 버튼, 사용자 입력 칸(여러 줄 지원), 그리고 Send 버튼이 표시된다.
사용자 입력:
다음 코드가 [...] const [type, data] = eventString.slice(6).replace(/\ndata: /g, '\n').split('\n', 2); [...] 다음과 같은 로그를 출력하는 문제를 고쳐 줘. [...]
에이전트 출력:
The problem stems from the `split('\n', 2)` part of your code. This function limits the split to a maximum of two parts. When `eventString` is processed, `type` correctly captures the first part (`M`), but `data` only captures the content until the *next* newline, effectively truncating the rest of your message.
To fix this, we need to first remove all the `data: ` prefixes from the `eventString` and then split the resulting string into `type` (the first line) and `data` (all subsequent lines).
Here's the corrected code:](https://media.hackers.pub/note-media/8fb85054-b96c-4846-883b-70dc14421ce5.webp)

