X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fmathed%2FMathParser.cpp;h=ffb828361cd24d48e404ec9594dba35c14e45f72;hb=02e82157ec583c3900e359de86be79fac6512387;hp=4856924590d3c49f2278c888646eb1a850b80641;hpb=5d48c7ef6a2e7316cda2fb656e7eb06bd7d3cdc1;p=lyx.git diff --git a/src/mathed/MathParser.cpp b/src/mathed/MathParser.cpp index 4856924590..ffb828361c 100644 --- a/src/mathed/MathParser.cpp +++ b/src/mathed/MathParser.cpp @@ -3,7 +3,7 @@ * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * - * \author André Pönitz + * \author André Pönitz * * Full author contact details are available in file CREDITS. */ @@ -42,10 +42,12 @@ following hack as starting point to write some macros: #include "InsetMathArray.h" #include "InsetMathBig.h" #include "InsetMathBrace.h" +#include "InsetMathCancelto.h" #include "InsetMathChar.h" #include "InsetMathColor.h" #include "InsetMathComment.h" #include "InsetMathDelim.h" +#include "InsetMathEnsureMath.h" #include "InsetMathEnv.h" #include "InsetMathFrac.h" #include "InsetMathKern.h" @@ -54,19 +56,28 @@ following hack as starting point to write some macros: #include "InsetMathRef.h" #include "InsetMathRoot.h" #include "InsetMathScript.h" +#include "InsetMathSideset.h" +#include "InsetMathSpace.h" #include "InsetMathSplit.h" #include "InsetMathSqrt.h" +#include "InsetMathStackrel.h" +#include "InsetMathString.h" #include "InsetMathTabular.h" #include "MathMacroTemplate.h" +#include "MathExtern.h" #include "MathFactory.h" #include "MathMacroArgument.h" #include "MathSupport.h" +#include "Buffer.h" +#include "BufferParams.h" +#include "Encoding.h" #include "Lexer.h" -#include "support/debug.h" #include "support/convert.h" +#include "support/debug.h" #include "support/docstream.h" +#include "support/unique_ptr.h" #include @@ -96,6 +107,55 @@ bool stared(docstring const & s) } +docstring const repl(docstring const & oldstr, char_type const c, + docstring const & macro, bool textmode = false) +{ + docstring newstr; + size_t i; + size_t j; + + for (i = 0, j = 0; i < oldstr.size(); ++i) { + if (c == oldstr[i]) { + newstr.append(oldstr, j, i - j); + newstr.append(macro); + j = i + 1; + if (macro.size() > 2 && j < oldstr.size()) + newstr += (textmode && oldstr[j] == ' ' ? '\\' : ' '); + } + } + + // Any substitution? + if (j == 0) + return oldstr; + + newstr.append(oldstr, j, i - j); + return newstr; +} + + +docstring escapeSpecialChars(docstring const & str, bool textmode) +{ + docstring const backslash = textmode ? from_ascii("\\textbackslash") + : from_ascii("\\backslash"); + docstring const caret = textmode ? from_ascii("\\textasciicircum") + : from_ascii("\\mathcircumflex"); + docstring const tilde = textmode ? from_ascii("\\textasciitilde") + : from_ascii("\\sim"); + + return repl(repl(repl(repl(repl(repl(repl(repl(repl(repl(str, + '\\', backslash, textmode), + '^', caret, textmode), + '~', tilde, textmode), + '_', from_ascii("\\_")), + '$', from_ascii("\\$")), + '#', from_ascii("\\#")), + '&', from_ascii("\\&")), + '%', from_ascii("\\%")), + '{', from_ascii("\\{")), + '}', from_ascii("\\}")); +} + + /*! * Add the row \p cellrow to \p grid. * \returns wether the row could be added. Adding a row can fail for @@ -159,7 +219,7 @@ bool addCol(InsetMathGrid & grid, InsetMathGrid::col_type & cellcol) /*! - * Check wether the last row is empty and remove it if yes. + * Check whether the last row is empty and remove it if yes. * Otherwise the following code * \verbatim \begin{array}{|c|c|} @@ -170,12 +230,18 @@ bool addCol(InsetMathGrid & grid, InsetMathGrid::col_type & cellcol) * \endverbatim * will result in a grid with 3 rows (+ the dummy row that is always present), * because the last '\\' opens a new row. + * Do never delete a row that contains a multicolumn, even if all cells empty, + * since the multicolumn information would get lost otherwise. + * Note that this is only needed for inner-hull grid types, such as array + * or aligned, but not for outer-hull grid types, such as eqnarray or align. */ void delEmptyLastRow(InsetMathGrid & grid) { InsetMathGrid::row_type const row = grid.nrows() - 1; for (InsetMathGrid::col_type col = 0; col < grid.ncols(); ++col) { - if (!grid.cell(grid.index(row, col)).empty()) + InsetMathGrid::idx_type const idx = grid.index(row, col); + if (!grid.cell(idx).empty() || + grid.cellinfo(idx).multi_ != InsetMathGrid::CELL_NORMAL) return; } // Copy the row information of the empty row (which would contain the @@ -186,6 +252,19 @@ void delEmptyLastRow(InsetMathGrid & grid) } +/*! + * Tell whether the environment name corresponds to an inner-hull grid type. + */ +bool innerHull(docstring const & name) +{ + // For [bB]matrix, [vV]matrix, and pmatrix we can check the suffix only + return name == "array" || name == "cases" || name == "aligned" + || name == "alignedat" || name == "gathered" || name == "split" + || name == "subarray" || name == "tabular" || name == "matrix" + || name == "smallmatrix" || name.substr(1) == "matrix"; +} + + // These are TeX's catcodes enum CatCode { catEscape, // 0 backslash @@ -263,9 +342,9 @@ public: /// char_type character() const { return char_; } /// - docstring asString() const { return cs_.size() ? cs_ : docstring(1, char_); } + docstring asString() const { return !cs_.empty() ? cs_ : docstring(1, char_); } /// - docstring asInput() const { return cs_.size() ? '\\' + cs_ : docstring(1, char_); } + docstring asInput() const { return !cs_.empty() ? '\\' + cs_ : docstring(1, char_); } private: /// @@ -279,7 +358,7 @@ private: ostream & operator<<(ostream & os, Token const & t) { - if (t.cs().size()) { + if (!t.cs().empty()) { docstring const & cs = t.cs(); // FIXME: For some strange reason, the stream operator instanciate // a new Token before outputting the contents of t.cs(). @@ -303,28 +382,34 @@ class Parser { public: /// typedef InsetMath::mode_type mode_type; + /// + typedef Parse::flags parse_mode; /// - Parser(Lexer & lex); + Parser(Lexer & lex, parse_mode mode, Buffer * buf); /// Only use this for reading from .lyx file format, for the reason /// see Parser::tokenize(istream &). - Parser(istream & is); + Parser(istream & is, parse_mode mode, Buffer * buf); /// - Parser(docstring const & str); + Parser(docstring const & str, parse_mode mode, Buffer * buf); /// bool parse(MathAtom & at); /// - void parse(MathData & array, unsigned flags, mode_type mode); + bool parse(MathData & array, unsigned flags, mode_type mode); /// - void parse1(InsetMathGrid & grid, unsigned flags, mode_type mode, + bool parse1(InsetMathGrid & grid, unsigned flags, mode_type mode, bool numbered); /// - MathData parse(unsigned flags, mode_type mode); - /// int lineno() const { return lineno_; } /// void putback(); + /// store current position + void pushPosition(); + /// restore previous position + void popPosition(); + /// forget last saved position + void dropPosition(); private: /// @@ -348,8 +433,6 @@ private: /// void push_back(Token const & t); /// - void pop_back(); - /// Token const & prevToken() const; /// Token const & nextToken() const; @@ -372,28 +455,37 @@ private: vector tokens_; /// unsigned pos_; + /// + std::vector positions_; /// Stack of active environments vector environments_; + /// + parse_mode mode_; + /// + bool success_; + /// + Buffer * buffer_; }; -Parser::Parser(Lexer & lexer) - : lineno_(lexer.getLineNo()), pos_(0) +Parser::Parser(Lexer & lexer, parse_mode mode, Buffer * buf) + : lineno_(lexer.lineNumber()), pos_(0), mode_(mode), success_(true), + buffer_(buf) { tokenize(lexer.getStream()); lexer.eatLine(); } -Parser::Parser(istream & is) - : lineno_(0), pos_(0) +Parser::Parser(istream & is, parse_mode mode, Buffer * buf) + : lineno_(0), pos_(0), mode_(mode), success_(true), buffer_(buf) { tokenize(is); } -Parser::Parser(docstring const & str) - : lineno_(0), pos_(0) +Parser::Parser(docstring const & str, parse_mode mode, Buffer * buf) + : lineno_(0), pos_(0), mode_(mode), success_(true), buffer_(buf) { tokenize(str); } @@ -405,12 +497,6 @@ void Parser::push_back(Token const & t) } -void Parser::pop_back() -{ - tokens_.pop_back(); -} - - Token const & Parser::prevToken() const { static const Token dummy; @@ -446,6 +532,25 @@ void Parser::putback() } +void Parser::pushPosition() +{ + positions_.push_back(pos_); +} + + +void Parser::popPosition() +{ + pos_ = positions_.back(); + positions_.pop_back(); +} + + +void Parser::dropPosition() +{ + positions_.pop_back(); +} + + bool Parser::good() const { return pos_ < tokens_.size(); @@ -456,7 +561,6 @@ char_type Parser::getChar() { if (!good()) { error("The input stream is not well..."); - putback(); return 0; } return tokens_[pos_++].character(); @@ -465,9 +569,12 @@ char_type Parser::getChar() docstring Parser::getArg(char_type left, char_type right) { + docstring result; skipSpaces(); - docstring result; + if (!good()) + return result; + char_type c = getChar(); if (c != left) @@ -515,7 +622,9 @@ void Parser::tokenize(istream & is) void Parser::tokenize(docstring const & buffer) { - idocstringstream is(buffer, ios::in | ios::binary); + idocstringstream is(mode_ & Parse::VERBATIM + ? escapeSpecialChars(buffer, mode_ & Parse::TEXTMODE) + : buffer, ios::in | ios::binary); char_type c; while (is.get(c)) { @@ -548,6 +657,8 @@ void Parser::tokenize(docstring const & buffer) if (!is) { error("unexpected end of input"); } else { + if (c == '\n') + c = ' '; docstring s(1, c); if (catcode(c) == catLetter) { // collect letters @@ -569,7 +680,8 @@ void Parser::tokenize(docstring const & buffer) } case catIgnore: { - lyxerr << "ignoring a char: " << int(c) << endl; + if (!(mode_ & Parse::QUIET)) + lyxerr << "ignoring a char: " << int(c) << endl; break; } @@ -598,28 +710,32 @@ void Parser::dump() const void Parser::error(string const & msg) { - lyxerr << "Line ~" << lineno_ << ": Math parse error: " << msg << endl; - dump(); - //exit(1); + success_ = false; + if (!(mode_ & Parse::QUIET)) { + lyxerr << "Line ~" << lineno_ << ": Math parse error: " + << msg << endl; + dump(); + } } bool Parser::parse(MathAtom & at) { skipSpaces(); - MathData ar; + MathData ar(buffer_); parse(ar, false, InsetMath::UNDECIDED_MODE); if (ar.size() != 1 || ar.front()->getType() == hullNone) { - lyxerr << "unusual contents found: " << ar << endl; - at = MathAtom(new InsetMathPar(ar)); + if (!(mode_ & Parse::QUIET)) + lyxerr << "unusual contents found: " << ar << endl; + at = MathAtom(new InsetMathPar(buffer_, ar)); //if (at->nargs() > 0) // at.nucleus()->cell(0) = ar; //else // lyxerr << "unusual contents found: " << ar << endl; - return true; - } - at = ar[0]; - return true; + success_ = false; + } else + at = ar[0]; + return success_; } @@ -634,7 +750,7 @@ docstring Parser::parse_verbatim_option() putback(); res += '{' + parse_verbatim_item() + '}'; } else - res += t.asString(); + res += t.asInput(); } } return res; @@ -653,26 +769,19 @@ docstring Parser::parse_verbatim_item() res += '{' + parse_verbatim_item() + '}'; } else - res += t.asString(); + res += t.asInput(); } } return res; } -MathData Parser::parse(unsigned flags, mode_type mode) +bool Parser::parse(MathData & array, unsigned flags, mode_type mode) { - MathData ar; - parse(ar, flags, mode); - return ar; -} - - -void Parser::parse(MathData & array, unsigned flags, mode_type mode) -{ - InsetMathGrid grid(1, 1); + InsetMathGrid grid(buffer_, 1, 1); parse1(grid, flags, mode, false); array = grid.cell(0); + return success_; } @@ -683,13 +792,14 @@ void Parser::parse2(MathAtom & at, const unsigned flags, const mode_type mode, } -void Parser::parse1(InsetMathGrid & grid, unsigned flags, +bool Parser::parse1(InsetMathGrid & grid, unsigned flags, const mode_type mode, const bool numbered) { int limits = 0; InsetMathGrid::row_type cellrow = 0; InsetMathGrid::col_type cellcol = 0; MathData * cell = &grid.cell(grid.index(cellrow, cellcol)); + Buffer * buf = buffer_; if (grid.asHullInset()) grid.asHullInset()->numbered(cellrow, numbered); @@ -715,7 +825,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, // skip the brace and collect everything to the next matching // closing brace parse1(grid, FLAG_BRACE_LAST, mode, numbered); - return; + return success_; } // handle only this single token, leave the loop if done @@ -729,7 +839,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, if (t.cat() != catBegin) { error("opening brace expected"); - return; + return success_; } // skip the brace and collect everything to the next matching @@ -747,7 +857,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, // no option found, put back token and we are done putback(); } - return; + return success_; } // @@ -759,25 +869,49 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, Token const & n = getToken(); if (n.cat() == catMath) { // TeX's $$...$$ syntax for displayed math - cell->push_back(MathAtom(new InsetMathHull(hullEquation))); - parse2(cell->back(), FLAG_SIMPLE, InsetMath::MATH_MODE, false); - getToken(); // skip the second '$' token + if (mode == InsetMath::UNDECIDED_MODE) { + cell->push_back(MathAtom(new InsetMathHull(buf, hullEquation))); + parse2(cell->back(), FLAG_SIMPLE, InsetMath::MATH_MODE, false); + getToken(); // skip the second '$' token + } else { + // This is not an outer hull and display math is + // not allowed inside text mode environments. + error("bad math environment $$"); + break; + } } else { // simple $...$ stuff putback(); - cell->push_back(MathAtom(new InsetMathHull(hullSimple))); - parse2(cell->back(), FLAG_SIMPLE, InsetMath::MATH_MODE, false); + if (mode == InsetMath::UNDECIDED_MODE) { + cell->push_back(MathAtom(new InsetMathHull(buf, hullSimple))); + parse2(cell->back(), FLAG_SIMPLE, InsetMath::MATH_MODE, false); + } else { + // Don't create nested math hulls (bug #5392) + cell->push_back(MathAtom(new InsetMathEnsureMath(buf))); + parse(cell->back().nucleus()->cell(0), FLAG_SIMPLE, InsetMath::MATH_MODE); + } } } else if (flags & FLAG_SIMPLE) { // this is the end of the formula - return; + return success_; } else { - error("something strange in the parser"); - break; + Token const & n = getToken(); + if (n.cat() == catMath) { + error("something strange in the parser"); + break; + } else { + // This is inline math ($...$), but the parser thinks we are + // already in math mode and latex would issue an error, unless we + // are inside a text mode user macro. We have no way to tell, so + // let's play safe by using \ensuremath, as it will work in any case. + putback(); + cell->push_back(MathAtom(new InsetMathEnsureMath(buf))); + parse(cell->back().nucleus()->cell(0), FLAG_SIMPLE, InsetMath::MATH_MODE); + } } } @@ -796,11 +930,12 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cat() == catParameter) { Token const & n = getToken(); - cell->push_back(MathAtom(new MathMacroArgument(n.character()-'0'))); + if (n.character()) + cell->push_back(MathAtom(new MathMacroArgument(n.character()-'0'))); } else if (t.cat() == catActive) - cell->push_back(MathAtom(new InsetMathChar(t.character()))); + cell->push_back(MathAtom(new InsetMathSpace(string(1, t.character()), ""))); else if (t.cat() == catBegin) { MathData ar; @@ -815,9 +950,9 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cat() == catEnd) { if (flags & FLAG_BRACE_LAST) - return; + return success_; error("found '}' unexpectedly"); - //BOOST_ASSERT(false); + //LASSERT(false, /**/); //add(cell, '}', LM_TC_TEX); } @@ -825,7 +960,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, //lyxerr << " column now " << (cellcol + 1) // << " max: " << grid.ncols() << endl; if (flags & FLAG_ALIGN) - return; + return success_; if (addCol(grid, cellcol)) cell = &grid.cell(grid.index(cellrow, cellcol)); } @@ -834,15 +969,15 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, bool up = (t.cat() == catSuper); // we need no new script inset if the last thing was a scriptinset, // which has that script already not the same script already - if (!cell->size()) - cell->push_back(MathAtom(new InsetMathScript(up))); + if (cell->empty()) + cell->push_back(MathAtom(new InsetMathScript(buf, up))); else if (cell->back()->asScriptInset() && !cell->back()->asScriptInset()->has(up)) cell->back().nucleus()->asScriptInset()->ensure(up); else if (cell->back()->asScriptInset()) - cell->push_back(MathAtom(new InsetMathScript(up))); + cell->push_back(MathAtom(new InsetMathScript(buf, up))); else - cell->back() = MathAtom(new InsetMathScript(cell->back(), up)); + cell->back() = MathAtom(new InsetMathScript(buf, cell->back(), up)); InsetMathScript * p = cell->back().nucleus()->asScriptInset(); // special handling of {}-bases // Here we could remove the brace inset for things @@ -856,6 +991,14 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, // in an unreliable way. See this thread // http://www.mail-archive.com/lyx-devel%40lists.lyx.org/msg104917.html // for more details. + // However, we remove empty braces because they look + // ugly on screen and we are sure that they were added + // by the write() method (and will be re-added on save). + if (p->nuc().size() == 1 && + p->nuc().back()->asBraceInset() && + p->nuc().back()->asBraceInset()->cell(0).empty()) + p->nuc().erase(0); + parse(p->cell(p->idxOfScript(up)), FLAG_ITEM, mode); if (limits) { p->limits(limits); @@ -865,11 +1008,27 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.character() == ']' && (flags & FLAG_BRACK_LAST)) { //lyxerr << "finished reading option" << endl; - return; + return success_; } - else if (t.cat() == catOther) - cell->push_back(MathAtom(new InsetMathChar(t.character()))); + else if (t.cat() == catOther) { + char_type c = t.character(); + if (isAsciiOrMathAlpha(c) + || mode_ & Parse::VERBATIM + || !(mode_ & Parse::USETEXT) + || mode == InsetMath::TEXT_MODE) { + cell->push_back(MathAtom(new InsetMathChar(c))); + } else { + MathAtom at = createInsetMath("text", buf); + at.nucleus()->cell(0).push_back(MathAtom(new InsetMathChar(c))); + while (nextToken().cat() == catOther + && !isAsciiOrMathAlpha(nextToken().character())) { + c = getToken().character(); + at.nucleus()->cell(0).push_back(MathAtom(new InsetMathChar(c))); + } + cell->push_back(at); + } + } else if (t.cat() == catComment) { docstring s; @@ -877,9 +1036,9 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, Token const & t = getToken(); if (t.cat() == catNewline) break; - s += t.asString(); + s += t.asInput(); } - cell->push_back(MathAtom(new InsetMathComment(s))); + cell->push_back(MathAtom(new InsetMathComment(buf, s))); skipSpaces(); } @@ -888,164 +1047,197 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, // else if (t.cs() == "lyxlock") { - if (cell->size()) + if (!cell->empty()) cell->back().nucleus()->lock(true); } else if ((t.cs() == "global" && nextToken().cs() == "def") || - t.cs() == "def" || - t.cs() == "newcommand" || - t.cs() == "renewcommand" || - t.cs() == "newlyxcommand" || - t.cs() == "newcommandx" || - t.cs() == "renewcommandx") - { - MacroType type = MacroTypeNewcommand; - if (t.cs() == "global") { + t.cs() == "def") { + if (t.cs() == "global") getToken(); - type = MacroTypeDef; + + // get name + docstring name = getToken().cs(); + + // read parameters + int nargs = 0; + docstring pars; + while (good() && nextToken().cat() != catBegin) { + pars += getToken().cs(); + ++nargs; } - if (t.cs() == "def") - type = MacroTypeDef; - if (t.cs() == "newcommandx" || t.cs() == "renewcommandx") - type = MacroTypeNewcommandx; - docstring name; + nargs /= 2; + + // read definition + MathData def; + parse(def, FLAG_ITEM, InsetMath::UNDECIDED_MODE); + + // is a version for display attached? + skipSpaces(); + MathData display; + if (nextToken().cat() == catBegin) + parse(display, FLAG_ITEM, InsetMath::MATH_MODE); + + cell->push_back(MathAtom(new MathMacroTemplate(buf, + name, nargs, 0, MacroTypeDef, + vector(), def, display))); + + if (buf && (mode_ & Parse::TRACKMACRO)) + buf->usermacros.insert(name); + } + + else if (t.cs() == "newcommand" || + t.cs() == "renewcommand" || + t.cs() == "newlyxcommand") { + // get name + if (getToken().cat() != catBegin) { + error("'{' in \\newcommand expected (1) "); + return success_; + } + docstring name = getToken().cs(); + if (getToken().cat() != catEnd) { + error("'}' in \\newcommand expected"); + return success_; + } + + // get arity + docstring const arg = getArg('[', ']'); int nargs = 0; + if (!arg.empty()) + nargs = convert(arg); + + // optional argument given? + skipSpaces(); int optionals = 0; vector optionalValues; - if (type == MacroTypeDef) { - // get name - name = getToken().cs(); + while (nextToken().character() == '[') { + getToken(); + optionalValues.push_back(MathData()); + parse(optionalValues[optionals], FLAG_BRACK_LAST, mode); + ++optionals; + } - // read parameter - docstring pars; - while (good() && nextToken().cat() != catBegin) { - pars += getToken().cs(); - ++nargs; - } - nargs /= 2; - //lyxerr << "read \\def parameter list '" << pars << "'" << endl; + MathData def; + parse(def, FLAG_ITEM, InsetMath::UNDECIDED_MODE); - } else if (type == MacroTypeNewcommandx) { - // \newcommandx{\foo}[2][usedefault, addprefix=\global,1=default]{#1,#2} - // name - if (nextToken().cat() == catBegin) { - getToken(); - name = getToken().cs(); - if (getToken().cat() != catEnd) { - error("'}' in \\newcommandx expected"); - return; - } - } else - name = getToken().cs(); - - // arity - docstring const arg = getArg('[', ']'); - if (arg.empty()) { - error("[num] in \\newcommandx expected"); - return; - } - nargs = convert(arg); - - // options - if (nextToken().character() == '[') { - // skip '[' - getToken(); - - // handle 'opt=value' options, separated by ','. - skipSpaces(); - while (nextToken().character() != ']' && good()) { - if (nextToken().character() >= '1' - && nextToken().character() <= '9') { - // optional value -> get parameter number - int n = getChar() - '0'; - if (n > nargs) { - error("Arity of \\newcommandx too low for given optional parameter."); - return; - } - - // skip '=' - if (getToken().character() != '=') { - error("'=' and optional parameter value expected for \\newcommandx"); - return; - } - - // get value - optionalValues.resize(max(size_t(n), optionalValues.size())); - optionalValues[n - 1].clear(); - while (nextToken().character() != ']' - && nextToken().character() != ',') { - MathData data; - parse(data, FLAG_ITEM, InsetMath::UNDECIDED_MODE); - optionalValues[n - 1].append(data); - } - optionals = max(n, optionals); - } else if (nextToken().cat() == catLetter) { - // we in fact ignore every non-optional - // parameters - - // get option name - docstring opt; - while (nextToken().cat() == catLetter) - opt += getChar(); - - // value? - skipSpaces(); - MathData value; - if (nextToken().character() == '=') { - getToken(); - while (nextToken().character() != ']' - && nextToken().character() != ',') - parse(value, FLAG_ITEM, InsetMath::UNDECIDED_MODE); - } - } else { - error("option for \\newcommandx expected"); - return; - } + // is a version for display attached? + skipSpaces(); + MathData display; + if (nextToken().cat() == catBegin) + parse(display, FLAG_ITEM, InsetMath::MATH_MODE); - // skip komma - skipSpaces(); - if (nextToken().character() == ',') { - getChar(); - skipSpaces(); - } else if (nextToken().character() != ']') { - error("Expecting ',' or ']' in options of \\newcommandx"); - return; - } - } - - // skip ']' - if (!good()) - return; - getToken(); - } - } else { - if (getToken().cat() != catBegin) { - error("'{' in \\newcommand expected (1) "); - return; - } + cell->push_back(MathAtom(new MathMacroTemplate(buf, + name, nargs, optionals, MacroTypeNewcommand, + optionalValues, def, display))); - name = getToken().cs(); + if (buf && (mode_ & Parse::TRACKMACRO)) + buf->usermacros.insert(name); + } + else if (t.cs() == "newcommandx" || + t.cs() == "renewcommandx") { + // \newcommandx{\foo}[2][usedefault, addprefix=\global,1=default]{#1,#2} + // get name + docstring name; + if (nextToken().cat() == catBegin) { + getToken(); + name = getToken().cs(); if (getToken().cat() != catEnd) { - error("'}' in \\newcommand expected"); - return; + error("'}' in \\newcommandx expected"); + return success_; } + } else + name = getToken().cs(); - docstring const arg = getArg('[', ']'); - if (!arg.empty()) - nargs = convert(arg); + // get arity + docstring const arg = getArg('[', ']'); + if (arg.empty()) { + error("[num] in \\newcommandx expected"); + return success_; + } + int nargs = convert(arg); + + // get options + int optionals = 0; + vector optionalValues; + if (nextToken().character() == '[') { + // skip '[' + getToken(); - // optional argument given? + // handle 'opt=value' options, separated by ','. skipSpaces(); - while (nextToken().character() == '[') { - getToken(); - optionalValues.push_back(MathData()); - parse(optionalValues[optionals], FLAG_BRACK_LAST, mode); - ++optionals; + while (nextToken().character() != ']' && good()) { + if (nextToken().character() >= '1' + && nextToken().character() <= '9') { + // optional value -> get parameter number + int n = getChar() - '0'; + if (n > nargs) { + error("Arity of \\newcommandx too low " + "for given optional parameter."); + return success_; + } + + // skip '=' + if (getToken().character() != '=') { + error("'=' and optional parameter value " + "expected for \\newcommandx"); + return success_; + } + + // get value + int optNum = max(size_t(n), optionalValues.size()); + optionalValues.resize(optNum); + optionalValues[n - 1].clear(); + while (nextToken().character() != ']' + && nextToken().character() != ',') { + MathData data; + parse(data, FLAG_ITEM, InsetMath::UNDECIDED_MODE); + optionalValues[n - 1].append(data); + } + optionals = max(n, optionals); + } else if (nextToken().cat() == catLetter) { + // we in fact ignore every non-optional + // parameter + + // get option name + docstring opt; + while (nextToken().cat() == catLetter) + opt += getChar(); + + // value? + skipSpaces(); + MathData value; + if (nextToken().character() == '=') { + getToken(); + while (nextToken().character() != ']' + && nextToken().character() != ',') + parse(value, FLAG_ITEM, + InsetMath::UNDECIDED_MODE); + } + } else { + error("option for \\newcommandx expected"); + return success_; + } + + // skip komma + skipSpaces(); + if (nextToken().character() == ',') { + getChar(); + skipSpaces(); + } else if (nextToken().character() != ']') { + error("Expecting ',' or ']' in options " + "of \\newcommandx"); + return success_; + } } + + // skip ']' + if (!good()) + return success_; + getToken(); } + // get definition MathData def; parse(def, FLAG_ITEM, InsetMath::UNDECIDED_MODE); @@ -1055,17 +1247,31 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, if (nextToken().cat() == catBegin) parse(display, FLAG_ITEM, InsetMath::MATH_MODE); - cell->push_back(MathAtom(new MathMacroTemplate(name, nargs, - optionals, type, optionalValues, def, display))); + cell->push_back(MathAtom(new MathMacroTemplate(buf, + name, nargs, optionals, MacroTypeNewcommandx, + optionalValues, def, display))); + + if (buf && (mode_ & Parse::TRACKMACRO)) + buf->usermacros.insert(name); } else if (t.cs() == "(") { - cell->push_back(MathAtom(new InsetMathHull(hullSimple))); - parse2(cell->back(), FLAG_SIMPLE2, InsetMath::MATH_MODE, false); + if (mode == InsetMath::UNDECIDED_MODE) { + cell->push_back(MathAtom(new InsetMathHull(buf, hullSimple))); + parse2(cell->back(), FLAG_SIMPLE2, InsetMath::MATH_MODE, false); + } else { + // Don't create nested math hulls (bug #5392) + cell->push_back(MathAtom(new InsetMathEnsureMath(buf))); + parse(cell->back().nucleus()->cell(0), FLAG_SIMPLE2, InsetMath::MATH_MODE); + } } else if (t.cs() == "[") { - cell->push_back(MathAtom(new InsetMathHull(hullEquation))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment ["); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullEquation))); parse2(cell->back(), FLAG_EQUATION, InsetMath::MATH_MODE, false); } @@ -1094,9 +1300,9 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, // probably need to refine this test. // Right now we only have to test for // single line hull insets. - if (grid.nrows() > 1) + if (grid.nrows() > 1 && innerHull(name)) delEmptyLastRow(grid); - return; + return success_; } } else error("found 'end' unexpectedly"); @@ -1104,27 +1310,49 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cs() == ")") { if (flags & FLAG_SIMPLE2) - return; + return success_; error("found '\\)' unexpectedly"); } else if (t.cs() == "]") { if (flags & FLAG_EQUATION) - return; + return success_; error("found '\\]' unexpectedly"); } else if (t.cs() == "\\") { if (flags & FLAG_ALIGN) - return; - bool added = false; + return success_; + bool starred = false; + docstring arg; if (nextToken().asInput() == "*") { getToken(); - added = addRow(grid, cellrow, docstring(), false); - } else if (good()) - added = addRow(grid, cellrow, getArg('[', ']')); - else + starred = true; + } else if (nextToken().asInput() == "[") + arg = getArg('[', ']'); + else if (!good()) error("missing token after \\\\"); + // skip "{}" added in front of "[" (the + // counterpart is in InsetMathGrid::eolString()) + // skip spaces because formula could come from tex2lyx + bool skipBraces = false; + pushPosition(); + if (nextToken().cat() == catBegin) { + getToken(); + if (nextToken().cat() == catEnd) { + getToken(); + pushPosition(); + skipSpaces(); + if (nextToken().asInput() == "[") + skipBraces = true; + popPosition(); + } + } + if (skipBraces) + dropPosition(); + else + popPosition(); + bool const added = addRow(grid, cellrow, arg, !starred); if (added) { cellcol = 0; if (grid.asHullInset()) @@ -1135,54 +1363,61 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, } } -#if 0 - else if (t.cs() == "multicolumn") { - // extract column count and insert dummy cells + else if (t.cs() == "multicolumn" && grid.handlesMulticolumn()) { + // if the columns are specified numerically, + // extract column count and insert dummy cells, + // otherwise parse it as an user macro MathData count; parse(count, FLAG_ITEM, mode); - int cols = 1; - if (!extractNumber(count, cols)) { - lyxerr << " can't extract number of cells from " << count << endl; - } - // resize the table if necessary - for (int i = 0; i < cols; ++i) { - if (addCol(grid, cellcol)) { - cell = &grid.cell(grid.index( - cellrow, cellcol)); - // mark this as dummy - grid.cellinfo(grid.index( - cellrow, cellcol)).dummy_ = true; + int cols; + if (extractNumber(count, cols)) { + // resize the table if necessary + size_t first = grid.index(cellrow, cellcol); + for (int i = 1; i < cols; ++i) { + if (addCol(grid, cellcol)) { + size_t const idx = grid.index(cellrow, cellcol); + grid.cellinfo(idx).multi_ = + InsetMathGrid::CELL_PART_OF_MULTICOLUMN; + } } - } - // the last cell is the real thing, not a dummy - grid.cellinfo(grid.index(cellrow, cellcol)).dummy_ = false; - - // read special alignment - MathData align; - parse(align, FLAG_ITEM, mode); - //grid.cellinfo(grid.index(cellrow, cellcol)).align_ = extractString(align); - // parse the remaining contents into the "real" cell - parse(*cell, FLAG_ITEM, mode); - } -#endif + // the first cell is the real thing, not a dummy + cell = &grid.cell(first); + grid.cellinfo(first).multi_ = + InsetMathGrid::CELL_BEGIN_OF_MULTICOLUMN; - else if (t.cs() == "limits") - limits = 1; + // read special alignment + MathData align; + parse(align, FLAG_ITEM, mode); + grid.cellinfo(first).align_ = asString(align); - else if (t.cs() == "nolimits") - limits = -1; - - else if (t.cs() == "nonumber") { - if (grid.asHullInset()) - grid.asHullInset()->numbered(cellrow, false); + // parse the remaining contents into the "real" cell + parse(*cell, FLAG_ITEM, mode); + } else { + MathAtom at = MathAtom(new MathMacro(buf, t.cs())); + cell->push_back(at); + cell->push_back(MathAtom(new InsetMathBrace(count))); + } } - else if (t.cs() == "number") { - if (grid.asHullInset()) - grid.asHullInset()->numbered(cellrow, true); + else if (t.cs() == "limits" || t.cs() == "nolimits") { + CatCode const cat = nextToken().cat(); + if (cat == catSuper || cat == catSub) + limits = t.cs() == "limits" ? 1 : -1; + else { + MathAtom at = createInsetMath(t.cs(), buf); + cell->push_back(at); + } } + // \notag is the same as \nonumber if amsmath is used + else if ((t.cs() == "nonumber" || t.cs() == "notag") && + grid.asHullInset()) + grid.asHullInset()->numbered(cellrow, false); + + else if (t.cs() == "number" && grid.asHullInset()) + grid.asHullInset()->numbered(cellrow, true); + else if (t.cs() == "hline") { grid.rowinfo(cellrow).lines_ ++; } @@ -1190,56 +1425,140 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cs() == "sqrt") { MathData ar; parse(ar, FLAG_OPTION, mode); - if (ar.size()) { - cell->push_back(MathAtom(new InsetMathRoot)); + if (!ar.empty()) { + cell->push_back(MathAtom(new InsetMathRoot(buf))); cell->back().nucleus()->cell(0) = ar; parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode); } else { - cell->push_back(MathAtom(new InsetMathSqrt)); + cell->push_back(MathAtom(new InsetMathSqrt(buf))); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); } } + else if (t.cs() == "cancelto") { + MathData ar; + parse(ar, FLAG_ITEM, mode); + cell->push_back(MathAtom(new InsetMathCancelto(buf))); + cell->back().nucleus()->cell(1) = ar; + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + } + else if (t.cs() == "unit") { // Allowed formats \unit[val]{unit} MathData ar; parse(ar, FLAG_OPTION, mode); - if (ar.size()) { - cell->push_back(MathAtom(new InsetMathFrac(InsetMathFrac::UNIT))); + if (!ar.empty()) { + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::UNIT))); cell->back().nucleus()->cell(0) = ar; parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode); } else { - cell->push_back(MathAtom(new InsetMathFrac(InsetMathFrac::UNIT, 1))); + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::UNIT, 1))); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); } } + else if (t.cs() == "unitfrac") { // Here allowed formats are \unitfrac[val]{num}{denom} MathData ar; parse(ar, FLAG_OPTION, mode); - if (ar.size()) { - cell->push_back(MathAtom(new InsetMathFrac(InsetMathFrac::UNITFRAC, 3))); + if (!ar.empty()) { + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::UNITFRAC, 3))); cell->back().nucleus()->cell(2) = ar; } else { - cell->push_back(MathAtom(new InsetMathFrac(InsetMathFrac::UNITFRAC))); + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::UNITFRAC))); } parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode); } + else if (t.cs() == "cfrac") { + // allowed formats are \cfrac[pos]{num}{denom} + docstring const arg = getArg('[', ']'); + //lyxerr << "got so far: '" << arg << "'" << endl; + if (arg == "l") + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::CFRACLEFT))); + else if (arg == "r") + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::CFRACRIGHT))); + else if (arg.empty() || arg == "c") + cell->push_back(MathAtom(new InsetMathFrac(buf, InsetMathFrac::CFRAC))); + else { + error("found invalid optional argument"); + break; + } + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode); + } + + else if (t.cs() == "sideset") { + // Here allowed formats are \sideset{_{bl}^{tl}}{_{br}^{tr}}{operator} + MathData ar[2]; + InsetMathScript * script[2] = {0, 0}; + for (int i = 0; i < 2; ++i) { + parse(ar[i], FLAG_ITEM, mode); + if (ar[i].size() == 1) + script[i] = ar[i][0].nucleus()->asScriptInset(); + } + bool const hasscript[2] = {script[0] ? true : false, script[1] ? true : false}; + cell->push_back(MathAtom(new InsetMathSideset(buf, hasscript[0], hasscript[1]))); + if (hasscript[0]) { + if (script[0]->hasDown()) + cell->back().nucleus()->cell(1) = script[0]->down(); + if (script[0]->hasUp()) + cell->back().nucleus()->cell(2) = script[0]->up(); + } else + cell->back().nucleus()->cell(1) = ar[0]; + if (hasscript[1]) { + if (script[1]->hasDown()) + cell->back().nucleus()->cell(2 + hasscript[0]) = script[1]->down(); + if (script[1]->hasUp()) + cell->back().nucleus()->cell(3 + hasscript[0]) = script[1]->up(); + } else + cell->back().nucleus()->cell(2 + hasscript[0]) = ar[1]; + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + } + + else if (t.cs() == "stackrel") { + // Here allowed formats are \stackrel[subscript]{superscript}{operator} + MathData ar; + parse(ar, FLAG_OPTION, mode); + cell->push_back(MathAtom(new InsetMathStackrel(buf, !ar.empty()))); + if (!ar.empty()) + cell->back().nucleus()->cell(2) = ar; + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode); + } + else if (t.cs() == "xrightarrow" || t.cs() == "xleftarrow") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(1), FLAG_OPTION, mode); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); } - else if (t.cs() == "ref" || t.cs() == "prettyref" || - t.cs() == "pageref" || t.cs() == "vpageref" || t.cs() == "vref") { - cell->push_back(MathAtom(new InsetMathRef(t.cs()))); + else if (t.cs() == "xhookrightarrow" || t.cs() == "xhookleftarrow" || + t.cs() == "xRightarrow" || t.cs() == "xLeftarrow" || + t.cs() == "xleftrightarrow" || t.cs() == "xLeftrightarrow" || + t.cs() == "xrightharpoondown" || t.cs() == "xrightharpoonup" || + t.cs() == "xleftharpoondown" || t.cs() == "xleftharpoonup" || + t.cs() == "xleftrightharpoons" || t.cs() == "xrightleftharpoons" || + t.cs() == "xmapsto") { + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(1), FLAG_OPTION, mode); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); } + else if (t.cs() == "ref" || t.cs() == "eqref" || t.cs() == "prettyref" + || t.cs() == "pageref" || t.cs() == "vpageref" || t.cs() == "vref") { + cell->push_back(MathAtom(new InsetMathRef(buf, t.cs()))); + docstring const opt = parse_verbatim_option(); + docstring const ref = parse_verbatim_item(); + if (!opt.empty()) { + cell->back().nucleus()->cell(1).push_back( + MathAtom(new InsetMathString(opt))); + } + cell->back().nucleus()->cell(0).push_back( + MathAtom(new InsetMathString(ref))); + } + else if (t.cs() == "left") { skipSpaces(); Token const & tl = getToken(); @@ -1254,37 +1573,46 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, skipSpaces(); Token const & tr = getToken(); docstring const r = tr.cs() == "|" ? from_ascii("Vert") : tr.asString(); - cell->push_back(MathAtom(new InsetMathDelim(l, r, ar))); + cell->push_back(MathAtom(new InsetMathDelim(buf, l, r, ar))); } else if (t.cs() == "right") { if (flags & FLAG_RIGHT) - return; + return success_; //lyxerr << "got so far: '" << cell << "'" << endl; error("Unmatched right delimiter"); - return; + return success_; } else if (t.cs() == "begin") { docstring const name = getArg('{', '}'); + + if (name.empty()) { + success_ = false; + error("found invalid environment"); + return success_; + } + environments_.push_back(name); if (name == "array" || name == "subarray") { docstring const valign = parse_verbatim_option() + 'c'; docstring const halign = parse_verbatim_item(); - cell->push_back(MathAtom(new InsetMathArray(name, (char)valign[0], halign))); + cell->push_back(MathAtom(new InsetMathArray(buf, name, + InsetMathGrid::guessColumns(halign), 1, (char)valign[0], halign))); parse2(cell->back(), FLAG_END, mode, false); } else if (name == "tabular") { docstring const valign = parse_verbatim_option() + 'c'; docstring const halign = parse_verbatim_item(); - cell->push_back(MathAtom(new InsetMathTabular(name, (char)valign[0], halign))); + cell->push_back(MathAtom(new InsetMathTabular(buf, name, + InsetMathGrid::guessColumns(halign), 1, (char)valign[0], halign))); parse2(cell->back(), FLAG_END, InsetMath::TEXT_MODE, false); } else if (name == "split" || name == "cases") { - cell->push_back(createInsetMath(name)); + cell->push_back(createInsetMath(name, buf)); parse2(cell->back(), FLAG_END, mode, false); } @@ -1292,92 +1620,145 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, docstring const valign = parse_verbatim_option() + 'c'; // ignore this for a while getArg('{', '}'); - cell->push_back(MathAtom(new InsetMathSplit(name, (char)valign[0]))); + cell->push_back(MathAtom(new InsetMathSplit(buf, name, (char)valign[0]))); parse2(cell->back(), FLAG_END, mode, false); } else if (name == "math") { - cell->push_back(MathAtom(new InsetMathHull(hullSimple))); - parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, true); + if (mode == InsetMath::UNDECIDED_MODE) { + cell->push_back(MathAtom(new InsetMathHull(buf, hullSimple))); + parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, false); + } else { + // Don't create nested math hulls (bug #5392) + cell->push_back(MathAtom(new InsetMathEnsureMath(buf))); + parse(cell->back().nucleus()->cell(0), FLAG_END, InsetMath::MATH_MODE); + } } else if (name == "equation" || name == "equation*" || name == "displaymath") { - cell->push_back(MathAtom(new InsetMathHull(hullEquation))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullEquation))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, (name == "equation")); } else if (name == "eqnarray" || name == "eqnarray*") { - cell->push_back(MathAtom(new InsetMathHull(hullEqnArray))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullEqnArray))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "align" || name == "align*") { - cell->push_back(MathAtom(new InsetMathHull(hullAlign))); - parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); + if (mode == InsetMath::UNDECIDED_MODE) { + cell->push_back(MathAtom(new InsetMathHull(buf, hullAlign))); + parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); + } else { + cell->push_back(MathAtom(new InsetMathSplit(buf, name, + 'c', !stared(name)))); + parse2(cell->back(), FLAG_END, mode, !stared(name)); + } } else if (name == "flalign" || name == "flalign*") { - cell->push_back(MathAtom(new InsetMathHull(hullFlAlign))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullFlAlign))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "alignat" || name == "alignat*") { + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } // ignore this for a while getArg('{', '}'); - cell->push_back(MathAtom(new InsetMathHull(hullAlignAt))); + cell->push_back(MathAtom(new InsetMathHull(buf, hullAlignAt))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "xalignat" || name == "xalignat*") { + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } // ignore this for a while getArg('{', '}'); - cell->push_back(MathAtom(new InsetMathHull(hullXAlignAt))); + cell->push_back(MathAtom(new InsetMathHull(buf, hullXAlignAt))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "xxalignat") { + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } // ignore this for a while getArg('{', '}'); - cell->push_back(MathAtom(new InsetMathHull(hullXXAlignAt))); + cell->push_back(MathAtom(new InsetMathHull(buf, hullXXAlignAt))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "multline" || name == "multline*") { - cell->push_back(MathAtom(new InsetMathHull(hullMultline))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullMultline))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (name == "gather" || name == "gather*") { - cell->push_back(MathAtom(new InsetMathHull(hullGather))); + if (mode != InsetMath::UNDECIDED_MODE) { + error("bad math environment " + name); + break; + } + cell->push_back(MathAtom(new InsetMathHull(buf, hullGather))); parse2(cell->back(), FLAG_END, InsetMath::MATH_MODE, !stared(name)); } else if (latexkeys const * l = in_word_set(name)) { if (l->inset == "matrix") { - cell->push_back(createInsetMath(name)); + cell->push_back(createInsetMath(name, buf)); parse2(cell->back(), FLAG_END, mode, false); } else if (l->inset == "split") { docstring const valign = parse_verbatim_option() + 'c'; - cell->push_back(MathAtom(new InsetMathSplit(name, (char)valign[0]))); + cell->push_back(MathAtom( + new InsetMathSplit(buf, name, (char)valign[0]))); parse2(cell->back(), FLAG_END, mode, false); } else { - dump(); - lyxerr << "found math environment `" << to_utf8(name) - << "' in symbols file with unsupported inset `" - << to_utf8(l->inset) << "'." << endl; + success_ = false; + if (!(mode_ & Parse::QUIET)) { + dump(); + lyxerr << "found math environment `" + << to_utf8(name) + << "' in symbols file with unsupported inset `" + << l->inset + << "'." << endl; + } // create generic environment inset - cell->push_back(MathAtom(new InsetMathEnv(name))); + cell->push_back(MathAtom(new InsetMathEnv(buf, name))); parse(cell->back().nucleus()->cell(0), FLAG_END, mode); } } else { - dump(); - lyxerr << "found unknown math environment '" << to_utf8(name) - << "'" << endl; + success_ = false; + if (!(mode_ & Parse::QUIET)) { + dump(); + lyxerr << "found unknown math environment '" + << to_utf8(name) << "'" << endl; + } // create generic environment inset - cell->push_back(MathAtom(new InsetMathEnv(name))); + cell->push_back(MathAtom(new InsetMathEnv(buf, name))); parse(cell->back().nucleus()->cell(0), FLAG_END, mode); } } @@ -1385,17 +1766,24 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cs() == "kern") { // FIXME: A hack... docstring s; + int num_tokens = 0; while (true) { Token const & t = getToken(); + ++num_tokens; if (!good()) { - putback(); + s.clear(); + while (num_tokens--) + putback(); break; } s += t.character(); if (isValidLength(to_utf8(s))) break; } - cell->push_back(MathAtom(new InsetMathKern(s))); + if (s.empty()) + cell->push_back(MathAtom(new InsetMathKern)); + else + cell->push_back(MathAtom(new InsetMathKern(s))); } else if (t.cs() == "label") { @@ -1406,54 +1794,74 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, if (grid.asHullInset()) { grid.asHullInset()->label(cellrow, label); } else { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); cell->push_back(MathAtom(new InsetMathBrace(ar))); } } - else if (t.cs() == "choose" || t.cs() == "over" || t.cs() == "atop") { - MathAtom at = createInsetMath(t.cs()); + else if (t.cs() == "choose" || t.cs() == "over" + || t.cs() == "atop" || t.cs() == "brace" + || t.cs() == "brack") { + MathAtom at = createInsetMath(t.cs(), buf); at.nucleus()->cell(0) = *cell; cell->clear(); parse(at.nucleus()->cell(1), flags, mode); cell->push_back(at); - return; + return success_; } else if (t.cs() == "color") { docstring const color = parse_verbatim_item(); - cell->push_back(MathAtom(new InsetMathColor(true, color))); + cell->push_back(MathAtom(new InsetMathColor(buf, true, color))); parse(cell->back().nucleus()->cell(0), flags, mode); - return; + return success_; } else if (t.cs() == "textcolor") { docstring const color = parse_verbatim_item(); - cell->push_back(MathAtom(new InsetMathColor(false, color))); + cell->push_back(MathAtom(new InsetMathColor(buf, false, color))); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, InsetMath::TEXT_MODE); } else if (t.cs() == "normalcolor") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), flags, mode); - return; + return success_; } else if (t.cs() == "substack") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse2(cell->back(), FLAG_ITEM, mode, false); + // Delete empty last row if present + InsetMathGrid & subgrid = + *(cell->back().nucleus()->asGridInset()); + if (subgrid.nrows() > 1) + delEmptyLastRow(subgrid); } else if (t.cs() == "xymatrix") { odocstringstream os; while (good() && nextToken().cat() != catBegin) os << getToken().asInput(); - cell->push_back(createInsetMath(t.cs() + os.str())); + cell->push_back(createInsetMath(t.cs() + os.str(), buf)); + parse2(cell->back(), FLAG_ITEM, mode, false); + // Delete empty last row if present + InsetMathGrid & subgrid = + *(cell->back().nucleus()->asGridInset()); + if (subgrid.nrows() > 1) + delEmptyLastRow(subgrid); + } + + else if (t.cs() == "Diagram") { + odocstringstream os; + while (good() && nextToken().cat() != catBegin) + os << getToken().asInput(); + cell->push_back(createInsetMath(t.cs() + os.str(), buf)); parse2(cell->back(), FLAG_ITEM, mode, false); } else if (t.cs() == "framebox" || t.cs() == "makebox") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), FLAG_OPTION, InsetMath::TEXT_MODE); parse(cell->back().nucleus()->cell(1), FLAG_OPTION, InsetMath::TEXT_MODE); parse(cell->back().nucleus()->cell(2), FLAG_ITEM, InsetMath::TEXT_MODE); @@ -1462,41 +1870,121 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, else if (t.cs() == "tag") { if (nextToken().character() == '*') { getToken(); - cell->push_back(createInsetMath(t.cs() + '*')); + cell->push_back(createInsetMath(t.cs() + '*', buf)); } else - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, InsetMath::TEXT_MODE); } + else if (t.cs() == "hspace") { + bool const prot = nextToken().character() == '*'; + if (prot) + getToken(); + docstring const name = t.cs(); + docstring const arg = parse_verbatim_item(); + Length length; + if (prot && arg == "\\fill") + cell->push_back(MathAtom(new InsetMathSpace("hspace*{\\fill}", ""))); + else if (isValidLength(to_utf8(arg), &length)) + cell->push_back(MathAtom(new InsetMathSpace(length, prot))); + else { + // Since the Length class cannot use length variables + // we must not create an InsetMathSpace. + cell->push_back(MathAtom(new MathMacro(buf, name))); + MathData ar; + mathed_parse_cell(ar, '{' + arg + '}', mode_); + cell->append(ar); + } + } + + else if (t.cs() == "smash") { + skipSpaces(); + if (nextToken().asInput() == "[") { + // Since the phantom inset cannot handle optional arguments + // other than b and t, we must not create an InsetMathPhantom + // if opt is different from b and t (bug 8967). + docstring const opt = parse_verbatim_option(); + if (opt == "t" || opt == "b") { + cell->push_back(createInsetMath(t.cs() + opt, buf)); + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + } else { + docstring const arg = parse_verbatim_item(); + cell->push_back(MathAtom(new MathMacro(buf, t.cs()))); + MathData ar; + mathed_parse_cell(ar, '[' + opt + ']', mode_); + cell->append(ar); + ar = MathData(); + mathed_parse_cell(ar, '{' + arg + '}', mode_); + cell->append(ar); + } + } + else { + cell->push_back(createInsetMath(t.cs(), buf)); + parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode); + } + } + #if 0 else if (t.cs() == "infer") { MathData ar; parse(ar, FLAG_OPTION, mode); - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse2(cell->back(), FLAG_ITEM, mode, false); } +#endif - // Disabled - else if (1 && t.cs() == "ar") { - auto_ptr p(new InsetMathXYArrow); - // try to read target - parse(p->cell(0), FLAG_OTPTION, mode); - // try to read label - if (nextToken().cat() == catSuper || nextToken().cat() == catSub) { - p->up_ = nextToken().cat() == catSuper; - getToken(); - parse(p->cell(1), FLAG_ITEM, mode); - //lyxerr << "read label: " << p->cell(1) << endl; + else if (t.cs() == "lyxmathsym") { + skipSpaces(); + if (getToken().cat() != catBegin) { + error("'{' expected in \\" + t.cs()); + return success_; } - - cell->push_back(MathAtom(p.release())); - //lyxerr << "read cell: " << cell << endl; + int count = 0; + docstring cmd; + CatCode cat = nextToken().cat(); + while (good() && (count || cat != catEnd)) { + if (cat == catBegin) + ++count; + else if (cat == catEnd) + --count; + cmd += getToken().asInput(); + cat = nextToken().cat(); + } + if (getToken().cat() != catEnd) { + error("'}' expected in \\" + t.cs()); + return success_; + } + bool termination; + docstring rem; + do { + cmd = Encodings::fromLaTeXCommand(cmd, + Encodings::MATH_CMD | Encodings::TEXT_CMD, + termination, rem); + for (size_t i = 0; i < cmd.size(); ++i) + cell->push_back(MathAtom(new InsetMathChar(cmd[i]))); + if (!rem.empty()) { + char_type c = rem[0]; + cell->push_back(MathAtom(new InsetMathChar(c))); + cmd = rem.substr(1); + rem.clear(); + } else + cmd.clear(); + } while (!cmd.empty()); } -#endif - else if (t.cs().size()) { + else if (!t.cs().empty()) { + bool const no_mhchem = + (t.cs() == "ce" || t.cs() == "cf") + && buf && buf->params().use_package("mhchem") == + BufferParams::package_off; + + bool const is_user_macro = no_mhchem || + (buf && (mode_ & Parse::TRACKMACRO + ? buf->usermacros.count(t.cs()) != 0 + : buf->getMacro(t.cs(), false) != 0)); + latexkeys const * l = in_word_set(t.cs()); - if (l) { + if (l && !is_user_macro) { if (l->inset == "big") { skipSpaces(); docstring const delim = getToken().asInput(); @@ -1504,39 +1992,39 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, cell->push_back(MathAtom( new InsetMathBig(t.cs(), delim))); else { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); putback(); } } else if (l->inset == "font") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), FLAG_ITEM, asMode(mode, l->extra)); } else if (l->inset == "oldfont") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), flags | FLAG_ALIGN, asMode(mode, l->extra)); if (prevToken().cat() != catAlign && prevToken().cs() != "\\") - return; + return success_; putback(); } else if (l->inset == "style") { - cell->push_back(createInsetMath(t.cs())); + cell->push_back(createInsetMath(t.cs(), buf)); parse(cell->back().nucleus()->cell(0), flags | FLAG_ALIGN, mode); if (prevToken().cat() != catAlign && prevToken().cs() != "\\") - return; + return success_; putback(); } else { - MathAtom at = createInsetMath(t.cs()); + MathAtom at = createInsetMath(t.cs(), buf); for (InsetMath::idx_type i = 0; i < at->nargs(); ++i) parse(at.nucleus()->cell(i), FLAG_ITEM, asMode(mode, l->extra)); @@ -1545,26 +2033,87 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, } else { - MathAtom at = createInsetMath(t.cs()); - InsetMath::mode_type m = mode; - //if (m == InsetMath::UNDECIDED_MODE) - //lyxerr << "default creation: m1: " << m << endl; - if (at->currentMode() != InsetMath::UNDECIDED_MODE) - m = at->currentMode(); - //lyxerr << "default creation: m2: " << m << endl; - InsetMath::idx_type start = 0; - // this fails on \bigg[...\bigg] - //MathData opt; - //parse(opt, FLAG_OPTION, InsetMath::VERBATIM_MODE); - //if (opt.size()) { - // start = 1; - // at.nucleus()->cell(0) = opt; - //} - for (InsetMath::idx_type i = start; i < at->nargs(); ++i) { - parse(at.nucleus()->cell(i), FLAG_ITEM, m); - skipSpaces(); + bool is_unicode_symbol = false; + if (mode == InsetMath::TEXT_MODE && !is_user_macro) { + int num_tokens = 0; + docstring cmd = prevToken().asInput(); + CatCode cat = nextToken().cat(); + if (cat == catBegin) { + int count = 0; + while (good() && (count || cat != catEnd)) { + cat = nextToken().cat(); + cmd += getToken().asInput(); + ++num_tokens; + if (cat == catBegin) + ++count; + else if (cat == catEnd) + --count; + } + } + bool is_combining; + bool termination; + char_type c = Encodings::fromLaTeXCommand(cmd, + Encodings::MATH_CMD | Encodings::TEXT_CMD, + is_combining, termination); + if (is_combining) { + if (cat == catLetter) + cmd += '{'; + cmd += getToken().asInput(); + ++num_tokens; + if (cat == catLetter) + cmd += '}'; + c = Encodings::fromLaTeXCommand(cmd, + Encodings::MATH_CMD | Encodings::TEXT_CMD, + is_combining, termination); + } + if (c) { + if (termination) { + if (nextToken().cat() == catBegin) { + getToken(); + if (nextToken().cat() == catEnd) { + getToken(); + num_tokens += 2; + } else + putback(); + } else { + while (nextToken().cat() == catSpace) { + getToken(); + ++num_tokens; + } + } + } + is_unicode_symbol = true; + cell->push_back(MathAtom(new InsetMathChar(c))); + } else { + while (num_tokens--) + putback(); + } + } + if (!is_unicode_symbol) { + MathAtom at = is_user_macro ? + MathAtom(new MathMacro(buf, t.cs())) + : createInsetMath(t.cs(), buf); + InsetMath::mode_type m = mode; + //if (m == InsetMath::UNDECIDED_MODE) + //lyxerr << "default creation: m1: " << m << endl; + if (at->currentMode() != InsetMath::UNDECIDED_MODE) + m = at->currentMode(); + //lyxerr << "default creation: m2: " << m << endl; + InsetMath::idx_type start = 0; + // this fails on \bigg[...\bigg] + //MathData opt; + //parse(opt, FLAG_OPTION, InsetMath::VERBATIM_MODE); + //if (!opt.empty()) { + // start = 1; + // at.nucleus()->cell(0) = opt; + //} + for (InsetMath::idx_type i = start; i < at->nargs(); ++i) { + parse(at.nucleus()->cell(i), FLAG_ITEM, m); + if (mode == InsetMath::MATH_MODE) + skipSpaces(); + } + cell->push_back(at); } - cell->push_back(at); } } @@ -1574,6 +2123,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, break; } } + return success_; } @@ -1581,33 +2131,48 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, } // anonymous namespace -void mathed_parse_cell(MathData & ar, docstring const & str) +// FIXME This will likely need some work. +char const * latexkeys::MathMLtype() const +{ + if (extra == "mathord") + return "mi"; + return "mo"; +} + + +bool mathed_parse_cell(MathData & ar, docstring const & str, Parse::flags f) { - Parser(str).parse(ar, 0, InsetMath::MATH_MODE); + return Parser(str, f, ar.buffer()).parse(ar, 0, f & Parse::TEXTMODE ? + InsetMath::TEXT_MODE : InsetMath::MATH_MODE); } -void mathed_parse_cell(MathData & ar, istream & is) +bool mathed_parse_cell(MathData & ar, istream & is, Parse::flags f) { - Parser(is).parse(ar, 0, InsetMath::MATH_MODE); + return Parser(is, f, ar.buffer()).parse(ar, 0, f & Parse::TEXTMODE ? + InsetMath::TEXT_MODE : InsetMath::MATH_MODE); } -bool mathed_parse_normal(MathAtom & t, docstring const & str) +bool mathed_parse_normal(Buffer * buf, MathAtom & t, docstring const & str, + Parse::flags f) { - return Parser(str).parse(t); + return Parser(str, f, buf).parse(t); } -bool mathed_parse_normal(MathAtom & t, Lexer & lex) +bool mathed_parse_normal(Buffer * buf, MathAtom & t, Lexer & lex, + Parse::flags f) { - return Parser(lex).parse(t); + return Parser(lex, f, buf).parse(t); } -void mathed_parse_normal(InsetMathGrid & grid, docstring const & str) +bool mathed_parse_normal(InsetMathGrid & grid, docstring const & str, + Parse::flags f) { - Parser(str).parse1(grid, 0, InsetMath::MATH_MODE, false); + return Parser(str, f, &grid.buffer()).parse1(grid, 0, f & Parse::TEXTMODE ? + InsetMath::TEXT_MODE : InsetMath::MATH_MODE, false); }