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