]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
hyperref support for tex2lyx
[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::get_token()
225 {
226         static const Token dummy;
227         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
228         return good() ? tokens_[pos_++] : dummy;
229 }
230
231
232 bool Parser::isParagraph()
233 {
234         // A new paragraph in TeX ist started
235         // - either by a newline, following any amount of whitespace
236         //   characters (including zero), and another newline
237         // - or the token \par
238         if (curr_token().cat() == catNewline &&
239             (curr_token().cs().size() > 1 ||
240              (next_token().cat() == catSpace &&
241               pos_ < tokens_.size() - 1 &&
242               tokens_[pos_ + 1].cat() == catNewline)))
243                 return true;
244         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
245                 return true;
246         return false;
247 }
248
249
250 bool Parser::skip_spaces(bool skip_comments)
251 {
252         // We just silently return if we have no more tokens.
253         // skip_spaces() should be callable at any time,
254         // the caller must check p::good() anyway.
255         bool skipped = false;
256         while (good()) {
257                 get_token();
258                 if (isParagraph()) {
259                         putback();
260                         break;
261                 }
262                 if (curr_token().cat() == catSpace ||
263                     curr_token().cat() == catNewline) {
264                         skipped = true;
265                         continue;
266                 }
267                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
268                         continue;
269                 if (skip_comments && curr_token().cat() == catComment)
270                         cerr << "  Ignoring comment: " << curr_token().asInput();
271                 else {
272                         putback();
273                         break;
274                 }
275         }
276         return skipped;
277 }
278
279
280 void Parser::unskip_spaces(bool skip_comments)
281 {
282         while (pos_ > 0) {
283                 if ( curr_token().cat() == catSpace ||
284                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
285                         putback();
286                 else if (skip_comments && curr_token().cat() == catComment) {
287                         // TODO: Get rid of this
288                         cerr << "Unignoring comment: " << curr_token().asInput();
289                         putback();
290                 }
291                 else
292                         break;
293         }
294 }
295
296
297 void Parser::putback()
298 {
299         --pos_;
300 }
301
302
303 void Parser::pushPosition()
304 {
305         positions_.push_back(pos_);
306 }
307
308
309 void Parser::popPosition()
310 {
311         pos_ = positions_.back();
312         positions_.pop_back();
313 }
314
315
316 bool Parser::good()
317 {
318         if (pos_ < tokens_.size())
319                 return true;
320         tokenize_one();
321         return pos_ < tokens_.size();
322 }
323
324
325 char Parser::getChar()
326 {
327         if (!good())
328                 error("The input stream is not well...");
329         return get_token().character();
330 }
331
332
333 bool Parser::hasOpt()
334 {
335         // An optional argument can occur in any of the following forms:
336         // - \foo[bar]
337         // - \foo [bar]
338         // - \foo
339         //   [bar]
340         // - \foo %comment
341         //   [bar]
342
343         // remember current position
344         unsigned int oldpos = pos_;
345         // skip spaces and comments
346         while (good()) {
347                 get_token();
348                 if (isParagraph()) {
349                         putback();
350                         break;
351                 }
352                 if (curr_token().cat() == catSpace ||
353                     curr_token().cat() == catNewline ||
354                     curr_token().cat() == catComment)
355                         continue;
356                 putback();
357                 break;
358         }
359         bool const retval = (next_token().asInput() == "[");
360         pos_ = oldpos;
361         return retval;
362 }
363
364
365 Parser::Arg Parser::getFullArg(char left, char right)
366 {
367         skip_spaces(true);
368
369         // This is needed if a partial file ends with a command without arguments,
370         // e. g. \medskip
371         if (! good())
372                 return make_pair(false, string());
373
374         string result;
375         char c = getChar();
376
377         if (c != left) {
378                 putback();
379                 return make_pair(false, string());
380         } else
381                 while ((c = getChar()) != right && good()) {
382                         // Ignore comments
383                         if (curr_token().cat() == catComment) {
384                                 if (!curr_token().cs().empty())
385                                         cerr << "Ignoring comment: " << curr_token().asInput();
386                         }
387                         else
388                                 result += curr_token().asInput();
389                 }
390
391         return make_pair(true, result);
392 }
393
394
395 string Parser::getArg(char left, char right)
396 {
397         return getFullArg(left, right).second;
398 }
399
400
401 string Parser::getFullOpt()
402 {
403         Arg arg = getFullArg('[', ']');
404         if (arg.first)
405                 return '[' + arg.second + ']';
406         return string();
407 }
408
409
410 string Parser::getOpt(bool keepws)
411 {
412         string const res = getArg('[', ']');
413         if (res.empty()) {
414                 if (keepws)
415                         unskip_spaces(true);
416                 return string();
417         }
418         return '[' + res + ']';
419 }
420
421
422 string Parser::getOptContent()
423 // the same as getOpt but without the brackets
424 {
425         string const res = getArg('[', ']');
426         return res.empty() ? string() : res;
427 }
428
429
430 string Parser::getFullParentheseArg()
431 {
432         Arg arg = getFullArg('(', ')');
433         if (arg.first)
434                 return '(' + arg.second + ')';
435         return string();
436 }
437
438
439 string const Parser::verbatimEnvironment(string const & name)
440 {
441         if (!good())
442                 return string();
443
444         ostringstream os;
445         for (Token t = get_token(); good(); t = get_token()) {
446                 if (t.cat() == catBegin) {
447                         putback();
448                         os << '{' << verbatim_item() << '}';
449                 } else if (t.asInput() == "\\begin") {
450                         string const env = getArg('{', '}');
451                         os << "\\begin{" << env << '}'
452                            << verbatimEnvironment(env)
453                            << "\\end{" << env << '}';
454                 } else if (t.asInput() == "\\end") {
455                         string const end = getArg('{', '}');
456                         if (end != name)
457                                 cerr << "\\end{" << end
458                                      << "} does not match \\begin{" << name
459                                      << "}." << endl;
460                         return os.str();
461                 } else
462                         os << t.asInput();
463         }
464         cerr << "unexpected end of input" << endl;
465         return os.str();
466 }
467
468
469 void Parser::tokenize_one()
470 {
471         catInit();
472         char_type c;
473         if (!is_.get(c)) 
474                 return;
475
476         switch (catcode(c)) {
477         case catSpace: {
478                 docstring s(1, c);
479                 while (is_.get(c) && catcode(c) == catSpace)
480                         s += c;
481                 if (catcode(c) != catSpace)
482                         is_.putback(c);
483                 push_back(Token(s, catSpace));
484                 break;
485         }
486                 
487         case catNewline: {
488                 ++lineno_;
489                 docstring s(1, getNewline(is_, c));
490                 while (is_.get(c) && catcode(c) == catNewline) {
491                         ++lineno_;
492                         s += getNewline(is_, c);
493                 }
494                 if (catcode(c) != catNewline)
495                         is_.putback(c);
496                 push_back(Token(s, catNewline));
497                 break;
498         }
499                 
500         case catComment: {
501                 // We don't treat "%\n" combinations here specially because
502                 // we want to preserve them in the preamble
503                 docstring s;
504                 while (is_.get(c) && catcode(c) != catNewline)
505                         s += c;
506                 // handle possible DOS line ending
507                 if (catcode(c) == catNewline)
508                         c = getNewline(is_, c);
509                 // Note: The '%' at the beginning and the '\n' at the end
510                 // of the comment are not stored.
511                 ++lineno_;
512                 push_back(Token(s, catComment));
513                 break;
514         }
515                 
516         case catEscape: {
517                 is_.get(c);
518                 if (!is_) {
519                         error("unexpected end of input");
520                 } else {
521                         docstring s(1, c);
522                         if (catcode(c) == catLetter) {
523                                 // collect letters
524                                 while (is_.get(c) && catcode(c) == catLetter)
525                                         s += c;
526                                 if (catcode(c) != catLetter)
527                                         is_.putback(c);
528                         }
529                         push_back(Token(s, catEscape));
530                 }
531                 break;
532         }
533                 
534         case catIgnore: {
535                 cerr << "ignoring a char: " << c << "\n";
536                 break;
537         }
538                 
539         default:
540                 push_back(Token(docstring(1, c), catcode(c)));
541         }
542         //cerr << tokens_.back();
543 }
544
545
546 void Parser::dump() const
547 {
548         cerr << "\nTokens: ";
549         for (unsigned i = 0; i < tokens_.size(); ++i) {
550                 if (i == pos_)
551                         cerr << " <#> ";
552                 cerr << tokens_[i];
553         }
554         cerr << " pos: " << pos_ << "\n";
555 }
556
557
558 void Parser::error(string const & msg)
559 {
560         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
561         dump();
562         //exit(1);
563 }
564
565
566 string Parser::verbatimOption()
567 {
568         string res;
569         if (next_token().character() == '[') {
570                 Token t = get_token();
571                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
572                         if (t.cat() == catBegin) {
573                                 putback();
574                                 res += '{' + verbatim_item() + '}';
575                         } else
576                                 res += t.cs();
577                 }
578         }
579         return res;
580 }
581
582
583 string Parser::verbatim_item()
584 {
585         if (!good())
586                 error("stream bad");
587         skip_spaces();
588         if (next_token().cat() == catBegin) {
589                 Token t = get_token(); // skip brace
590                 string res;
591                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
592                         if (t.cat() == catBegin) {
593                                 putback();
594                                 res += '{' + verbatim_item() + '}';
595                         }
596                         else
597                                 res += t.asInput();
598                 }
599                 return res;
600         }
601         return get_token().asInput();
602 }
603
604
605 void Parser::reset()
606 {
607         pos_ = 0;
608 }
609
610
611 void Parser::setCatCode(char c, CatCode cat)
612 {
613         theCatcode[(unsigned char)c] = cat;
614 }
615
616
617 CatCode Parser::getCatCode(char c) const
618 {
619         return theCatcode[(unsigned char)c];
620 }
621
622
623 } // namespace lyx