]> git.lyx.org Git - features.git/blob - src/tex2lyx/Parser.cpp
let Parser store the stream it is working on
[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 "Parser.h"
14
15 #include <iostream>
16 #include <sstream>
17
18 using namespace std;
19
20 namespace lyx {
21
22 namespace {
23
24 CatCode theCatcode[256];
25
26 void catInit()
27 {
28         fill(theCatcode, theCatcode + 256, catOther);
29         fill(theCatcode + 'a', theCatcode + 'z' + 1, catLetter);
30         fill(theCatcode + 'A', theCatcode + 'Z' + 1, catLetter);
31
32         theCatcode[int('\\')] = catEscape;
33         theCatcode[int('{')]  = catBegin;
34         theCatcode[int('}')]  = catEnd;
35         theCatcode[int('$')]  = catMath;
36         theCatcode[int('&')]  = catAlign;
37         theCatcode[int('\n')] = catNewline;
38         theCatcode[int('#')]  = catParameter;
39         theCatcode[int('^')]  = catSuper;
40         theCatcode[int('_')]  = catSub;
41         theCatcode[0x7f]      = catIgnore;
42         theCatcode[int(' ')]  = catSpace;
43         theCatcode[int('\t')] = catSpace;
44         theCatcode[int('\r')] = catNewline;
45         theCatcode[int('~')]  = catActive;
46         theCatcode[int('%')]  = catComment;
47
48         // This is wrong!
49         theCatcode[int('@')]  = catLetter;
50 }
51
52
53 /*!
54  * Translate a line ending to '\n'.
55  * \p c must have catcode catNewline, and it must be the last character read
56  * from \p is.
57  */
58 char getNewline(istream & is, char c)
59 {
60         // we have to handle 3 different line endings:
61         // - UNIX (\n)
62         // - MAC  (\r)
63         // - DOS  (\r\n)
64         if (c == '\r') {
65                 // MAC or DOS
66                 if (is.get(c) && c != '\n') {
67                         // MAC
68                         is.putback(c);
69                 }
70                 return '\n';
71         }
72         // UNIX
73         return c;
74 }
75
76 }
77
78
79 //
80 // catcodes
81 //
82
83 CatCode catcode(unsigned char c)
84 {
85         return theCatcode[c];
86 }
87
88
89
90 //
91 // Token
92 //
93
94 ostream & operator<<(ostream & os, Token const & t)
95 {
96         if (t.cat() == catComment)
97                 os << '%' << t.cs() << '\n';
98         else if (t.cat() == catSpace)
99                 os << t.cs();
100         else if (t.cat() == catEscape)
101                 os << '\\' << t.cs() << ' ';
102         else if (t.cat() == catLetter)
103                 os << t.character();
104         else if (t.cat() == catNewline)
105                 os << "[" << t.cs().size() << "\\n," << t.cat() << "]\n";
106         else
107                 os << '[' << t.character() << ',' << t.cat() << ']';
108         return os;
109 }
110
111
112 string Token::asString() const
113 {
114         return cs_.size() ? cs_ : string(1, char_);
115 }
116
117
118 string Token::asInput() const
119 {
120         if (cat_ == catComment)
121                 return '%' + cs_ + '\n';
122         if (cat_ == catSpace || cat_ == catNewline)
123                 return cs_;
124         return char_ ? string(1, char_) : '\\' + cs_;
125 }
126
127
128 //
129 // Parser
130 //
131
132
133 Parser::Parser(istream & is)
134         : lineno_(0), pos_(0), iss_(0), is_(is)
135 {
136         tokenize();
137 }
138
139
140 Parser::Parser(string const & s)
141         : lineno_(0), pos_(0), iss_(new istringstream(s)), is_(*iss_)
142 {
143         tokenize();
144 }
145
146
147 Parser::~Parser()
148 {
149         delete iss_;
150 }
151
152
153 void Parser::push_back(Token const & t)
154 {
155         tokens_.push_back(t);
156 }
157
158
159 Token const & Parser::prev_token() const
160 {
161         static const Token dummy;
162         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
163 }
164
165
166 Token const & Parser::curr_token() const
167 {
168         static const Token dummy;
169         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
170 }
171
172
173 Token const & Parser::next_token() const
174 {
175         static const Token dummy;
176         return good() ? tokens_[pos_] : dummy;
177 }
178
179
180 Token const & Parser::get_token()
181 {
182         static const Token dummy;
183         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
184         return good() ? tokens_[pos_++] : dummy;
185 }
186
187
188 bool Parser::isParagraph() const
189 {
190         // A new paragraph in TeX ist started
191         // - either by a newline, following any amount of whitespace
192         //   characters (including zero), and another newline
193         // - or the token \par
194         if (curr_token().cat() == catNewline &&
195             (curr_token().cs().size() > 1 ||
196              (next_token().cat() == catSpace &&
197               pos_ < tokens_.size() - 1 &&
198               tokens_[pos_ + 1].cat() == catNewline)))
199                 return true;
200         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
201                 return true;
202         return false;
203 }
204
205
206 void Parser::skip_spaces(bool skip_comments)
207 {
208         // We just silently return if we have no more tokens.
209         // skip_spaces() should be callable at any time,
210         // the caller must check p::good() anyway.
211         while (good()) {
212                 get_token();
213                 if (isParagraph()) {
214                         putback();
215                         break;
216                 }
217                 if ( curr_token().cat() == catSpace ||
218                      curr_token().cat() == catNewline ||
219                     (curr_token().cat() == catComment && curr_token().cs().empty()))
220                         continue;
221                 if (skip_comments && curr_token().cat() == catComment)
222                         cerr << "  Ignoring comment: " << curr_token().asInput();
223                 else {
224                         putback();
225                         break;
226                 }
227         }
228 }
229
230
231 void Parser::unskip_spaces(bool skip_comments)
232 {
233         while (pos_ > 0) {
234                 if ( curr_token().cat() == catSpace ||
235                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
236                         putback();
237                 else if (skip_comments && curr_token().cat() == catComment) {
238                         // TODO: Get rid of this
239                         cerr << "Unignoring comment: " << curr_token().asInput();
240                         putback();
241                 }
242                 else
243                         break;
244         }
245 }
246
247
248 void Parser::putback()
249 {
250         --pos_;
251 }
252
253
254 bool Parser::good() const
255 {
256         return pos_ < tokens_.size();
257 }
258
259
260 char Parser::getChar()
261 {
262         if (!good())
263                 error("The input stream is not well...");
264         return tokens_[pos_++].character();
265 }
266
267
268 Parser::Arg Parser::getFullArg(char left, char right)
269 {
270         skip_spaces(true);
271
272         // This is needed if a partial file ends with a command without arguments,
273         // e. g. \medskip
274         if (! good())
275                 return make_pair(false, string());
276
277         string result;
278         char c = getChar();
279
280         if (c != left) {
281                 putback();
282                 return make_pair(false, string());
283         } else
284                 while ((c = getChar()) != right && good()) {
285                         // Ignore comments
286                         if (curr_token().cat() == catComment) {
287                                 if (!curr_token().cs().empty())
288                                         cerr << "Ignoring comment: " << curr_token().asInput();
289                         }
290                         else
291                                 result += curr_token().asInput();
292                 }
293
294         return make_pair(true, result);
295 }
296
297
298 string Parser::getArg(char left, char right)
299 {
300         return getFullArg(left, right).second;
301 }
302
303
304 string Parser::getFullOpt()
305 {
306         Arg arg = getFullArg('[', ']');
307         if (arg.first)
308                 return '[' + arg.second + ']';
309         return string();
310 }
311
312
313 string Parser::getOpt()
314 {
315         string const res = getArg('[', ']');
316         return res.empty() ? string() : '[' + res + ']';
317 }
318
319
320 string Parser::getFullParentheseArg()
321 {
322         Arg arg = getFullArg('(', ')');
323         if (arg.first)
324                 return '(' + arg.second + ')';
325         return string();
326 }
327
328
329 string const Parser::verbatimEnvironment(string const & name)
330 {
331         if (!good())
332                 return string();
333
334         ostringstream os;
335         for (Token t = get_token(); good(); t = get_token()) {
336                 if (t.cat() == catBegin) {
337                         putback();
338                         os << '{' << verbatim_item() << '}';
339                 } else if (t.asInput() == "\\begin") {
340                         string const env = getArg('{', '}');
341                         os << "\\begin{" << env << '}'
342                            << verbatimEnvironment(env)
343                            << "\\end{" << env << '}';
344                 } else if (t.asInput() == "\\end") {
345                         string const end = getArg('{', '}');
346                         if (end != name)
347                                 cerr << "\\end{" << end
348                                      << "} does not match \\begin{" << name
349                                      << "}." << endl;
350                         return os.str();
351                 } else
352                         os << t.asInput();
353         }
354         cerr << "unexpected end of input" << endl;
355         return os.str();
356 }
357
358
359 void Parser::tokenize_one()
360 {
361         char c;
362         if (!is_.get(c)) 
363                 return;
364         //cerr << "reading c: " << c << "\n";
365
366         switch (catcode(c)) {
367         case catSpace: {
368                 string s(1, c);
369                 while (is_.get(c) && catcode(c) == catSpace)
370                         s += c;
371                 if (catcode(c) != catSpace)
372                         is_.putback(c);
373                 push_back(Token(s, catSpace));
374                 break;
375         }
376                 
377         case catNewline: {
378                 ++lineno_;
379                 string s(1, getNewline(is_, c));
380                 while (is_.get(c) && catcode(c) == catNewline) {
381                         ++lineno_;
382                         s += getNewline(is_, c);
383                 }
384                 if (catcode(c) != catNewline)
385                         is_.putback(c);
386                 push_back(Token(s, catNewline));
387                 break;
388         }
389                 
390         case catComment: {
391                 // We don't treat "%\n" combinations here specially because
392                 // we want to preserve them in the preamble
393                 string s;
394                 while (is_.get(c) && catcode(c) != catNewline)
395                         s += c;
396                 // handle possible DOS line ending
397                 if (catcode(c) == catNewline)
398                         c = getNewline(is_, c);
399                 // Note: The '%' at the beginning and the '\n' at the end
400                 // of the comment are not stored.
401                 ++lineno_;
402                 push_back(Token(s, catComment));
403                 break;
404         }
405                 
406         case catEscape: {
407                 is_.get(c);
408                 if (!is_) {
409                         error("unexpected end of input");
410                 } else {
411                         string s(1, c);
412                         if (catcode(c) == catLetter) {
413                                 // collect letters
414                                 while (is_.get(c) && catcode(c) == catLetter)
415                                         s += c;
416                                 if (catcode(c) != catLetter)
417                                         is_.putback(c);
418                         }
419                         push_back(Token(s, catEscape));
420                 }
421                 break;
422         }
423                 
424         case catIgnore: {
425                 cerr << "ignoring a char: " << int(c) << "\n";
426                 break;
427         }
428                 
429         default:
430                 push_back(Token(c, catcode(c)));
431         }
432 }
433
434
435 void Parser::tokenize()
436 {
437         static bool init_done = false;
438
439         if (!init_done) {
440                 catInit();
441                 init_done = true;
442         }
443
444         while (is_) 
445                 tokenize_one();
446 }
447
448
449 void Parser::dump() const
450 {
451         cerr << "\nTokens: ";
452         for (unsigned i = 0; i < tokens_.size(); ++i) {
453                 if (i == pos_)
454                         cerr << " <#> ";
455                 cerr << tokens_[i];
456         }
457         cerr << " pos: " << pos_ << "\n";
458 }
459
460
461 void Parser::error(string const & msg)
462 {
463         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
464         dump();
465         //exit(1);
466 }
467
468
469 string Parser::verbatimOption()
470 {
471         string res;
472         if (next_token().character() == '[') {
473                 Token t = get_token();
474                 for (Token t = get_token(); t.character() != ']' && good(); t = get_token()) {
475                         if (t.cat() == catBegin) {
476                                 putback();
477                                 res += '{' + verbatim_item() + '}';
478                         } else
479                                 res += t.asString();
480                 }
481         }
482         return res;
483 }
484
485
486 string Parser::verbatim_item()
487 {
488         if (!good())
489                 error("stream bad");
490         skip_spaces();
491         if (next_token().cat() == catBegin) {
492                 Token t = get_token(); // skip brace
493                 string res;
494                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
495                         if (t.cat() == catBegin) {
496                                 putback();
497                                 res += '{' + verbatim_item() + '}';
498                         }
499                         else
500                                 res += t.asInput();
501                 }
502                 return res;
503         }
504         return get_token().asInput();
505 }
506
507
508 void Parser::reset()
509 {
510         pos_ = 0;
511 }
512
513
514 void Parser::setCatCode(char c, CatCode cat)
515 {
516         theCatcode[(unsigned char)c] = cat;
517 }
518
519
520 CatCode Parser::getCatCode(char c) const
521 {
522         return theCatcode[(unsigned char)c];
523 }
524
525
526 } // namespace lyx