Mercurial > code
comparison C++/modules/Ini/Ini.cpp @ 334:0b576ee64d45
* Create brand new hierarchy
* Rename DynLib to Dynlib
* Remove some warnings
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 08 Mar 2015 14:26:33 +0100 |
parents | C++/Ini.cpp@43b4163470c2 |
children | d5ec1174b707 |
comparison
equal
deleted
inserted
replaced
333:412ca7a5e1ea | 334:0b576ee64d45 |
---|---|
1 /* | |
2 * Ini.cpp -- .ini file parsing | |
3 * | |
4 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #include <cctype> | |
20 #include <cerrno> | |
21 #include <cstring> | |
22 #include <fstream> | |
23 #include <iostream> | |
24 #include <iterator> | |
25 #include <memory> | |
26 #include <ostream> | |
27 #include <sstream> | |
28 #include <vector> | |
29 | |
30 #if defined(_WIN32) | |
31 # include <Shlwapi.h> // for PathIsRelative | |
32 #endif | |
33 | |
34 #include "Ini.h" | |
35 | |
36 namespace { | |
37 | |
38 /* -------------------------------------------------------- | |
39 * Tokens | |
40 * -------------------------------------------------------- */ | |
41 | |
42 enum class TokenType { | |
43 Comment = '#', | |
44 SectionBegin = '[', | |
45 SectionEnd = ']', | |
46 Escape = '\\', | |
47 QuoteSimple = '\'', | |
48 QuoteDouble = '"', | |
49 NewLine = '\n', | |
50 Assign = '=', | |
51 Include = '@', | |
52 Word, | |
53 Space | |
54 }; | |
55 | |
56 class Token { | |
57 private: | |
58 TokenType m_type; | |
59 int m_line; | |
60 int m_position; | |
61 std::string m_value; | |
62 | |
63 public: | |
64 inline Token(TokenType type, int line, int position, std::string value = "") | |
65 : m_type(type) | |
66 , m_line(line) | |
67 , m_position(position) | |
68 , m_value(std::move(value)) | |
69 { | |
70 } | |
71 | |
72 inline TokenType type() const noexcept | |
73 { | |
74 return m_type; | |
75 } | |
76 | |
77 inline int line() const noexcept | |
78 { | |
79 return m_line; | |
80 } | |
81 | |
82 inline int position() const noexcept | |
83 { | |
84 return m_position; | |
85 } | |
86 | |
87 inline std::string value() const | |
88 { | |
89 switch (m_type) { | |
90 case TokenType::Comment: | |
91 return "#"; | |
92 case TokenType::SectionBegin: | |
93 return "["; | |
94 case TokenType::SectionEnd: | |
95 return "]"; | |
96 case TokenType::QuoteSimple: | |
97 return "'"; | |
98 case TokenType::QuoteDouble: | |
99 return "\""; | |
100 case TokenType::NewLine: | |
101 return "\n"; | |
102 case TokenType::Assign: | |
103 return "="; | |
104 case TokenType::Include: | |
105 return "@"; | |
106 case TokenType::Space: | |
107 return m_value; | |
108 case TokenType::Word: | |
109 return m_value; | |
110 default: | |
111 break; | |
112 } | |
113 | |
114 return ""; | |
115 } | |
116 | |
117 inline std::string toString() const | |
118 { | |
119 switch (m_type) { | |
120 case TokenType::Comment: | |
121 return "'#'"; | |
122 case TokenType::SectionBegin: | |
123 return "'['"; | |
124 case TokenType::SectionEnd: | |
125 return "']'"; | |
126 case TokenType::QuoteSimple: | |
127 return "'"; | |
128 case TokenType::QuoteDouble: | |
129 return "\""; | |
130 case TokenType::NewLine: | |
131 return "<newline>"; | |
132 case TokenType::Assign: | |
133 return "="; | |
134 case TokenType::Include: | |
135 return "@"; | |
136 case TokenType::Space: | |
137 return "<blank>"; | |
138 case TokenType::Word: | |
139 return "`" + m_value + "'"; | |
140 default: | |
141 break; | |
142 } | |
143 | |
144 return ""; | |
145 } | |
146 }; | |
147 | |
148 using TokenStack = std::vector<Token>; | |
149 | |
150 /* -------------------------------------------------------- | |
151 * IniBuilder | |
152 * -------------------------------------------------------- */ | |
153 | |
154 class IniBuilder { | |
155 private: | |
156 std::string m_path; | |
157 std::string m_base; | |
158 Ini &m_ini; | |
159 | |
160 private: | |
161 inline bool isReserved(char c) const noexcept | |
162 { | |
163 return c == '\n' || c == '#' || c == '"' || c == '\'' || c == '=' || c == '[' || c == ']' || c == '@'; | |
164 } | |
165 | |
166 std::string base(std::string path) | |
167 { | |
168 auto pos = path.find_last_of("/\\"); | |
169 | |
170 if (pos != std::string::npos) { | |
171 path.erase(pos); | |
172 } else { | |
173 path = "."; | |
174 } | |
175 | |
176 return path; | |
177 } | |
178 | |
179 #if defined(_WIN32) | |
180 bool isAbsolute(const std::string &path) | |
181 { | |
182 return !PathIsRelative(path.c_str()); | |
183 } | |
184 #else | |
185 bool isAbsolute(const std::string &path) | |
186 { | |
187 return path.size() > 0 && path[0] == '/'; | |
188 } | |
189 #endif | |
190 | |
191 std::vector<Token> analyze(std::istream &stream) const | |
192 { | |
193 std::istreambuf_iterator<char> it(stream); | |
194 std::istreambuf_iterator<char> end; | |
195 std::vector<Token> tokens; | |
196 | |
197 int lineno{1}; | |
198 int position{0}; | |
199 | |
200 while (it != end) { | |
201 std::string value; | |
202 | |
203 if (isReserved(*it)) { | |
204 while (it != end && isReserved(*it)) { | |
205 // Single character tokens | |
206 switch (*it) { | |
207 case '\n': | |
208 ++lineno; | |
209 position = 0; | |
210 case '#': | |
211 case '[': | |
212 case ']': | |
213 case '\'': | |
214 case '"': | |
215 case '=': | |
216 case '@': | |
217 tokens.push_back({ static_cast<TokenType>(*it), lineno, position }); | |
218 ++it; | |
219 ++position; | |
220 default: | |
221 break; | |
222 } | |
223 } | |
224 } else if (std::isspace(*it)) { | |
225 while (it != end && std::isspace(*it) && *it != '\n') { | |
226 value.push_back(*it++); | |
227 } | |
228 | |
229 tokens.push_back({ TokenType::Space, lineno, position, std::move(value) }); | |
230 } else { | |
231 while (it != end && !std::isspace(*it) && !isReserved(*it)) { | |
232 value.push_back(*it++); | |
233 } | |
234 | |
235 tokens.push_back({ TokenType::Word, lineno, position, std::move(value) }); | |
236 } | |
237 } | |
238 | |
239 return tokens; | |
240 } | |
241 | |
242 void readComment(TokenStack::iterator &it, TokenStack::iterator end) | |
243 { | |
244 while (it != end && it->type() != TokenType::NewLine) { | |
245 ++ it; | |
246 } | |
247 | |
248 // remove new line | |
249 ++ it; | |
250 } | |
251 | |
252 void readSpace(TokenStack::iterator &it, TokenStack::iterator end) | |
253 { | |
254 while (it != end && it->type() == TokenType::Space) { | |
255 ++ it; | |
256 } | |
257 } | |
258 | |
259 void readNewLine(TokenStack::iterator &it, TokenStack::iterator end) | |
260 { | |
261 while (it != end && it->type() == TokenType::NewLine) { | |
262 ++ it; | |
263 } | |
264 } | |
265 | |
266 IniSection readSection(TokenStack::iterator &it, TokenStack::iterator end) | |
267 { | |
268 if (++it == end || it->type() != TokenType::Word) { | |
269 throw IniError(it->line(), it->position(), "word expected after [, got " + it->toString()); | |
270 } | |
271 | |
272 IniSection section(it->value()); | |
273 | |
274 if (++it == end || it->type() != TokenType::SectionEnd) { | |
275 throw IniError(it->line(), it->position(), "] expected, got " + it->toString()); | |
276 } | |
277 | |
278 // Remove ] | |
279 ++ it; | |
280 | |
281 if (it == end) { | |
282 return section; | |
283 } | |
284 | |
285 while (it != end && it->type() != TokenType::SectionBegin) { | |
286 if (it->type() == TokenType::Space) { | |
287 readSpace(it, end); | |
288 } else if (it->type() == TokenType::NewLine) { | |
289 readNewLine(it, end); | |
290 } else if (it->type() == TokenType::Comment) { | |
291 readComment(it, end); | |
292 } else if (it->type() == TokenType::Word) { | |
293 section.push_back(readOption(it, end)); | |
294 } else { | |
295 throw IniError(it->line(), it->position(), "unexpected token " + it->toString()); | |
296 } | |
297 } | |
298 | |
299 return section; | |
300 } | |
301 | |
302 IniOption readOption(TokenStack::iterator &it, TokenStack::iterator end) | |
303 { | |
304 std::string key = it->value(); | |
305 | |
306 if (++it == end) { | |
307 throw IniError(it->line(), it->position(), "expected '=' after option declaration, got <EOF>"); | |
308 } | |
309 | |
310 readSpace(it, end); | |
311 | |
312 if (it == end || it->type() != TokenType::Assign) { | |
313 throw IniError(it->line(), it->position(), "expected '=' after option declaration, got " + it++->toString()); | |
314 } | |
315 | |
316 readSpace(++it, end); | |
317 | |
318 std::ostringstream oss; | |
319 | |
320 if (it->type() == TokenType::QuoteSimple || it->type() == TokenType::QuoteDouble) { | |
321 TokenStack::iterator save = it++; | |
322 | |
323 while (it != end && it->type() != save->type()) { | |
324 oss << it++->value(); | |
325 } | |
326 | |
327 if (it == end) | |
328 throw IniError(save->line(), save->position(), "undisclosed quote: " + save->toString() + " expected"); | |
329 | |
330 ++ it; | |
331 } else if (it->type() == TokenType::Word) { | |
332 oss << it++->value(); | |
333 } else if (it->type() != TokenType::NewLine && it->type() != TokenType::Comment) { | |
334 // No value requested, must be NewLine or comment | |
335 throw IniError(it->line(), it->position(), "expected option value after '=', got " + it->toString()); | |
336 } | |
337 | |
338 | |
339 return IniOption(std::move(key), oss.str()); | |
340 } | |
341 | |
342 void readInclude(TokenStack::iterator &it, TokenStack::iterator end) | |
343 { | |
344 if (++it == end || (it->type() != TokenType::Word || it->value() != "include")) { | |
345 throw IniError(it->line(), it->position(), "expected `include' after '@' token, got " + it->toString()); | |
346 } | |
347 | |
348 readSpace(++it, end); | |
349 | |
350 // Quotes mandatory | |
351 TokenStack::iterator save = it; | |
352 | |
353 if (it == end || (it->type() != TokenType::QuoteSimple && it->type() != TokenType::QuoteDouble)) { | |
354 throw IniError(it->line(), it->position(), "expected filename after @include statement"); | |
355 } | |
356 | |
357 // Filename | |
358 if (++it == end || it->type() != TokenType::Word) { | |
359 throw IniError(it->line(), it->position(), "expected filename after @include statement"); | |
360 } | |
361 | |
362 std::string value = it->value(); | |
363 std::string fullpath; | |
364 | |
365 if (isAbsolute(value)) { | |
366 fullpath = value; | |
367 } else { | |
368 fullpath = m_base + "/" + it->value(); | |
369 } | |
370 | |
371 // Must be closed with the same quote | |
372 if (++it == end || it->type() != save->type()) { | |
373 throw IniError(save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected"); | |
374 } | |
375 | |
376 // Remove quote | |
377 ++ it; | |
378 | |
379 IniBuilder(m_ini, fullpath); | |
380 } | |
381 | |
382 public: | |
383 IniBuilder(Ini &ini, std::string path) | |
384 : m_path(path) | |
385 , m_base(base(std::move(path))) | |
386 , m_ini(ini) | |
387 { | |
388 std::ifstream file(m_path); | |
389 | |
390 if (!file.is_open()) | |
391 throw std::runtime_error(std::strerror(errno)); | |
392 | |
393 std::vector<Token> ts = analyze(file); | |
394 | |
395 auto it = ts.begin(); | |
396 auto end = ts.end(); | |
397 | |
398 while (it != end) { | |
399 if (it->type() == TokenType::Space) { | |
400 readSpace(it, end); | |
401 } else if (it->type() == TokenType::NewLine) { | |
402 readNewLine(it, end); | |
403 } else if (it->type() == TokenType::Comment) { | |
404 readComment(it, end); | |
405 } else if (it->type() == TokenType::Include) { | |
406 readInclude(it, end); | |
407 } else if (it->type() == TokenType::SectionBegin) { | |
408 m_ini.push_back(readSection(it, end)); | |
409 } else { | |
410 throw IniError(it->line(), it->position(), "unexpected " + it->toString() + " on root document"); | |
411 } | |
412 } | |
413 } | |
414 }; | |
415 | |
416 } // !namespace | |
417 | |
418 /* -------------------------------------------------------- | |
419 * Ini | |
420 * -------------------------------------------------------- */ | |
421 | |
422 Ini::Ini(const std::string &path) | |
423 { | |
424 IniBuilder(*this, path); | |
425 } |