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