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