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