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