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