Tulip  6.0.0
Large graphs analysis and drawing
TLPParser.h
1 /*
2  *
3  * This file is part of Tulip (https://tulip.labri.fr)
4  *
5  * Authors: David Auber and the Tulip development Team
6  * from LaBRI, University of Bordeaux
7  *
8  * Tulip is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation, either version 3
11  * of the License, or (at your option) any later version.
12  *
13  * Tulip is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  * See the GNU General Public License for more details.
17  *
18  */
19 ///@cond DOXYGEN_HIDDEN
20 
21 #ifndef TLPPARSER_H
22 #define TLPPARSER_H
23 
24 #include <sstream>
25 #include <string>
26 #include <list>
27 
28 namespace tlp {
29 
30 class PluginProgress;
31 struct TLPParser;
32 
33 struct ParserError {
34  ParserError(int err = 0, int lin = 0, int cha = 0)
35  : errorNumber(err), lineInFile(lin), charInLine(cha) {}
36  int errorNumber;
37  int lineInFile;
38  int charInLine;
39 };
40 
41 struct TLPValue {
42  std::string str;
43  long integer;
44  double real;
45  bool boolean;
46  std::pair<long, long> range;
47 };
48 
49 enum TLPToken {
50  BOOLTOKEN,
51  ENDOFSTREAM,
52  STRINGTOKEN,
53  INTTOKEN,
54  DOUBLETOKEN,
55  IDTOKEN,
56  ERRORINFILE,
57  OPENTOKEN,
58  CLOSETOKEN,
59  COMMENTTOKEN,
60  RANGETOKEN
61 };
62 //=====================================================================================
63 struct TLPTokenParser {
64  int curLine;
65  std::istream &is;
66  TLPTokenParser(std::istream &i) : curLine(0), is(i) {}
67 
68  bool newLine(char ch, int &curPos) {
69  if (ch != '\n') {
70  is.get(ch);
71 
72  if (ch != '\n') {
73  is.unget();
74  return false;
75  }
76 
77  ++curPos;
78  }
79 
80  ++curLine;
81  return true;
82  }
83 
84  TLPToken nextToken(TLPValue &val, int &curPos) {
85  val.str.erase();
86  bool endOfStream = false, strGet = false, slashMode = false, started = false, stop = false,
87  strComment = false;
88  char ch;
89 
90  while ((!stop) && (endOfStream = !(is.get(ch).fail()))) {
91  ++curPos;
92 
93  if (strGet)
94  switch (ch) {
95  case 13:
96  case '\n':
97  if (!newLine(ch, curPos))
98  break;
99 
100  val.str += ch;
101  break;
102 
103  case '\t':
104  val.str += " ";
105  break;
106 
107  case '\\':
108 
109  if (!slashMode) {
110  slashMode = true;
111  } else {
112  val.str += ch;
113  slashMode = false;
114  }
115 
116  break;
117 
118  case '"':
119 
120  if (!slashMode) {
121  return STRINGTOKEN;
122  } else {
123  val.str += ch;
124  slashMode = false;
125  }
126 
127  break;
128 
129  case 'n':
130  if (slashMode) {
131  val.str += '\n';
132  slashMode = false;
133  break;
134  }
135  [[fallthrough]];
136  default:
137  if (!slashMode)
138  val.str += ch;
139 
140  slashMode = false;
141  break;
142  }
143  else if (strComment)
144  switch (ch) {
145  case 13:
146  case '\n':
147  if (!newLine(ch, curPos))
148  break;
149 
150  stop = true;
151  return COMMENTTOKEN;
152  break;
153 
154  default:
155  val.str += ch;
156  break;
157  }
158  else
159  switch (ch) {
160  case ' ':
161  case '\t':
162  if (started)
163  stop = true;
164 
165  break;
166 
167  case 13:
168  case '\n':
169  if (!newLine(ch, curPos))
170  break;
171 
172  if (started)
173  stop = true;
174 
175  break;
176 
177  case '(':
178  if (!started)
179  return OPENTOKEN;
180  else {
181  --curPos;
182  is.unget();
183  stop = true;
184  }
185 
186  break;
187 
188  case ')':
189  if (!started)
190  return CLOSETOKEN;
191  else {
192  --curPos;
193  is.unget();
194  stop = true;
195  }
196 
197  break;
198 
199  case '"':
200  strGet = true;
201 
202  if (started) {
203  --curPos;
204  is.unget();
205  stop = true;
206  } else
207  started = true;
208 
209  break;
210 
211  case ';':
212  strComment = true;
213 
214  if (started) {
215  --curPos;
216  is.unget();
217  stop = true;
218  } else
219  started = true;
220 
221  break;
222 
223  default:
224  val.str += ch;
225  started = true;
226  break;
227  }
228  }
229 
230  if (!started && !endOfStream)
231  return ENDOFSTREAM;
232 
233  char *endPtr = nullptr;
234  const char *cstr = val.str.c_str();
235  errno = 0;
236  long resultl = strtol(cstr, &endPtr, 10);
237 
238  if (errno == ERANGE)
239  return ERRORINFILE;
240 
241  unsigned long strlength = val.str.length();
242 
243  if (endPtr == (cstr + strlength)) {
244  val.integer = resultl;
245  return INTTOKEN;
246  }
247 
248  // check for a range
249  if (endPtr > cstr && (cstr + strlength) > (endPtr + 2)) {
250  val.range.first = resultl;
251 
252  if ((endPtr[0] == '.') && (endPtr[1] == '.')) {
253  char *beginPtr = endPtr + 2;
254  errno = 0;
255  resultl = strtol(beginPtr, &endPtr, 10);
256 
257  if (errno == ERANGE)
258  return ERRORINFILE;
259 
260  if (endPtr == (cstr + strlength)) {
261  if (resultl < val.range.first)
262  return ERRORINFILE;
263 
264  val.range.second = resultl;
265  return RANGETOKEN;
266  }
267  }
268  }
269 
270  endPtr = nullptr;
271 
272  double resultd = strtod(cstr, &endPtr);
273 
274  if (errno == ERANGE)
275  return ERRORINFILE;
276 
277  if (endPtr == (cstr + strlength)) {
278  val.real = resultd;
279  return DOUBLETOKEN;
280  }
281 
282  if (strcasecmp(cstr, "true") == 0) {
283  val.boolean = true;
284  return BOOLTOKEN;
285  }
286 
287  if (strcasecmp(cstr, "false") == 0) {
288  val.boolean = false;
289  return BOOLTOKEN;
290  }
291 
292  if (started)
293  return STRINGTOKEN;
294 
295  return ERRORINFILE;
296  }
297 };
298 //=====================================================================================
299 struct TLPBuilder {
300  virtual ~TLPBuilder() {}
301  virtual bool addBool(const bool) = 0;
302  virtual bool addInt(const int) = 0;
303  virtual bool addRange(int, int) = 0;
304  virtual bool addDouble(const double) = 0;
305  virtual bool addString(const std::string &) = 0;
306  virtual bool addStruct(const std::string &, TLPBuilder *&) = 0;
307  virtual bool close() = 0;
308  virtual bool canRead() {
309  return false;
310  }
311  virtual bool read(std::istream &) {
312  return false;
313  }
314 
315  TLPParser *parser;
316 };
317 
318 struct TLPTrue : public TLPBuilder {
319  bool addBool(const bool) override {
320  return true;
321  }
322  bool addInt(const int) override {
323  return true;
324  }
325  bool addRange(int, int) override {
326  return true;
327  }
328  bool addDouble(const double) override {
329  return true;
330  }
331  bool addString(const std::string &) override {
332  return true;
333  }
334  bool addStruct(const std::string & /*structName*/, TLPBuilder *&newBuilder) override {
335  newBuilder = new TLPTrue();
336  return true;
337  }
338  bool close() override {
339  return true;
340  }
341 };
342 
343 struct TLPFalse : public TLPBuilder {
344  bool addBool(const bool) override {
345  return false;
346  }
347  bool addInt(const int) override {
348  return false;
349  }
350  bool addRange(int, int) override {
351  return false;
352  }
353  bool addDouble(const double) override {
354  return false;
355  }
356  bool addString(const std::string &) override {
357  return false;
358  }
359  bool addStruct(const std::string & /*structName*/, TLPBuilder *&newBuilder) override {
360  newBuilder = new TLPFalse();
361  return false;
362  }
363  bool close() override {
364  return true;
365  }
366 };
367 //=====================================================================================
368 struct TLPParser {
369  std::list<TLPBuilder *> builderStack;
370  std::istream &inputStream;
371  TLPTokenParser *tokenParser;
372  PluginProgress *pluginProgress;
373  std::string errorMsg;
374  int fileSize, curPos;
375  bool displayComment;
376 
377  TLPParser(std::istream &inputStream, TLPBuilder *builder, PluginProgress *pluginProgress,
378  int size, bool dispComment = false)
379  : inputStream(inputStream), pluginProgress(pluginProgress), fileSize(size), curPos(0),
380  displayComment(dispComment) {
381  builderStack.push_front(builder);
382  builder->parser = this;
383  }
384 
385  ~TLPParser() {
386  while (!builderStack.empty()) {
387  TLPBuilder *builder = builderStack.front();
388  builderStack.pop_front();
389 
390  if (builderStack.empty() || builder != builderStack.front())
391  delete builder;
392  }
393  }
394 
395  bool formatError(const std::string &value) {
396  std::stringstream ess;
397  ess << "Error when parsing '" << value.c_str() << "' at line " << tokenParser->curLine + 1;
398 
399  if (errno)
400  ess << std::endl << tlp::getStrError();
401  else if (!errorMsg.empty())
402  ess << std::endl << errorMsg;
403 
404  pluginProgress->setError(ess.str());
405  return false;
406  }
407 
408  bool parse() {
409  TLPTokenParser tParser(inputStream);
410  tokenParser = &tParser;
411  TLPToken currentToken;
412  TLPValue currentValue;
413 
414  while ((currentToken = tokenParser->nextToken(currentValue, curPos)) != ENDOFSTREAM) {
415  if (curPos % 2000 == 1)
416  if (pluginProgress->progress(curPos, fileSize) != TLP_CONTINUE)
417  return pluginProgress->state() != TLP_CANCEL;
418 
419  switch (currentToken) {
420  case OPENTOKEN:
421  currentToken = tokenParser->nextToken(currentValue, curPos);
422 
423  if (currentToken != STRINGTOKEN)
424  return formatError(currentValue.str); // we can throw an exception
425 
426  TLPBuilder *newBuilder;
427 
428  if (builderStack.front()->addStruct(currentValue.str, newBuilder)) {
429  newBuilder->parser = this;
430  builderStack.push_front(newBuilder);
431 
432  if (newBuilder->canRead())
433  if (!newBuilder->read(inputStream))
434  return formatError(currentValue.str);
435  } else
436  return formatError(currentValue.str);
437 
438  break;
439 
440  case BOOLTOKEN:
441 
442  if (!builderStack.front()->addBool(currentValue.boolean))
443  return formatError(currentValue.str);
444 
445  break;
446 
447  case INTTOKEN:
448 
449  if (!builderStack.front()->addInt(currentValue.integer))
450  return formatError(currentValue.str);
451 
452  break;
453 
454  case RANGETOKEN:
455 
456  if (!builderStack.front()->addRange(currentValue.range.first, currentValue.range.second))
457  return formatError(currentValue.str);
458 
459  break;
460 
461  case DOUBLETOKEN:
462 
463  if (!builderStack.front()->addDouble(currentValue.real))
464  return formatError(currentValue.str);
465 
466  break;
467 
468  case STRINGTOKEN:
469 
470  if (!builderStack.front()->addString(currentValue.str))
471  return formatError(currentValue.str);
472 
473  break;
474 
475  case CLOSETOKEN:
476 
477  if (builderStack.front()->close()) {
478  TLPBuilder *builder = builderStack.front();
479  builderStack.pop_front();
480 
481  if (builder != builderStack.front())
482  delete builder;
483  } else
484  return formatError(currentValue.str);
485 
486  break;
487 
488  case ERRORINFILE:
489  return formatError(currentValue.str);
490 
491  case ENDOFSTREAM:
492  return true;
493 
494  case COMMENTTOKEN:
495 
496  if (displayComment)
497  tlp::debug() << "Comment line:" << tokenParser->curLine << "->" << currentValue.str;
498 
499  break;
500 
501  default:
502  break;
503  }
504  }
505 
506  if (pluginProgress)
507  pluginProgress->progress(fileSize, fileSize);
508 
509  return true;
510  }
511 };
512 } // namespace tlp
513 //=====================================================================================
514 #endif // TLPPARSER_H
515 
516 ///@endcond
@ TLP_CONTINUE
@ TLP_CANCEL
char * getStrError()
Returns the error message associated to errno.