]> git.lyx.org Git - features.git/blob - src/tex2lyx/Parser.cpp
Improve tex2lyx roundtrip of test-insets.tex:
[features.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 //
132 // Parser
133 //
134
135
136 Parser::Parser(idocstream & is)
137         : lineno_(0), pos_(0), iss_(0), is_(is), encoding_latex_("utf8")
138 {
139 }
140
141
142 Parser::Parser(string const & s)
143         : lineno_(0), pos_(0), 
144           iss_(new idocstringstream(from_utf8(s))), is_(*iss_), 
145           encoding_latex_("utf8")
146 {
147 }
148
149
150 Parser::~Parser()
151 {
152         delete iss_;
153 }
154
155
156 void Parser::setEncoding(std::string const & e)
157 {
158         Encoding const * enc = encodings.fromLaTeXName(e);
159         if (!enc) {
160                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
161                 return;
162         }
163         //cerr << "setting encoding to " << enc->iconvName() << std::endl;
164         is_ << lyx::setEncoding(enc->iconvName());
165         encoding_latex_ = e;
166 }
167
168
169 void Parser::push_back(Token const & t)
170 {
171         tokens_.push_back(t);
172 }
173
174
175 // We return a copy here because the tokens_ vector may get reallocated
176 Token const Parser::prev_token() const
177 {
178         static const Token dummy;
179         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
180 }
181
182
183 // We return a copy here because the tokens_ vector may get reallocated
184 Token const Parser::curr_token() const
185 {
186         static const Token dummy;
187         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
188 }
189
190
191 // We return a copy here because the tokens_ vector may get reallocated
192 Token const Parser::next_token()
193 {
194         static const Token dummy;
195         return good() ? tokens_[pos_] : dummy;
196 }
197
198
199 // We return a copy here because the tokens_ vector may get reallocated
200 Token const Parser::get_token()
201 {
202         static const Token dummy;
203         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
204         return good() ? tokens_[pos_++] : dummy;
205 }
206
207
208 bool Parser::isParagraph()
209 {
210         // A new paragraph in TeX ist started
211         // - either by a newline, following any amount of whitespace
212         //   characters (including zero), and another newline
213         // - or the token \par
214         if (curr_token().cat() == catNewline &&
215             (curr_token().cs().size() > 1 ||
216              (next_token().cat() == catSpace &&
217               pos_ < tokens_.size() - 1 &&
218               tokens_[pos_ + 1].cat() == catNewline)))
219                 return true;
220         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
221                 return true;
222         return false;
223 }
224
225
226 bool Parser::skip_spaces(bool skip_comments)
227 {
228         // We just silently return if we have no more tokens.
229         // skip_spaces() should be callable at any time,
230         // the caller must check p::good() anyway.
231         bool skipped = false;
232         while (good()) {
233                 get_token();
234                 if (isParagraph()) {
235                         putback();
236                         break;
237                 }
238                 if (curr_token().cat() == catSpace ||
239                     curr_token().cat() == catNewline) {
240                         skipped = true;
241                         continue;
242                 }
243                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
244                         continue;
245                 if (skip_comments && curr_token().cat() == catComment)
246                         cerr << "  Ignoring comment: " << curr_token().asInput();
247                 else {
248                         putback();
249                         break;
250                 }
251         }
252         return skipped;
253 }
254
255
256 void Parser::unskip_spaces(bool skip_comments)
257 {
258         while (pos_ > 0) {
259                 if ( curr_token().cat() == catSpace ||
260                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
261                         putback();
262                 else if (skip_comments && curr_token().cat() == catComment) {
263                         // TODO: Get rid of this
264                         cerr << "Unignoring comment: " << curr_token().asInput();
265                         putback();
266                 }
267                 else
268                         break;
269         }
270 }
271
272
273 void Parser::putback()
274 {
275         --pos_;
276 }
277
278
279 bool Parser::good()
280 {
281         if (pos_ < tokens_.size())
282                 return true;
283         tokenize_one();
284         return pos_ < tokens_.size();
285 }
286
287
288 char Parser::getChar()
289 {
290         if (!good())
291                 error("The input stream is not well...");
292         return get_token().character();
293 }
294
295
296 bool Parser::hasOpt()
297 {
298         // An optional argument can occur in any of the following forms:
299         // - \foo[bar]
300         // - \foo [bar]
301         // - \foo
302         //   [bar]
303         // - \foo %comment
304         //   [bar]
305
306         // remember current position
307         unsigned int oldpos = pos_;
308         // skip spaces and comments
309         while (good()) {
310                 get_token();
311                 if (isParagraph()) {
312                         putback();
313                         break;
314                 }
315                 if (curr_token().cat() == catSpace ||
316                     curr_token().cat() == catNewline ||
317                     curr_token().cat() == catComment)
318                         continue;
319                 putback();
320                 break;
321         }
322         bool const retval = (next_token().asInput() == "[");
323         pos_ = oldpos;
324         return retval;
325 }
326
327
328 Parser::Arg Parser::getFullArg(char left, char right)
329 {
330         skip_spaces(true);
331
332         // This is needed if a partial file ends with a command without arguments,
333         // e. g. \medskip
334         if (! good())
335                 return make_pair(false, string());
336
337         string result;
338         char c = getChar();
339
340         if (c != left) {
341                 putback();
342                 return make_pair(false, string());
343         } else
344                 while ((c = getChar()) != right && good()) {
345                         // Ignore comments
346                         if (curr_token().cat() == catComment) {
347                                 if (!curr_token().cs().empty())
348                                         cerr << "Ignoring comment: " << curr_token().asInput();
349                         }
350                         else
351                                 result += curr_token().asInput();
352                 }
353
354         return make_pair(true, result);
355 }
356
357
358 string Parser::getArg(char left, char right)
359 {
360         return getFullArg(left, right).second;
361 }
362
363
364 string Parser::getFullOpt()
365 {
366         Arg arg = getFullArg('[', ']');
367         if (arg.first)
368                 return '[' + arg.second + ']';
369         return string();
370 }
371
372
373 string Parser::getOpt(bool keepws)
374 {
375         string const res = getArg('[', ']');
376         if (res.empty()) {
377                 if (keepws)
378                         unskip_spaces(true);
379                 return string();
380         }
381         return '[' + res + ']';
382 }
383
384
385 string Parser::getOptContent()
386 // the same as getOpt but without the brackets
387 {
388         string const res = getArg('[', ']');
389         return res.empty() ? string() : res;
390 }
391
392
393 string Parser::getFullParentheseArg()
394 {
395         Arg arg = getFullArg('(', ')');
396         if (arg.first)
397                 return '(' + arg.second + ')';
398         return string();
399 }
400
401
402 string const Parser::verbatimEnvironment(string const & name)
403 {
404         if (!good())
405                 return string();
406
407         ostringstream os;
408         for (Token t = get_token(); good(); t = get_token()) {
409                 if (t.cat() == catBegin) {
410                         putback();
411                         os << '{' << verbatim_item() << '}';
412                 } else if (t.asInput() == "\\begin") {
413                         string const env = getArg('{', '}');
414                         os << "\\begin{" << env << '}'
415                            << verbatimEnvironment(env)
416                            << "\\end{" << env << '}';
417                 } else if (t.asInput() == "\\end") {
418                         string const end = getArg('{', '}');
419                         if (end != name)
420                                 cerr << "\\end{" << end
421                                      << "} does not match \\begin{" << name
422                                      << "}." << endl;
423                         return os.str();
424                 } else
425                         os << t.asInput();
426         }
427         cerr << "unexpected end of input" << endl;
428         return os.str();
429 }
430
431
432 void Parser::tokenize_one()
433 {
434         catInit();
435         char_type c;
436         if (!is_.get(c)) 
437                 return;
438
439         switch (catcode(c)) {
440         case catSpace: {
441                 docstring s(1, c);
442                 while (is_.get(c) && catcode(c) == catSpace)
443                         s += c;
444                 if (catcode(c) != catSpace)
445                         is_.putback(c);
446                 push_back(Token(s, catSpace));
447                 break;
448         }
449                 
450         case catNewline: {
451                 ++lineno_;
452                 docstring s(1, getNewline(is_, c));
453                 while (is_.get(c) && catcode(c) == catNewline) {
454                         ++lineno_;
455                         s += getNewline(is_, c);
456                 }
457                 if (catcode(c) != catNewline)
458                         is_.putback(c);
459                 push_back(Token(s, catNewline));
460                 break;
461         }
462                 
463         case catComment: {
464                 // We don't treat "%\n" combinations here specially because
465                 // we want to preserve them in the preamble
466                 docstring s;
467                 while (is_.get(c) && catcode(c) != catNewline)
468                         s += c;
469                 // handle possible DOS line ending
470                 if (catcode(c) == catNewline)
471                         c = getNewline(is_, c);
472                 // Note: The '%' at the beginning and the '\n' at the end
473                 // of the comment are not stored.
474                 ++lineno_;
475                 push_back(Token(s, catComment));
476                 break;
477         }
478                 
479         case catEscape: {
480                 is_.get(c);
481                 if (!is_) {
482                         error("unexpected end of input");
483                 } else {
484                         docstring s(1, c);
485                         if (catcode(c) == catLetter) {
486                                 // collect letters
487                                 while (is_.get(c) && catcode(c) == catLetter)
488                                         s += c;
489                                 if (catcode(c) != catLetter)
490                                         is_.putback(c);
491                         }
492                         push_back(Token(s, catEscape));
493                 }
494                 break;
495         }
496                 
497         case catIgnore: {
498                 cerr << "ignoring a char: " << c << "\n";
499                 break;
500         }
501                 
502         default:
503                 push_back(Token(docstring(1, c), catcode(c)));
504         }
505         //cerr << tokens_.back();
506 }
507
508
509 void Parser::dump() const
510 {
511         cerr << "\nTokens: ";
512         for (unsigned i = 0; i < tokens_.size(); ++i) {
513                 if (i == pos_)
514                         cerr << " <#> ";
515                 cerr << tokens_[i];
516         }
517         cerr << " pos: " << pos_ << "\n";
518 }
519
520
521 void Parser::error(string const & msg)
522 {
523         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
524         dump();
525         //exit(1);
526 }
527
528
529 string Parser::verbatimOption()
530 {
531         string res;
532         if (next_token().character() == '[') {
533                 Token t = get_token();
534                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
535                         if (t.cat() == catBegin) {
536                                 putback();
537                                 res += '{' + verbatim_item() + '}';
538                         } else
539                                 res += t.cs();
540                 }
541         }
542         return res;
543 }
544
545
546 string Parser::verbatim_item()
547 {
548         if (!good())
549                 error("stream bad");
550         skip_spaces();
551         if (next_token().cat() == catBegin) {
552                 Token t = get_token(); // skip brace
553                 string res;
554                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
555                         if (t.cat() == catBegin) {
556                                 putback();
557                                 res += '{' + verbatim_item() + '}';
558                         }
559                         else
560                                 res += t.asInput();
561                 }
562                 return res;
563         }
564         return get_token().asInput();
565 }
566
567
568 void Parser::reset()
569 {
570         pos_ = 0;
571 }
572
573
574 void Parser::setCatCode(char c, CatCode cat)
575 {
576         theCatcode[(unsigned char)c] = cat;
577 }
578
579
580 CatCode Parser::getCatCode(char c) const
581 {
582         return theCatcode[(unsigned char)c];
583 }
584
585
586 } // namespace lyx