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