]> git.lyx.org Git - features.git/blob - src/tex2lyx/Parser.cpp
Update stats display if a stat is (un)selected
[features.git] / src / tex2lyx / Parser.cpp
1 /**
2  * \file Parser.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Parser.h"
14
15 #include "Encoding.h"
16 #include "support/lstrings.h"
17 #include "support/textutils.h"
18
19 #include <cstdint>
20 #include <iostream>
21
22 using namespace std;
23 using namespace lyx::support;
24
25 namespace lyx {
26
27 namespace {
28
29 /*!
30  * Translate a line ending to '\n'.
31  * \p c must have catcode catNewline, and it must be the last character read
32  * from \p is.
33  */
34 char_type getNewline(iparserdocstream & is, char_type c)
35 {
36         // we have to handle 3 different line endings:
37         // - UNIX (\n)
38         // - MAC  (\r)
39         // - DOS  (\r\n)
40         if (c == '\r') {
41                 // MAC or DOS
42                 char_type wc;
43                 if (is.get(wc) && wc != '\n') {
44                         // MAC
45                         is.putback(wc);
46                 }
47                 return '\n';
48         }
49         // UNIX
50         return c;
51 }
52
53 } // namespace
54
55 //
56 // Token
57 //
58
59 ostream & operator<<(ostream & os, Token const & t)
60 {
61         if (t.cat() == catComment)
62                 os << '%' << t.cs() << '\n';
63         else if (t.cat() == catSpace)
64                 os << t.cs();
65         else if (t.cat() == catEscape)
66                 os << '\\' << t.cs() << ' ';
67         else if (t.cat() == catLetter)
68                 os << t.cs();
69         else if (t.cat() == catNewline)
70                 os << "[" << t.cs().size() << "\\n," << t.cat() << "]\n";
71         else
72                 os << '[' << t.cs() << ',' << t.cat() << ']';
73         return os;
74 }
75
76
77 string Token::asInput() const
78 {
79         if (cat_ == catComment)
80                 return '%' + cs_ + '\n';
81         if (cat_ == catEscape)
82                 return '\\' + cs_;
83         return cs_;
84 }
85
86
87 bool Token::isAlnumASCII() const
88 {
89         return cat_ == catLetter ||
90                (cat_ == catOther && cs_.length() == 1 && isDigitASCII(cs_[0]));
91 }
92
93
94 #ifdef FILEDEBUG
95 void debugToken(std::ostream & os, Token const & t, unsigned int flags)
96 {
97         char sep = ' ';
98         os << "t: " << t << " flags: " << flags;
99         if (flags & FLAG_BRACE_LAST) { os << sep << "BRACE_LAST"; sep = '|'; }
100         if (flags & FLAG_RIGHT     ) { os << sep << "RIGHT"     ; sep = '|'; }
101         if (flags & FLAG_END       ) { os << sep << "END"       ; sep = '|'; }
102         if (flags & FLAG_BRACK_LAST) { os << sep << "BRACK_LAST"; sep = '|'; }
103         if (flags & FLAG_TEXTMODE  ) { os << sep << "TEXTMODE"  ; sep = '|'; }
104         if (flags & FLAG_ITEM      ) { os << sep << "ITEM"      ; sep = '|'; }
105         if (flags & FLAG_LEAVE     ) { os << sep << "LEAVE"     ; sep = '|'; }
106         if (flags & FLAG_SIMPLE    ) { os << sep << "SIMPLE"    ; sep = '|'; }
107         if (flags & FLAG_EQUATION  ) { os << sep << "EQUATION"  ; sep = '|'; }
108         if (flags & FLAG_SIMPLE2   ) { os << sep << "SIMPLE2"   ; sep = '|'; }
109         if (flags & FLAG_OPTION    ) { os << sep << "OPTION"    ; sep = '|'; }
110         if (flags & FLAG_BRACED    ) { os << sep << "BRACED"    ; sep = '|'; }
111         if (flags & FLAG_CELL      ) { os << sep << "CELL"      ; sep = '|'; }
112         if (flags & FLAG_TABBING   ) { os << sep << "TABBING"   ; sep = '|'; }
113         os << "\n";
114 }
115 #endif
116
117
118 //
119 // Wrapper
120 //
121
122 void iparserdocstream::setEncoding(std::string const & e)
123 {
124         is_ << lyx::setEncoding(e);
125 }
126
127
128 void iparserdocstream::putback(char_type c)
129 {
130         s_ = c + s_;
131 }
132
133
134 void iparserdocstream::putback(docstring const & s)
135 {
136         s_ = s + s_;
137 }
138
139
140 iparserdocstream & iparserdocstream::get(char_type &c)
141 {
142         if (s_.empty())
143                 is_.get(c);
144         else {
145                 //cerr << "unparsed: " << to_utf8(s_) <<endl;
146                 c = s_[0];
147                 s_.erase(0,1);
148         }
149         return *this;
150 }
151
152
153 //
154 // Parser
155 //
156
157
158 Parser::Parser(idocstream & is, std::string const & fixedenc)
159         : lineno_(0), pos_(0), iss_(nullptr), is_(is),
160           encoding_iconv_(fixedenc.empty() ? "UTF-8" : fixedenc),
161           theCatcodesType_(NORMAL_CATCODES), curr_cat_(UNDECIDED_CATCODES),
162           fixed_enc_(!fixedenc.empty())
163 {
164         if (fixed_enc_)
165                 is_.setEncoding(fixedenc);
166         catInit();
167 }
168
169
170 Parser::Parser(string const & s)
171         : lineno_(0), pos_(0),
172           iss_(new idocstringstream(from_utf8(s))), is_(*iss_),
173           encoding_iconv_("UTF-8"),
174           theCatcodesType_(NORMAL_CATCODES), curr_cat_(UNDECIDED_CATCODES),
175           // An idocstringstream can not change the encoding
176           fixed_enc_(true)
177 {
178         catInit();
179 }
180
181
182 Parser::~Parser()
183 {
184         delete iss_;
185 }
186
187
188 void Parser::deparse()
189 {
190         string s;
191         for(size_type i = pos_ ; i < tokens_.size() ; ++i) {
192                 s += tokens_[i].asInput();
193         }
194         is_.putback(from_utf8(s));
195         tokens_.erase(tokens_.begin() + pos_, tokens_.end());
196         // make sure that next token is read
197         tokenize_one();
198 }
199
200
201 bool Parser::setEncoding(std::string const & e, int p)
202 {
203         // We may (and need to) use unsafe encodings here: Since the text is
204         // converted to unicode while reading from is_, we never see text in
205         // the original encoding of the parser, but operate on utf8 strings
206         // instead. Therefore, we cannot misparse high bytes as {, } or \\.
207         Encoding const * const enc = encodings.fromLaTeXName(e, p, true);
208         if (!enc) {
209                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
210                 return false;
211         }
212         return setEncoding(enc->iconvName());
213 }
214
215
216 void Parser::catInit()
217 {
218         if (curr_cat_ == theCatcodesType_)
219                 return;
220         curr_cat_ = theCatcodesType_;
221
222         fill(theCatcode_, theCatcode_ + 256, catOther);
223         fill(theCatcode_ + 'a', theCatcode_ + 'z' + 1, catLetter);
224         fill(theCatcode_ + 'A', theCatcode_ + 'Z' + 1, catLetter);
225         // This is wrong!
226         theCatcode_[int('@')]  = catLetter;
227
228         if (theCatcodesType_ == NORMAL_CATCODES) {
229                 theCatcode_[int('\\')] = catEscape;
230                 theCatcode_[int('{')]  = catBegin;
231                 theCatcode_[int('}')]  = catEnd;
232                 theCatcode_[int('$')]  = catMath;
233                 theCatcode_[int('&')]  = catAlign;
234                 theCatcode_[int('\n')] = catNewline;
235                 theCatcode_[int('#')]  = catParameter;
236                 theCatcode_[int('^')]  = catSuper;
237                 theCatcode_[int('_')]  = catSub;
238                 theCatcode_[0x7f]      = catIgnore;
239                 theCatcode_[int(' ')]  = catSpace;
240                 theCatcode_[int('\t')] = catSpace;
241                 theCatcode_[int('\r')] = catNewline;
242                 theCatcode_[int('~')]  = catActive;
243                 theCatcode_[int('%')]  = catComment;
244         }
245 }
246
247 CatCode Parser::catcode(char_type c) const
248 {
249         if (c < 256)
250                 return theCatcode_[(unsigned char)c];
251         return catOther;
252 }
253
254
255 void Parser::setCatcode(char c, CatCode cat)
256 {
257         theCatcode_[(unsigned char)c] = cat;
258         deparse();
259 }
260
261
262 void Parser::setCatcodes(cat_type t)
263 {
264         theCatcodesType_ = t;
265         deparse();
266 }
267
268
269 bool Parser::setEncoding(std::string const & e)
270 {
271         //cerr << "setting encoding to " << e << std::endl;
272         encoding_iconv_ = e;
273         // If the encoding is fixed, we must not change the stream encoding
274         // (because the whole input uses that encoding, e.g. if it comes from
275         // the clipboard). We still need to track the original encoding in
276         // encoding_iconv_, so that the generated output is correct.
277         if (!fixed_enc_)
278                 is_.setEncoding(e);
279         return true;
280 }
281
282
283 void Parser::push_back(Token const & t)
284 {
285         tokens_.push_back(t);
286 }
287
288
289 // We return a copy here because the tokens_ vector may get reallocated
290 Token const Parser::prev_token() const
291 {
292         static const Token dummy;
293         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
294 }
295
296
297 // We return a copy here because the tokens_ vector may get reallocated
298 Token const Parser::curr_token() const
299 {
300         static const Token dummy;
301         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
302 }
303
304
305 // We return a copy here because the tokens_ vector may get reallocated
306 Token const Parser::next_token()
307 {
308         static const Token dummy;
309         if (!good())
310                 return dummy;
311         if (pos_ >= tokens_.size())
312                 tokenize_one();
313         return pos_ < tokens_.size() ? tokens_[pos_] : dummy;
314 }
315
316
317 // We return a copy here because the tokens_ vector may get reallocated
318 Token const Parser::next_next_token()
319 {
320         static const Token dummy;
321         if (!good())
322                 return dummy;
323         // If tokenize_one() has not been called after the last get_token() we
324         // need to tokenize two more tokens.
325         if (pos_ >= tokens_.size())
326                 tokenize_one();
327         if (pos_ + 1 >= tokens_.size())
328                 tokenize_one();
329         return pos_ + 1 < tokens_.size() ? tokens_[pos_ + 1] : dummy;
330 }
331
332
333 // We return a copy here because the tokens_ vector may get reallocated
334 Token const Parser::get_token()
335 {
336         static const Token dummy;
337         if (!good())
338                 return dummy;
339         if (pos_ >= tokens_.size()) {
340                 tokenize_one();
341                 if (pos_ >= tokens_.size())
342                         return dummy;
343         }
344         // cerr << "looking at token " << tokens_[pos_]
345         //      << " pos: " << pos_ << '\n';
346         return tokens_[pos_++];
347 }
348
349
350 bool Parser::isParagraph()
351 {
352         // A new paragraph in TeX is started
353         // - either by a newline, following any amount of whitespace
354         //   characters (including zero), and another newline
355         // - or the token \par
356         if (curr_token().cat() == catNewline &&
357             (curr_token().cs().size() > 1 ||
358              (next_token().cat() == catSpace &&
359               next_next_token().cat() == catNewline)))
360                 return true;
361         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
362                 return true;
363         return false;
364 }
365
366
367 bool Parser::skip_spaces(bool skip_comments)
368 {
369         // We just silently return if we have no more tokens.
370         // skip_spaces() should be callable at any time,
371         // the caller must check p::good() anyway.
372         bool skipped = false;
373         while (good()) {
374                 get_token();
375                 if (isParagraph()) {
376                         putback();
377                         break;
378                 }
379                 if (curr_token().cat() == catSpace ||
380                     curr_token().cat() == catNewline) {
381                         skipped = true;
382                         continue;
383                 }
384                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
385                         continue;
386                 if (skip_comments && curr_token().cat() == catComment) {
387                         // If positions_ is not empty we are doing some kind
388                         // of look ahead
389                         if (!positions_.empty())
390                                 cerr << "  Ignoring comment: "
391                                      << curr_token().asInput();
392                 } else {
393                         putback();
394                         break;
395                 }
396         }
397         return skipped;
398 }
399
400
401 void Parser::unskip_spaces(bool skip_comments)
402 {
403         while (pos_ > 0) {
404                 if ( curr_token().cat() == catSpace ||
405                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
406                         putback();
407                 else if (skip_comments && curr_token().cat() == catComment) {
408                         // TODO: Get rid of this
409                         // If positions_ is not empty we are doing some kind
410                         // of look ahead
411                         if (!positions_.empty())
412                                 cerr << "Unignoring comment: "
413                                      << curr_token().asInput();
414                         putback();
415                 }
416                 else
417                         break;
418         }
419 }
420
421
422 void Parser::putback()
423 {
424         --pos_;
425 }
426
427
428 void Parser::pushPosition()
429 {
430         positions_.push_back(pos_);
431 }
432
433
434 void Parser::popPosition()
435 {
436         pos_ = positions_.back();
437         positions_.pop_back();
438         deparse();
439 }
440
441
442 void Parser::dropPosition()
443 {
444         positions_.pop_back();
445 }
446
447
448 bool Parser::good() const
449 {
450         if (pos_ < tokens_.size())
451                 return true;
452         if (!is_.good())
453                 return false;
454         return is_.peek() != idocstream::traits_type::eof();
455 }
456
457
458 bool Parser::hasOpt(string const & l)
459 {
460         // An optional argument can occur in any of the following forms:
461         // - \foo[bar]
462         // - \foo [bar]
463         // - \foo
464         //   [bar]
465         // - \foo %comment
466         //   [bar]
467
468         // remember current position
469         unsigned int oldpos = pos_;
470         // skip spaces and comments
471         while (good()) {
472                 get_token();
473                 if (isParagraph()) {
474                         putback();
475                         break;
476                 }
477                 if (curr_token().cat() == catSpace ||
478                     curr_token().cat() == catNewline ||
479                     curr_token().cat() == catComment)
480                         continue;
481                 putback();
482                 break;
483         }
484         bool const retval = (next_token().asInput() == l);
485         pos_ = oldpos;
486         return retval;
487 }
488
489
490 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
491 {
492         skip_spaces(true);
493
494         // This is needed if a partial file ends with a command without arguments,
495         // e. g. \medskip
496         if (! good())
497                 return make_pair(false, string());
498
499         int group_level = 0;
500         string result;
501         Token t = get_token();
502
503         if (t.cat() == catComment || t.cat() == catEscape ||
504             t.character() != left) {
505                 putback();
506                 return make_pair(false, string());
507         } else {
508                 while (good()) {
509                         t = get_token();
510                         // honor grouping
511                         if (left != '{' && t.cat() == catBegin) {
512                                 ++group_level;
513                                 continue;
514                         }
515                         if (left != '{' && t.cat() == catEnd) {
516                                 --group_level;
517                                 continue;
518                         }
519                         // Ignore comments
520                         if (t.cat() == catComment) {
521                                 if (!t.cs().empty())
522                                         cerr << "Ignoring comment: " << t.asInput();
523                                 continue;
524                         }
525                         if (allow_escaping) {
526                                 if (t.cat() != catEscape && t.character() == right
527                                     && group_level == 0)
528                                         break;
529                         } else {
530                                 if (t.character() == right) {
531                                         if (t.cat() == catEscape)
532                                                 result += '\\';
533                                         if (group_level == 0)
534                                                 break;
535                                 }
536                         }
537                         result += t.asInput();
538                 }
539         }
540         return make_pair(true, result);
541 }
542
543
544 string Parser::getArg(char left, char right, bool allow_escaping)
545 {
546         return getFullArg(left, right, allow_escaping).second;
547 }
548
549
550 string Parser::getFullOpt(bool keepws, char left, char right)
551 {
552         Arg arg = getFullArg(left, right);
553         if (arg.first)
554                 return left + arg.second + right;
555         if (keepws)
556                 unskip_spaces(true);
557         return string();
558 }
559
560
561 string Parser::getOpt(bool keepws)
562 {
563         string const res = getArg('[', ']');
564         if (res.empty()) {
565                 if (keepws)
566                         unskip_spaces(true);
567                 return string();
568         }
569         return '[' + res + ']';
570 }
571
572
573 string Parser::getFullParentheseArg()
574 {
575         Arg arg = getFullArg('(', ')');
576         if (arg.first)
577                 return '(' + arg.second + ')';
578         return string();
579 }
580
581
582 bool Parser::hasListPreamble(string const & itemcmd)
583 {
584         // remember current position
585         unsigned int oldpos = pos_;
586         // jump over arguments
587         if (hasOpt())
588                 getOpt();
589         if (hasOpt("{"))
590                 getArg('{', '}');
591         // and swallow spaces and comments
592         skip_spaces(true);
593         // we have a list preamble if the next thing
594         // that follows is not the \item command
595         bool res =  next_token().cs() != itemcmd;
596         // back to orig position
597         pos_ = oldpos;
598         return res;
599 }
600
601
602 string const Parser::ertEnvironment(string const & name)
603 {
604         if (!good())
605                 return string();
606
607         ostringstream os;
608         for (Token t = get_token(); good(); t = get_token()) {
609                 if (t.cat() == catBegin) {
610                         putback();
611                         os << '{' << verbatim_item() << '}';
612                 } else if (t.asInput() == "\\begin") {
613                         string const env = getArg('{', '}');
614                         os << "\\begin{" << env << '}'
615                            << ertEnvironment(env)
616                            << "\\end{" << env << '}';
617                 } else if (t.asInput() == "\\end") {
618                         string const end = getArg('{', '}');
619                         if (end != name)
620                                 cerr << "\\end{" << end
621                                      << "} does not match \\begin{" << name
622                                      << "}." << endl;
623                         return os.str();
624                 } else
625                         os << t.asInput();
626         }
627         cerr << "unexpected end of input" << endl;
628         return os.str();
629 }
630
631
632 string const Parser::plainEnvironment(string const & name)
633 {
634         if (!good())
635                 return string();
636
637         ostringstream os;
638         for (Token t = get_token(); good(); t = get_token()) {
639                 if (t.asInput() == "\\end") {
640                         string const end = getArg('{', '}');
641                         if (end == name)
642                                 return os.str();
643                         else
644                                 os << "\\end{" << end << '}';
645                 } else
646                         os << t.asInput();
647         }
648         cerr << "unexpected end of input" << endl;
649         return os.str();
650 }
651
652
653 string const Parser::plainCommand(char left, char right, string const & name)
654 {
655         if (!good())
656                 return string();
657         // check if first token is really the start character
658         Token tok = get_token();
659         if (tok.character() != left) {
660                 cerr << "first character does not match start character of command \\" << name << endl;
661                 return string();
662         }
663         ostringstream os;
664         for (Token t = get_token(); good(); t = get_token()) {
665                 if (t.character() == right) {
666                         return os.str();
667                 } else
668                         os << t.asInput();
669         }
670         cerr << "unexpected end of input" << endl;
671         return os.str();
672 }
673
674
675 string const Parser::getCommandLatexParam()
676 {
677         if (!good())
678                 return string();
679         string res;
680         size_t offset = 0;
681         while (true) {
682                 if (pos_ + offset >= tokens_.size())
683                         tokenize_one();
684                 if (pos_ + offset >= tokens_.size())
685                         break;
686                 Token t = tokens_[pos_ + offset];
687                 if (t.cat() == catBegin)
688                         break;
689                 res += t.asInput();
690                 ++offset;
691         }
692         return res;
693 }
694
695
696 Parser::Arg Parser::verbatimStuff(string const & end_string, bool const allow_linebreak)
697 {
698         if (!good())
699                 return Arg(false, string());
700
701         pushPosition();
702         ostringstream oss;
703         size_t match_index = 0;
704         setCatcodes(VERBATIM_CATCODES);
705         for (Token t = get_token(); good(); t = get_token()) {
706                 // FIXME t.asInput() might be longer than we need ?
707                 if (t.asInput() == end_string.substr(match_index,
708                                                      t.asInput().length())) {
709                         match_index += t.asInput().length();
710                         if (match_index >= end_string.length())
711                                 break;
712                 } else {
713                         if (!allow_linebreak && t.asInput() == "\n") {
714                                 cerr << "unexpected end of input" << endl;
715                                 popPosition();
716                                 setCatcodes(NORMAL_CATCODES);
717                                 return Arg(false, string());
718                         }
719                         if (match_index) {
720                                 oss << end_string.substr(0, match_index)
721                                     << t.asInput();
722                                 match_index = 0;
723                         } else
724                                 oss << t.asInput();
725                 }
726         }
727
728         if (!good()) {
729                 cerr << "unexpected end of input" << endl;
730                 popPosition();
731                 setCatcodes(NORMAL_CATCODES);
732                 return Arg(false, string());
733         }
734         setCatcodes(NORMAL_CATCODES);
735         dropPosition();
736         return Arg(true, oss.str());
737 }
738
739
740 string const Parser::verbatimEnvironment(string const & name)
741 {
742         //FIXME: do something if endstring is not found
743         string s = verbatimStuff("\\end{" + name + "}").second;
744         // ignore one newline at beginning or end of string
745         if (prefixIs(s, "\n"))
746                 s.erase(0,1);
747         if (suffixIs(s, "\n"))
748                 s.erase(s.length() - 1,1);
749         return s;
750 }
751
752
753 string Parser::verbatimOption()
754 {
755         string res;
756         if (next_token().character() == '[') {
757                 Token t = get_token();
758                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
759                         if (t.cat() == catBegin) {
760                                 putback();
761                                 res += '{' + verbatim_item() + '}';
762                         } else
763                                 res += t.asInput();
764                 }
765         }
766         return res;
767 }
768
769
770 string Parser::verbatim_item()
771 {
772         if (!good())
773                 error("stream bad");
774         skip_spaces();
775         if (next_token().cat() == catBegin) {
776                 Token t = get_token(); // skip brace
777                 string res;
778                 for (t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
779                         if (t.cat() == catBegin) {
780                                 putback();
781                                 res += '{' + verbatim_item() + '}';
782                         }
783                         else
784                                 res += t.asInput();
785                 }
786                 return res;
787         }
788         return get_token().asInput();
789 }
790
791
792 void Parser::tokenize_one()
793 {
794         catInit();
795         char_type c;
796         if (!is_.get(c))
797                 return;
798
799         switch (catcode(c)) {
800         case catSpace: {
801                 docstring s(1, c);
802                 while (is_.get(c) && catcode(c) == catSpace)
803                         s += c;
804                 if (catcode(c) != catSpace)
805                         is_.putback(c);
806                 push_back(Token(s, catSpace));
807                 break;
808         }
809
810         case catNewline: {
811                 ++lineno_;
812                 docstring s(1, getNewline(is_, c));
813                 while (is_.get(c) && catcode(c) == catNewline) {
814                         ++lineno_;
815                         s += getNewline(is_, c);
816                 }
817                 if (catcode(c) != catNewline)
818                         is_.putback(c);
819                 push_back(Token(s, catNewline));
820                 break;
821         }
822
823         case catComment: {
824                 // We don't treat "%\n" combinations here specially because
825                 // we want to preserve them in the preamble
826                 docstring s;
827                 while (is_.get(c) && catcode(c) != catNewline)
828                         s += c;
829                 // handle possible DOS line ending
830                 if (catcode(c) == catNewline)
831                         c = getNewline(is_, c);
832                 // Note: The '%' at the beginning and the '\n' at the end
833                 // of the comment are not stored.
834                 ++lineno_;
835                 push_back(Token(s, catComment));
836                 break;
837         }
838
839         case catEscape: {
840                 is_.get(c);
841                 if (!is_) {
842                         error("unexpected end of input");
843                 } else {
844                         docstring s(1, c);
845                         if (catcode(c) == catLetter) {
846                                 // collect letters
847                                 while (is_.get(c) && catcode(c) == catLetter)
848                                         s += c;
849                                 if (catcode(c) != catLetter)
850                                         is_.putback(c);
851                         }
852                         push_back(Token(s, catEscape));
853                 }
854                 break;
855         }
856
857         case catIgnore: {
858                 cerr << "ignoring a char: " << static_cast<uint32_t>(c) << "\n";
859                 break;
860         }
861
862         default:
863                 push_back(Token(docstring(1, c), catcode(c)));
864         }
865         //cerr << tokens_.back();
866 }
867
868
869 void Parser::dump() const
870 {
871         cerr << "\nTokens: ";
872         for (unsigned i = 0; i < tokens_.size(); ++i) {
873                 if (i == pos_)
874                         cerr << " <#> ";
875                 cerr << tokens_[i];
876         }
877         cerr << " pos: " << pos_ << "\n";
878 }
879
880
881 void Parser::error(string const & msg) const
882 {
883         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
884         dump();
885         //exit(1);
886 }
887
888
889 void Parser::reset()
890 {
891         pos_ = 0;
892 }
893
894
895 } // namespace lyx