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