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