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