]> git.lyx.org Git - lyx.git/blob - src/Lexer.cpp
Embedding: saving inzip name to .lyx file so that embedded files can always be found...
[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 pushed_table {
115         public:
116                 ///
117                 pushed_table()
118                         : table_elem(0), table_siz(0) {}
119                 ///
120                 pushed_table(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<pushed_table> pushed;
129 };
130
131
132
133 namespace {
134
135 class compare_tags
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, compare_tags())) {
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, compare_tags());
202                 lyxerr << "\nSorted:" << endl;
203                 printTable(lyxerr);
204         }
205 }
206
207
208 void Lexer::Pimpl::pushTable(keyword_item * tab, int num)
209 {
210         pushed_table 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         pushed_table 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, compare_tags());
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 == '\\') is.putback(c); // put it back
578                         status = LEX_TOKEN;
579                 }
580
581                 if (c == '\n')
582                         ++lineno;
583
584         }
585         if (status)
586                 return true;
587
588         status = is.eof() ? LEX_FEOF: LEX_UNDEF;
589         buff.clear();
590         return false;
591 }
592
593
594 bool Lexer::Pimpl::inputAvailable()
595 {
596         return is.good();
597 }
598
599
600 void Lexer::Pimpl::pushToken(string const & pt)
601 {
602         pushTok = pt;
603 }
604
605
606
607
608 //////////////////////////////////////////////////////////////////////
609 //
610 // Lexer
611 //
612 //////////////////////////////////////////////////////////////////////
613
614 Lexer::Lexer(keyword_item * tab, int num)
615         : pimpl_(new Pimpl(tab, num))
616 {}
617
618
619 Lexer::~Lexer()
620 {
621         delete pimpl_;
622 }
623
624
625 bool Lexer::isOK() const
626 {
627         return pimpl_->inputAvailable();
628 }
629
630
631 void Lexer::setLineNo(int l)
632 {
633         pimpl_->lineno = l;
634 }
635
636
637 int Lexer::getLineNo() const
638 {
639         return pimpl_->lineno;
640 }
641
642
643 istream & Lexer::getStream()
644 {
645         return pimpl_->is;
646 }
647
648
649 void Lexer::pushTable(keyword_item * tab, int num)
650 {
651         pimpl_->pushTable(tab, num);
652 }
653
654
655 void Lexer::popTable()
656 {
657         pimpl_->popTable();
658 }
659
660
661 void Lexer::printTable(ostream & os)
662 {
663         pimpl_->printTable(os);
664 }
665
666
667 void Lexer::printError(string const & message) const
668 {
669         pimpl_->printError(message);
670 }
671
672
673 bool Lexer::setFile(FileName const & filename)
674 {
675         return pimpl_->setFile(filename);
676 }
677
678
679 void Lexer::setStream(istream & i)
680 {
681         pimpl_->setStream(i);
682 }
683
684
685 void Lexer::setCommentChar(char c)
686 {
687         pimpl_->setCommentChar(c);
688 }
689
690 int Lexer::lex()
691 {
692         return pimpl_->lex();
693 }
694
695
696 int Lexer::getInteger() const
697 {
698         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
699         if (!lastReadOk_) {
700                 pimpl_->printError("integer token missing");
701                 return -1;
702         }
703
704         if (isStrInt(pimpl_->getString()))
705                 return convert<int>(pimpl_->getString());
706
707         lastReadOk_ = false;
708         pimpl_->printError("Bad integer `$$Token'");
709         return -1;
710 }
711
712
713 double Lexer::getFloat() const
714 {
715         // replace comma with dot in case the file was written with
716         // the wrong locale (should be rare, but is easy enough to
717         // avoid).
718         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
719         if (!lastReadOk_) {
720                 pimpl_->printError("float token missing");
721                 return -1;
722         }
723
724         string const str = subst(pimpl_->getString(), ",", ".");
725         if (isStrDbl(str))
726                 return convert<double>(str);
727
728         lastReadOk_ = false;
729         pimpl_->printError("Bad float `$$Token'");
730         return -1;
731 }
732
733
734 string const Lexer::getString() const
735 {
736         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
737
738         if (lastReadOk_)
739         return pimpl_->getString();
740
741         return string();
742 }
743
744
745 docstring const Lexer::getDocString() const
746 {
747         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
748
749         if (lastReadOk_)
750                 return pimpl_->getDocString();
751
752         return docstring();
753 }
754
755
756 // I would prefer to give a tag number instead of an explicit token
757 // here, but it is not possible because Buffer::readDocument uses
758 // explicit tokens (JMarc)
759 string const Lexer::getLongString(string const & endtoken)
760 {
761         string str, prefix;
762         bool firstline = true;
763
764         while (pimpl_->is) { //< eatLine only reads from is, not from pushTok
765                 if (!eatLine())
766                         // blank line in the file being read
767                         continue;
768
769                 string const token = trim(getString(), " \t");
770
771                 LYXERR(Debug::PARSER, "LongString: `" << getString() << '\'');
772
773                 // We do a case independent comparison, like search_kw does.
774                 if (compare_ascii_no_case(token, endtoken) == 0)
775                         break;
776
777                 string tmpstr = getString();
778                 if (firstline) {
779                         string::size_type i(tmpstr.find_first_not_of(' '));
780                         if (i != string::npos)
781                                 prefix = tmpstr.substr(0, i);
782                         firstline = false;
783                         LYXERR(Debug::PARSER, "Prefix = `" << prefix << "\'");
784                 }
785
786                 // further lines in long strings may have the same
787                 // whitespace prefix as the first line. Remove it.
788                 if (prefix.length() && prefixIs(tmpstr, prefix)) {
789                         tmpstr.erase(0, prefix.length() - 1);
790                 }
791
792                 str += ltrim(tmpstr, "\t") + '\n';
793         }
794
795         if (!pimpl_->is) {
796                 printError("Long string not ended by `" + endtoken + '\'');
797         }
798
799         return str;
800 }
801
802
803 bool Lexer::getBool() const
804 {
805         if (pimpl_->getString() == "true") {
806                 lastReadOk_ = true;
807                 return true;
808         } else if (pimpl_->getString() != "false") {
809                 pimpl_->printError("Bad boolean `$$Token'. "
810                                    "Use \"false\" or \"true\"");
811                 lastReadOk_ = false;
812         }
813         lastReadOk_ = true;
814         return false;
815 }
816
817
818 bool Lexer::eatLine()
819 {
820         return pimpl_->eatLine();
821 }
822
823
824 bool Lexer::next(bool esc)
825 {
826         return pimpl_->next(esc);
827 }
828
829
830 bool Lexer::nextToken()
831 {
832         return pimpl_->nextToken();
833 }
834
835
836 void Lexer::pushToken(string const & pt)
837 {
838         pimpl_->pushToken(pt);
839 }
840
841
842 Lexer::operator void const *() const
843 {
844         // This behaviour is NOT the same as the streams which would
845         // use fail() here. However, our implementation of getString() et al.
846         // can cause the eof() and fail() bits to be set, even though we
847         // haven't tried to read 'em.
848         return lastReadOk_? this : 0;
849 }
850
851
852 bool Lexer::operator!() const
853 {
854         return !lastReadOk_;
855 }
856
857
858 Lexer & Lexer::operator>>(string & s)
859 {
860         if (isOK()) {
861                 next();
862                 s = getString();
863         } else {
864                 lastReadOk_ = false;
865         }
866         return *this;
867 }
868
869
870 Lexer & Lexer::operator>>(docstring & s)
871 {
872         if (isOK()) {
873                 next();
874                 s = getDocString();
875         } else {
876                 lastReadOk_ = false;
877         }
878         return *this;
879 }
880
881
882 Lexer & Lexer::operator>>(double & s)
883 {
884         if (isOK()) {
885                 next();
886                 s = getFloat();
887         } else {
888                 lastReadOk_ = false;
889         }
890         return *this;
891 }
892
893
894 Lexer & Lexer::operator>>(int & s)
895 {
896         if (isOK()) {
897                 next();
898                 s = getInteger();
899         } else {
900                 lastReadOk_ = false;
901         }
902         return *this;
903 }
904
905
906 Lexer & Lexer::operator>>(unsigned int & s)
907 {
908         if (isOK()) {
909                 next();
910                 s = getInteger();
911         } else {
912                 lastReadOk_ = false;
913         }
914         return *this;
915 }
916
917
918 Lexer & Lexer::operator>>(bool & s)
919 {
920         if (isOK()) {
921                 next();
922                 s = getBool();
923         } else {
924                 lastReadOk_ = false;
925         }
926         return *this;
927 }
928
929
930 /// quotes a string, e.g. for use in preferences files or as an argument of the "log" dialog
931 string const Lexer::quoteString(string const & arg)
932 {
933         ostringstream os;
934         os << '"' << subst(subst(arg, "\\", "\\\\"), "\"", "\\\"") << '"';
935         return os.str();
936 }
937
938
939 } // namespace lyx