]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
cba63099c9afd395b979bc16a01cdb302dc2a8b5
[lyx.git] / src / tex2lyx / Parser.cpp
1 /**
2  * \file Parser.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Encoding.h"
14 #include "Parser.h"
15 #include "support/foreach.h"
16 #include "support/lstrings.h"
17 #include "support/textutils.h"
18
19 #include <iostream>
20
21 using namespace std;
22 using namespace lyx::support;
23
24 namespace lyx {
25
26 namespace {
27
28 /*!
29  * Translate a line ending to '\n'.
30  * \p c must have catcode catNewline, and it must be the last character read
31  * from \p is.
32  */
33 char_type getNewline(iparserdocstream & is, char_type c)
34 {
35         // we have to handle 3 different line endings:
36         // - UNIX (\n)
37         // - MAC  (\r)
38         // - DOS  (\r\n)
39         if (c == '\r') {
40                 // MAC or DOS
41                 char_type wc;
42                 if (is.get(wc) && wc != '\n') {
43                         // MAC
44                         is.putback(wc);
45                 }
46                 return '\n';
47         }
48         // UNIX
49         return c;
50 }
51
52 }
53
54 //
55 // Token
56 //
57
58 ostream & operator<<(ostream & os, Token const & t)
59 {
60         if (t.cat() == catComment)
61                 os << '%' << t.cs() << '\n';
62         else if (t.cat() == catSpace)
63                 os << t.cs();
64         else if (t.cat() == catEscape)
65                 os << '\\' << t.cs() << ' ';
66         else if (t.cat() == catLetter)
67                 os << t.cs();
68         else if (t.cat() == catNewline)
69                 os << "[" << t.cs().size() << "\\n," << t.cat() << "]\n";
70         else
71                 os << '[' << t.cs() << ',' << t.cat() << ']';
72         return os;
73 }
74
75
76 string Token::asInput() const
77 {
78         if (cat_ == catComment)
79                 return '%' + cs_ + '\n';
80         if (cat_ == catEscape)
81                 return '\\' + cs_;
82         return cs_;
83 }
84
85
86 bool Token::isAlnumASCII() const
87 {
88         return cat_ == catLetter ||
89                (cat_ == catOther && cs_.length() == 1 && isDigitASCII(cs_[0]));
90 }
91
92
93 #ifdef FILEDEBUG
94 void debugToken(std::ostream & os, Token const & t, unsigned int flags)
95 {
96         char sep = ' ';
97         os << "t: " << t << " flags: " << flags;
98         if (flags & FLAG_BRACE_LAST) { os << sep << "BRACE_LAST"; sep = '|'; }
99         if (flags & FLAG_RIGHT     ) { os << sep << "RIGHT"     ; sep = '|'; }
100         if (flags & FLAG_END       ) { os << sep << "END"       ; sep = '|'; }
101         if (flags & FLAG_BRACK_LAST) { os << sep << "BRACK_LAST"; sep = '|'; }
102         if (flags & FLAG_TEXTMODE  ) { os << sep << "TEXTMODE"  ; sep = '|'; }
103         if (flags & FLAG_ITEM      ) { os << sep << "ITEM"      ; sep = '|'; }
104         if (flags & FLAG_LEAVE     ) { os << sep << "LEAVE"     ; sep = '|'; }
105         if (flags & FLAG_SIMPLE    ) { os << sep << "SIMPLE"    ; sep = '|'; }
106         if (flags & FLAG_EQUATION  ) { os << sep << "EQUATION"  ; sep = '|'; }
107         if (flags & FLAG_SIMPLE2   ) { os << sep << "SIMPLE2"   ; sep = '|'; }
108         if (flags & FLAG_OPTION    ) { os << sep << "OPTION"    ; sep = '|'; }
109         if (flags & FLAG_BRACED    ) { os << sep << "BRACED"    ; sep = '|'; }
110         if (flags & FLAG_CELL      ) { os << sep << "CELL"      ; sep = '|'; }
111         if (flags & FLAG_TABBING   ) { os << sep << "TABBING"   ; sep = '|'; }
112         os << "\n";
113 }
114 #endif
115
116
117 //
118 // Wrapper
119 //
120
121 void iparserdocstream::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 bool Parser::hasOpt()
417 {
418         // An optional argument can occur in any of the following forms:
419         // - \foo[bar]
420         // - \foo [bar]
421         // - \foo
422         //   [bar]
423         // - \foo %comment
424         //   [bar]
425
426         // remember current position
427         unsigned int oldpos = pos_;
428         // skip spaces and comments
429         while (good()) {
430                 get_token();
431                 if (isParagraph()) {
432                         putback();
433                         break;
434                 }
435                 if (curr_token().cat() == catSpace ||
436                     curr_token().cat() == catNewline ||
437                     curr_token().cat() == catComment)
438                         continue;
439                 putback();
440                 break;
441         }
442         bool const retval = (next_token().asInput() == "[");
443         pos_ = oldpos;
444         return retval;
445 }
446
447
448 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
449 {
450         skip_spaces(true);
451
452         // This is needed if a partial file ends with a command without arguments,
453         // e. g. \medskip
454         if (! good())
455                 return make_pair(false, string());
456
457         string result;
458         Token t = get_token();
459
460         if (t.cat() == catComment || t.cat() == catEscape ||
461             t.character() != left) {
462                 putback();
463                 return make_pair(false, string());
464         } else {
465                 while (good()) {
466                         t = get_token();
467                         // Ignore comments
468                         if (t.cat() == catComment) {
469                                 if (!t.cs().empty())
470                                         cerr << "Ignoring comment: " << t.asInput();
471                                 continue;
472                         }
473                         if (allow_escaping) {
474                                 if (t.cat() != catEscape && t.character() == right)
475                                         break;
476                         } else {
477                                 if (t.character() == right) {
478                                         if (t.cat() == catEscape)
479                                                 result += '\\';
480                                         break;
481                                 }
482                         }
483                         result += t.asInput();
484                 }
485         }
486         return make_pair(true, result);
487 }
488
489
490 string Parser::getArg(char left, char right, bool allow_escaping)
491 {
492         return getFullArg(left, right, allow_escaping).second;
493 }
494
495
496 string Parser::getFullOpt(bool keepws)
497 {
498         Arg arg = getFullArg('[', ']');
499         if (arg.first)
500                 return '[' + arg.second + ']';
501         if (keepws)
502                 unskip_spaces(true);
503         return string();
504 }
505
506
507 string Parser::getOpt(bool keepws)
508 {
509         string const res = getArg('[', ']');
510         if (res.empty()) {
511                 if (keepws)
512                         unskip_spaces(true);
513                 return string();
514         }
515         return '[' + res + ']';
516 }
517
518
519 string Parser::getFullParentheseArg()
520 {
521         Arg arg = getFullArg('(', ')');
522         if (arg.first)
523                 return '(' + arg.second + ')';
524         return string();
525 }
526
527
528 string const Parser::ertEnvironment(string const & name)
529 {
530         if (!good())
531                 return string();
532
533         ostringstream os;
534         for (Token t = get_token(); good(); t = get_token()) {
535                 if (t.cat() == catBegin) {
536                         putback();
537                         os << '{' << verbatim_item() << '}';
538                 } else if (t.asInput() == "\\begin") {
539                         string const env = getArg('{', '}');
540                         os << "\\begin{" << env << '}'
541                            << ertEnvironment(env)
542                            << "\\end{" << env << '}';
543                 } else if (t.asInput() == "\\end") {
544                         string const end = getArg('{', '}');
545                         if (end != name)
546                                 cerr << "\\end{" << end
547                                      << "} does not match \\begin{" << name
548                                      << "}." << endl;
549                         return os.str();
550                 } else
551                         os << t.asInput();
552         }
553         cerr << "unexpected end of input" << endl;
554         return os.str();
555 }
556
557
558 string const Parser::plainEnvironment(string const & name)
559 {
560         if (!good())
561                 return string();
562
563         ostringstream os;
564         for (Token t = get_token(); good(); t = get_token()) {
565                 if (t.asInput() == "\\end") {
566                         string const end = getArg('{', '}');
567                         if (end == name)
568                                 return os.str();
569                         else
570                                 os << "\\end{" << end << '}';
571                 } else
572                         os << t.asInput();
573         }
574         cerr << "unexpected end of input" << endl;
575         return os.str();
576 }
577
578
579 string const Parser::plainCommand(char left, char right, string const & name)
580 {
581         if (!good())
582                 return string();
583         // check if first token is really the start character
584         Token tok = get_token();
585         if (tok.character() != left) {
586                 cerr << "first character does not match start character of command \\" << name << endl;
587                 return string();
588         }
589         ostringstream os;
590         for (Token t = get_token(); good(); t = get_token()) {
591                 if (t.character() == right) {
592                         return os.str();
593                 } else
594                         os << t.asInput();
595         }
596         cerr << "unexpected end of input" << endl;
597         return os.str();
598 }
599
600
601 string const Parser::verbatimStuff(string const & end_string)
602 {
603         if (!good())
604                 return string();
605
606         ostringstream oss;
607         size_t match_index = 0;
608         setCatcodes(VERBATIM_CATCODES);
609         for (Token t = get_token(); good(); t = get_token()) {
610                 // FIXME t.asInput() might be longer than we need ?
611                 if (t.asInput() == end_string.substr(match_index,
612                                                      t.asInput().length())) {
613                         match_index += t.asInput().length();
614                         if (match_index >= end_string.length())
615                                 break;
616                 } else if (match_index) {
617                         oss << end_string.substr(0, match_index) << t.asInput();
618                         match_index = 0;
619                 } else
620                         oss << t.asInput();
621         }
622         setCatcodes(NORMAL_CATCODES);
623         if (!good())
624                 cerr << "unexpected end of input" << endl;
625         return oss.str();
626 }
627
628
629 string const Parser::verbatimEnvironment(string const & name)
630 {
631         string s = verbatimStuff("\\end{" + name + "}");
632         // ignore one newline at beginning or end of string
633         if (prefixIs(s, "\n"))
634                 s.erase(0,1);
635         if (suffixIs(s, "\n"))
636                 s.erase(s.length() - 1,1);
637         return s;
638 }
639
640
641 string Parser::verbatimOption()
642 {
643         string res;
644         if (next_token().character() == '[') {
645                 Token t = get_token();
646                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
647                         if (t.cat() == catBegin) {
648                                 putback();
649                                 res += '{' + verbatim_item() + '}';
650                         } else
651                                 res += t.cs();
652                 }
653         }
654         return res;
655 }
656
657
658 string Parser::verbatim_item()
659 {
660         if (!good())
661                 error("stream bad");
662         skip_spaces();
663         if (next_token().cat() == catBegin) {
664                 Token t = get_token(); // skip brace
665                 string res;
666                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
667                         if (t.cat() == catBegin) {
668                                 putback();
669                                 res += '{' + verbatim_item() + '}';
670                         }
671                         else
672                                 res += t.asInput();
673                 }
674                 return res;
675         }
676         return get_token().asInput();
677 }
678
679
680 void Parser::tokenize_one()
681 {
682         catInit();
683         char_type c;
684         if (!is_.get(c))
685                 return;
686
687         switch (catcode(c)) {
688         case catSpace: {
689                 docstring s(1, c);
690                 while (is_.get(c) && catcode(c) == catSpace)
691                         s += c;
692                 if (catcode(c) != catSpace)
693                         is_.putback(c);
694                 push_back(Token(s, catSpace));
695                 break;
696         }
697
698         case catNewline: {
699                 ++lineno_;
700                 docstring s(1, getNewline(is_, c));
701                 while (is_.get(c) && catcode(c) == catNewline) {
702                         ++lineno_;
703                         s += getNewline(is_, c);
704                 }
705                 if (catcode(c) != catNewline)
706                         is_.putback(c);
707                 push_back(Token(s, catNewline));
708                 break;
709         }
710
711         case catComment: {
712                 // We don't treat "%\n" combinations here specially because
713                 // we want to preserve them in the preamble
714                 docstring s;
715                 while (is_.get(c) && catcode(c) != catNewline)
716                         s += c;
717                 // handle possible DOS line ending
718                 if (catcode(c) == catNewline)
719                         c = getNewline(is_, c);
720                 // Note: The '%' at the beginning and the '\n' at the end
721                 // of the comment are not stored.
722                 ++lineno_;
723                 push_back(Token(s, catComment));
724                 break;
725         }
726
727         case catEscape: {
728                 is_.get(c);
729                 if (!is_) {
730                         error("unexpected end of input");
731                 } else {
732                         docstring s(1, c);
733                         if (catcode(c) == catLetter) {
734                                 // collect letters
735                                 while (is_.get(c) && catcode(c) == catLetter)
736                                         s += c;
737                                 if (catcode(c) != catLetter)
738                                         is_.putback(c);
739                         }
740                         push_back(Token(s, catEscape));
741                 }
742                 break;
743         }
744
745         case catIgnore: {
746                 cerr << "ignoring a char: " << c << "\n";
747                 break;
748         }
749
750         default:
751                 push_back(Token(docstring(1, c), catcode(c)));
752         }
753         //cerr << tokens_.back();
754 }
755
756
757 void Parser::dump() const
758 {
759         cerr << "\nTokens: ";
760         for (unsigned i = 0; i < tokens_.size(); ++i) {
761                 if (i == pos_)
762                         cerr << " <#> ";
763                 cerr << tokens_[i];
764         }
765         cerr << " pos: " << pos_ << "\n";
766 }
767
768
769 void Parser::error(string const & msg)
770 {
771         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
772         dump();
773         //exit(1);
774 }
775
776
777 void Parser::reset()
778 {
779         pos_ = 0;
780 }
781
782
783 } // namespace lyx