-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAuthoringRymlErrorPolicy.cpp
More file actions
187 lines (169 loc) · 6.32 KB
/
Copy pathAuthoringRymlErrorPolicy.cpp
File metadata and controls
187 lines (169 loc) · 6.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include "AuthoringRymlErrorPolicy.h"
#include <mutex>
#include <stdexcept>
#include <ryml/ryml.hpp>
#include <ryml/ryml_std.hpp>
namespace Authoring
{
namespace
{
// 메시지 앞에 **채널 이름**을 붙인다. ryml은 에러를 basic/parse/visit
// 셋으로 나누고, 하나만 덮으면 나머지는 여전히 abort한다. 그런데
// what() 문자열만 보면 어느 채널을 타고 왔는지 알 수 없어서,
// 게이트가 "둘 다 덮였다"를 단정하지 못한다. 태그가 그것을 가능하게 한다.
[[noreturn]] void ThrowFromRyml(const char* channel, ryml::csubstr message)
{
// 메시지가 비어 오는 경우가 있다(내부 assert 경로). 빈 what()은
// 진단에서 "예외가 안 왔다"와 구분되지 않으므로 자리표를 채운다.
std::string text = "[";
text += channel;
text += "] ";
if (nullptr == message.str || 0 == message.len)
{
text += "(no message)";
}
else
{
text.append(message.str, message.len);
}
throw std::runtime_error(text);
}
void Install()
{
// ryml 0.16은 에러 콜백을 basic/parse/visit 셋으로 나눈다. **하나만
// 덮으면 나머지 경로에서 여전히 abort한다** — 실측으로 만난 두 실패가
// 각각 basic(CRLF)과 parse(멀티라인 키)였다. 셋을 모두 덮는다.
ryml::Callbacks callbacks = ryml::get_callbacks();
callbacks.m_error_basic = [](ryml::csubstr msg,
ryml::ErrorDataBasic const&, void*)
{
ThrowFromRyml("basic", msg);
};
callbacks.m_error_parse = [](ryml::csubstr msg,
ryml::ErrorDataParse const&, void*)
{
ThrowFromRyml("parse", msg);
};
callbacks.m_error_visit = [](ryml::csubstr msg,
ryml::ErrorDataVisit const&, void*)
{
ThrowFromRyml("visit", msg);
};
// 할당자 콜백은 **건드리지 않는다.** ryml 기본 할당자를 그대로 두는
// 것이 여기서는 옳다 — 이 저장소는 전역 operator new 교체가 yaml-cpp
// 내부 할당에서 터져 철회된 이력이 있다(메모리 계층 3중). 파서를
// 옮기면서 할당자까지 함께 바꾸면 실패가 섞여 원인을 못 가른다.
ryml::set_callbacks(callbacks);
}
}
void EnsureRymlErrorPolicy()
{
// WriteDocument는 장기 보관 Document의 backend이므로 SceneManager 같은
// 전역 서비스가 정적 초기화 중 생성할 수 있다. namespace 전역 once_flag를
// 쓰면 다른 TU의 초기화 순서에 따라 아직 생성되지 않은 flag를 건드리는
// 정적 초기화 순서 결함이 된다. 함수 지역 정적은 첫 호출에 초기화되므로
// 그 순서와 무관하다.
static std::once_flag installOnce;
std::call_once(installOnce, &Install);
}
RymlErrorPolicyProbe ProbeRymlErrorPolicy()
{
EnsureRymlErrorPolicy();
RymlErrorPolicyProbe probe;
std::string loneCrChannel;
std::string tabChannel;
const auto channelOf = [](const std::string& message) -> std::string
{
// 메시지는 "[basic] ..." 꼴이다. 태그가 없으면 빈 문자열을 돌려
// "채널을 알 수 없음"을 통과로 읽지 않게 한다.
if (message.size() < 2 || message[0] != '[') return {};
const std::size_t close = message.find(']');
if (std::string::npos == close) return {};
return message.substr(1, close - 1);
};
const auto record = [&probe](const std::exception& exception)
{
if (probe.firstMessage.empty()) probe.firstMessage = exception.what();
};
// ── basic 채널: 홀로 선 \r ──────────────────────────────────────────
//
// 옛 Mac 개행이거나 \rLF가 반쯤 깨진 파일이다. 외부 도구·수기 편집·
// 잘못된 병합으로 실제로 들어온다. ryml은 여기서 abort한다.
try
{
const ryml::Tree tree = ryml::parse_in_arena(
ryml::to_csubstr("root:\r key: value"));
(void)tree;
}
catch (const std::exception& exception)
{
probe.threwOnLoneCr = true;
loneCrChannel = channelOf(exception.what());
record(exception);
}
// ── parse 채널: 탭 들여쓰기 ─────────────────────────────────────────
//
// YAML 명세가 금지하는 형태이고 에디터 설정 하나로 쉽게 만들어진다.
try
{
const ryml::Tree tree = ryml::parse_in_arena(
ryml::to_csubstr("root:\n\tkey: value\n"));
(void)tree;
}
catch (const std::exception& exception)
{
probe.threwOnTabIndent = true;
tabChannel = channelOf(exception.what());
record(exception);
}
probe.coveredDistinctChannels =
!loneCrChannel.empty() && !tabChannel.empty() && (loneCrChannel != tabChannel);
// ── 대조군 1: 정상 문서는 여전히 읽힌다 ─────────────────────────────
try
{
const ryml::Tree tree = ryml::parse_in_arena(
ryml::to_csubstr("root:\n key: value\n list:\n - 1\n - 2\n"));
probe.parsedValidDocument =
(tree.size() > 0) && tree.rootref().has_child("root");
}
catch (const std::exception&)
{
probe.parsedValidDocument = false;
}
// ── 대조군 2: \rLF는 정상 파싱된다 ──────────────────────────────────
//
// 이 단정이 깨지면 ryml의 개행 처리가 바뀐 것이고, 그러면 파싱 전 정규화
// 사본이 다시 필요해진다 — D3-b의 성능 계산이 달라지는 지점이다.
try
{
const ryml::Tree tree = ryml::parse_in_arena(
ryml::to_csubstr("root:\r\n key: value\r\n list:\r\n - 1\r\n - 2\r\n"));
probe.parsedCrLfDocument =
(tree.size() > 0) && tree.rootref().has_child("root");
}
catch (const std::exception&)
{
probe.parsedCrLfDocument = false;
}
return probe;
}
RymlParseAttempt TryParseWithPolicy(const std::string& text)
{
EnsureRymlErrorPolicy();
RymlParseAttempt attempt;
try
{
const ryml::Tree tree = ryml::parse_in_arena(
ryml::csubstr(text.data(), text.size()));
attempt.nodeCount = static_cast<std::uint64_t>(tree.size());
attempt.parsed = true;
}
catch (const std::exception& exception)
{
attempt.threw = true;
attempt.message = exception.what();
if (attempt.message.empty()) attempt.message = "(empty what())";
}
return attempt;
}
}