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