]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/Parser.cpp
* src/insets/InsetNote.h:
[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 arg.second;
311 }
312
313
314 string Parser::getOpt()
315 {
316         string const res = getArg('[', ']');
317         return res.empty() ? string() : '[' + res + ']';
318 }
319
320
321 string const Parser::verbatimEnvironment(string const & name)
322 {
323         if (!good())
324                 return string();
325
326         ostringstream os;
327         for (Token t = get_token(); good(); t = get_token()) {
328                 if (t.cat() == catBegin) {
329                         putback();
330                         os << '{' << verbatim_item() << '}';
331                 } else if (t.asInput() == "\\begin") {
332                         string const env = getArg('{', '}');
333                         os << "\\begin{" << env << '}'
334                            << verbatimEnvironment(env)
335                            << "\\end{" << env << '}';
336                 } else if (t.asInput() == "\\end") {
337                         string const end = getArg('{', '}');
338                         if (end != name)
339                                 cerr << "\\end{" << end
340                                      << "} does not match \\begin{" << name
341                                      << "}." << endl;
342                         return os.str();
343                 } else
344                         os << t.asInput();
345         }
346         cerr << "unexpected end of input" << endl;
347         return os.str();
348 }
349
350
351 void Parser::tokenize(istream & is)
352 {
353         static bool init_done = false;
354
355         if (!init_done) {
356                 catInit();
357                 init_done = true;
358         }
359
360         char c;
361         while (is.get(c)) {
362                 //cerr << "reading c: " << c << "\n";
363
364                 switch (catcode(c)) {
365                         case catSpace: {
366                                 string s(1, c);
367                                 while (is.get(c) && catcode(c) == catSpace)
368                                         s += c;
369                                 if (catcode(c) != catSpace)
370                                         is.putback(c);
371                                 push_back(Token(s, catSpace));
372                                 break;
373                         }
374
375                         case catNewline: {
376                                 ++lineno_;
377                                 string s(1, getNewline(is, c));
378                                 while (is.get(c) && catcode(c) == catNewline) {
379                                         ++lineno_;
380                                         s += getNewline(is, c);
381                                 }
382                                 if (catcode(c) != catNewline)
383                                         is.putback(c);
384                                 push_back(Token(s, catNewline));
385                                 break;
386                         }
387
388                         case catComment: {
389                                 // We don't treat "%\n" combinations here specially because
390                                 // we want to preserve them in the preamble
391                                 string s;
392                                 while (is.get(c) && catcode(c) != catNewline)
393                                         s += c;
394                                 // handle possible DOS line ending
395                                 if (catcode(c) == catNewline)
396                                         c = getNewline(is, c);
397                                 // Note: The '%' at the beginning and the '\n' at the end
398                                 // of the comment are not stored.
399                                 ++lineno_;
400                                 push_back(Token(s, catComment));
401                                 break;
402                         }
403
404                         case catEscape: {
405                                 is.get(c);
406                                 if (!is) {
407                                         error("unexpected end of input");
408                                 } else {
409                                         string s(1, c);
410                                         if (catcode(c) == catLetter) {
411                                                 // collect letters
412                                                 while (is.get(c) && catcode(c) == catLetter)
413                                                         s += c;
414                                                 if (catcode(c) != catLetter)
415                                                         is.putback(c);
416                                         }
417                                         push_back(Token(s, catEscape));
418                                 }
419                                 break;
420                         }
421
422                         case catIgnore: {
423                                 cerr << "ignoring a char: " << int(c) << "\n";
424                                 break;
425                         }
426
427                         default:
428                                 push_back(Token(c, catcode(c)));
429                 }
430         }
431 }
432
433
434 void Parser::dump() const
435 {
436         cerr << "\nTokens: ";
437         for (unsigned i = 0; i < tokens_.size(); ++i) {
438                 if (i == pos_)
439                         cerr << " <#> ";
440                 cerr << tokens_[i];
441         }
442         cerr << " pos: " << pos_ << "\n";
443 }
444
445
446 void Parser::error(string const & msg)
447 {
448         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
449         dump();
450         //exit(1);
451 }
452
453
454 string Parser::verbatimOption()
455 {
456         string res;
457         if (next_token().character() == '[') {
458                 Token t = get_token();
459                 for (Token t = get_token(); t.character() != ']' && good(); t = get_token()) {
460                         if (t.cat() == catBegin) {
461                                 putback();
462                                 res += '{' + verbatim_item() + '}';
463                         } else
464                                 res += t.asString();
465                 }
466         }
467         return res;
468 }
469
470
471 string Parser::verbatim_item()
472 {
473         if (!good())
474                 error("stream bad");
475         skip_spaces();
476         if (next_token().cat() == catBegin) {
477                 Token t = get_token(); // skip brace
478                 string res;
479                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
480                         if (t.cat() == catBegin) {
481                                 putback();
482                                 res += '{' + verbatim_item() + '}';
483                         }
484                         else
485                                 res += t.asInput();
486                 }
487                 return res;
488         }
489         return get_token().asInput();
490 }
491
492
493 void Parser::reset()
494 {
495         pos_ = 0;
496 }
497
498
499 void Parser::setCatCode(char c, CatCode cat)
500 {
501         theCatcode[(unsigned char)c] = cat;
502 }
503
504
505 CatCode Parser::getCatCode(char c) const
506 {
507         return theCatcode[(unsigned char)c];
508 }
509
510
511 } // namespace lyx