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