]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
6f7dbf31e916704483ece7e1b384f0d227083999
[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/textutils.h"
16
17 #include <iostream>
18
19 using namespace std;
20
21 namespace lyx {
22
23 namespace {
24
25 CatCode theCatcode[256];
26
27 void catInit()
28 {
29         static bool init_done = false;
30         if (init_done)
31                 return;
32         init_done = true;
33
34         fill(theCatcode, theCatcode + 256, catOther);
35         fill(theCatcode + 'a', theCatcode + 'z' + 1, catLetter);
36         fill(theCatcode + 'A', theCatcode + 'Z' + 1, catLetter);
37
38         theCatcode[int('\\')] = catEscape;
39         theCatcode[int('{')]  = catBegin;
40         theCatcode[int('}')]  = catEnd;
41         theCatcode[int('$')]  = catMath;
42         theCatcode[int('&')]  = catAlign;
43         theCatcode[int('\n')] = catNewline;
44         theCatcode[int('#')]  = catParameter;
45         theCatcode[int('^')]  = catSuper;
46         theCatcode[int('_')]  = catSub;
47         theCatcode[0x7f]      = catIgnore;
48         theCatcode[int(' ')]  = catSpace;
49         theCatcode[int('\t')] = catSpace;
50         theCatcode[int('\r')] = catNewline;
51         theCatcode[int('~')]  = catActive;
52         theCatcode[int('%')]  = catComment;
53
54         // This is wrong!
55         theCatcode[int('@')]  = catLetter;
56 }
57
58 /*!
59  * Translate a line ending to '\n'.
60  * \p c must have catcode catNewline, and it must be the last character read
61  * from \p is.
62  */
63 char_type getNewline(idocstream & is, char_type c)
64 {
65         // we have to handle 3 different line endings:
66         // - UNIX (\n)
67         // - MAC  (\r)
68         // - DOS  (\r\n)
69         if (c == '\r') {
70                 // MAC or DOS
71                 char_type wc;
72                 if (is.get(wc) && wc != '\n') {
73                         // MAC
74                         is.putback(wc);
75                 }
76                 return '\n';
77         }
78         // UNIX
79         return c;
80 }
81
82 CatCode catcode(char_type c)
83 {
84         if (c < 256)
85                 return theCatcode[(unsigned char)c];
86         return catOther;
87 }
88
89 }
90
91
92 //
93 // Token
94 //
95
96 ostream & operator<<(ostream & os, Token const & t)
97 {
98         if (t.cat() == catComment)
99                 os << '%' << t.cs() << '\n';
100         else if (t.cat() == catSpace)
101                 os << t.cs();
102         else if (t.cat() == catEscape)
103                 os << '\\' << t.cs() << ' ';
104         else if (t.cat() == catLetter)
105                 os << t.cs();
106         else if (t.cat() == catNewline)
107                 os << "[" << t.cs().size() << "\\n," << t.cat() << "]\n";
108         else
109                 os << '[' << t.cs() << ',' << t.cat() << ']';
110         return os;
111 }
112
113
114 string Token::asInput() const
115 {
116         if (cat_ == catComment)
117                 return '%' + cs_ + '\n';
118         if (cat_ == catEscape)
119                 return '\\' + cs_;
120         return cs_;
121 }
122
123
124 bool Token::isAlnumASCII() const
125 {
126         return cat_ == catLetter ||
127                (cat_ == catOther && cs_.length() == 1 && isDigitASCII(cs_[0]));
128 }
129
130
131 #ifdef FILEDEBUG
132 void debugToken(std::ostream & os, Token const & t, unsigned int flags)
133 {
134         char sep = ' ';
135         os << "t: " << t << " flags: " << flags;
136         if (flags & FLAG_BRACE_LAST) { os << sep << "BRACE_LAST"; sep = '|'; }
137         if (flags & FLAG_RIGHT     ) { os << sep << "RIGHT"     ; sep = '|'; }
138         if (flags & FLAG_END       ) { os << sep << "END"       ; sep = '|'; }
139         if (flags & FLAG_BRACK_LAST) { os << sep << "BRACK_LAST"; sep = '|'; }
140         if (flags & FLAG_TEXTMODE  ) { os << sep << "TEXTMODE"  ; sep = '|'; }
141         if (flags & FLAG_ITEM      ) { os << sep << "ITEM"      ; sep = '|'; }
142         if (flags & FLAG_LEAVE     ) { os << sep << "LEAVE"     ; sep = '|'; }
143         if (flags & FLAG_SIMPLE    ) { os << sep << "SIMPLE"    ; sep = '|'; }
144         if (flags & FLAG_EQUATION  ) { os << sep << "EQUATION"  ; sep = '|'; }
145         if (flags & FLAG_SIMPLE2   ) { os << sep << "SIMPLE2"   ; sep = '|'; }
146         if (flags & FLAG_OPTION    ) { os << sep << "OPTION"    ; sep = '|'; }
147         if (flags & FLAG_BRACED    ) { os << sep << "BRACED"    ; sep = '|'; }
148         if (flags & FLAG_CELL      ) { os << sep << "CELL"      ; sep = '|'; }
149         if (flags & FLAG_TABBING   ) { os << sep << "TABBING"   ; sep = '|'; }
150         os << "\n";
151 }
152 #endif
153
154
155 //
156 // Parser
157 //
158
159
160 Parser::Parser(idocstream & is)
161         : lineno_(0), pos_(0), iss_(0), is_(is), encoding_iconv_("UTF-8")
162 {
163 }
164
165
166 Parser::Parser(string const & s)
167         : lineno_(0), pos_(0),
168           iss_(new idocstringstream(from_utf8(s))), is_(*iss_),
169           encoding_iconv_("UTF-8")
170 {
171 }
172
173
174 Parser::~Parser()
175 {
176         delete iss_;
177 }
178
179
180 void Parser::setEncoding(std::string const & e, int const & p)
181 {
182         // We may (and need to) use unsafe encodings here: Since the text is
183         // converted to unicode while reading from is_, we never see text in
184         // the original encoding of the parser, but operate on utf8 strings
185         // instead. Therefore, we cannot misparse high bytes as {, } or \\.
186         Encoding const * const enc = encodings.fromLaTeXName(e, p, true);
187         if (!enc) {
188                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
189                 return;
190         }
191         setEncoding(enc->iconvName());
192 }
193
194
195 void Parser::setEncoding(std::string const & e)
196 {
197         //cerr << "setting encoding to " << e << std::endl;
198         is_ << lyx::setEncoding(e);
199         encoding_iconv_ = e;
200 }
201
202
203 void Parser::push_back(Token const & t)
204 {
205         tokens_.push_back(t);
206 }
207
208
209 // We return a copy here because the tokens_ vector may get reallocated
210 Token const Parser::prev_token() const
211 {
212         static const Token dummy;
213         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
214 }
215
216
217 // We return a copy here because the tokens_ vector may get reallocated
218 Token const Parser::curr_token() const
219 {
220         static const Token dummy;
221         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
222 }
223
224
225 // We return a copy here because the tokens_ vector may get reallocated
226 Token const Parser::next_token()
227 {
228         static const Token dummy;
229         return good() ? tokens_[pos_] : dummy;
230 }
231
232
233 // We return a copy here because the tokens_ vector may get reallocated
234 Token const Parser::next_next_token()
235 {
236         static const Token dummy;
237         // If good() has not been called after the last get_token() we need
238         // to tokenize two more tokens.
239         if (pos_ + 1 >= tokens_.size()) {
240                 tokenize_one();
241                 tokenize_one();
242         }
243         return pos_ + 1 < tokens_.size() ? tokens_[pos_ + 1] : dummy;
244 }
245
246
247 // We return a copy here because the tokens_ vector may get reallocated
248 Token const Parser::get_token()
249 {
250         static const Token dummy;
251         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
252         return good() ? tokens_[pos_++] : dummy;
253 }
254
255
256 bool Parser::isParagraph()
257 {
258         // A new paragraph in TeX ist started
259         // - either by a newline, following any amount of whitespace
260         //   characters (including zero), and another newline
261         // - or the token \par
262         if (curr_token().cat() == catNewline &&
263             (curr_token().cs().size() > 1 ||
264              (next_token().cat() == catSpace &&
265               next_next_token().cat() == catNewline)))
266                 return true;
267         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
268                 return true;
269         return false;
270 }
271
272
273 bool Parser::skip_spaces(bool skip_comments)
274 {
275         // We just silently return if we have no more tokens.
276         // skip_spaces() should be callable at any time,
277         // the caller must check p::good() anyway.
278         bool skipped = false;
279         while (good()) {
280                 get_token();
281                 if (isParagraph()) {
282                         putback();
283                         break;
284                 }
285                 if (curr_token().cat() == catSpace ||
286                     curr_token().cat() == catNewline) {
287                         skipped = true;
288                         continue;
289                 }
290                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
291                         continue;
292                 if (skip_comments && curr_token().cat() == catComment) {
293                         // If positions_ is not empty we are doing some kind
294                         // of look ahead
295                         if (!positions_.empty())
296                                 cerr << "  Ignoring comment: "
297                                      << curr_token().asInput();
298                 } else {
299                         putback();
300                         break;
301                 }
302         }
303         return skipped;
304 }
305
306
307 void Parser::unskip_spaces(bool skip_comments)
308 {
309         while (pos_ > 0) {
310                 if ( curr_token().cat() == catSpace ||
311                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
312                         putback();
313                 else if (skip_comments && curr_token().cat() == catComment) {
314                         // TODO: Get rid of this
315                         // If positions_ is not empty we are doing some kind
316                         // of look ahead
317                         if (!positions_.empty())
318                                 cerr << "Unignoring comment: "
319                                      << curr_token().asInput();
320                         putback();
321                 }
322                 else
323                         break;
324         }
325 }
326
327
328 void Parser::putback()
329 {
330         --pos_;
331 }
332
333
334 void Parser::pushPosition()
335 {
336         positions_.push_back(pos_);
337 }
338
339
340 void Parser::popPosition()
341 {
342         pos_ = positions_.back();
343         positions_.pop_back();
344 }
345
346
347 bool Parser::good()
348 {
349         if (pos_ < tokens_.size())
350                 return true;
351         tokenize_one();
352         return pos_ < tokens_.size();
353 }
354
355
356 char Parser::getChar()
357 {
358         if (!good())
359                 error("The input stream is not well...");
360         return get_token().character();
361 }
362
363
364 bool Parser::hasOpt()
365 {
366         // An optional argument can occur in any of the following forms:
367         // - \foo[bar]
368         // - \foo [bar]
369         // - \foo
370         //   [bar]
371         // - \foo %comment
372         //   [bar]
373
374         // remember current position
375         unsigned int oldpos = pos_;
376         // skip spaces and comments
377         while (good()) {
378                 get_token();
379                 if (isParagraph()) {
380                         putback();
381                         break;
382                 }
383                 if (curr_token().cat() == catSpace ||
384                     curr_token().cat() == catNewline ||
385                     curr_token().cat() == catComment)
386                         continue;
387                 putback();
388                 break;
389         }
390         bool const retval = (next_token().asInput() == "[");
391         pos_ = oldpos;
392         return retval;
393 }
394
395
396 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
397 {
398         skip_spaces(true);
399
400         // This is needed if a partial file ends with a command without arguments,
401         // e. g. \medskip
402         if (! good())
403                 return make_pair(false, string());
404
405         string result;
406         Token t = get_token();
407
408         if (t.cat() == catComment || t.cat() == catEscape ||
409             t.character() != left) {
410                 putback();
411                 return make_pair(false, string());
412         } else {
413                 for (t = get_token(); good(); t = get_token()) {
414                         // Ignore comments
415                         if (t.cat() == catComment) {
416                                 if (!t.cs().empty())
417                                         cerr << "Ignoring comment: " << t.asInput();
418                                 continue;
419                         }
420                         if (allow_escaping) {
421                                 if (t.cat() != catEscape && t.character() == right)
422                                         break;
423                         } else {
424                                 if (t.character() == right) {
425                                         if (t.cat() == catEscape)
426                                                 result += '\\';
427                                         break;
428                                 }
429                         }
430                         result += t.asInput();
431                 }
432         }
433         return make_pair(true, result);
434 }
435
436
437 string Parser::getArg(char left, char right, bool allow_escaping)
438 {
439         return getFullArg(left, right, allow_escaping).second;
440 }
441
442
443 string Parser::getFullOpt(bool keepws)
444 {
445         Arg arg = getFullArg('[', ']');
446         if (arg.first)
447                 return '[' + arg.second + ']';
448         if (keepws)
449                 unskip_spaces(true);
450         return string();
451 }
452
453
454 string Parser::getOpt(bool keepws)
455 {
456         string const res = getArg('[', ']');
457         if (res.empty()) {
458                 if (keepws)
459                         unskip_spaces(true);
460                 return string();
461         }
462         return '[' + res + ']';
463 }
464
465
466 string Parser::getFullParentheseArg()
467 {
468         Arg arg = getFullArg('(', ')');
469         if (arg.first)
470                 return '(' + arg.second + ')';
471         return string();
472 }
473
474
475 string const Parser::verbatimEnvironment(string const & name)
476 {
477         if (!good())
478                 return string();
479
480         ostringstream os;
481         for (Token t = get_token(); good(); t = get_token()) {
482                 if (t.cat() == catBegin) {
483                         putback();
484                         os << '{' << verbatim_item() << '}';
485                 } else if (t.asInput() == "\\begin") {
486                         string const env = getArg('{', '}');
487                         os << "\\begin{" << env << '}'
488                            << verbatimEnvironment(env)
489                            << "\\end{" << env << '}';
490                 } else if (t.asInput() == "\\end") {
491                         string const end = getArg('{', '}');
492                         if (end != name)
493                                 cerr << "\\end{" << end
494                                      << "} does not match \\begin{" << name
495                                      << "}." << endl;
496                         return os.str();
497                 } else
498                         os << t.asInput();
499         }
500         cerr << "unexpected end of input" << endl;
501         return os.str();
502 }
503
504
505 string const Parser::plainEnvironment(string const & name)
506 {
507         if (!good())
508                 return string();
509
510         ostringstream os;
511         for (Token t = get_token(); good(); t = get_token()) {
512                 if (t.asInput() == "\\end") {
513                         string const end = getArg('{', '}');
514                         if (end == name)
515                                 return os.str();
516                         else
517                                 os << "\\end{" << end << '}';
518                 } else
519                         os << t.asInput();
520         }
521         cerr << "unexpected end of input" << endl;
522         return os.str();
523 }
524
525
526 string const Parser::plainCommand(char left, char right, string const & name)
527 {
528         if (!good())
529                 return string();
530         // check if first token is really the start character
531         Token tok = get_token();
532         if (tok.character() != left) {
533                 cerr << "first character does not match start character of command \\" << name << endl;
534                 return string();
535         }
536         ostringstream os;
537         for (Token t = get_token(); good(); t = get_token()) {
538                 if (t.character() == right) {
539                         return os.str();
540                 } else
541                         os << t.asInput();
542         }
543         cerr << "unexpected end of input" << endl;
544         return os.str();
545 }
546
547
548 void Parser::tokenize_one()
549 {
550         catInit();
551         char_type c;
552         if (!is_.get(c))
553                 return;
554
555         switch (catcode(c)) {
556         case catSpace: {
557                 docstring s(1, c);
558                 while (is_.get(c) && catcode(c) == catSpace)
559                         s += c;
560                 if (catcode(c) != catSpace)
561                         is_.putback(c);
562                 push_back(Token(s, catSpace));
563                 break;
564         }
565
566         case catNewline: {
567                 ++lineno_;
568                 docstring s(1, getNewline(is_, c));
569                 while (is_.get(c) && catcode(c) == catNewline) {
570                         ++lineno_;
571                         s += getNewline(is_, c);
572                 }
573                 if (catcode(c) != catNewline)
574                         is_.putback(c);
575                 push_back(Token(s, catNewline));
576                 break;
577         }
578
579         case catComment: {
580                 // We don't treat "%\n" combinations here specially because
581                 // we want to preserve them in the preamble
582                 docstring s;
583                 while (is_.get(c) && catcode(c) != catNewline)
584                         s += c;
585                 // handle possible DOS line ending
586                 if (catcode(c) == catNewline)
587                         c = getNewline(is_, c);
588                 // Note: The '%' at the beginning and the '\n' at the end
589                 // of the comment are not stored.
590                 ++lineno_;
591                 push_back(Token(s, catComment));
592                 break;
593         }
594
595         case catEscape: {
596                 is_.get(c);
597                 if (!is_) {
598                         error("unexpected end of input");
599                 } else {
600                         docstring s(1, c);
601                         if (catcode(c) == catLetter) {
602                                 // collect letters
603                                 while (is_.get(c) && catcode(c) == catLetter)
604                                         s += c;
605                                 if (catcode(c) != catLetter)
606                                         is_.putback(c);
607                         }
608                         push_back(Token(s, catEscape));
609                 }
610                 break;
611         }
612
613         case catIgnore: {
614                 cerr << "ignoring a char: " << c << "\n";
615                 break;
616         }
617
618         default:
619                 push_back(Token(docstring(1, c), catcode(c)));
620         }
621         //cerr << tokens_.back();
622 }
623
624
625 void Parser::dump() const
626 {
627         cerr << "\nTokens: ";
628         for (unsigned i = 0; i < tokens_.size(); ++i) {
629                 if (i == pos_)
630                         cerr << " <#> ";
631                 cerr << tokens_[i];
632         }
633         cerr << " pos: " << pos_ << "\n";
634 }
635
636
637 void Parser::error(string const & msg)
638 {
639         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
640         dump();
641         //exit(1);
642 }
643
644
645 string Parser::verbatimOption()
646 {
647         string res;
648         if (next_token().character() == '[') {
649                 Token t = get_token();
650                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
651                         if (t.cat() == catBegin) {
652                                 putback();
653                                 res += '{' + verbatim_item() + '}';
654                         } else
655                                 res += t.cs();
656                 }
657         }
658         return res;
659 }
660
661
662 string Parser::verbatim_item()
663 {
664         if (!good())
665                 error("stream bad");
666         skip_spaces();
667         if (next_token().cat() == catBegin) {
668                 Token t = get_token(); // skip brace
669                 string res;
670                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
671                         if (t.cat() == catBegin) {
672                                 putback();
673                                 res += '{' + verbatim_item() + '}';
674                         }
675                         else
676                                 res += t.asInput();
677                 }
678                 return res;
679         }
680         return get_token().asInput();
681 }
682
683
684 void Parser::reset()
685 {
686         pos_ = 0;
687 }
688
689
690 void Parser::setCatCode(char c, CatCode cat)
691 {
692         theCatcode[(unsigned char)c] = cat;
693 }
694
695
696 CatCode Parser::getCatCode(char c) const
697 {
698         return theCatcode[(unsigned char)c];
699 }
700
701
702 } // namespace lyx