]> git.lyx.org Git - features.git/blob - src/tex2lyx/Parser.cpp
d0954cf5c1adb611cbfd9230d76b8cdf7f59736f
[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 + 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 Parser::Arg Parser::verbatimStuff(string const & end_string, bool const allow_linebreak)
635 {
636         if (!good())
637                 return Arg(false, string());
638
639         pushPosition();
640         ostringstream oss;
641         size_t match_index = 0;
642         setCatcodes(VERBATIM_CATCODES);
643         for (Token t = get_token(); good(); t = get_token()) {
644                 // FIXME t.asInput() might be longer than we need ?
645                 if (t.asInput() == end_string.substr(match_index,
646                                                      t.asInput().length())) {
647                         match_index += t.asInput().length();
648                         if (match_index >= end_string.length())
649                                 break;
650                 } else {
651                         if (!allow_linebreak && t.asInput() == "\n") {
652                                 cerr << "unexpected end of input" << endl;
653                                 popPosition();
654                                 setCatcodes(NORMAL_CATCODES);
655                                 return Arg(false, string());
656                         }
657                         if (match_index) {
658                                 oss << end_string.substr(0, match_index) 
659                                     << t.asInput();
660                                 match_index = 0;
661                         } else
662                                 oss << t.asInput();
663                 }
664         }
665
666         if (!good()) {
667                 cerr << "unexpected end of input" << endl;
668                 popPosition();
669                 setCatcodes(NORMAL_CATCODES);
670                 return Arg(false, string());
671         }
672         setCatcodes(NORMAL_CATCODES);
673         dropPosition();
674         return Arg(true, oss.str());
675 }
676
677
678 string const Parser::verbatimEnvironment(string const & name)
679 {
680         //FIXME: do something if endstring is not found
681         string s = verbatimStuff("\\end{" + name + "}").second;
682         // ignore one newline at beginning or end of string
683         if (prefixIs(s, "\n"))
684                 s.erase(0,1);
685         if (suffixIs(s, "\n"))
686                 s.erase(s.length() - 1,1);
687         return s;
688 }
689
690
691 string Parser::verbatimOption()
692 {
693         string res;
694         if (next_token().character() == '[') {
695                 Token t = get_token();
696                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
697                         if (t.cat() == catBegin) {
698                                 putback();
699                                 res += '{' + verbatim_item() + '}';
700                         } else
701                                 res += t.cs();
702                 }
703         }
704         return res;
705 }
706
707
708 string Parser::verbatim_item()
709 {
710         if (!good())
711                 error("stream bad");
712         skip_spaces();
713         if (next_token().cat() == catBegin) {
714                 Token t = get_token(); // skip brace
715                 string res;
716                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
717                         if (t.cat() == catBegin) {
718                                 putback();
719                                 res += '{' + verbatim_item() + '}';
720                         }
721                         else
722                                 res += t.asInput();
723                 }
724                 return res;
725         }
726         return get_token().asInput();
727 }
728
729
730 void Parser::tokenize_one()
731 {
732         catInit();
733         char_type c;
734         if (!is_.get(c))
735                 return;
736
737         switch (catcode(c)) {
738         case catSpace: {
739                 docstring s(1, c);
740                 while (is_.get(c) && catcode(c) == catSpace)
741                         s += c;
742                 if (catcode(c) != catSpace)
743                         is_.putback(c);
744                 push_back(Token(s, catSpace));
745                 break;
746         }
747
748         case catNewline: {
749                 ++lineno_;
750                 docstring s(1, getNewline(is_, c));
751                 while (is_.get(c) && catcode(c) == catNewline) {
752                         ++lineno_;
753                         s += getNewline(is_, c);
754                 }
755                 if (catcode(c) != catNewline)
756                         is_.putback(c);
757                 push_back(Token(s, catNewline));
758                 break;
759         }
760
761         case catComment: {
762                 // We don't treat "%\n" combinations here specially because
763                 // we want to preserve them in the preamble
764                 docstring s;
765                 while (is_.get(c) && catcode(c) != catNewline)
766                         s += c;
767                 // handle possible DOS line ending
768                 if (catcode(c) == catNewline)
769                         c = getNewline(is_, c);
770                 // Note: The '%' at the beginning and the '\n' at the end
771                 // of the comment are not stored.
772                 ++lineno_;
773                 push_back(Token(s, catComment));
774                 break;
775         }
776
777         case catEscape: {
778                 is_.get(c);
779                 if (!is_) {
780                         error("unexpected end of input");
781                 } else {
782                         docstring s(1, c);
783                         if (catcode(c) == catLetter) {
784                                 // collect letters
785                                 while (is_.get(c) && catcode(c) == catLetter)
786                                         s += c;
787                                 if (catcode(c) != catLetter)
788                                         is_.putback(c);
789                         }
790                         push_back(Token(s, catEscape));
791                 }
792                 break;
793         }
794
795         case catIgnore: {
796                 cerr << "ignoring a char: " << c << "\n";
797                 break;
798         }
799
800         default:
801                 push_back(Token(docstring(1, c), catcode(c)));
802         }
803         //cerr << tokens_.back();
804 }
805
806
807 void Parser::dump() const
808 {
809         cerr << "\nTokens: ";
810         for (unsigned i = 0; i < tokens_.size(); ++i) {
811                 if (i == pos_)
812                         cerr << " <#> ";
813                 cerr << tokens_[i];
814         }
815         cerr << " pos: " << pos_ << "\n";
816 }
817
818
819 void Parser::error(string const & msg)
820 {
821         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
822         dump();
823         //exit(1);
824 }
825
826
827 void Parser::reset()
828 {
829         pos_ = 0;
830 }
831
832
833 } // namespace lyx