]> git.lyx.org Git - lyx.git/blob - src/Lexer.cpp
5136c672073d6dc451360a22f6b088915c1a66f3
[lyx.git] / src / Lexer.cpp
1 /**
2  * \file Lexer.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author John Levon
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "Lexer.h"
17
18 #include "support/convert.h"
19 #include "support/debug.h"
20 #include "support/FileName.h"
21 #include "support/filetools.h"
22 #include "support/gzstream.h"
23 #include "support/lstrings.h"
24 #include "support/lyxalgo.h"
25 #include "support/types.h"
26
27 #include <functional>
28 #include <istream>
29 #include <stack>
30 #include <vector>
31
32 using namespace std;
33 using namespace lyx::support;
34
35 namespace lyx {
36
37 //////////////////////////////////////////////////////////////////////
38 //
39 // Lexer::Pimpl
40 //
41 //////////////////////////////////////////////////////////////////////
42
43
44 ///
45 class Lexer::Pimpl {
46 public:
47         ///
48         Pimpl(LexerKeyword * tab, int num);
49         ///
50         string const getString() const;
51         ///
52         docstring const getDocString() const;
53         ///
54         void printError(string const & message) const;
55         ///
56         void printTable(ostream & os);
57         ///
58         void pushTable(LexerKeyword * tab, int num);
59         ///
60         void popTable();
61         ///
62         bool setFile(FileName const & filename);
63         ///
64         void setStream(istream & i);
65         ///
66         void setCommentChar(char c);
67         ///
68         bool next(bool esc = false);
69         ///
70         int searchKeyword(char const * const tag) const;
71         ///
72         int lex();
73         ///
74         bool eatLine();
75         ///
76         bool nextToken();
77         /// test if there is a pushed token or the stream is ok
78         bool inputAvailable();
79         ///
80         void pushToken(string const &);
81         /// fb_ is only used to open files, the stream is accessed through is.
82         filebuf fb_;
83
84         /// gz_ is only used to open files, the stream is accessed through is.
85         gz::gzstreambuf gz_;
86
87         /// the stream that we use.
88         istream is;
89         ///
90         string name;
91         ///
92         LexerKeyword * table;
93         ///
94         int no_items;
95         ///
96         string buff;
97         ///
98         int status;
99         ///
100         int lineno;
101         ///
102         string pushTok;
103         ///
104         char commentChar;
105         /// used for error messages
106         string context;
107 private:
108         /// non-copyable
109         Pimpl(Pimpl const &);
110         void operator=(Pimpl const &);
111
112         ///
113         void verifyTable();
114         ///
115         class PushedTable {
116         public:
117                 ///
118                 PushedTable()
119                         : table_elem(0), table_siz(0) {}
120                 ///
121                 PushedTable(LexerKeyword * ki, int siz)
122                         : table_elem(ki), table_siz(siz) {}
123                 ///
124                 LexerKeyword * table_elem;
125                 ///
126                 int table_siz;
127         };
128         ///
129         stack<PushedTable> pushed;
130 };
131
132
133
134 namespace {
135
136 class CompareTags
137         : public binary_function<LexerKeyword, LexerKeyword, bool> {
138 public:
139         // used by lower_bound, sort and sorted
140         bool operator()(LexerKeyword const & a, LexerKeyword const & b) const
141         {
142                 // we use the ascii version, because in turkish, 'i'
143                 // is not the lowercase version of 'I', and thus
144                 // turkish locale breaks parsing of tags.
145                 return compare_ascii_no_case(a.tag, b.tag) < 0;
146         }
147 };
148
149 } // end of anon namespace
150
151
152 Lexer::Pimpl::Pimpl(LexerKeyword * tab, int num)
153         : is(&fb_), table(tab), no_items(num),
154           status(0), lineno(0), commentChar('#')
155 {
156         verifyTable();
157 }
158
159
160 string const Lexer::Pimpl::getString() const
161 {
162         return buff;
163 }
164
165
166 docstring const Lexer::Pimpl::getDocString() const
167 {
168         return from_utf8(buff);
169 }
170
171
172 void Lexer::Pimpl::printError(string const & message) const
173 {
174         string const tmpmsg = subst(message, "$$Token", getString());
175         lyxerr << "LyX: " << tmpmsg << " [around line " << lineno
176                 << " of file " << to_utf8(makeDisplayPath(name)) << ']' << endl;
177 }
178
179
180 void Lexer::Pimpl::printTable(ostream & os)
181 {
182         os << "\nNumber of tags: " << no_items << endl;
183         for (int i= 0; i < no_items; ++i)
184                 os << "table[" << i
185                    << "]:  tag: `" << table[i].tag
186                    << "'  code:" << table[i].code << '\n';
187         os.flush();
188 }
189
190
191 void Lexer::Pimpl::verifyTable()
192 {
193         // Check if the table is sorted and if not, sort it.
194         if (table
195             && !lyx::sorted(table, table + no_items, CompareTags())) {
196                 lyxerr << "The table passed to Lexer is not sorted!\n"
197                        << "Tell the developers to fix it!" << endl;
198                 // We sort it anyway to avoid problems.
199                 lyxerr << "\nUnsorted:" << endl;
200                 printTable(lyxerr);
201
202                 sort(table, table + no_items, CompareTags());
203                 lyxerr << "\nSorted:" << endl;
204                 printTable(lyxerr);
205         }
206 }
207
208
209 void Lexer::Pimpl::pushTable(LexerKeyword * tab, int num)
210 {
211         PushedTable tmppu(table, no_items);
212         pushed.push(tmppu);
213
214         table = tab;
215         no_items = num;
216
217         verifyTable();
218 }
219
220
221 void Lexer::Pimpl::popTable()
222 {
223         if (pushed.empty()) {
224                 lyxerr << "Lexer error: nothing to pop!" << endl;
225                 return;
226         }
227
228         PushedTable tmp = pushed.top();
229         pushed.pop();
230         table = tmp.table_elem;
231         no_items = tmp.table_siz;
232 }
233
234
235 bool Lexer::Pimpl::setFile(FileName const & filename)
236 {
237         // Check the format of the file.
238         string const format = filename.guessFormatFromContents();
239
240         if (format == "gzip" || format == "zip" || format == "compress") {
241                 LYXERR(Debug::LYXLEX, "lyxlex: compressed");
242                 // The check only outputs a debug message, because it triggers
243                 // a bug in compaq cxx 6.2, where is_open() returns 'true' for
244                 // a fresh new filebuf.  (JMarc)
245                 if (gz_.is_open() || istream::off_type(is.tellg()) > -1)
246                         LYXERR(Debug::LYXLEX, "Error in LyXLex::setFile: "
247                                 "file or stream already set.");
248                 gz_.open(filename.toFilesystemEncoding().c_str(), ios::in);
249                 is.rdbuf(&gz_);
250                 name = filename.absFilename();
251                 lineno = 0;
252                 return gz_.is_open() && is.good();
253         } else {
254                 LYXERR(Debug::LYXLEX, "lyxlex: UNcompressed");
255
256                 // The check only outputs a debug message, because it triggers
257                 // a bug in compaq cxx 6.2, where is_open() returns 'true' for
258                 // a fresh new filebuf.  (JMarc)
259                 if (fb_.is_open() || istream::off_type(is.tellg()) > 0) {
260                         LYXERR(Debug::LYXLEX, "Error in Lexer::setFile: "
261                                 "file or stream already set.");
262                 }
263                 fb_.open(filename.toFilesystemEncoding().c_str(), ios::in);
264                 is.rdbuf(&fb_);
265                 name = filename.absFilename();
266                 lineno = 0;
267                 return fb_.is_open() && is.good();
268         }
269 }
270
271
272 void Lexer::Pimpl::setStream(istream & i)
273 {
274         if (fb_.is_open() || istream::off_type(is.tellg()) > 0) {
275                 LYXERR(Debug::LYXLEX, "Error in Lexer::setStream: "
276                         "file or stream already set.");
277         }
278         is.rdbuf(i.rdbuf());
279         lineno = 0;
280 }
281
282
283 void Lexer::Pimpl::setCommentChar(char c)
284 {
285         commentChar = c;
286 }
287
288
289 bool Lexer::Pimpl::next(bool esc /* = false */)
290 {
291         if (!pushTok.empty()) {
292                 // There can have been a whole line pushed so
293                 // we extract the first word and leaves the rest
294                 // in pushTok. (Lgb)
295                 if (pushTok[0] == '\\' && pushTok.find(' ') != string::npos) {
296                         buff.clear();
297                         pushTok = split(pushTok, buff, ' ');
298                 } else {
299                         buff = pushTok;
300                         pushTok.clear();
301                 }
302                 status = LEX_TOKEN;
303                 return true;
304         }
305
306
307         unsigned char c = 0; // getc() returns an int
308         char cc = 0;
309         status = 0;
310         while (is && !status) {
311                 is.get(cc);
312                 c = cc;
313
314                 // skip ','s
315                 if (esc && c == ',')
316                         continue;
317
318                 
319                 if (c == commentChar) {
320                         // Read rest of line (fast :-)
321 #if 1
322                         // That is not fast... (Lgb)
323                         string dummy;
324                         getline(is, dummy);
325
326                         LYXERR(Debug::LYXLEX, "Comment read: `" << c << dummy << '\'');
327 #else
328                         // unfortunately ignore is buggy (Lgb)
329                         is.ignore(100, '\n');
330 #endif
331                         ++lineno;
332                         continue;
333                 }
334
335                 if (c == '\"') {
336                         buff.clear();
337
338                         if (esc) {
339
340                                 bool escaped = false;
341                                 do {
342                                         escaped = false;
343                                         is.get(cc);
344                                         c = cc;
345                                         if (c == '\r') continue;
346                                         if (c == '\\') {
347                                                 // escape the next char
348                                                 is.get(cc);
349                                                 c = cc;
350                                                 if (c == '\"' || c == '\\')
351                                                         escaped = true;
352                                                 else
353                                                         buff.push_back('\\');
354                                         }
355                                         buff.push_back(c);
356
357                                         if (!escaped && c == '\"')
358                                                 break;
359                                 } while (c != '\n' && is);
360
361                         } else {
362
363                                 do {
364                                         is.get(cc);
365                                         c = cc;
366                                         if (c != '\r')
367                                                 buff.push_back(c);
368                                 } while (c != '\"' && c != '\n' && is);
369
370                         }
371
372                         if (c != '\"') {
373                                 printError("Missing quote");
374                                 if (c == '\n')
375                                         ++lineno;
376                         }
377
378                         buff.resize(buff.size() - 1);
379                         status = LEX_DATA;
380                         break;
381                 }
382
383                 if (!esc && c == ',')
384                         continue;              /* Skip ','s */
385
386                         // using relational operators with chars other
387                         // than == and != is not safe. And if it is done
388                         // the type _have_ to be unsigned. It usually a
389                         // lot better to use the functions from cctype
390                 if (c > ' ' && is)  {
391                         buff.clear();
392
393                         do {
394                                 if (esc && c == '\\') {
395                                         // escape the next char
396                                         is.get(cc);
397                                         c = cc;
398                                         //escaped = true;
399                                 }
400                                 buff.push_back(c);
401                                 is.get(cc);
402                                 c = cc;
403                         } while (c > ' ' && c != ',' && is);
404                         status = LEX_TOKEN;
405                 }
406
407                 if (!esc && c == '\r' && is) {
408                         // The Windows support has lead to the
409                         // possibility of "\r\n" at the end of
410                         // a line.  This will stop LyX choking
411                         // when it expected to find a '\n'
412                         is.get(cc);
413                         c = cc;
414                 }
415
416                 if (c == '\n')
417                         ++lineno;
418
419         }
420         if (status)
421                 return true;
422
423         status = is.eof() ? LEX_FEOF: LEX_UNDEF;
424         buff.clear();
425         return false;
426 }
427
428
429 int Lexer::Pimpl::searchKeyword(char const * const tag) const
430 {
431         LexerKeyword search_tag = { tag, 0 };
432         LexerKeyword * res =
433                 lower_bound(table, table + no_items,
434                             search_tag, CompareTags());
435         // use the compare_ascii_no_case instead of compare_no_case,
436         // because in turkish, 'i' is not the lowercase version of 'I',
437         // and thus turkish locale breaks parsing of tags.
438         if (res != table + no_items
439             && !compare_ascii_no_case(res->tag, tag))
440                 return res->code;
441         return LEX_UNDEF;
442 }
443
444
445 int Lexer::Pimpl::lex()
446 {
447         //NOTE: possible bug.
448         if (next() && status == LEX_TOKEN)
449                 return searchKeyword(getString().c_str());
450         return status;
451 }
452
453
454 bool Lexer::Pimpl::eatLine()
455 {
456         buff.clear();
457
458         unsigned char c = '\0';
459         char cc = 0;
460         while (is && c != '\n') {
461                 is.get(cc);
462                 c = cc;
463                 //LYXERR(Debug::LYXLEX, "Lexer::EatLine read char: `" << c << '\'');
464                 if (c != '\r')
465                         buff.push_back(c);
466         }
467
468         if (c == '\n') {
469                 ++lineno;
470                 buff.resize(buff.size() - 1);
471                 status = LEX_DATA;
472                 return true;
473         } else if (buff.length() > 0) { // last line
474                 status = LEX_DATA;
475                 return true;
476         } else {
477                 return false;
478         }
479 }
480
481
482 bool Lexer::Pimpl::nextToken()
483 {
484         if (!pushTok.empty()) {
485                 // There can have been a whole line pushed so
486                 // we extract the first word and leaves the rest
487                 // in pushTok. (Lgb)
488                 if (pushTok[0] == '\\' && pushTok.find(' ') != string::npos) {
489                         buff.clear();
490                         pushTok = split(pushTok, buff, ' ');
491                 } else {
492                         buff = pushTok;
493                         pushTok.clear();
494                 }
495                 status = LEX_TOKEN;
496                 return true;
497         }
498
499         status = 0;
500         while (is && !status) {
501                 unsigned char c = 0;
502                 char cc = 0;
503                 is.get(cc);
504                 c = cc;
505                 if (c >= ' ' && is) {
506                         buff.clear();
507
508                         if (c == '\\') { // first char == '\\'
509                                 do {
510                                         buff.push_back(c);
511                                         is.get(cc);
512                                         c = cc;
513                                 } while (c > ' ' && c != '\\' && is);
514                         } else {
515                                 do {
516                                         buff.push_back(c);
517                                         is.get(cc);
518                                         c = cc;
519                                 } while (c >= ' ' && c != '\\' && is);
520                         }
521
522                         if (c == '\\')
523                                 is.putback(c); // put it back
524                         status = LEX_TOKEN;
525                 }
526
527                 if (c == '\n')
528                         ++lineno;
529
530         }
531         if (status)
532                 return true;
533
534         status = is.eof() ? LEX_FEOF: LEX_UNDEF;
535         buff.clear();
536         return false;
537 }
538
539
540 bool Lexer::Pimpl::inputAvailable()
541 {
542         return is.good();
543 }
544
545
546 void Lexer::Pimpl::pushToken(string const & pt)
547 {
548         pushTok = pt;
549 }
550
551
552
553
554 //////////////////////////////////////////////////////////////////////
555 //
556 // Lexer
557 //
558 //////////////////////////////////////////////////////////////////////
559
560 Lexer::Lexer()
561         : pimpl_(new Pimpl(0, 0))
562 {}
563
564
565 void Lexer::init(LexerKeyword * tab, int num)
566 {
567          pimpl_ = new Pimpl(tab, num);
568 }
569
570
571 Lexer::~Lexer()
572 {
573         delete pimpl_;
574 }
575
576
577 bool Lexer::isOK() const
578 {
579         return pimpl_->inputAvailable();
580 }
581
582
583 void Lexer::setLineNumber(int l)
584 {
585         pimpl_->lineno = l;
586 }
587
588
589 int Lexer::lineNumber() const
590 {
591         return pimpl_->lineno;
592 }
593
594
595 istream & Lexer::getStream()
596 {
597         return pimpl_->is;
598 }
599
600
601 void Lexer::pushTable(LexerKeyword * tab, int num)
602 {
603         pimpl_->pushTable(tab, num);
604 }
605
606
607 void Lexer::popTable()
608 {
609         pimpl_->popTable();
610 }
611
612
613 void Lexer::printTable(ostream & os)
614 {
615         pimpl_->printTable(os);
616 }
617
618
619 void Lexer::printError(string const & message) const
620 {
621         pimpl_->printError(message);
622 }
623
624
625 bool Lexer::setFile(FileName const & filename)
626 {
627         return pimpl_->setFile(filename);
628 }
629
630
631 void Lexer::setStream(istream & i)
632 {
633         pimpl_->setStream(i);
634 }
635
636
637 void Lexer::setCommentChar(char c)
638 {
639         pimpl_->setCommentChar(c);
640 }
641
642 int Lexer::lex()
643 {
644         return pimpl_->lex();
645 }
646
647
648 int Lexer::getInteger() const
649 {
650         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
651         if (!lastReadOk_) {
652                 pimpl_->printError("integer token missing");
653                 return -1;
654         }
655
656         if (isStrInt(pimpl_->getString()))
657                 return convert<int>(pimpl_->getString());
658
659         lastReadOk_ = false;
660         pimpl_->printError("Bad integer `$$Token'");
661         return -1;
662 }
663
664
665 double Lexer::getFloat() const
666 {
667         // replace comma with dot in case the file was written with
668         // the wrong locale (should be rare, but is easy enough to
669         // avoid).
670         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
671         if (!lastReadOk_) {
672                 pimpl_->printError("float token missing");
673                 return -1;
674         }
675
676         string const str = subst(pimpl_->getString(), ",", ".");
677         if (isStrDbl(str))
678                 return convert<double>(str);
679
680         lastReadOk_ = false;
681         pimpl_->printError("Bad float `$$Token'");
682         return -1;
683 }
684
685
686 string const Lexer::getString() const
687 {
688         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
689
690         if (lastReadOk_)
691         return pimpl_->getString();
692
693         return string();
694 }
695
696
697 docstring const Lexer::getDocString() const
698 {
699         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
700
701         if (lastReadOk_)
702                 return pimpl_->getDocString();
703
704         return docstring();
705 }
706
707
708 // I would prefer to give a tag number instead of an explicit token
709 // here, but it is not possible because Buffer::readDocument uses
710 // explicit tokens (JMarc)
711 string const Lexer::getLongString(string const & endtoken)
712 {
713         string str;
714         string prefix;
715         bool firstline = true;
716
717         while (pimpl_->is) { //< eatLine only reads from is, not from pushTok
718                 if (!eatLine())
719                         // blank line in the file being read
720                         continue;
721
722                 string const token = trim(getString(), " \t");
723
724                 LYXERR(Debug::PARSER, "LongString: `" << getString() << '\'');
725
726                 // We do a case independent comparison, like searchKeyword does.
727                 if (compare_ascii_no_case(token, endtoken) == 0)
728                         break;
729
730                 string tmpstr = getString();
731                 if (firstline) {
732                         size_t i = tmpstr.find_first_not_of(' ');
733                         if (i != string::npos)
734                                 prefix = tmpstr.substr(0, i);
735                         firstline = false;
736                         LYXERR(Debug::PARSER, "Prefix = `" << prefix << "\'");
737                 }
738
739                 // further lines in long strings may have the same
740                 // whitespace prefix as the first line. Remove it.
741                 if (prefix.length() && prefixIs(tmpstr, prefix))
742                         tmpstr.erase(0, prefix.length() - 1);
743
744                 str += ltrim(tmpstr, "\t") + '\n';
745         }
746
747         if (!pimpl_->is)
748                 printError("Long string not ended by `" + endtoken + '\'');
749
750         return str;
751 }
752
753
754 bool Lexer::getBool() const
755 {
756         string const s = pimpl_->getString();   
757         if (s == "false" || s == "0") {
758                 lastReadOk_ = true;
759                 return false;
760         }
761         if (s == "true" || s == "1") {
762                 lastReadOk_ = true;
763                 return true;
764         }
765         pimpl_->printError("Bad boolean `$$Token'. "
766                                  "Use \"false\" or \"true\"");
767         lastReadOk_ = false;
768 }
769
770
771 bool Lexer::eatLine()
772 {
773         return pimpl_->eatLine();
774 }
775
776
777 bool Lexer::next(bool esc)
778 {
779         return pimpl_->next(esc);
780 }
781
782
783 bool Lexer::nextToken()
784 {
785         return pimpl_->nextToken();
786 }
787
788
789 void Lexer::pushToken(string const & pt)
790 {
791         pimpl_->pushToken(pt);
792 }
793
794
795 Lexer::operator void const *() const
796 {
797         // This behaviour is NOT the same as the streams which would
798         // use fail() here. However, our implementation of getString() et al.
799         // can cause the eof() and fail() bits to be set, even though we
800         // haven't tried to read 'em.
801         return lastReadOk_? this : 0;
802 }
803
804
805 bool Lexer::operator!() const
806 {
807         return !lastReadOk_;
808 }
809
810
811 Lexer & Lexer::operator>>(string & s)
812 {
813         if (isOK()) {
814                 next();
815                 s = getString();
816         } else {
817                 lastReadOk_ = false;
818         }
819         return *this;
820 }
821
822
823 Lexer & Lexer::operator>>(docstring & s)
824 {
825         if (isOK()) {
826                 next();
827                 s = getDocString();
828         } else {
829                 lastReadOk_ = false;
830         }
831         return *this;
832 }
833
834
835 Lexer & Lexer::operator>>(double & s)
836 {
837         if (isOK()) {
838                 next();
839                 s = getFloat();
840         } else {
841                 lastReadOk_ = false;
842         }
843         return *this;
844 }
845
846
847 Lexer & Lexer::operator>>(int & s)
848 {
849         if (isOK()) {
850                 next();
851                 s = getInteger();
852         } else {
853                 lastReadOk_ = false;
854         }
855         return *this;
856 }
857
858
859 Lexer & Lexer::operator>>(unsigned int & s)
860 {
861         if (isOK()) {
862                 next();
863                 s = getInteger();
864         } else {
865                 lastReadOk_ = false;
866         }
867         return *this;
868 }
869
870
871 Lexer & Lexer::operator>>(bool & s)
872 {
873         if (isOK()) {
874                 next();
875                 s = getBool();
876         } else {
877                 lastReadOk_ = false;
878         }
879         return *this;
880 }
881
882
883 Lexer & Lexer::operator>>(char & c)
884 {
885         string s;
886         operator>>(s);
887         if (!s.empty())
888                 c = s[0];
889         return *this;
890 }
891
892
893 // quotes a string, e.g. for use in preferences files or as an argument
894 // of the "log" dialog
895 string Lexer::quoteString(string const & arg)
896 {
897         string res;
898         res += '"';
899         res += subst(subst(arg, "\\", "\\\\"), "\"", "\\\"");
900         res += '"';
901         return res;
902 }
903
904
905 Lexer & Lexer::operator>>(char const * required)
906 {
907         string token;
908         *this >> token;
909         if (token != required) {
910                 LYXERR0("Missing '" << required << "'-tag in " << pimpl_->context 
911                         << ". Got " << token << " instead. Line: " << lineNumber());
912                 pushToken(token);
913         }
914         return *this;
915 }
916
917
918 bool Lexer::checkFor(char const * required)
919 {
920         string token;
921         *this >> token;
922         if (token == required)
923                 return true;
924         pushToken(token);
925         return false;
926 }
927
928
929 void Lexer::setContext(std::string const & str)
930 {
931         pimpl_->context = str;
932 }
933
934
935 } // namespace lyx