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