scanner.cpp
1 //------------------------------------------------------------------------------
2 // scanner.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301, USA.
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2010 "Blzut3" <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 
24 #include <cstdlib>
25 #include <cstdio>
26 #include <cmath>
27 
28 #include "scanner.h"
29 
30 DClass<Scanner>
31 {
32  public:
33  Scanner::ParserState nextState, prevState, state;
34 
35  char* data;
36  unsigned int length;
37 
38  unsigned int line;
39  unsigned int lineStart;
40  unsigned int logicalPosition;
41  unsigned int scanPos;
42 
43  bool needNext; // If checkToken returns false this will be false.
44 
45  QString scriptIdentifier;
46 };
47 
48 DClass<Scanner::ParserState>
49 {
50  public:
51  QString str;
52  unsigned int number;
53  double decimal;
54  bool boolean;
55  char token;
56  unsigned int tokenLine;
57  unsigned int tokenLinePosition;
58  unsigned int scanPos;
59 };
60 
61 DPointered(Scanner::ParserState);
62 DPointered(Scanner)
63 
64 void (*Scanner::messageHandler)(MessageLevel, const char*, va_list) = NULL;
65 
66 static const char* const TokenNames[TK_NumSpecialTokens] =
67 {
68  "Identifier",
69  "String Constant",
70  "Integer Constant",
71  "Float Constant",
72  "Boolean Constant",
73  "Logical And",
74  "Logical Or",
75  "Equals",
76  "Not Equals",
77  "Greater Than or Equals"
78  "Less Than or Equals",
79  "Left Shift",
80  "Right Shift",
81  "Increment",
82  "Decrement",
83  "Pointer Member",
84  "Scope Resolution",
85  "Macro Concatenation",
86  "Assign Sum",
87  "Assign Difference",
88  "Assign Product",
89  "Assign Quotient",
90  "Assign Modulus",
91  "Assign Left Shift",
92  "Assign Right Shift",
93  "Assign Bitwise And",
94  "Assign Bitwise Or",
95  "Assign Exclusive Or",
96  "Ellipsis"
97 };
98 
100 
101 Scanner::Scanner(const char* data, int length)
102 {
103  d->line = 1;
104  d->lineStart = 0;
105  d->logicalPosition = 0;
106  d->scanPos = 0;
107  d->needNext = true;
108  if(length == -1)
109  length = strlen(data);
110  d->length = length;
111  d->data = new char[length];
112  memcpy(d->data, data, length);
113 
115 
116  d->state.setScanPos(d->scanPos);
117 }
118 
119 Scanner::~Scanner()
120 {
121  delete[] d->data;
122 }
123 
124 // Here's my answer to the preprocessor screwing up line numbers. What we do is
125 // after a new line in CheckForWhitespace, look for a comment in the form of
126 // "/*meta:filename:line*/"
127 void Scanner::checkForMeta()
128 {
129  if(d->scanPos+10 < d->length)
130  {
131  char metaCheck[8];
132  memcpy(metaCheck, d->data+d->scanPos, 7);
133  metaCheck[7] = 0;
134  if(strcmp(metaCheck, "/*meta:") == 0)
135  {
136  d->scanPos += 7;
137  int metaStart = d->scanPos;
138  int fileLength = 0;
139  int lineLength = 0;
140  while(d->scanPos < d->length)
141  {
142  char thisChar = d->data[d->scanPos];
143  char nextChar = d->scanPos+1 < d->length ? d->data[d->scanPos+1] : 0;
144  if(thisChar == '*' && nextChar == '/')
145  {
146  lineLength = d->scanPos-metaStart-1-fileLength;
147  d->scanPos += 2;
148  break;
149  }
150  if(thisChar == ':' && fileLength == 0)
151  fileLength = d->scanPos-metaStart;
152  d->scanPos++;
153  }
154  if(fileLength > 0 && lineLength > 0)
155  {
156  setScriptIdentifier(QString::fromAscii(d->data+metaStart, fileLength));
157  QString lineNumber = QString::fromAscii(d->data+metaStart+fileLength+1, lineLength);
158  d->line = atoi(lineNumber.toAscii().constData());
159  d->lineStart = d->scanPos;
160  }
161  }
162  }
163 }
164 
166 {
167  int comment = 0; // 1 = till next new line, 2 = till end block
168  while(d->scanPos < d->length)
169  {
170  char cur = d->data[d->scanPos];
171  char next = d->scanPos+1 < d->length ? d->data[d->scanPos+1] : 0;
172  if(comment == 2)
173  {
174  if(cur != '*' || next != '/')
175  {
176  if(cur == '\n' || cur == '\r')
177  {
178  d->scanPos++;
179  if(comment == 1)
180  comment = 0;
181 
182  // Do a quick check for Windows style new line
183  if(cur == '\r' && next == '\n')
184  d->scanPos++;
185  incrementLine();
186  }
187  else
188  d->scanPos++;
189  }
190  else
191  {
192  comment = 0;
193  d->scanPos += 2;
194  }
195  continue;
196  }
197 
198  if(cur == ' ' || cur == '\t' || cur == 0)
199  d->scanPos++;
200  else if(cur == '\n' || cur == '\r')
201  {
202  d->scanPos++;
203  if(comment == 1)
204  comment = 0;
205 
206  // Do a quick check for Windows style new line
207  if(cur == '\r' && next == '\n')
208  d->scanPos++;
209  incrementLine();
210  checkForMeta();
211  }
212  else if(cur == '/' && comment == 0)
213  {
214  switch(next)
215  {
216  case '/':
217  comment = 1;
218  break;
219  case '*':
220  comment = 2;
221  break;
222  default:
223  return;
224  }
225  d->scanPos += 2;
226  }
227  else
228  {
229  if(comment == 0)
230  return;
231  else
232  d->scanPos++;
233  }
234  }
235 }
236 
237 bool Scanner::checkToken(char token)
238 {
239  if(d->needNext)
240  {
241  if(!nextToken(false))
242  return false;
243  }
244 
245  // An int can also be a float.
246  if(d->nextState.token() == token || (d->nextState.token() == TK_IntConst && token == TK_FloatConst))
247  {
248  d->needNext = true;
249  expandState();
250  return true;
251  }
252  d->needNext = false;
253  return false;
254 }
255 
256 int Scanner::currentLine() const
257 {
258  return d->state.tokenLine();
259 }
260 
261 int Scanner::currentLinePos() const
262 {
263  return d->state.tokenLinePosition();
264 }
265 
266 int Scanner::currentPos() const
267 {
268  return d->logicalPosition;
269 }
270 
271 unsigned int Scanner::currentScanPos() const
272 {
273  return d->scanPos;
274 }
275 
277 {
278  d->scanPos = d->nextState.scanPos();
279  d->logicalPosition = d->scanPos;
281 
282  d->prevState = d->state;
283  d->state = d->nextState;
284 }
285 
287 {
288  d->line++;
289  d->lineStart = d->scanPos;
290 }
291 
292 bool Scanner::nextString()
293 {
294  d->nextState.setTokenLine(d->line);
295  d->nextState.setTokenLinePosition(d->scanPos - d->lineStart);
296  d->nextState.setToken(TK_NoToken);
297  if(!d->needNext)
298  d->scanPos = d->state.scanPos();
300  if(d->scanPos >= d->length)
301  return false;
302 
303  int start = d->scanPos;
304  int end = d->scanPos;
305  bool quoted = d->data[d->scanPos] == '"';
306  if(quoted) // String Constant
307  {
308  end = ++start; // Remove starting quote
309  d->scanPos++;
310  while(d->scanPos < d->length)
311  {
312  char cur = d->data[d->scanPos];
313  if(cur == '"')
314  end = d->scanPos;
315  else if(cur == '\\')
316  {
317  d->scanPos += 2;
318  continue;
319  }
320  d->scanPos++;
321  if(start != end)
322  break;
323  }
324  }
325  else // Unquoted string
326  {
327  while(d->scanPos < d->length)
328  {
329  char cur = d->data[d->scanPos];
330  switch(cur)
331  {
332  default:
333  break;
334  case ' ':
335  case '\t':
336  case '\n':
337  case '\r':
338  end = d->scanPos;
339  break;
340  }
341  if(start != end)
342  break;
343  d->scanPos++;
344  }
345  if(d->scanPos == d->length)
346  end = d->scanPos;
347  }
348  if(end-start > 0)
349  {
350  d->nextState.setScanPos(d->scanPos);
351  QString thisString = QString::fromAscii(d->data+start, end-start);
352  if(quoted)
353  unescape(thisString);
354  d->nextState.setStr(thisString);
355  d->nextState.setToken(TK_StringConst);
356  expandState();
357  d->needNext = true;
358  return true;
359  }
361  return false;
362 }
363 
364 bool Scanner::nextToken(bool autoExpandState)
365 {
366  if(!d->needNext)
367  {
368  d->needNext = true;
369  if(autoExpandState)
370  expandState();
371  return true;
372  }
373 
374  d->nextState.setTokenLine(d->line);
375  d->nextState.setTokenLinePosition(d->scanPos - d->lineStart);
376  d->nextState.setToken(TK_NoToken);
377  if(d->scanPos >= d->length)
378  {
379  if(autoExpandState)
380  expandState();
381  return false;
382  }
383 
384  unsigned int start = d->scanPos;
385  unsigned int end = d->scanPos;
386  int integerBase = 10;
387  bool floatHasDecimal = false;
388  bool floatHasExponent = false;
389  bool stringFinished = false; // Strings are the only things that can have 0 length tokens.
390 
391  char cur = d->data[d->scanPos++];
392  // Determine by first character
393  if(cur == '_' || (cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z'))
394  d->nextState.setToken(TK_Identifier);
395  else if(cur >= '0' && cur <= '9')
396  {
397  if(cur == '0')
398  integerBase = 8;
399  d->nextState.setToken(TK_IntConst);
400  }
401  else if(cur == '.' && d->scanPos < d->length && d->data[d->scanPos] != '.')
402  {
403  floatHasDecimal = true;
404  d->nextState.setToken(TK_FloatConst);
405  }
406  else if(cur == '"')
407  {
408  end = ++start; // Move the start up one character so we don't have to trim it later.
409  d->nextState.setToken(TK_StringConst);
410  }
411  else
412  {
413  end = d->scanPos;
414  d->nextState.setToken(cur);
415 
416  // Now check for operator tokens
417  if(d->scanPos < d->length)
418  {
419  char next = d->data[d->scanPos];
420  if(cur == '&' && next == '&')
421  d->nextState.setToken(TK_AndAnd);
422  else if(cur == '|' && next == '|')
423  d->nextState.setToken(TK_OrOr);
424  else if(
425  (cur == '<' && next == '<') ||
426  (cur == '>' && next == '>')
427  )
428  {
429  // Next for 3 character tokens
430  if(d->scanPos+1 > d->length && d->data[d->scanPos+1] == '=')
431  {
432  d->scanPos++;
433  d->nextState.setToken(cur == '<' ? TK_ShiftLeftEq : TK_ShiftRightEq);
434 
435  }
436  else
437  d->nextState.setToken(cur == '<' ? TK_ShiftLeft : TK_ShiftRight);
438  }
439  else if(cur == '#' && next == '#')
440  d->nextState.setToken(TK_MacroConcat);
441  else if(cur == ':' && next == ':')
442  d->nextState.setToken(TK_ScopeResolution);
443  else if(cur == '+' && next == '+')
444  d->nextState.setToken(TK_Increment);
445  else if(cur == '-')
446  {
447  if(next == '-')
448  d->nextState.setToken(TK_Decrement);
449  else if(next == '>')
450  d->nextState.setToken(TK_PointerMember);
451  }
452  else if(cur == '.' && next == '.' &&
453  d->scanPos+1 < d->length && d->data[d->scanPos+1] == '.')
454  {
455  d->nextState.setToken(TK_Ellipsis);
456  ++d->scanPos;
457  }
458  else if(next == '=')
459  {
460  switch(cur)
461  {
462  case '=':
463  d->nextState.setToken(TK_EqEq);
464  break;
465  case '!':
466  d->nextState.setToken(TK_NotEq);
467  break;
468  case '>':
469  d->nextState.setToken(TK_GtrEq);
470  break;
471  case '<':
472  d->nextState.setToken(TK_LessEq);
473  break;
474  case '+':
475  d->nextState.setToken(TK_AddEq);
476  break;
477  case '-':
478  d->nextState.setToken(TK_SubEq);
479  break;
480  case '*':
481  d->nextState.setToken(TK_MulEq);
482  break;
483  case '/':
484  d->nextState.setToken(TK_DivEq);
485  break;
486  case '%':
487  d->nextState.setToken(TK_ModEq);
488  break;
489  case '&':
490  d->nextState.setToken(TK_AndEq);
491  break;
492  case '|':
493  d->nextState.setToken(TK_OrEq);
494  break;
495  case '^':
496  d->nextState.setToken(TK_XorEq);
497  break;
498  default:
499  break;
500  }
501  }
502 
503  if(d->nextState.token() != cur)
504  {
505  d->scanPos++;
506  end = d->scanPos;
507  }
508  }
509  }
510 
511  if(start == end)
512  {
513  while(d->scanPos < d->length)
514  {
515  cur = d->data[d->scanPos];
516  switch(d->nextState.token())
517  {
518  default:
519  break;
520  case TK_Identifier:
521  if(cur != '_' && (cur < 'A' || cur > 'Z') && (cur < 'a' || cur > 'z') && (cur < '0' || cur > '9'))
522  end = d->scanPos;
523  break;
524  case TK_IntConst:
525  if(cur == '.' || (d->scanPos-1 != start && cur == 'e'))
526  d->nextState.setToken(TK_FloatConst);
527  else if((cur == 'x' || cur == 'X') && d->scanPos-1 == start)
528  {
529  integerBase = 16;
530  break;
531  }
532  else
533  {
534  switch(integerBase)
535  {
536  default:
537  if(cur < '0' || cur > '9')
538  end = d->scanPos;
539  break;
540  case 8:
541  if(cur < '0' || cur > '7')
542  end = d->scanPos;
543  break;
544  case 16:
545  if((cur < '0' || cur > '9') && (cur < 'A' || cur > 'F') && (cur < 'a' || cur > 'f'))
546  end = d->scanPos;
547  break;
548  }
549  break;
550  }
551  case TK_FloatConst:
552  if(cur < '0' || cur > '9')
553  {
554  if(!floatHasDecimal && cur == '.')
555  {
556  floatHasDecimal = true;
557  break;
558  }
559  else if(!floatHasExponent && cur == 'e')
560  {
561  floatHasDecimal = true;
562  floatHasExponent = true;
563  if(d->scanPos+1 < d->length)
564  {
565  char next = d->data[d->scanPos+1];
566  if((next < '0' || next > '9') && next != '+' && next != '-')
567  end = d->scanPos;
568  else
569  d->scanPos++;
570  }
571  break;
572  }
573  end = d->scanPos;
574  }
575  break;
576  case TK_StringConst:
577  if(cur == '"')
578  {
579  stringFinished = true;
580  end = d->scanPos;
581  d->scanPos++;
582  }
583  else if(cur == '\\')
584  d->scanPos++; // Will add two since the loop automatically adds one
585  break;
586  }
587  if(start == end && !stringFinished)
588  d->scanPos++;
589  else
590  break;
591  }
592  // Handle small tokens at the end of a file.
593  if(d->scanPos == d->length && !stringFinished)
594  end = d->scanPos;
595  }
596 
597  d->nextState.setScanPos(d->scanPos);
598  if(end-start > 0 || stringFinished)
599  {
600  d->nextState.setStr(QByteArray(d->data+start, end-start));
601  if(d->nextState.token() == TK_FloatConst)
602  {
603  if(floatHasDecimal && d->nextState.str().length() == 1)
604  {
605  // Don't treat a lone '.' as a decimal.
606  d->nextState.setToken('.');
607  }
608  else
609  {
610  d->nextState.setDecimal(d->nextState.str().toDouble(NULL));
611  d->nextState.setNumber(static_cast<int> (d->nextState.decimal()));
612  d->nextState.setBoolean(d->nextState.number() != 0);
613  }
614  }
615  else if(d->nextState.token() == TK_IntConst)
616  {
617  d->nextState.setNumber(d->nextState.str().toUInt(NULL, integerBase));
618  d->nextState.setDecimal(d->nextState.number());
619  d->nextState.setBoolean(d->nextState.number() != 0);
620  }
621  else if(d->nextState.token() == TK_Identifier)
622  {
623  // Check for a boolean constant.
624  if(d->nextState.str().compare("true") == 0)
625  {
626  d->nextState.setToken(TK_BoolConst);
627  d->nextState.setBoolean(true);
628  }
629  else if(d->nextState.str().compare("false") == 0)
630  {
631  d->nextState.setToken(TK_BoolConst);
632  d->nextState.setBoolean(false);
633  }
634  }
635  else if(d->nextState.token() == TK_StringConst)
636  {
637  QString str = d->nextState.str();
638  d->nextState.setStr(unescape(str));
639  }
640  if(autoExpandState)
641  expandState();
642  return true;
643  }
644  d->nextState.setToken(TK_NoToken);
645  if(autoExpandState)
646  expandState();
647  return false;
648 }
649 
650 void Scanner::mustGetToken(char token)
651 {
652  if(!checkToken(token))
653  {
654  expandState();
655  if(token < TK_NumSpecialTokens && d->state.token() < TK_NumSpecialTokens)
656  scriptMessage(Scanner::ML_ERROR, "Expected '%s' but got '%s' instead.", TokenNames[token], TokenNames[d->state.token()]);
657  else if(token < TK_NumSpecialTokens && d->state.token() >= TK_NumSpecialTokens)
658  scriptMessage(Scanner::ML_ERROR, "Expected '%s' but got '%c' instead.", TokenNames[token], d->state.token());
659  else if(token >= TK_NumSpecialTokens && d->state.token() < TK_NumSpecialTokens)
660  scriptMessage(Scanner::ML_ERROR, "Expected '%c' but got '%s' instead.", token, TokenNames[d->state.token()]);
661  else
662  scriptMessage(Scanner::ML_ERROR, "Expected '%c' but got '%c' instead.", token, d->state.token());
663  }
664 }
665 
666 void Scanner::rewind()
667 {
668  d->needNext = false;
669  d->nextState = d->state;
670  d->state = d->prevState;
671  d->scanPos = d->state.scanPos();
672 
673  d->line = d->prevState.tokenLine();
674  d->logicalPosition = d->prevState.tokenLinePosition();
675 }
676 
677 const char* Scanner::scriptData() const
678 {
679  return d->data;
680 }
681 
682 void Scanner::scriptMessage(MessageLevel level, const char* error, ...) const
683 {
684  const char* messageLevel;
685  switch(level)
686  {
687  default:
688  messageLevel = "Notice";
689  break;
690  case ML_WARNING:
691  messageLevel = "Warning";
692  break;
693  case ML_ERROR:
694  messageLevel = "Error";
695  break;
696  }
697 
698  char* newMessage = new char[strlen(error) + d->scriptIdentifier.length() + 25];
699  sprintf(newMessage, "%s:%d:%d:%s: %s\n", d->scriptIdentifier.toAscii().constData(), currentLine(), currentLinePos(), messageLevel, error);
700  va_list list;
701  va_start(list, error);
702  if(messageHandler)
703  messageHandler(level, newMessage, list);
704  else
705  vfprintf(stderr, newMessage, list);
706  va_end(list);
707  delete[] newMessage;
708 
709  if(!messageHandler && level == ML_ERROR)
710  exit(0);
711 
712 }
713 
714 void Scanner::setScriptIdentifier(const QString &ident)
715 {
716  d->scriptIdentifier = ident;
717 }
718 
719 int Scanner::skipLine()
720 {
721  int ret = currentPos();
722  while(d->logicalPosition < d->length)
723  {
724  char thisChar = d->data[d->logicalPosition];
725  char nextChar = d->logicalPosition+1 < d->length ? d->data[d->logicalPosition+1] : 0;
726  if(thisChar == '\n' || thisChar == '\r')
727  {
728  ret = d->logicalPosition++; // Return the first newline character we see.
729  if(nextChar == '\r')
730  d->logicalPosition++;
731  incrementLine();
733  break;
734  }
735  d->logicalPosition++;
736  }
737  if(d->logicalPosition > d->scanPos)
738  {
739  d->scanPos = d->logicalPosition;
741  d->needNext = true;
742  d->logicalPosition = d->scanPos;
743  }
744  return ret;
745 }
746 
747 Scanner::ParserState &Scanner::state()
748 {
749  return d->state;
750 }
751 
752 const Scanner::ParserState &Scanner::state() const
753 {
754  return d->state;
755 }
756 
758 {
759  return d->scanPos < d->length;
760 }
761 
763 // NOTE: Be sure that '\\' is the first thing in the array otherwise it will re-escape.
764 static char escapeCharacters[] = {'\\', '"', 0};
765 const QString& Scanner::escape(QString &str)
766 {
767  for(unsigned int i = 0;escapeCharacters[i] != 0;i++)
768  {
769  // += 2 because we'll be inserting 1 character.
770  for(int p = 0;p < str.length() && (p = str.indexOf(escapeCharacters[i], p)) != -1;p += 2)
771  {
772  str.insert(p, '\\');
773  }
774  }
775  return str;
776 }
777 const QString& Scanner::unescape(QString &str)
778 {
779  for(unsigned int i = 0;escapeCharacters[i] != 0;i++)
780  {
781  QString sequence = "\\" + QString(escapeCharacters[i]);
782  for(int p = 0;p < str.length() && (p = str.indexOf(sequence, p)) != -1;p++)
783  str.replace(str.indexOf(sequence, p), 2, escapeCharacters[i]);
784  }
785  return str;
786 }
788 
789 Scanner::ParserState::ParserState()
790 {
791 }
792 
793 Scanner::ParserState::~ParserState()
794 {
795 }
796 
797 const QString &Scanner::ParserState::str() const
798 {
799  return d->str;
800 }
801 
802 void Scanner::ParserState::setStr(const QString &v)
803 {
804  d->str = v;
805 }
806 
807 unsigned int Scanner::ParserState::number() const
808 {
809  return d->number;
810 }
811 
812 void Scanner::ParserState::setNumber(unsigned int v)
813 {
814  d->number = v;
815 }
816 
817 double Scanner::ParserState::decimal() const
818 {
819  return d->decimal;
820 }
821 
822 void Scanner::ParserState::setDecimal(double v)
823 {
824  d->decimal = v;
825 }
826 
827 bool Scanner::ParserState::boolean() const
828 {
829  return d->boolean;
830 }
831 
832 void Scanner::ParserState::setBoolean(bool v)
833 {
834  d->boolean = v;
835 }
836 
837 char Scanner::ParserState::token() const
838 {
839  return d->token;
840 }
841 
842 void Scanner::ParserState::setToken(char v)
843 {
844  d->token = v;
845 }
846 
847 unsigned int Scanner::ParserState::tokenLine() const
848 {
849  return d->tokenLine;
850 }
851 
852 void Scanner::ParserState::setTokenLine(unsigned int v)
853 {
854  d->tokenLine = v;
855 }
856 
857 unsigned int Scanner::ParserState::tokenLinePosition() const
858 {
859  return d->tokenLinePosition;
860 }
861 
862 void Scanner::ParserState::setTokenLinePosition(unsigned int v)
863 {
864  d->tokenLinePosition = v;
865 }
866 
867 unsigned int Scanner::ParserState::scanPos() const
868 {
869  return d->scanPos;
870 }
871 
872 void Scanner::ParserState::setScanPos(unsigned int v)
873 {
874  d->scanPos = v;
875 }
void incrementLine()
Definition: scanner.cpp:286
bool checkToken(char token)
Definition: scanner.cpp:237
void expandState()
Definition: scanner.cpp:276
void checkForWhitespace()
Definition: scanner.cpp:165
bool tokensLeft() const
Definition: scanner.cpp:757
Scanner reads scripts by checking individual tokens.
Definition: scanner.h:75
bool nextToken(bool autoExpandState=true)
Definition: scanner.cpp:364
const char * scriptData() const
Only can rewind one step.
Definition: scanner.cpp:677