]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
547f424b9dab64f0e9ea78543c629178f81fab3f
[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                 for (t = get_token(); good(); t = get_token()) {
466                         // Ignore comments
467                         if (t.cat() == catComment) {
468                                 if (!t.cs().empty())
469                                         cerr << "Ignoring comment: " << t.asInput();
470                                 continue;
471                         }
472                         if (allow_escaping) {
473                                 if (t.cat() != catEscape && t.character() == right)
474                                         break;
475                         } else {
476                                 if (t.character() == right) {
477                                         if (t.cat() == catEscape)
478                                                 result += '\\';
479                                         break;
480                                 }
481                         }
482                         result += t.asInput();
483                 }
484         }
485         return make_pair(true, result);
486 }
487
488
489 string Parser::getArg(char left, char right, bool allow_escaping)
490 {
491         return getFullArg(left, right, allow_escaping).second;
492 }
493
494
495 string Parser::getFullOpt(bool keepws)
496 {
497         Arg arg = getFullArg('[', ']');
498         if (arg.first)
499                 return '[' + arg.second + ']';
500         if (keepws)
501                 unskip_spaces(true);
502         return string();
503 }
504
505
506 string Parser::getOpt(bool keepws)
507 {
508         string const res = getArg('[', ']');
509         if (res.empty()) {
510                 if (keepws)
511                         unskip_spaces(true);
512                 return string();
513         }
514         return '[' + res + ']';
515 }
516
517
518 string Parser::getFullParentheseArg()
519 {
520         Arg arg = getFullArg('(', ')');
521         if (arg.first)
522                 return '(' + arg.second + ')';
523         return string();
524 }
525
526
527 string const Parser::ertEnvironment(string const & name)
528 {
529         if (!good())
530                 return string();
531
532         ostringstream os;
533         for (Token t = get_token(); good(); t = get_token()) {
534                 if (t.cat() == catBegin) {
535                         putback();
536                         os << '{' << verbatim_item() << '}';
537                 } else if (t.asInput() == "\\begin") {
538                         string const env = getArg('{', '}');
539                         os << "\\begin{" << env << '}'
540                            << ertEnvironment(env)
541                            << "\\end{" << env << '}';
542                 } else if (t.asInput() == "\\end") {
543                         string const end = getArg('{', '}');
544                         if (end != name)
545                                 cerr << "\\end{" << end
546                                      << "} does not match \\begin{" << name
547                                      << "}." << endl;
548                         return os.str();
549                 } else
550                         os << t.asInput();
551         }
552         cerr << "unexpected end of input" << endl;
553         return os.str();
554 }
555
556
557 string const Parser::plainEnvironment(string const & name)
558 {
559         if (!good())
560                 return string();
561
562         ostringstream os;
563         for (Token t = get_token(); good(); t = get_token()) {
564                 if (t.asInput() == "\\end") {
565                         string const end = getArg('{', '}');
566                         if (end == name)
567                                 return os.str();
568                         else
569                                 os << "\\end{" << end << '}';
570                 } else
571                         os << t.asInput();
572         }
573         cerr << "unexpected end of input" << endl;
574         return os.str();
575 }
576
577
578 string const Parser::plainCommand(char left, char right, string const & name)
579 {
580         if (!good())
581                 return string();
582         // check if first token is really the start character
583         Token tok = get_token();
584         if (tok.character() != left) {
585                 cerr << "first character does not match start character of command \\" << name << endl;
586                 return string();
587         }
588         ostringstream os;
589         for (Token t = get_token(); good(); t = get_token()) {
590                 if (t.character() == right) {
591                         return os.str();
592                 } else
593                         os << t.asInput();
594         }
595         cerr << "unexpected end of input" << endl;
596         return os.str();
597 }
598
599
600 string const Parser::verbatimStuff(string const & end_string)
601 {
602         if (!good())
603                 return string();
604
605         ostringstream oss;
606         size_t match_index = 0;
607         setCatcodes(VERBATIM_CATCODES);
608         for (Token t = get_token(); good(); t = get_token()) {
609                 // FIXME t.asInput() might be longer than we need ?
610                 if (t.asInput() == end_string.substr(match_index,
611                                                      t.asInput().length())) {
612                         match_index += t.asInput().length();
613                         if (match_index >= end_string.length())
614                                 break;
615                 } else if (match_index) {
616                         oss << end_string.substr(0, match_index) << t.asInput();
617                         match_index = 0;
618                 } else
619                         oss << t.asInput();
620         }
621         setCatcodes(NORMAL_CATCODES);
622         if (!good())
623                 cerr << "unexpected end of input" << endl;
624         return oss.str();
625 }
626
627
628 string const Parser::verbatimEnvironment(string const & name)
629 {
630         string s = verbatimStuff("\\end{" + name + "}");
631         // ignore one newline at beginning or end of string
632         if (prefixIs(s, "\n"))
633                 s.erase(0,1);
634         if (suffixIs(s, "\n"))
635                 s.erase(s.length() - 1,1);
636         return s;
637 }
638
639
640 string Parser::verbatimOption()
641 {
642         string res;
643         if (next_token().character() == '[') {
644                 Token t = get_token();
645                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
646                         if (t.cat() == catBegin) {
647                                 putback();
648                                 res += '{' + verbatim_item() + '}';
649                         } else
650                                 res += t.cs();
651                 }
652         }
653         return res;
654 }
655
656
657 string Parser::verbatim_item()
658 {
659         if (!good())
660                 error("stream bad");
661         skip_spaces();
662         if (next_token().cat() == catBegin) {
663                 Token t = get_token(); // skip brace
664                 string res;
665                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
666                         if (t.cat() == catBegin) {
667                                 putback();
668                                 res += '{' + verbatim_item() + '}';
669                         }
670                         else
671                                 res += t.asInput();
672                 }
673                 return res;
674         }
675         return get_token().asInput();
676 }
677
678
679 void Parser::tokenize_one()
680 {
681         catInit();
682         char_type c;
683         if (!is_.get(c))
684                 return;
685
686         switch (catcode(c)) {
687         case catSpace: {
688                 docstring s(1, c);
689                 while (is_.get(c) && catcode(c) == catSpace)
690                         s += c;
691                 if (catcode(c) != catSpace)
692                         is_.putback(c);
693                 push_back(Token(s, catSpace));
694                 break;
695         }
696
697         case catNewline: {
698                 ++lineno_;
699                 docstring s(1, getNewline(is_, c));
700                 while (is_.get(c) && catcode(c) == catNewline) {
701                         ++lineno_;
702                         s += getNewline(is_, c);
703                 }
704                 if (catcode(c) != catNewline)
705                         is_.putback(c);
706                 push_back(Token(s, catNewline));
707                 break;
708         }
709
710         case catComment: {
711                 // We don't treat "%\n" combinations here specially because
712                 // we want to preserve them in the preamble
713                 docstring s;
714                 while (is_.get(c) && catcode(c) != catNewline)
715                         s += c;
716                 // handle possible DOS line ending
717                 if (catcode(c) == catNewline)
718                         c = getNewline(is_, c);
719                 // Note: The '%' at the beginning and the '\n' at the end
720                 // of the comment are not stored.
721                 ++lineno_;
722                 push_back(Token(s, catComment));
723                 break;
724         }
725
726         case catEscape: {
727                 is_.get(c);
728                 if (!is_) {
729                         error("unexpected end of input");
730                 } else {
731                         docstring s(1, c);
732                         if (catcode(c) == catLetter) {
733                                 // collect letters
734                                 while (is_.get(c) && catcode(c) == catLetter)
735                                         s += c;
736                                 if (catcode(c) != catLetter)
737                                         is_.putback(c);
738                         }
739                         push_back(Token(s, catEscape));
740                 }
741                 break;
742         }
743
744         case catIgnore: {
745                 cerr << "ignoring a char: " << c << "\n";
746                 break;
747         }
748
749         default:
750                 push_back(Token(docstring(1, c), catcode(c)));
751         }
752         //cerr << tokens_.back();
753 }
754
755
756 void Parser::dump() const
757 {
758         cerr << "\nTokens: ";
759         for (unsigned i = 0; i < tokens_.size(); ++i) {
760                 if (i == pos_)
761                         cerr << " <#> ";
762                 cerr << tokens_[i];
763         }
764         cerr << " pos: " << pos_ << "\n";
765 }
766
767
768 void Parser::error(string const & msg)
769 {
770         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
771         dump();
772         //exit(1);
773 }
774
775
776 void Parser::reset()
777 {
778         pos_ = 0;
779 }
780
781
782 } // namespace lyx