]> git.lyx.org Git - features.git/blob - src/tex2lyx/Parser.cpp
Fix import of shift-jis and big5 CJK texts.
[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 #ifdef FILEDEBUG
132 void debugToken(std::ostream & os, Token const & t, unsigned int flags)
133 {
134         char sep = ' ';
135         os << "t: " << t << " flags: " << flags;
136         if (flags & FLAG_BRACE_LAST) { os << sep << "BRACE_LAST"; sep = '|'; }
137         if (flags & FLAG_RIGHT     ) { os << sep << "RIGHT"     ; sep = '|'; }
138         if (flags & FLAG_END       ) { os << sep << "END"       ; sep = '|'; }
139         if (flags & FLAG_BRACK_LAST) { os << sep << "BRACK_LAST"; sep = '|'; }
140         if (flags & FLAG_TEXTMODE  ) { os << sep << "TEXTMODE"  ; sep = '|'; }
141         if (flags & FLAG_ITEM      ) { os << sep << "ITEM"      ; sep = '|'; }
142         if (flags & FLAG_LEAVE     ) { os << sep << "LEAVE"     ; sep = '|'; }
143         if (flags & FLAG_SIMPLE    ) { os << sep << "SIMPLE"    ; sep = '|'; }
144         if (flags & FLAG_EQUATION  ) { os << sep << "EQUATION"  ; sep = '|'; }
145         if (flags & FLAG_SIMPLE2   ) { os << sep << "SIMPLE2"   ; sep = '|'; }
146         if (flags & FLAG_OPTION    ) { os << sep << "OPTION"    ; sep = '|'; }
147         if (flags & FLAG_BRACED    ) { os << sep << "BRACED"    ; sep = '|'; }
148         if (flags & FLAG_CELL      ) { os << sep << "CELL"      ; sep = '|'; }
149         if (flags & FLAG_TABBING   ) { os << sep << "TABBING"   ; sep = '|'; }
150         os << "\n";
151 }
152 #endif
153
154
155 //
156 // Parser
157 //
158
159
160 Parser::Parser(idocstream & is)
161         : lineno_(0), pos_(0), iss_(0), is_(is), encoding_latex_("utf8")
162 {
163 }
164
165
166 Parser::Parser(string const & s)
167         : lineno_(0), pos_(0),
168           iss_(new idocstringstream(from_utf8(s))), is_(*iss_),
169           encoding_latex_("utf8")
170 {
171 }
172
173
174 Parser::~Parser()
175 {
176         delete iss_;
177 }
178
179
180 void Parser::setEncoding(std::string const & e)
181 {
182         // We may (and need to) use unsafe encodings here: Since the text is
183         // converted to unicode while reading from is_, we never see text in
184         // the original encoding of the parser, but operate on utf8 strings
185         // instead. Therefore, we cannot misparse high bytes as {, } or \\.
186         Encoding const * enc = encodings.fromLaTeXName(e, true);
187         if (!enc) {
188                 cerr << "Unknown encoding " << e << ". Ignoring." << std::endl;
189                 return;
190         }
191         //cerr << "setting encoding to " << enc->iconvName() << std::endl;
192         is_ << lyx::setEncoding(enc->iconvName());
193         encoding_latex_ = e;
194 }
195
196
197 void Parser::push_back(Token const & t)
198 {
199         tokens_.push_back(t);
200 }
201
202
203 // We return a copy here because the tokens_ vector may get reallocated
204 Token const Parser::prev_token() const
205 {
206         static const Token dummy;
207         return pos_ > 1 ? tokens_[pos_ - 2] : dummy;
208 }
209
210
211 // We return a copy here because the tokens_ vector may get reallocated
212 Token const Parser::curr_token() const
213 {
214         static const Token dummy;
215         return pos_ > 0 ? tokens_[pos_ - 1] : dummy;
216 }
217
218
219 // We return a copy here because the tokens_ vector may get reallocated
220 Token const Parser::next_token()
221 {
222         static const Token dummy;
223         return good() ? tokens_[pos_] : dummy;
224 }
225
226
227 // We return a copy here because the tokens_ vector may get reallocated
228 Token const Parser::next_next_token()
229 {
230         static const Token dummy;
231         // If good() has not been called after the last get_token() we need
232         // to tokenize two more tokens.
233         if (pos_ + 1 >= tokens_.size()) {
234                 tokenize_one();
235                 tokenize_one();
236         }
237         return pos_ + 1 < tokens_.size() ? tokens_[pos_ + 1] : dummy;
238 }
239
240
241 // We return a copy here because the tokens_ vector may get reallocated
242 Token const Parser::get_token()
243 {
244         static const Token dummy;
245         //cerr << "looking at token " << tokens_[pos_] << " pos: " << pos_ << '\n';
246         return good() ? tokens_[pos_++] : dummy;
247 }
248
249
250 bool Parser::isParagraph()
251 {
252         // A new paragraph in TeX ist started
253         // - either by a newline, following any amount of whitespace
254         //   characters (including zero), and another newline
255         // - or the token \par
256         if (curr_token().cat() == catNewline &&
257             (curr_token().cs().size() > 1 ||
258              (next_token().cat() == catSpace &&
259               next_next_token().cat() == catNewline)))
260                 return true;
261         if (curr_token().cat() == catEscape && curr_token().cs() == "par")
262                 return true;
263         return false;
264 }
265
266
267 bool Parser::skip_spaces(bool skip_comments)
268 {
269         // We just silently return if we have no more tokens.
270         // skip_spaces() should be callable at any time,
271         // the caller must check p::good() anyway.
272         bool skipped = false;
273         while (good()) {
274                 get_token();
275                 if (isParagraph()) {
276                         putback();
277                         break;
278                 }
279                 if (curr_token().cat() == catSpace ||
280                     curr_token().cat() == catNewline) {
281                         skipped = true;
282                         continue;
283                 }
284                 if ((curr_token().cat() == catComment && curr_token().cs().empty()))
285                         continue;
286                 if (skip_comments && curr_token().cat() == catComment) {
287                         // If positions_ is not empty we are doing some kind
288                         // of look ahead
289                         if (!positions_.empty())
290                                 cerr << "  Ignoring comment: "
291                                      << curr_token().asInput();
292                 } else {
293                         putback();
294                         break;
295                 }
296         }
297         return skipped;
298 }
299
300
301 void Parser::unskip_spaces(bool skip_comments)
302 {
303         while (pos_ > 0) {
304                 if ( curr_token().cat() == catSpace ||
305                     (curr_token().cat() == catNewline && curr_token().cs().size() == 1))
306                         putback();
307                 else if (skip_comments && curr_token().cat() == catComment) {
308                         // TODO: Get rid of this
309                         // If positions_ is not empty we are doing some kind
310                         // of look ahead
311                         if (!positions_.empty())
312                                 cerr << "Unignoring comment: "
313                                      << curr_token().asInput();
314                         putback();
315                 }
316                 else
317                         break;
318         }
319 }
320
321
322 void Parser::putback()
323 {
324         --pos_;
325 }
326
327
328 void Parser::pushPosition()
329 {
330         positions_.push_back(pos_);
331 }
332
333
334 void Parser::popPosition()
335 {
336         pos_ = positions_.back();
337         positions_.pop_back();
338 }
339
340
341 bool Parser::good()
342 {
343         if (pos_ < tokens_.size())
344                 return true;
345         tokenize_one();
346         return pos_ < tokens_.size();
347 }
348
349
350 char Parser::getChar()
351 {
352         if (!good())
353                 error("The input stream is not well...");
354         return get_token().character();
355 }
356
357
358 bool Parser::hasOpt()
359 {
360         // An optional argument can occur in any of the following forms:
361         // - \foo[bar]
362         // - \foo [bar]
363         // - \foo
364         //   [bar]
365         // - \foo %comment
366         //   [bar]
367
368         // remember current position
369         unsigned int oldpos = pos_;
370         // skip spaces and comments
371         while (good()) {
372                 get_token();
373                 if (isParagraph()) {
374                         putback();
375                         break;
376                 }
377                 if (curr_token().cat() == catSpace ||
378                     curr_token().cat() == catNewline ||
379                     curr_token().cat() == catComment)
380                         continue;
381                 putback();
382                 break;
383         }
384         bool const retval = (next_token().asInput() == "[");
385         pos_ = oldpos;
386         return retval;
387 }
388
389
390 Parser::Arg Parser::getFullArg(char left, char right, bool allow_escaping)
391 {
392         skip_spaces(true);
393
394         // This is needed if a partial file ends with a command without arguments,
395         // e. g. \medskip
396         if (! good())
397                 return make_pair(false, string());
398
399         string result;
400         Token t = get_token();
401
402         if (t.cat() == catComment || t.cat() == catEscape ||
403             t.character() != left) {
404                 putback();
405                 return make_pair(false, string());
406         } else {
407                 for (t = get_token(); good(); t = get_token()) {
408                         // Ignore comments
409                         if (t.cat() == catComment) {
410                                 if (!t.cs().empty())
411                                         cerr << "Ignoring comment: " << t.asInput();
412                                 continue;
413                         }
414                         if (allow_escaping) {
415                                 if (t.cat() != catEscape && t.character() == right)
416                                         break;
417                         } else {
418                                 if (t.character() == right) {
419                                         if (t.cat() == catEscape)
420                                                 result += '\\';
421                                         break;
422                                 }
423                         }
424                         result += t.asInput();
425                 }
426         }
427         return make_pair(true, result);
428 }
429
430
431 string Parser::getArg(char left, char right, bool allow_escaping)
432 {
433         return getFullArg(left, right, allow_escaping).second;
434 }
435
436
437 string Parser::getFullOpt(bool keepws)
438 {
439         Arg arg = getFullArg('[', ']');
440         if (arg.first)
441                 return '[' + arg.second + ']';
442         if (keepws)
443                 unskip_spaces(true);
444         return string();
445 }
446
447
448 string Parser::getOpt(bool keepws)
449 {
450         string const res = getArg('[', ']');
451         if (res.empty()) {
452                 if (keepws)
453                         unskip_spaces(true);
454                 return string();
455         }
456         return '[' + res + ']';
457 }
458
459
460 string Parser::getFullParentheseArg()
461 {
462         Arg arg = getFullArg('(', ')');
463         if (arg.first)
464                 return '(' + arg.second + ')';
465         return string();
466 }
467
468
469 string const Parser::verbatimEnvironment(string const & name)
470 {
471         if (!good())
472                 return string();
473
474         ostringstream os;
475         for (Token t = get_token(); good(); t = get_token()) {
476                 if (t.cat() == catBegin) {
477                         putback();
478                         os << '{' << verbatim_item() << '}';
479                 } else if (t.asInput() == "\\begin") {
480                         string const env = getArg('{', '}');
481                         os << "\\begin{" << env << '}'
482                            << verbatimEnvironment(env)
483                            << "\\end{" << env << '}';
484                 } else if (t.asInput() == "\\end") {
485                         string const end = getArg('{', '}');
486                         if (end != name)
487                                 cerr << "\\end{" << end
488                                      << "} does not match \\begin{" << name
489                                      << "}." << endl;
490                         return os.str();
491                 } else
492                         os << t.asInput();
493         }
494         cerr << "unexpected end of input" << endl;
495         return os.str();
496 }
497
498
499 string const Parser::plainEnvironment(string const & name)
500 {
501         if (!good())
502                 return string();
503
504         ostringstream os;
505         for (Token t = get_token(); good(); t = get_token()) {
506                 if (t.asInput() == "\\end") {
507                         string const end = getArg('{', '}');
508                         if (end == name)
509                                 return os.str();
510                         else
511                                 os << "\\end{" << end << '}';
512                 } else
513                         os << t.asInput();
514         }
515         cerr << "unexpected end of input" << endl;
516         return os.str();
517 }
518
519
520 string const Parser::plainCommand(char left, char right, string const & name)
521 {
522         if (!good())
523                 return string();
524         // check if first token is really the start character
525         Token tok = get_token();
526         if (tok.character() != left) {
527                 cerr << "first character does not match start character of command \\" << name << endl;
528                 return string();
529         }
530         ostringstream os;
531         for (Token t = get_token(); good(); t = get_token()) {
532                 if (t.character() == right) {
533                         return os.str();
534                 } else
535                         os << t.asInput();
536         }
537         cerr << "unexpected end of input" << endl;
538         return os.str();
539 }
540
541
542 void Parser::tokenize_one()
543 {
544         catInit();
545         char_type c;
546         if (!is_.get(c))
547                 return;
548
549         switch (catcode(c)) {
550         case catSpace: {
551                 docstring s(1, c);
552                 while (is_.get(c) && catcode(c) == catSpace)
553                         s += c;
554                 if (catcode(c) != catSpace)
555                         is_.putback(c);
556                 push_back(Token(s, catSpace));
557                 break;
558         }
559
560         case catNewline: {
561                 ++lineno_;
562                 docstring s(1, getNewline(is_, c));
563                 while (is_.get(c) && catcode(c) == catNewline) {
564                         ++lineno_;
565                         s += getNewline(is_, c);
566                 }
567                 if (catcode(c) != catNewline)
568                         is_.putback(c);
569                 push_back(Token(s, catNewline));
570                 break;
571         }
572
573         case catComment: {
574                 // We don't treat "%\n" combinations here specially because
575                 // we want to preserve them in the preamble
576                 docstring s;
577                 while (is_.get(c) && catcode(c) != catNewline)
578                         s += c;
579                 // handle possible DOS line ending
580                 if (catcode(c) == catNewline)
581                         c = getNewline(is_, c);
582                 // Note: The '%' at the beginning and the '\n' at the end
583                 // of the comment are not stored.
584                 ++lineno_;
585                 push_back(Token(s, catComment));
586                 break;
587         }
588
589         case catEscape: {
590                 is_.get(c);
591                 if (!is_) {
592                         error("unexpected end of input");
593                 } else {
594                         docstring s(1, c);
595                         if (catcode(c) == catLetter) {
596                                 // collect letters
597                                 while (is_.get(c) && catcode(c) == catLetter)
598                                         s += c;
599                                 if (catcode(c) != catLetter)
600                                         is_.putback(c);
601                         }
602                         push_back(Token(s, catEscape));
603                 }
604                 break;
605         }
606
607         case catIgnore: {
608                 cerr << "ignoring a char: " << c << "\n";
609                 break;
610         }
611
612         default:
613                 push_back(Token(docstring(1, c), catcode(c)));
614         }
615         //cerr << tokens_.back();
616 }
617
618
619 void Parser::dump() const
620 {
621         cerr << "\nTokens: ";
622         for (unsigned i = 0; i < tokens_.size(); ++i) {
623                 if (i == pos_)
624                         cerr << " <#> ";
625                 cerr << tokens_[i];
626         }
627         cerr << " pos: " << pos_ << "\n";
628 }
629
630
631 void Parser::error(string const & msg)
632 {
633         cerr << "Line ~" << lineno_ << ":  parse error: " << msg << endl;
634         dump();
635         //exit(1);
636 }
637
638
639 string Parser::verbatimOption()
640 {
641         string res;
642         if (next_token().character() == '[') {
643                 Token t = get_token();
644                 for (t = get_token(); t.character() != ']' && good(); t = get_token()) {
645                         if (t.cat() == catBegin) {
646                                 putback();
647                                 res += '{' + verbatim_item() + '}';
648                         } else
649                                 res += t.cs();
650                 }
651         }
652         return res;
653 }
654
655
656 string Parser::verbatim_item()
657 {
658         if (!good())
659                 error("stream bad");
660         skip_spaces();
661         if (next_token().cat() == catBegin) {
662                 Token t = get_token(); // skip brace
663                 string res;
664                 for (Token t = get_token(); t.cat() != catEnd && good(); t = get_token()) {
665                         if (t.cat() == catBegin) {
666                                 putback();
667                                 res += '{' + verbatim_item() + '}';
668                         }
669                         else
670                                 res += t.asInput();
671                 }
672                 return res;
673         }
674         return get_token().asInput();
675 }
676
677
678 void Parser::reset()
679 {
680         pos_ = 0;
681 }
682
683
684 void Parser::setCatCode(char c, CatCode cat)
685 {
686         theCatcode[(unsigned char)c] = cat;
687 }
688
689
690 CatCode Parser::getCatCode(char c) const
691 {
692         return theCatcode[(unsigned char)c];
693 }
694
695
696 } // namespace lyx