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