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 }