]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
01f14b8dc9da9f3e031e37f964f6060c0eb35ff0
[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_latex_("utf8")
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_latex_("utf8")
170 {
171 }
172
173
174 Parser::~Parser()
175 {
176         delete iss_;
177 }
178
179
180 void Parser::setEncoding(std::string const & e)
181 {
182         Encoding const * enc = encodings.fromLaTeXName(e);
183         if (!enc) {
184                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
185                 return;
186         }
187         //cerr << "setting encoding to " << enc->iconvName() << std::endl;
188         is_ << lyx::setEncoding(enc->iconvName());
189         encoding_latex_ = e;
190 }
191
192
193 void Parser::push_back(Token const & t)
194 {
195         tokens_.push_back(t);
196 }
197
198
199 // We return a copy here because the tokens_ vector may get reallocated
200 Token const Parser::prev_token() const
201 {
202         static const Token dummy;
203         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
204 }
205
206
207 // We return a copy here because the tokens_ vector may get reallocated
208 Token const Parser::curr_token() const
209 {
210         static const Token dummy;
211         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
212 }
213
214
215 // We return a copy here because the tokens_ vector may get reallocated
216 Token const Parser::next_token()
217 {
218         static const Token dummy;
219         return good() ? tokens_[pos_] : dummy;
220 }
221
222
223 // We return a copy here because the tokens_ vector may get reallocated
224 Token const Parser::next_next_token()
225 {
226         static const Token dummy;
227         // If good() has not been called after the last get_token() we need
228         // to tokenize two more tokens.
229         if (pos_ + 1 >= tokens_.size()) {
230                 tokenize_one();
231                 tokenize_one();
232         }
233         return pos_ + 1 < tokens_.size() ? tokens_[pos_ + 1] : dummy;
234 }
235
236
237 // We return a copy here because the tokens_ vector may get reallocated
238 Token const Parser::get_token()
239 {
240         static const Token dummy;
241         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
242         return good() ? tokens_[pos_++] : dummy;
243 }
244
245
246 bool Parser::isParagraph()
247 {
248         // A new paragraph in TeX ist started
249         // - either by a newline, following any amount of whitespace
250         //   characters (including zero), and another newline
251         // - or the token \par
252         if (curr_token().cat() == catNewline &&
253             (curr_token().cs().size() > 1 ||
254              (next_token().cat() == catSpace &&
255               next_next_token().cat() == catNewline)))
256                 return true;
257         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
258                 return true;
259         return false;
260 }
261
262
263 bool Parser::skip_spaces(bool skip_comments)
264 {
265         // We just silently return if we have no more tokens.
266         // skip_spaces() should be callable at any time,
267         // the caller must check p::good() anyway.
268         bool skipped = false;
269         while (good()) {
270                 get_token();
271                 if (isParagraph()) {
272                         putback();
273                         break;
274                 }
275                 if (curr_token().cat() == catSpace ||
276                     curr_token().cat() == catNewline) {
277                         skipped = true;
278                         continue;
279                 }
280                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
281                         continue;
282                 if (skip_comments && curr_token().cat() == catComment) {
283                         // If positions_ is not empty we are doing some kind
284                         // of look ahead
285                         if (!positions_.empty())
286                                 cerr << "  Ignoring comment: "
287                                      << curr_token().asInput();
288                 } else {
289                         putback();
290                         break;
291                 }
292         }
293         return skipped;
294 }
295
296
297 void Parser::unskip_spaces(bool skip_comments)
298 {
299         while (pos_ > 0) {
300                 if ( curr_token().cat() == catSpace ||
301                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
302                         putback();
303                 else if (skip_comments && curr_token().cat() == catComment) {
304                         // TODO: Get rid of this
305                         // If positions_ is not empty we are doing some kind
306                         // of look ahead
307                         if (!positions_.empty())
308                                 cerr << "Unignoring comment: "
309                                      << curr_token().asInput();
310                         putback();
311                 }
312                 else
313                         break;
314         }
315 }
316
317
318 void Parser::putback()
319 {
320         --pos_;
321 }
322
323
324 void Parser::pushPosition()
325 {
326         positions_.push_back(pos_);
327 }
328
329
330 void Parser::popPosition()
331 {
332         pos_ = positions_.back();
333         positions_.pop_back();
334 }
335
336
337 bool Parser::good()
338 {
339         if (pos_ < tokens_.size())
340                 return true;
341         tokenize_one();
342         return pos_ < tokens_.size();
343 }
344
345
346 char Parser::getChar()
347 {
348         if (!good())
349                 error("The input stream is not well...");
350         return get_token().character();
351 }
352
353
354 bool Parser::hasOpt()
355 {
356         // An optional argument can occur in any of the following forms:
357         // - \foo[bar]
358         // - \foo [bar]
359         // - \foo
360         //   [bar]
361         // - \foo %comment
362         //   [bar]
363
364         // remember current position
365         unsigned int oldpos = pos_;
366         // skip spaces and comments
367         while (good()) {
368                 get_token();
369                 if (isParagraph()) {
370                         putback();
371                         break;
372                 }
373                 if (curr_token().cat() == catSpace ||
374                     curr_token().cat() == catNewline ||
375                     curr_token().cat() == catComment)
376                         continue;
377                 putback();
378                 break;
379         }
380         bool const retval = (next_token().asInput() == "[");
381         pos_ = oldpos;
382         return retval;
383 }
384
385
386 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
387 {
388         skip_spaces(true);
389
390         // This is needed if a partial file ends with a command without arguments,
391         // e. g. \medskip
392         if (! good())
393                 return make_pair(false, string());
394
395         string result;
396         Token t = get_token();
397
398         if (t.cat() == catComment || t.cat() == catEscape ||
399             t.character() != left) {
400                 putback();
401                 return make_pair(false, string());
402         } else {
403                 for (t = get_token(); good(); t = get_token()) {
404                         // Ignore comments
405                         if (t.cat() == catComment) {
406                                 if (!t.cs().empty())
407                                         cerr << "Ignoring comment: " << t.asInput();
408                                 continue;
409                         }
410                         if (allow_escaping) {
411                                 if (t.cat() != catEscape && t.character() == right)
412                                         break;
413                         } else {
414                                 if (t.character() == right) {
415                                         if (t.cat() == catEscape)
416                                                 result += '\\';
417                                         break;
418                                 }
419                         }
420                         result += t.asInput();
421                 }
422         }
423         return make_pair(true, result);
424 }
425
426
427 string Parser::getArg(char left, char right, bool allow_escaping)
428 {
429         return getFullArg(left, right, allow_escaping).second;
430 }
431
432
433 string Parser::getFullOpt(bool keepws)
434 {
435         Arg arg = getFullArg('[', ']');
436         if (arg.first)
437                 return '[' + arg.second + ']';
438         if (keepws)
439                 unskip_spaces(true);
440         return string();
441 }
442
443
444 string Parser::getOpt(bool keepws)
445 {
446         string const res = getArg('[', ']');
447         if (res.empty()) {
448                 if (keepws)
449                         unskip_spaces(true);
450                 return string();
451         }
452         return '[' + res + ']';
453 }
454
455
456 string Parser::getFullParentheseArg()
457 {
458         Arg arg = getFullArg('(', ')');
459         if (arg.first)
460                 return '(' + arg.second + ')';
461         return string();
462 }
463
464
465 string const Parser::verbatimEnvironment(string const & name)
466 {
467         if (!good())
468                 return string();
469
470         ostringstream os;
471         for (Token t = get_token(); good(); t = get_token()) {
472                 if (t.cat() == catBegin) {
473                         putback();
474                         os << '{' << verbatim_item() << '}';
475                 } else if (t.asInput() == "\\begin") {
476                         string const env = getArg('{', '}');
477                         os << "\\begin{" << env << '}'
478                            << verbatimEnvironment(env)
479                            << "\\end{" << env << '}';
480                 } else if (t.asInput() == "\\end") {
481                         string const end = getArg('{', '}');
482                         if (end != name)
483                                 cerr << "\\end{" << end
484                                      << "} does not match \\begin{" << name
485                                      << "}." << endl;
486                         return os.str();
487                 } else
488                         os << t.asInput();
489         }
490         cerr << "unexpected end of input" << endl;
491         return os.str();
492 }
493
494
495 string const Parser::plainEnvironment(string const & name)
496 {
497         if (!good())
498                 return string();
499
500         ostringstream os;
501         for (Token t = get_token(); good(); t = get_token()) {
502                 if (t.asInput() == "\\end") {
503                         string const end = getArg('{', '}');
504                         if (end == name)
505                                 return os.str();
506                         else
507                                 os << "\\end{" << end << '}';
508                 } else
509                         os << t.asInput();
510         }
511         cerr << "unexpected end of input" << endl;
512         return os.str();
513 }
514
515
516 string const Parser::plainCommand(char left, char right, string const & name)
517 {
518         if (!good())
519                 return string();
520         // check if first token is really the start character
521         Token tok = get_token();
522         if (tok.character() != left) {
523                 cerr << "first character does not match start character of command \\" << name << endl;
524                 return string();
525         }
526         ostringstream os;
527         for (Token t = get_token(); good(); t = get_token()) {
528                 if (t.character() == right) {
529                         return os.str();
530                 } else
531                         os << t.asInput();
532         }
533         cerr << "unexpected end of input" << endl;
534         return os.str();
535 }
536
537
538 void Parser::tokenize_one()
539 {
540         catInit();
541         char_type c;
542         if (!is_.get(c)) 
543                 return;
544
545         switch (catcode(c)) {
546         case catSpace: {
547                 docstring s(1, c);
548                 while (is_.get(c) && catcode(c) == catSpace)
549                         s += c;
550                 if (catcode(c) != catSpace)
551                         is_.putback(c);
552                 push_back(Token(s, catSpace));
553                 break;
554         }
555
556         case catNewline: {
557                 ++lineno_;
558                 docstring s(1, getNewline(is_, c));
559                 while (is_.get(c) && catcode(c) == catNewline) {
560                         ++lineno_;
561                         s += getNewline(is_, c);
562                 }
563                 if (catcode(c) != catNewline)
564                         is_.putback(c);
565                 push_back(Token(s, catNewline));
566                 break;
567         }
568
569         case catComment: {
570                 // We don't treat "%\n" combinations here specially because
571                 // we want to preserve them in the preamble
572                 docstring s;
573                 while (is_.get(c) && catcode(c) != catNewline)
574                         s += c;
575                 // handle possible DOS line ending
576                 if (catcode(c) == catNewline)
577                         c = getNewline(is_, c);
578                 // Note: The '%' at the beginning and the '\n' at the end
579                 // of the comment are not stored.
580                 ++lineno_;
581                 push_back(Token(s, catComment));
582                 break;
583         }
584
585         case catEscape: {
586                 is_.get(c);
587                 if (!is_) {
588                         error("unexpected end of input");
589                 } else {
590                         docstring s(1, c);
591                         if (catcode(c) == catLetter) {
592                                 // collect letters
593                                 while (is_.get(c) && catcode(c) == catLetter)
594                                         s += c;
595                                 if (catcode(c) != catLetter)
596                                         is_.putback(c);
597                         }
598                         push_back(Token(s, catEscape));
599                 }
600                 break;
601         }
602
603         case catIgnore: {
604                 cerr << "ignoring a char: " << c << "\n";
605                 break;
606         }
607
608         default:
609                 push_back(Token(docstring(1, c), catcode(c)));
610         }
611         //cerr << tokens_.back();
612 }
613
614
615 void Parser::dump() const
616 {
617         cerr << "\nTokens: ";
618         for (unsigned i = 0; i < tokens_.size(); ++i) {
619                 if (i == pos_)
620                         cerr << " <#> ";
621                 cerr << tokens_[i];
622         }
623         cerr << " pos: " << pos_ << "\n";
624 }
625
626
627 void Parser::error(string const & msg)
628 {
629         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
630         dump();
631         //exit(1);
632 }
633
634
635 string Parser::verbatimOption()
636 {
637         string res;
638         if (next_token().character() == '[') {
639                 Token t = get_token();
640                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
641                         if (t.cat() == catBegin) {
642                                 putback();
643                                 res += '{' + verbatim_item() + '}';
644                         } else
645                                 res += t.cs();
646                 }
647         }
648         return res;
649 }
650
651
652 string Parser::verbatim_item()
653 {
654         if (!good())
655                 error("stream bad");
656         skip_spaces();
657         if (next_token().cat() == catBegin) {
658                 Token t = get_token(); // skip brace
659                 string res;
660                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
661                         if (t.cat() == catBegin) {
662                                 putback();
663                                 res += '{' + verbatim_item() + '}';
664                         }
665                         else
666                                 res += t.asInput();
667                 }
668                 return res;
669         }
670         return get_token().asInput();
671 }
672
673
674 void Parser::reset()
675 {
676         pos_ = 0;
677 }
678
679
680 void Parser::setCatCode(char c, CatCode cat)
681 {
682         theCatcode[(unsigned char)c] = cat;
683 }
684
685
686 CatCode Parser::getCatCode(char c) const
687 {
688         return theCatcode[(unsigned char)c];
689 }
690
691
692 } // namespace lyx