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