]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
Fix failing test caused by 89b2c54c00.
[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 "Encoding.h"
14 #include "Parser.h"
15 #include "support/foreach.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 }
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 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)
158         : lineno_(0), pos_(0), iss_(0), is_(is), encoding_iconv_("UTF-8"),
159           theCatcodesType_(NORMAL_CATCODES), curr_cat_(UNDECIDED_CATCODES)
160 {
161 }
162
163
164 Parser::Parser(string const & s)
165         : lineno_(0), pos_(0),
166           iss_(new idocstringstream(from_utf8(s))), is_(*iss_),
167           encoding_iconv_("UTF-8"),
168           theCatcodesType_(NORMAL_CATCODES), curr_cat_(UNDECIDED_CATCODES)
169 {
170 }
171
172
173 Parser::~Parser()
174 {
175         delete iss_;
176 }
177
178
179 void Parser::deparse()
180 {
181         string s;
182         for(size_type i = pos_ ; i < tokens_.size() ; ++i) {
183                 s += tokens_[i].asInput();
184         }
185         is_.putback(from_utf8(s));
186         tokens_.erase(tokens_.begin() + pos_, tokens_.end());
187         // make sure that next token is read
188         tokenize_one();
189 }
190
191
192 bool Parser::setEncoding(std::string const & e, int const & p)
193 {
194         // We may (and need to) use unsafe encodings here: Since the text is
195         // converted to unicode while reading from is_, we never see text in
196         // the original encoding of the parser, but operate on utf8 strings
197         // instead. Therefore, we cannot misparse high bytes as {, } or \\.
198         Encoding const * const enc = encodings.fromLaTeXName(e, p, true);
199         if (!enc) {
200                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
201                 return false;
202         }
203         return setEncoding(enc->iconvName());
204 }
205
206
207 void Parser::catInit()
208 {
209         if (curr_cat_ == theCatcodesType_)
210                 return;
211         curr_cat_ = theCatcodesType_;
212
213         fill(theCatcode_, theCatcode_ + 256, catOther);
214         fill(theCatcode_ + 'a', theCatcode_ + 'z' + 1, catLetter);
215         fill(theCatcode_ + 'A', theCatcode_ + 'Z' + 1, catLetter);
216         // This is wrong!
217         theCatcode_[int('@')]  = catLetter;
218
219         if (theCatcodesType_ == NORMAL_CATCODES) {
220                 theCatcode_[int('\\')] = catEscape;
221                 theCatcode_[int('{')]  = catBegin;
222                 theCatcode_[int('}')]  = catEnd;
223                 theCatcode_[int('$')]  = catMath;
224                 theCatcode_[int('&')]  = catAlign;
225                 theCatcode_[int('\n')] = catNewline;
226                 theCatcode_[int('#')]  = catParameter;
227                 theCatcode_[int('^')]  = catSuper;
228                 theCatcode_[int('_')]  = catSub;
229                 theCatcode_[0x7f]      = catIgnore;
230                 theCatcode_[int(' ')]  = catSpace;
231                 theCatcode_[int('\t')] = catSpace;
232                 theCatcode_[int('\r')] = catNewline;
233                 theCatcode_[int('~')]  = catActive;
234                 theCatcode_[int('%')]  = catComment;
235         }
236 }
237
238 CatCode Parser::catcode(char_type c) const
239 {
240         if (c < 256)
241                 return theCatcode_[(unsigned char)c];
242         return catOther;
243 }
244
245
246 void Parser::setCatcode(char c, CatCode cat)
247 {
248         theCatcode_[(unsigned char)c] = cat;
249         deparse();
250 }
251
252
253 void Parser::setCatcodes(cat_type t)
254 {
255         theCatcodesType_ = t;
256         deparse();
257 }
258
259
260 bool Parser::setEncoding(std::string const & e)
261 {
262         //cerr << "setting encoding to " << e << std::endl;
263         encoding_iconv_ = e;
264         is_.setEncoding(e);
265         return true;
266 }
267
268
269 void Parser::push_back(Token const & t)
270 {
271         tokens_.push_back(t);
272 }
273
274
275 // We return a copy here because the tokens_ vector may get reallocated
276 Token const Parser::prev_token() const
277 {
278         static const Token dummy;
279         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
280 }
281
282
283 // We return a copy here because the tokens_ vector may get reallocated
284 Token const Parser::curr_token() const
285 {
286         static const Token dummy;
287         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
288 }
289
290
291 // We return a copy here because the tokens_ vector may get reallocated
292 Token const Parser::next_token()
293 {
294         static const Token dummy;
295         if (!good())
296                 return dummy;
297         if (pos_ >= tokens_.size())
298                 tokenize_one();
299         return pos_ < tokens_.size() ? tokens_[pos_] : dummy;
300 }
301
302
303 // We return a copy here because the tokens_ vector may get reallocated
304 Token const Parser::next_next_token()
305 {
306         static const Token dummy;
307         if (!good())
308                 return dummy;
309         // If tokenize_one() has not been called after the last get_token() we
310         // need to tokenize two more tokens.
311         if (pos_ >= tokens_.size()) {
312                 tokenize_one();
313                 if (pos_ + 1 >= tokens_.size())
314                         tokenize_one();
315         }
316         return pos_ + 1 < tokens_.size() ? tokens_[pos_ + 1] : dummy;
317 }
318
319
320 // We return a copy here because the tokens_ vector may get reallocated
321 Token const Parser::get_token()
322 {
323         static const Token dummy;
324         if (!good())
325                 return dummy;
326         if (pos_ >= tokens_.size()) {
327                 tokenize_one();
328                 if (pos_ >= tokens_.size())
329                         return dummy;
330         }
331         // cerr << "looking at token " << tokens_[pos_] 
332         //      << " pos: " << pos_ << '\n';
333         return tokens_[pos_++];
334 }
335
336
337 bool Parser::isParagraph()
338 {
339         // A new paragraph in TeX ist started
340         // - either by a newline, following any amount of whitespace
341         //   characters (including zero), and another newline
342         // - or the token \par
343         if (curr_token().cat() == catNewline &&
344             (curr_token().cs().size() > 1 ||
345              (next_token().cat() == catSpace &&
346               next_next_token().cat() == catNewline)))
347                 return true;
348         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
349                 return true;
350         return false;
351 }
352
353
354 bool Parser::skip_spaces(bool skip_comments)
355 {
356         // We just silently return if we have no more tokens.
357         // skip_spaces() should be callable at any time,
358         // the caller must check p::good() anyway.
359         bool skipped = false;
360         while (good()) {
361                 get_token();
362                 if (isParagraph()) {
363                         putback();
364                         break;
365                 }
366                 if (curr_token().cat() == catSpace ||
367                     curr_token().cat() == catNewline) {
368                         skipped = true;
369                         continue;
370                 }
371                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
372                         continue;
373                 if (skip_comments && curr_token().cat() == catComment) {
374                         // If positions_ is not empty we are doing some kind
375                         // of look ahead
376                         if (!positions_.empty())
377                                 cerr << "  Ignoring comment: "
378                                      << curr_token().asInput();
379                 } else {
380                         putback();
381                         break;
382                 }
383         }
384         return skipped;
385 }
386
387
388 void Parser::unskip_spaces(bool skip_comments)
389 {
390         while (pos_ > 0) {
391                 if ( curr_token().cat() == catSpace ||
392                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
393                         putback();
394                 else if (skip_comments && curr_token().cat() == catComment) {
395                         // TODO: Get rid of this
396                         // If positions_ is not empty we are doing some kind
397                         // of look ahead
398                         if (!positions_.empty())
399                                 cerr << "Unignoring comment: "
400                                      << curr_token().asInput();
401                         putback();
402                 }
403                 else
404                         break;
405         }
406 }
407
408
409 void Parser::putback()
410 {
411         --pos_;
412 }
413
414
415 void Parser::pushPosition()
416 {
417         positions_.push_back(pos_);
418 }
419
420
421 void Parser::popPosition()
422 {
423         pos_ = positions_.back();
424         positions_.pop_back();
425         deparse();
426 }
427
428
429 void Parser::dropPosition()
430 {
431         positions_.pop_back();
432 }
433
434
435 bool Parser::good()
436 {
437         if (pos_ < tokens_.size())
438                 return true;
439         if (!is_.good())
440                 return false;
441         return is_.peek() != idocstream::traits_type::eof();
442 }
443
444
445 bool Parser::hasOpt()
446 {
447         // An optional argument can occur in any of the following forms:
448         // - \foo[bar]
449         // - \foo [bar]
450         // - \foo
451         //   [bar]
452         // - \foo %comment
453         //   [bar]
454
455         // remember current position
456         unsigned int oldpos = pos_;
457         // skip spaces and comments
458         while (good()) {
459                 get_token();
460                 if (isParagraph()) {
461                         putback();
462                         break;
463                 }
464                 if (curr_token().cat() == catSpace ||
465                     curr_token().cat() == catNewline ||
466                     curr_token().cat() == catComment)
467                         continue;
468                 putback();
469                 break;
470         }
471         bool const retval = (next_token().asInput() == "[");
472         pos_ = oldpos;
473         return retval;
474 }
475
476
477 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
478 {
479         skip_spaces(true);
480
481         // This is needed if a partial file ends with a command without arguments,
482         // e. g. \medskip
483         if (! good())
484                 return make_pair(false, string());
485
486         string result;
487         Token t = get_token();
488
489         if (t.cat() == catComment || t.cat() == catEscape ||
490             t.character() != left) {
491                 putback();
492                 return make_pair(false, string());
493         } else {
494                 while (good()) {
495                         t = get_token();
496                         // Ignore comments
497                         if (t.cat() == catComment) {
498                                 if (!t.cs().empty())
499                                         cerr << "Ignoring comment: " << t.asInput();
500                                 continue;
501                         }
502                         if (allow_escaping) {
503                                 if (t.cat() != catEscape && t.character() == right)
504                                         break;
505                         } else {
506                                 if (t.character() == right) {
507                                         if (t.cat() == catEscape)
508                                                 result += '\\';
509                                         break;
510                                 }
511                         }
512                         result += t.asInput();
513                 }
514         }
515         return make_pair(true, result);
516 }
517
518
519 string Parser::getArg(char left, char right, bool allow_escaping)
520 {
521         return getFullArg(left, right, allow_escaping).second;
522 }
523
524
525 string Parser::getFullOpt(bool keepws)
526 {
527         Arg arg = getFullArg('[', ']');
528         if (arg.first)
529                 return '[' + arg.second + ']';
530         if (keepws)
531                 unskip_spaces(true);
532         return string();
533 }
534
535
536 string Parser::getOpt(bool keepws)
537 {
538         string const res = getArg('[', ']');
539         if (res.empty()) {
540                 if (keepws)
541                         unskip_spaces(true);
542                 return string();
543         }
544         return '[' + res + ']';
545 }
546
547
548 string Parser::getFullParentheseArg()
549 {
550         Arg arg = getFullArg('(', ')');
551         if (arg.first)
552                 return '(' + arg.second + ')';
553         return string();
554 }
555
556
557 string const Parser::ertEnvironment(string const & name)
558 {
559         if (!good())
560                 return string();
561
562         ostringstream os;
563         for (Token t = get_token(); good(); t = get_token()) {
564                 if (t.cat() == catBegin) {
565                         putback();
566                         os << '{' << verbatim_item() << '}';
567                 } else if (t.asInput() == "\\begin") {
568                         string const env = getArg('{', '}');
569                         os << "\\begin{" << env << '}'
570                            << ertEnvironment(env)
571                            << "\\end{" << env << '}';
572                 } else if (t.asInput() == "\\end") {
573                         string const end = getArg('{', '}');
574                         if (end != name)
575                                 cerr << "\\end{" << end
576                                      << "} does not match \\begin{" << name
577                                      << "}." << endl;
578                         return os.str();
579                 } else
580                         os << t.asInput();
581         }
582         cerr << "unexpected end of input" << endl;
583         return os.str();
584 }
585
586
587 string const Parser::plainEnvironment(string const & name)
588 {
589         if (!good())
590                 return string();
591
592         ostringstream os;
593         for (Token t = get_token(); good(); t = get_token()) {
594                 if (t.asInput() == "\\end") {
595                         string const end = getArg('{', '}');
596                         if (end == name)
597                                 return os.str();
598                         else
599                                 os << "\\end{" << end << '}';
600                 } else
601                         os << t.asInput();
602         }
603         cerr << "unexpected end of input" << endl;
604         return os.str();
605 }
606
607
608 string const Parser::plainCommand(char left, char right, string const & name)
609 {
610         if (!good())
611                 return string();
612         // check if first token is really the start character
613         Token tok = get_token();
614         if (tok.character() != left) {
615                 cerr << "first character does not match start character of command \\" << name << endl;
616                 return string();
617         }
618         ostringstream os;
619         for (Token t = get_token(); good(); t = get_token()) {
620                 if (t.character() == right) {
621                         return os.str();
622                 } else
623                         os << t.asInput();
624         }
625         cerr << "unexpected end of input" << endl;
626         return os.str();
627 }
628
629
630 Parser::Arg Parser::verbatimStuff(string const & end_string, bool const allow_linebreak)
631 {
632         if (!good())
633                 return Arg(false, string());
634
635         pushPosition();
636         ostringstream oss;
637         size_t match_index = 0;
638         setCatcodes(VERBATIM_CATCODES);
639         for (Token t = get_token(); good(); t = get_token()) {
640                 // FIXME t.asInput() might be longer than we need ?
641                 if (t.asInput() == end_string.substr(match_index,
642                                                      t.asInput().length())) {
643                         match_index += t.asInput().length();
644                         if (match_index >= end_string.length())
645                                 break;
646                 } else {
647                         if (!allow_linebreak && t.asInput() == "\n") {
648                                 cerr << "unexpected end of input" << endl;
649                                 popPosition();
650                                 setCatcodes(NORMAL_CATCODES);
651                                 return Arg(false, string());
652                         }
653                         if (match_index) {
654                                 oss << end_string.substr(0, match_index) 
655                                     << t.asInput();
656                                 match_index = 0;
657                         } else
658                                 oss << t.asInput();
659                 }
660         }
661
662         if (!good()) {
663                 cerr << "unexpected end of input" << endl;
664                 popPosition();
665                 setCatcodes(NORMAL_CATCODES);
666                 return Arg(false, string());
667         }
668         setCatcodes(NORMAL_CATCODES);
669         dropPosition();
670         return Arg(true, oss.str());
671 }
672
673
674 string const Parser::verbatimEnvironment(string const & name)
675 {
676         //FIXME: do something if endstring is not found
677         string s = verbatimStuff("\\end{" + name + "}").second;
678         // ignore one newline at beginning or end of string
679         if (prefixIs(s, "\n"))
680                 s.erase(0,1);
681         if (suffixIs(s, "\n"))
682                 s.erase(s.length() - 1,1);
683         return s;
684 }
685
686
687 string Parser::verbatimOption()
688 {
689         string res;
690         if (next_token().character() == '[') {
691                 Token t = get_token();
692                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
693                         if (t.cat() == catBegin) {
694                                 putback();
695                                 res += '{' + verbatim_item() + '}';
696                         } else
697                                 res += t.cs();
698                 }
699         }
700         return res;
701 }
702
703
704 string Parser::verbatim_item()
705 {
706         if (!good())
707                 error("stream bad");
708         skip_spaces();
709         if (next_token().cat() == catBegin) {
710                 Token t = get_token(); // skip brace
711                 string res;
712                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
713                         if (t.cat() == catBegin) {
714                                 putback();
715                                 res += '{' + verbatim_item() + '}';
716                         }
717                         else
718                                 res += t.asInput();
719                 }
720                 return res;
721         }
722         return get_token().asInput();
723 }
724
725
726 void Parser::tokenize_one()
727 {
728         catInit();
729         char_type c;
730         if (!is_.get(c))
731                 return;
732
733         switch (catcode(c)) {
734         case catSpace: {
735                 docstring s(1, c);
736                 while (is_.get(c) && catcode(c) == catSpace)
737                         s += c;
738                 if (catcode(c) != catSpace)
739                         is_.putback(c);
740                 push_back(Token(s, catSpace));
741                 break;
742         }
743
744         case catNewline: {
745                 ++lineno_;
746                 docstring s(1, getNewline(is_, c));
747                 while (is_.get(c) && catcode(c) == catNewline) {
748                         ++lineno_;
749                         s += getNewline(is_, c);
750                 }
751                 if (catcode(c) != catNewline)
752                         is_.putback(c);
753                 push_back(Token(s, catNewline));
754                 break;
755         }
756
757         case catComment: {
758                 // We don't treat "%\n" combinations here specially because
759                 // we want to preserve them in the preamble
760                 docstring s;
761                 while (is_.get(c) && catcode(c) != catNewline)
762                         s += c;
763                 // handle possible DOS line ending
764                 if (catcode(c) == catNewline)
765                         c = getNewline(is_, c);
766                 // Note: The '%' at the beginning and the '\n' at the end
767                 // of the comment are not stored.
768                 ++lineno_;
769                 push_back(Token(s, catComment));
770                 break;
771         }
772
773         case catEscape: {
774                 is_.get(c);
775                 if (!is_) {
776                         error("unexpected end of input");
777                 } else {
778                         docstring s(1, c);
779                         if (catcode(c) == catLetter) {
780                                 // collect letters
781                                 while (is_.get(c) && catcode(c) == catLetter)
782                                         s += c;
783                                 if (catcode(c) != catLetter)
784                                         is_.putback(c);
785                         }
786                         push_back(Token(s, catEscape));
787                 }
788                 break;
789         }
790
791         case catIgnore: {
792                 cerr << "ignoring a char: " << c << "\n";
793                 break;
794         }
795
796         default:
797                 push_back(Token(docstring(1, c), catcode(c)));
798         }
799         //cerr << tokens_.back();
800 }
801
802
803 void Parser::dump() const
804 {
805         cerr << "\nTokens: ";
806         for (unsigned i = 0; i < tokens_.size(); ++i) {
807                 if (i == pos_)
808                         cerr << " <#> ";
809                 cerr << tokens_[i];
810         }
811         cerr << " pos: " << pos_ << "\n";
812 }
813
814
815 void Parser::error(string const & msg)
816 {
817         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
818         dump();
819         //exit(1);
820 }
821
822
823 void Parser::reset()
824 {
825         pos_ = 0;
826 }
827
828
829 } // namespace lyx