/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include "parse.hxx" #include "starmath.hrc" #include "smdll.hxx" #include "smmod.hxx" #include "config.hxx" #include "node.hxx" using namespace ::com::sun::star; using namespace ::com::sun::star::i18n; namespace { template < typename T > T* lcl_popOrZero( ::std::stack & rStack ) { if (rStack.empty()) return 0; T* pTmp = rStack.top(); rStack.pop(); return pTmp; } } SmToken::SmToken() : eType (TUNKNOWN), cMathChar ('\0') { nGroup = nCol = nRow = nLevel = 0; } SmToken::SmToken(SmTokenType eTokenType, sal_Unicode cMath, const sal_Char* pText, sal_uLong nTokenGroup, sal_uInt16 nTokenLevel) { eType = eTokenType; cMathChar = cMath; aText = OUString::createFromAscii(pText); nGroup = nTokenGroup; nLevel = nTokenLevel; nCol = nRow = 0; } static const SmTokenTableEntry aTokenTable[] = { { "Im" , TIM, MS_IM, TGSTANDALONE, 5 }, { "MZ23", TDEBUG, '\0', TGATTRIBUT, 0 }, { "Re" , TRE, MS_RE, TGSTANDALONE, 5 }, { "abs", TABS, '\0', TGUNOPER, 13 }, { "arcosh", TACOSH, '\0', TGFUNCTION, 5 }, { "arcoth", TACOTH, '\0', TGFUNCTION, 5 }, { "acute", TACUTE, MS_ACUTE, TGATTRIBUT, 5 }, { "aleph" , TALEPH, MS_ALEPH, TGSTANDALONE, 5 }, { "alignb", TALIGNC, '\0', TGALIGN | TGDISCARDED, 0}, { "alignc", TALIGNC, '\0', TGALIGN, 0}, { "alignl", TALIGNL, '\0', TGALIGN, 0}, { "alignm", TALIGNC, '\0', TGALIGN | TGDISCARDED, 0}, { "alignr", TALIGNR, '\0', TGALIGN, 0}, { "alignt", TALIGNC, '\0', TGALIGN | TGDISCARDED, 0}, { "and", TAND, MS_AND, TGPRODUCT, 0}, { "approx", TAPPROX, MS_APPROX, TGRELATION, 0}, { "arccos", TACOS, '\0', TGFUNCTION, 5}, { "arccot", TACOT, '\0', TGFUNCTION, 5}, { "arcsin", TASIN, '\0', TGFUNCTION, 5}, { "arctan", TATAN, '\0', TGFUNCTION, 5}, { "arsinh", TASINH, '\0', TGFUNCTION, 5}, { "artanh", TATANH, '\0', TGFUNCTION, 5}, { "backepsilon" , TBACKEPSILON, MS_BACKEPSILON, TGSTANDALONE, 5}, { "bar", TBAR, MS_BAR, TGATTRIBUT, 5}, { "binom", TBINOM, '\0', 0, 5 }, { "black", TBLACK, '\0', TGCOLOR, 0}, { "blue", TBLUE, '\0', TGCOLOR, 0}, { "bold", TBOLD, '\0', TGFONTATTR, 5}, { "boper", TBOPER, '\0', TGPRODUCT, 0}, { "breve", TBREVE, MS_BREVE, TGATTRIBUT, 5}, { "bslash", TBACKSLASH, MS_BACKSLASH, TGPRODUCT, 0 }, { "cdot", TCDOT, MS_CDOT, TGPRODUCT, 0}, { "check", TCHECK, MS_CHECK, TGATTRIBUT, 5}, { "circ" , TCIRC, MS_CIRC, TGSTANDALONE, 5}, { "circle", TCIRCLE, MS_CIRCLE, TGATTRIBUT, 5}, { "color", TCOLOR, '\0', TGFONTATTR, 5}, { "coprod", TCOPROD, MS_COPROD, TGOPER, 5}, { "cos", TCOS, '\0', TGFUNCTION, 5}, { "cosh", TCOSH, '\0', TGFUNCTION, 5}, { "cot", TCOT, '\0', TGFUNCTION, 5}, { "coth", TCOTH, '\0', TGFUNCTION, 5}, { "csub", TCSUB, '\0', TGPOWER, 0}, { "csup", TCSUP, '\0', TGPOWER, 0}, { "cyan", TCYAN, '\0', TGCOLOR, 0}, { "dddot", TDDDOT, MS_DDDOT, TGATTRIBUT, 5}, { "ddot", TDDOT, MS_DDOT, TGATTRIBUT, 5}, { "def", TDEF, MS_DEF, TGRELATION, 0}, { "div", TDIV, MS_DIV, TGPRODUCT, 0}, { "divides", TDIVIDES, MS_LINE, TGRELATION, 0}, { "dlarrow" , TDLARROW, MS_DLARROW, TGSTANDALONE, 5}, { "dlrarrow" , TDLRARROW, MS_DLRARROW, TGSTANDALONE, 5}, { "dot", TDOT, MS_DOT, TGATTRIBUT, 5}, { "dotsaxis", TDOTSAXIS, MS_DOTSAXIS, TGSTANDALONE, 5}, // 5 to continue expression { "dotsdiag", TDOTSDIAG, MS_DOTSUP, TGSTANDALONE, 5}, { "dotsdown", TDOTSDOWN, MS_DOTSDOWN, TGSTANDALONE, 5}, { "dotslow", TDOTSLOW, MS_DOTSLOW, TGSTANDALONE, 5}, { "dotsup", TDOTSUP, MS_DOTSUP, TGSTANDALONE, 5}, { "dotsvert", TDOTSVERT, MS_DOTSVERT, TGSTANDALONE, 5}, { "downarrow" , TDOWNARROW, MS_DOWNARROW, TGSTANDALONE, 5}, { "drarrow" , TDRARROW, MS_DRARROW, TGSTANDALONE, 5}, { "emptyset" , TEMPTYSET, MS_EMPTYSET, TGSTANDALONE, 5}, { "equiv", TEQUIV, MS_EQUIV, TGRELATION, 0}, { "exists", TEXISTS, MS_EXISTS, TGSTANDALONE, 5}, { "notexists", TNOTEXISTS, MS_NOTEXISTS, TGSTANDALONE, 5}, { "exp", TEXP, '\0', TGFUNCTION, 5}, { "fact", TFACT, MS_FACT, TGUNOPER, 5}, { "fixed", TFIXED, '\0', TGFONT, 0}, { "font", TFONT, '\0', TGFONTATTR, 5}, { "forall", TFORALL, MS_FORALL, TGSTANDALONE, 5}, { "from", TFROM, '\0', TGLIMIT, 0}, { "func", TFUNC, '\0', TGFUNCTION, 5}, { "ge", TGE, MS_GE, TGRELATION, 0}, { "geslant", TGESLANT, MS_GESLANT, TGRELATION, 0 }, { "gg", TGG, MS_GG, TGRELATION, 0}, { "grave", TGRAVE, MS_GRAVE, TGATTRIBUT, 5}, { "green", TGREEN, '\0', TGCOLOR, 0}, { "gt", TGT, MS_GT, TGRELATION, 0}, { "hat", THAT, MS_HAT, TGATTRIBUT, 5}, { "hbar" , THBAR, MS_HBAR, TGSTANDALONE, 5}, { "iiint", TIIINT, MS_IIINT, TGOPER, 5}, { "iint", TIINT, MS_IINT, TGOPER, 5}, { "in", TIN, MS_IN, TGRELATION, 0}, { "infinity" , TINFINITY, MS_INFINITY, TGSTANDALONE, 5}, { "infty" , TINFINITY, MS_INFINITY, TGSTANDALONE, 5}, { "int", TINT, MS_INT, TGOPER, 5}, { "intd", TINTD, MS_INT, TGUNOPER, 5}, { "intersection", TINTERSECT, MS_INTERSECT, TGPRODUCT, 0}, { "ital", TITALIC, '\0', TGFONTATTR, 5}, { "italic", TITALIC, '\0', TGFONTATTR, 5}, { "lambdabar" , TLAMBDABAR, MS_LAMBDABAR, TGSTANDALONE, 5}, { "langle", TLANGLE, MS_LMATHANGLE, TGLBRACES, 5}, { "lbrace", TLBRACE, MS_LBRACE, TGLBRACES, 5}, { "lceil", TLCEIL, MS_LCEIL, TGLBRACES, 5}, { "ldbracket", TLDBRACKET, MS_LDBRACKET, TGLBRACES, 5}, { "ldline", TLDLINE, MS_DVERTLINE, TGLBRACES, 5}, { "le", TLE, MS_LE, TGRELATION, 0}, { "left", TLEFT, '\0', 0, 5}, { "leftarrow" , TLEFTARROW, MS_LEFTARROW, TGSTANDALONE, 5}, { "leslant", TLESLANT, MS_LESLANT, TGRELATION, 0 }, { "lfloor", TLFLOOR, MS_LFLOOR, TGLBRACES, 5}, { "lim", TLIM, '\0', TGOPER, 5}, { "liminf", TLIMINF, '\0', TGOPER, 5}, { "limsup", TLIMSUP, '\0', TGOPER, 5}, { "lint", TLINT, MS_LINT, TGOPER, 5}, { "ll", TLL, MS_LL, TGRELATION, 0}, { "lline", TLLINE, MS_VERTLINE, TGLBRACES, 5}, { "llint", TLLINT, MS_LLINT, TGOPER, 5}, { "lllint", TLLLINT, MS_LLLINT, TGOPER, 5}, { "ln", TLN, '\0', TGFUNCTION, 5}, { "log", TLOG, '\0', TGFUNCTION, 5}, { "lsub", TLSUB, '\0', TGPOWER, 0}, { "lsup", TLSUP, '\0', TGPOWER, 0}, { "lt", TLT, MS_LT, TGRELATION, 0}, { "magenta", TMAGENTA, '\0', TGCOLOR, 0}, { "matrix", TMATRIX, '\0', 0, 5}, { "minusplus", TMINUSPLUS, MS_MINUSPLUS, TGUNOPER | TGSUM, 5}, { "mline", TMLINE, MS_VERTLINE, 0, 0}, //! not in TGRBRACES, Level 0 { "nabla", TNABLA, MS_NABLA, TGSTANDALONE, 5}, { "nbold", TNBOLD, '\0', TGFONTATTR, 5}, { "ndivides", TNDIVIDES, MS_NDIVIDES, TGRELATION, 0}, { "neg", TNEG, MS_NEG, TGUNOPER, 5 }, { "neq", TNEQ, MS_NEQ, TGRELATION, 0}, { "newline", TNEWLINE, '\0', 0, 0}, { "ni", TNI, MS_NI, TGRELATION, 0}, { "nitalic", TNITALIC, '\0', TGFONTATTR, 5}, { "none", TNONE, '\0', TGLBRACES | TGRBRACES, 0}, { "nospace", TNOSPACE, '\0', TGSTANDALONE, 5}, { "notin", TNOTIN, MS_NOTIN, TGRELATION, 0}, { "nroot", TNROOT, MS_SQRT, TGUNOPER, 5}, { "nsubset", TNSUBSET, MS_NSUBSET, TGRELATION, 0 }, { "nsupset", TNSUPSET, MS_NSUPSET, TGRELATION, 0 }, { "nsubseteq", TNSUBSETEQ, MS_NSUBSETEQ, TGRELATION, 0 }, { "nsupseteq", TNSUPSETEQ, MS_NSUPSETEQ, TGRELATION, 0 }, { "odivide", TODIVIDE, MS_ODIVIDE, TGPRODUCT, 0}, { "odot", TODOT, MS_ODOT, TGPRODUCT, 0}, { "ominus", TOMINUS, MS_OMINUS, TGSUM, 0}, { "oper", TOPER, '\0', TGOPER, 5}, { "oplus", TOPLUS, MS_OPLUS, TGSUM, 0}, { "or", TOR, MS_OR, TGSUM, 0}, { "ortho", TORTHO, MS_ORTHO, TGRELATION, 0}, { "otimes", TOTIMES, MS_OTIMES, TGPRODUCT, 0}, { "over", TOVER, '\0', TGPRODUCT, 0}, { "overbrace", TOVERBRACE, MS_OVERBRACE, TGPRODUCT, 5}, { "overline", TOVERLINE, '\0', TGATTRIBUT, 5}, { "overstrike", TOVERSTRIKE, '\0', TGATTRIBUT, 5}, { "owns", TNI, MS_NI, TGRELATION, 0}, { "parallel", TPARALLEL, MS_DLINE, TGRELATION, 0}, { "partial", TPARTIAL, MS_PARTIAL, TGSTANDALONE, 5 }, { "phantom", TPHANTOM, '\0', TGFONTATTR, 5}, { "plusminus", TPLUSMINUS, MS_PLUSMINUS, TGUNOPER | TGSUM, 5}, { "prec", TPRECEDES, MS_PRECEDES, TGRELATION, 0 }, { "preccurlyeq", TPRECEDESEQUAL, MS_PRECEDESEQUAL, TGRELATION, 0 }, { "precsim", TPRECEDESEQUIV, MS_PRECEDESEQUIV, TGRELATION, 0 }, { "nprec", TNOTPRECEDES, MS_NOTPRECEDES, TGRELATION, 0 }, { "prod", TPROD, MS_PROD, TGOPER, 5}, { "prop", TPROP, MS_PROP, TGRELATION, 0}, { "rangle", TRANGLE, MS_RMATHANGLE, TGRBRACES, 0}, //! 0 to terminate expression { "rbrace", TRBRACE, MS_RBRACE, TGRBRACES, 0}, { "rceil", TRCEIL, MS_RCEIL, TGRBRACES, 0}, { "rdbracket", TRDBRACKET, MS_RDBRACKET, TGRBRACES, 0}, { "rdline", TRDLINE, MS_DVERTLINE, TGRBRACES, 0}, { "red", TRED, '\0', TGCOLOR, 0}, { "rfloor", TRFLOOR, MS_RFLOOR, TGRBRACES, 0}, //! 0 to terminate expression { "right", TRIGHT, '\0', 0, 0}, { "rightarrow" , TRIGHTARROW, MS_RIGHTARROW, TGSTANDALONE, 5}, { "rline", TRLINE, MS_VERTLINE, TGRBRACES, 0}, //! 0 to terminate expression { "rsub", TRSUB, '\0', TGPOWER, 0}, { "rsup", TRSUP, '\0', TGPOWER, 0}, { "sans", TSANS, '\0', TGFONT, 0}, { "serif", TSERIF, '\0', TGFONT, 0}, { "setC" , TSETC, MS_SETC, TGSTANDALONE, 5}, { "setN" , TSETN, MS_SETN, TGSTANDALONE, 5}, { "setQ" , TSETQ, MS_SETQ, TGSTANDALONE, 5}, { "setR" , TSETR, MS_SETR, TGSTANDALONE, 5}, { "setZ" , TSETZ, MS_SETZ, TGSTANDALONE, 5}, { "setminus", TBACKSLASH, MS_BACKSLASH, TGPRODUCT, 0 }, { "sim", TSIM, MS_SIM, TGRELATION, 0}, { "simeq", TSIMEQ, MS_SIMEQ, TGRELATION, 0}, { "sin", TSIN, '\0', TGFUNCTION, 5}, { "sinh", TSINH, '\0', TGFUNCTION, 5}, { "size", TSIZE, '\0', TGFONTATTR, 5}, { "slash", TSLASH, MS_SLASH, TGPRODUCT, 0 }, { "sqrt", TSQRT, MS_SQRT, TGUNOPER, 5}, { "stack", TSTACK, '\0', 0, 5}, { "sub", TRSUB, '\0', TGPOWER, 0}, { "subset", TSUBSET, MS_SUBSET, TGRELATION, 0}, { "succ", TSUCCEEDS, MS_SUCCEEDS, TGRELATION, 0 }, { "succcurlyeq", TSUCCEEDSEQUAL, MS_SUCCEEDSEQUAL, TGRELATION, 0 }, { "succsim", TSUCCEEDSEQUIV, MS_SUCCEEDSEQUIV, TGRELATION, 0 }, { "nsucc", TNOTSUCCEEDS, MS_NOTSUCCEEDS, TGRELATION, 0 }, { "subseteq", TSUBSETEQ, MS_SUBSETEQ, TGRELATION, 0}, { "sum", TSUM, MS_SUM, TGOPER, 5}, { "sup", TRSUP, '\0', TGPOWER, 0}, { "supset", TSUPSET, MS_SUPSET, TGRELATION, 0}, { "supseteq", TSUPSETEQ, MS_SUPSETEQ, TGRELATION, 0}, { "tan", TTAN, '\0', TGFUNCTION, 5}, { "tanh", TTANH, '\0', TGFUNCTION, 5}, { "tilde", TTILDE, MS_TILDE, TGATTRIBUT, 5}, { "times", TTIMES, MS_TIMES, TGPRODUCT, 0}, { "to", TTO, '\0', TGLIMIT, 0}, { "toward", TTOWARD, MS_RIGHTARROW, TGRELATION, 0}, { "transl", TTRANSL, MS_TRANSL, TGRELATION, 0}, { "transr", TTRANSR, MS_TRANSR, TGRELATION, 0}, { "underbrace", TUNDERBRACE, MS_UNDERBRACE, TGPRODUCT, 5}, { "underline", TUNDERLINE, '\0', TGATTRIBUT, 5}, { "union", TUNION, MS_UNION, TGSUM, 0}, { "uoper", TUOPER, '\0', TGUNOPER, 5}, { "uparrow" , TUPARROW, MS_UPARROW, TGSTANDALONE, 5}, { "vec", TVEC, MS_VEC, TGATTRIBUT, 5}, { "white", TWHITE, '\0', TGCOLOR, 0}, { "widebslash", TWIDEBACKSLASH, MS_BACKSLASH, TGPRODUCT, 0 }, { "widehat", TWIDEHAT, MS_HAT, TGATTRIBUT, 5}, { "widetilde", TWIDETILDE, MS_TILDE, TGATTRIBUT, 5}, { "wideslash", TWIDESLASH, MS_SLASH, TGPRODUCT, 0 }, { "widevec", TWIDEVEC, MS_VEC, TGATTRIBUT, 5}, { "wp" , TWP, MS_WP, TGSTANDALONE, 5}, { "yellow", TYELLOW, '\0', TGCOLOR, 0}, { "", TEND, '\0', 0, 0} }; const SmTokenTableEntry * SmParser::GetTokenTableEntry( const OUString &rName ) { const SmTokenTableEntry * pRes = 0; if (!rName.isEmpty()) { for (size_t i = 0; i < SAL_N_ELEMENTS(aTokenTable); ++i) { if (rName.equalsIgnoreAsciiCase( OUString::createFromAscii(aTokenTable[i].pIdent) )) { pRes = &aTokenTable[i]; break; } } } return pRes; } #if OSL_DEBUG_LEVEL > 1 static const sal_Unicode aDelimiterTable[] = { ' ', '\t', '\n', '\r', '+', '-', '*', '/', '=', '#', '%', '\\', '"', '~', '`', '>', '<', '&', '|', '(', ')', '{', '}', '[', ']', '^', '_', '\0' // end of list symbol }; bool SmParser::IsDelimiter( const OUString &rTxt, sal_Int32 nPos ) // returns 'true' iff cChar is '\0' or a delimiter { assert(nPos <= rTxt.getLength()); //index out of range if (nPos == rTxt.getLength()) return true; sal_Unicode cChar = rTxt[nPos]; // check if 'cChar' is in the delimiter table const sal_Unicode *pDelim = &aDelimiterTable[0]; for ( ; *pDelim != 0; pDelim++) if (*pDelim == cChar) break; sal_Int16 nTypJp = SM_MOD()->GetSysLocale().GetCharClass().getType( rTxt, nPos ); bool bIsDelim = (*pDelim != 0 || nTypJp == com::sun::star::i18n::UnicodeType::SPACE_SEPARATOR || nTypJp == com::sun::star::i18n::UnicodeType::CONTROL); return bIsDelim; } #endif void SmParser::Replace( sal_Int32 nPos, sal_Int32 nLen, const OUString &rText ) { OSL_ENSURE( nPos + nLen <= m_aBufferString.getLength(), "argument mismatch" ); m_aBufferString = m_aBufferString.replaceAt( nPos, nLen, rText ); sal_Int32 nChg = rText.getLength() - nLen; m_nBufferIndex = m_nBufferIndex + nChg; m_nTokenIndex = m_nTokenIndex + nChg; } // First character may be any alphabetic const sal_Int32 coStartFlags = KParseTokens::ANY_LETTER | KParseTokens::IGNORE_LEADING_WS; // Continuing characters may be any alphabetic const sal_Int32 coContFlags = (coStartFlags & ~KParseTokens::IGNORE_LEADING_WS) | KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; // user-defined char continuing characters may be any alphanumeric or dot. const sal_Int32 coUserDefinedCharContFlags = ((KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::IGNORE_LEADING_WS | KParseTokens::ASC_DOT) & ~KParseTokens::IGNORE_LEADING_WS) | KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; // First character for numbers, may be any numeric or dot const sal_Int32 coNumStartFlags = KParseTokens::ASC_DIGIT | KParseTokens::ASC_DOT | KParseTokens::IGNORE_LEADING_WS; // Continuing characters for numbers, may be any numeric or dot. const sal_Int32 coNumContFlags = coNumStartFlags & ~KParseTokens::IGNORE_LEADING_WS; void SmParser::NextToken() { static const OUString aEmptyStr; sal_Int32 nBufLen = m_aBufferString.getLength(); ParseResult aRes; sal_Int32 nRealStart; bool bCont; CharClass aCC(SM_MOD()->GetSysLocale().GetLanguageTag()); do { // skip white spaces while (UnicodeType::SPACE_SEPARATOR == aCC.getType( m_aBufferString, m_nBufferIndex )) ++m_nBufferIndex; // Try to parse a number. This should be independent from the locale // setting, so temporarily set the language to English. // See https://issues.apache.org/ooo/show_bug.cgi?id=45779 LanguageTag aOldLoc(aCC.getLanguageTag()); aCC.setLanguageTag(LanguageTag(m_aDotLoc)); aRes = aCC.parsePredefinedToken(KParseType::ASC_NUMBER, m_aBufferString, m_nBufferIndex, coNumStartFlags, aEmptyStr, coNumContFlags, aEmptyStr); aCC.setLanguageTag(aOldLoc); if (aRes.TokenType == 0) { // Try again with the default token parsing. aRes = aCC.parseAnyToken(m_aBufferString, m_nBufferIndex, coStartFlags, aEmptyStr, coContFlags, aEmptyStr); } nRealStart = m_nBufferIndex + aRes.LeadingWhiteSpace; m_nBufferIndex = nRealStart; bCont = false; if ( aRes.TokenType == 0 && nRealStart < nBufLen && '\n' == m_aBufferString[ nRealStart ] ) { // keep data needed for tokens row and col entry up to date ++m_Row; m_nBufferIndex = m_nColOff = nRealStart + 1; bCont = true; } else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) { if (nRealStart + 2 <= nBufLen && m_aBufferString.match("%%", nRealStart)) { //SkipComment m_nBufferIndex = nRealStart + 2; while (m_nBufferIndex < nBufLen && '\n' != m_aBufferString[ m_nBufferIndex ]) ++m_nBufferIndex; bCont = true; } } } while (bCont); // set index of current token m_nTokenIndex = m_nBufferIndex; m_aCurToken.nRow = m_Row; m_aCurToken.nCol = nRealStart - m_nColOff + 1; bool bHandled = true; if (nRealStart >= nBufLen) { m_aCurToken.eType = TEND; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 0; m_aCurToken.aText = ""; } else if (aRes.TokenType & KParseType::ANY_NUMBER) { sal_Int32 n = aRes.EndPos - nRealStart; OSL_ENSURE( n >= 0, "length < 0" ); m_aCurToken.eType = TNUMBER; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = m_aBufferString.copy( nRealStart, n ); #if OSL_DEBUG_LEVEL > 1 if (!IsDelimiter( m_aBufferString, aRes.EndPos )) SAL_WARN( "starmath", "identifier really finished? (compatibility!)" ); #endif } else if (aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING) { m_aCurToken.eType = TTEXT; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = aRes.DequotedNameOrString; m_aCurToken.nRow = m_Row; m_aCurToken.nCol = nRealStart - m_nColOff + 2; } else if (aRes.TokenType & KParseType::IDENTNAME) { sal_Int32 n = aRes.EndPos - nRealStart; OSL_ENSURE( n >= 0, "length < 0" ); OUString aName( m_aBufferString.copy( nRealStart, n ) ); const SmTokenTableEntry *pEntry = GetTokenTableEntry( aName ); if (pEntry) { m_aCurToken.eType = pEntry->eType; m_aCurToken.cMathChar = pEntry->cMathChar; m_aCurToken.nGroup = pEntry->nGroup; m_aCurToken.nLevel = pEntry->nLevel; m_aCurToken.aText = OUString::createFromAscii( pEntry->pIdent ); } else { m_aCurToken.eType = TIDENT; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = aName; #if OSL_DEBUG_LEVEL > 1 if (!IsDelimiter( m_aBufferString, aRes.EndPos )) SAL_WARN( "starmath", "identifier really finished? (compatibility!)" ); #endif } } else if (aRes.TokenType == 0 && '_' == m_aBufferString[ nRealStart ]) { m_aCurToken.eType = TRSUB; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = TGPOWER; m_aCurToken.nLevel = 0; m_aCurToken.aText = "_"; aRes.EndPos = nRealStart + 1; } else if (aRes.TokenType & KParseType::BOOLEAN) { sal_Int32 &rnEndPos = aRes.EndPos; if (rnEndPos - nRealStart <= 2) { sal_Unicode ch = m_aBufferString[ nRealStart ]; switch (ch) { case '<': { if (m_aBufferString.match("<<", nRealStart)) { m_aCurToken.eType = TLL; m_aCurToken.cMathChar = MS_LL; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = "<<"; rnEndPos = nRealStart + 2; } else if (m_aBufferString.match("<=", nRealStart)) { m_aCurToken.eType = TLE; m_aCurToken.cMathChar = MS_LE; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = "<="; rnEndPos = nRealStart + 2; } else if (m_aBufferString.match("<>", nRealStart)) { m_aCurToken.eType = TNEQ; m_aCurToken.cMathChar = MS_NEQ; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = "<>"; rnEndPos = nRealStart + 2; } else if (m_aBufferString.match("", nRealStart)) { m_aCurToken.eType = TPLACE; m_aCurToken.cMathChar = MS_PLACE; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = ""; rnEndPos = nRealStart + 3; } else { m_aCurToken.eType = TLT; m_aCurToken.cMathChar = MS_LT; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = "<"; } } break; case '>': { if (m_aBufferString.match(">=", nRealStart)) { m_aCurToken.eType = TGE; m_aCurToken.cMathChar = MS_GE; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = ">="; rnEndPos = nRealStart + 2; } else if (m_aBufferString.match(">>", nRealStart)) { m_aCurToken.eType = TGG; m_aCurToken.cMathChar = MS_GG; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = ">>"; rnEndPos = nRealStart + 2; } else { m_aCurToken.eType = TGT; m_aCurToken.cMathChar = MS_GT; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = ">"; } } break; default: bHandled = false; } } } else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) { sal_Int32 &rnEndPos = aRes.EndPos; if (rnEndPos - nRealStart == 1) { sal_Unicode ch = m_aBufferString[ nRealStart ]; switch (ch) { case '%': { //! modifies aRes.EndPos OSL_ENSURE( rnEndPos >= nBufLen || '%' != m_aBufferString[ rnEndPos ], "unexpected comment start" ); // get identifier of user-defined character ParseResult aTmpRes = aCC.parseAnyToken( m_aBufferString, rnEndPos, KParseTokens::ANY_LETTER, aEmptyStr, coUserDefinedCharContFlags, aEmptyStr ); sal_Int32 nTmpStart = rnEndPos + aTmpRes.LeadingWhiteSpace; // default setting for the case that no identifier // i.e. a valid symbol-name is following the '%' // character m_aCurToken.eType = TTEXT; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = OUString(); m_aCurToken.nRow = m_Row; m_aCurToken.nCol = nTmpStart - m_nColOff; if (aTmpRes.TokenType & KParseType::IDENTNAME) { sal_Int32 n = aTmpRes.EndPos - nTmpStart; m_aCurToken.eType = TSPECIAL; m_aCurToken.aText = m_aBufferString.copy( nTmpStart-1, n+1 ); OSL_ENSURE( aTmpRes.EndPos > rnEndPos, "empty identifier" ); if (aTmpRes.EndPos > rnEndPos) rnEndPos = aTmpRes.EndPos; else ++rnEndPos; } // if no symbol-name was found we start-over with // finding the next token right afer the '%' sign. // I.e. we leave rnEndPos unmodified. } break; case '[': { m_aCurToken.eType = TLBRACKET; m_aCurToken.cMathChar = MS_LBRACKET; m_aCurToken.nGroup = TGLBRACES; m_aCurToken.nLevel = 5; m_aCurToken.aText = "["; } break; case '\\': { m_aCurToken.eType = TESCAPE; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = "\\"; } break; case ']': { m_aCurToken.eType = TRBRACKET; m_aCurToken.cMathChar = MS_RBRACKET; m_aCurToken.nGroup = TGRBRACES; m_aCurToken.nLevel = 0; m_aCurToken.aText = "]"; } break; case '^': { m_aCurToken.eType = TRSUP; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = TGPOWER; m_aCurToken.nLevel = 0; m_aCurToken.aText = "^"; } break; case '`': { m_aCurToken.eType = TSBLANK; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = TGBLANK; m_aCurToken.nLevel = 5; m_aCurToken.aText = "`"; } break; case '{': { m_aCurToken.eType = TLGROUP; m_aCurToken.cMathChar = MS_LBRACE; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = "{"; } break; case '|': { m_aCurToken.eType = TOR; m_aCurToken.cMathChar = MS_OR; m_aCurToken.nGroup = TGSUM; m_aCurToken.nLevel = 0; m_aCurToken.aText = "|"; } break; case '}': { m_aCurToken.eType = TRGROUP; m_aCurToken.cMathChar = MS_RBRACE; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 0; m_aCurToken.aText = "}"; } break; case '~': { m_aCurToken.eType = TBLANK; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = TGBLANK; m_aCurToken.nLevel = 5; m_aCurToken.aText = "~"; } break; case '#': { if (m_aBufferString.match("##", nRealStart)) { m_aCurToken.eType = TDPOUND; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 0; m_aCurToken.aText = "##"; rnEndPos = nRealStart + 2; } else { m_aCurToken.eType = TPOUND; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 0; m_aCurToken.aText = "#"; } } break; case '&': { m_aCurToken.eType = TAND; m_aCurToken.cMathChar = MS_AND; m_aCurToken.nGroup = TGPRODUCT; m_aCurToken.nLevel = 0; m_aCurToken.aText = "&"; } break; case '(': { m_aCurToken.eType = TLPARENT; m_aCurToken.cMathChar = MS_LPARENT; m_aCurToken.nGroup = TGLBRACES; m_aCurToken.nLevel = 5; //! 0 to continue expression m_aCurToken.aText = "("; } break; case ')': { m_aCurToken.eType = TRPARENT; m_aCurToken.cMathChar = MS_RPARENT; m_aCurToken.nGroup = TGRBRACES; m_aCurToken.nLevel = 0; //! 0 to terminate expression m_aCurToken.aText = ")"; } break; case '*': { m_aCurToken.eType = TMULTIPLY; m_aCurToken.cMathChar = MS_MULTIPLY; m_aCurToken.nGroup = TGPRODUCT; m_aCurToken.nLevel = 0; m_aCurToken.aText = "*"; } break; case '+': { if (m_aBufferString.match("+-", nRealStart)) { m_aCurToken.eType = TPLUSMINUS; m_aCurToken.cMathChar = MS_PLUSMINUS; m_aCurToken.nGroup = TGUNOPER | TGSUM; m_aCurToken.nLevel = 5; m_aCurToken.aText = "+-"; rnEndPos = nRealStart + 2; } else { m_aCurToken.eType = TPLUS; m_aCurToken.cMathChar = MS_PLUS; m_aCurToken.nGroup = TGUNOPER | TGSUM; m_aCurToken.nLevel = 5; m_aCurToken.aText = "+"; } } break; case '-': { if (m_aBufferString.match("-+", nRealStart)) { m_aCurToken.eType = TMINUSPLUS; m_aCurToken.cMathChar = MS_MINUSPLUS; m_aCurToken.nGroup = TGUNOPER | TGSUM; m_aCurToken.nLevel = 5; m_aCurToken.aText = "-+"; rnEndPos = nRealStart + 2; } else { m_aCurToken.eType = TMINUS; m_aCurToken.cMathChar = MS_MINUS; m_aCurToken.nGroup = TGUNOPER | TGSUM; m_aCurToken.nLevel = 5; m_aCurToken.aText = "-"; } } break; case '.': { // Only one character? Then it can't be a number. if (m_nBufferIndex < m_aBufferString.getLength() - 1) { // for compatibility with SO5.2 // texts like .34 ...56 ... h ...78..90 // will be treated as numbers m_aCurToken.eType = TNUMBER; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; sal_Int32 nTxtStart = m_nBufferIndex; sal_Unicode cChar; do { cChar = m_aBufferString[ ++m_nBufferIndex ]; } while ( cChar == '.' || rtl::isAsciiDigit( cChar ) ); m_aCurToken.aText = m_aBufferString.copy( nTxtStart, m_nBufferIndex - nTxtStart ); aRes.EndPos = m_nBufferIndex; } else bHandled = false; } break; case '/': { m_aCurToken.eType = TDIVIDEBY; m_aCurToken.cMathChar = MS_SLASH; m_aCurToken.nGroup = TGPRODUCT; m_aCurToken.nLevel = 0; m_aCurToken.aText = "/"; } break; case '=': { m_aCurToken.eType = TASSIGN; m_aCurToken.cMathChar = MS_ASSIGN; m_aCurToken.nGroup = TGRELATION; m_aCurToken.nLevel = 0; m_aCurToken.aText = "="; } break; default: bHandled = false; } } } else bHandled = false; if (!bHandled) { m_aCurToken.eType = TCHARACTER; m_aCurToken.cMathChar = '\0'; m_aCurToken.nGroup = 0; m_aCurToken.nLevel = 5; m_aCurToken.aText = m_aBufferString.copy( nRealStart, 1 ); aRes.EndPos = nRealStart + 1; } if (TEND != m_aCurToken.eType) m_nBufferIndex = aRes.EndPos; } // grammar void SmParser::Table() { SmNodeArray LineArray; Line(); while (m_aCurToken.eType == TNEWLINE) { NextToken(); Line(); } if (m_aCurToken.eType != TEND) Error(PE_UNEXPECTED_CHAR); sal_uLong n = m_aNodeStack.size(); LineArray.resize(n); for (sal_uLong i = 0; i < n; i++) { LineArray[n - (i + 1)] = m_aNodeStack.top(); m_aNodeStack.pop(); } SmStructureNode *pSNode = new SmTableNode(m_aCurToken); pSNode->SetSubNodes(LineArray); m_aNodeStack.push(pSNode); } void SmParser::Align() // parse alignment info (if any), then go on with rest of expression { SmStructureNode *pSNode = 0; if (TokenInGroup(TGALIGN)) { pSNode = new SmAlignNode(m_aCurToken); NextToken(); // allow for just one align statement in 5.0 if (TokenInGroup(TGALIGN)) { Error(PE_DOUBLE_ALIGN); delete pSNode; return; } } Expression(); if (pSNode) { pSNode->SetSubNodes(lcl_popOrZero(m_aNodeStack), 0); m_aNodeStack.push(pSNode); } } void SmParser::Line() { sal_uInt16 n = 0; SmNodeArray ExpressionArray; ExpressionArray.resize(n); // start with single expression that may have an alignment statement // (and go on with expressions that must not have alignment // statements in 'while' loop below. See also 'Expression()'.) if (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) { Align(); ExpressionArray.resize(++n); ExpressionArray[n - 1] = lcl_popOrZero(m_aNodeStack); } while (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) { Expression(); ExpressionArray.resize(++n); ExpressionArray[n - 1] = lcl_popOrZero(m_aNodeStack); } //If there's no expression, add an empty one. //this is to avoid a formula tree without any caret //positions, in visual formula editor. if(ExpressionArray.empty()) { SmToken aTok = SmToken(); aTok.eType = TNEWLINE; ExpressionArray.push_back(new SmExpressionNode(aTok)); } SmStructureNode *pSNode = new SmLineNode(m_aCurToken); pSNode->SetSubNodes(ExpressionArray); m_aNodeStack.push(pSNode); } void SmParser::Expression() { bool bUseExtraSpaces = true; if (!m_aNodeStack.empty()) { SmNode *pNode = m_aNodeStack.top(); m_aNodeStack.pop(); if (pNode->GetToken().eType == TNOSPACE) bUseExtraSpaces = false; else m_aNodeStack.push(pNode); // push the node from above again (now to be used as argument to this current 'nospace' node) } sal_uInt16 n = 0; SmNodeArray RelationArray; RelationArray.resize(n); Relation(); RelationArray.resize(++n); RelationArray[n - 1] = lcl_popOrZero(m_aNodeStack); while (m_aCurToken.nLevel >= 4) { Relation(); RelationArray.resize(++n); RelationArray[n - 1] = lcl_popOrZero(m_aNodeStack); } if (n > 1) { SmExpressionNode *pSNode = new SmExpressionNode(m_aCurToken); pSNode->SetSubNodes(RelationArray); pSNode->SetUseExtraSpaces(bUseExtraSpaces); m_aNodeStack.push(pSNode); } else { // This expression has only one node so just push this node. m_aNodeStack.push(RelationArray[0]); } } void SmParser::Relation() { Sum(); while (TokenInGroup(TGRELATION)) { SmStructureNode *pSNode = new SmBinHorNode(m_aCurToken); SmNode *pFirst = lcl_popOrZero(m_aNodeStack); OpSubSup(); SmNode *pSecond = lcl_popOrZero(m_aNodeStack); Sum(); pSNode->SetSubNodes(pFirst, pSecond, lcl_popOrZero(m_aNodeStack)); m_aNodeStack.push(pSNode); } } void SmParser::Sum() { Product(); while (TokenInGroup(TGSUM)) { SmStructureNode *pSNode = new SmBinHorNode(m_aCurToken); SmNode *pFirst = lcl_popOrZero(m_aNodeStack); OpSubSup(); SmNode *pSecond = lcl_popOrZero(m_aNodeStack); Product(); pSNode->SetSubNodes(pFirst, pSecond, lcl_popOrZero(m_aNodeStack)); m_aNodeStack.push(pSNode); } } void SmParser::Product() { Power(); while (TokenInGroup(TGPRODUCT)) { SmStructureNode *pSNode; SmNode *pFirst = lcl_popOrZero(m_aNodeStack), *pOper; bool bSwitchArgs = false; SmTokenType eType = m_aCurToken.eType; switch (eType) { case TOVER: pSNode = new SmBinVerNode(m_aCurToken); pOper = new SmRectangleNode(m_aCurToken); NextToken(); break; case TBOPER: pSNode = new SmBinHorNode(m_aCurToken); NextToken(); //Let the glyph node know it's a binary operation m_aCurToken.eType = TBOPER; m_aCurToken.nGroup = TGPRODUCT; GlyphSpecial(); pOper = lcl_popOrZero(m_aNodeStack); break; case TOVERBRACE : case TUNDERBRACE : pSNode = new SmVerticalBraceNode(m_aCurToken); pOper = new SmMathSymbolNode(m_aCurToken); NextToken(); break; case TWIDEBACKSLASH: case TWIDESLASH: { SmBinDiagonalNode *pSTmp = new SmBinDiagonalNode(m_aCurToken); pSTmp->SetAscending(eType == TWIDESLASH); pSNode = pSTmp; pOper = new SmPolyLineNode(m_aCurToken); NextToken(); bSwitchArgs = true; break; } default: pSNode = new SmBinHorNode(m_aCurToken); OpSubSup(); pOper = lcl_popOrZero(m_aNodeStack); } Power(); if (bSwitchArgs) { //! vgl siehe SmBinDiagonalNode::Arrange pSNode->SetSubNodes(pFirst, lcl_popOrZero(m_aNodeStack), pOper); } else { pSNode->SetSubNodes(pFirst, pOper, lcl_popOrZero(m_aNodeStack)); } m_aNodeStack.push(pSNode); } } void SmParser::SubSup(sal_uLong nActiveGroup) { OSL_ENSURE(nActiveGroup == TGPOWER || nActiveGroup == TGLIMIT, "Sm: wrong token group"); if (!TokenInGroup(nActiveGroup)) // already finish return; SmSubSupNode *pNode = new SmSubSupNode(m_aCurToken); //! Of course 'm_aCurToken' is just the first sub-/supscript token. //! It should be of no further interest. The positions of the //! sub-/supscripts will be identified by the corresponding subnodes //! index in the 'aSubNodes' array (enum value from 'SmSubSup'). pNode->SetUseLimits(nActiveGroup == TGLIMIT); // initialize subnodes array SmNodeArray aSubNodes; aSubNodes.resize(1 + SUBSUP_NUM_ENTRIES); aSubNodes[0] = lcl_popOrZero(m_aNodeStack); for (sal_uInt16 i = 1; i < aSubNodes.size(); i++) aSubNodes[i] = NULL; // process all sub-/supscripts int nIndex = 0; while (TokenInGroup(nActiveGroup)) { SmTokenType eType (m_aCurToken.eType); // skip sub-/supscript token NextToken(); // get sub-/supscript node on top of stack if (eType == TFROM || eType == TTO) { // parse limits in old 4.0 and 5.0 style Relation(); } else Term(true); switch (eType) { case TRSUB : nIndex = (int) RSUB; break; case TRSUP : nIndex = (int) RSUP; break; case TFROM : case TCSUB : nIndex = (int) CSUB; break; case TTO : case TCSUP : nIndex = (int) CSUP; break; case TLSUB : nIndex = (int) LSUB; break; case TLSUP : nIndex = (int) LSUP; break; default : SAL_WARN( "starmath", "unknown case"); } nIndex++; OSL_ENSURE(1 <= nIndex && nIndex <= 1 + SUBSUP_NUM_ENTRIES, "SmParser::Power() : sub-/supscript index falsch"); // set sub-/supscript if not already done if (aSubNodes[nIndex] != NULL) Error(PE_DOUBLE_SUBSUPSCRIPT); aSubNodes[nIndex] = lcl_popOrZero(m_aNodeStack); } pNode->SetSubNodes(aSubNodes); m_aNodeStack.push(pNode); } void SmParser::OpSubSup() { // push operator symbol m_aNodeStack.push(new SmMathSymbolNode(m_aCurToken)); // skip operator token NextToken(); // get sub- supscripts if any if (TokenInGroup(TGPOWER)) SubSup(TGPOWER); } void SmParser::Power() { // get body for sub- supscripts on top of stack Term(false); SubSup(TGPOWER); } void SmParser::Blank() { OSL_ENSURE(TokenInGroup(TGBLANK), "Sm : wrong token"); SmBlankNode *pBlankNode = new SmBlankNode(m_aCurToken); while (TokenInGroup(TGBLANK)) { pBlankNode->IncreaseBy(m_aCurToken); NextToken(); } // Ignore trailing spaces, if corresponding option is set if ( m_aCurToken.eType == TNEWLINE || (m_aCurToken.eType == TEND && SM_MOD()->GetConfig()->IsIgnoreSpacesRight()) ) { pBlankNode->Clear(); } m_aNodeStack.push(pBlankNode); } void SmParser::Term(bool bGroupNumberIdent) { switch (m_aCurToken.eType) { case TESCAPE : Escape(); break; case TNOSPACE : case TLGROUP : { bool bNoSpace = m_aCurToken.eType == TNOSPACE; if (bNoSpace) // push 'no space' node and continue to parse expression { m_aNodeStack.push(new SmExpressionNode(m_aCurToken)); NextToken(); } if (m_aCurToken.eType != TLGROUP) { m_aNodeStack.pop(); // get rid of the 'no space' node pushed above Term(false); } else { NextToken(); // allow for empty group if (m_aCurToken.eType == TRGROUP) { if (bNoSpace) // get rid of the 'no space' node pushed above m_aNodeStack.pop(); SmStructureNode *pSNode = new SmExpressionNode(m_aCurToken); pSNode->SetSubNodes(NULL, NULL); m_aNodeStack.push(pSNode); NextToken(); } else // go as usual { Align(); if (m_aCurToken.eType != TRGROUP) Error(PE_RGROUP_EXPECTED); else NextToken(); } } } break; case TLEFT : Brace(); break; case TBLANK : case TSBLANK : Blank(); break; case TTEXT : m_aNodeStack.push(new SmTextNode(m_aCurToken, FNT_TEXT)); NextToken(); break; case TCHARACTER : m_aNodeStack.push(new SmTextNode(m_aCurToken, FNT_VARIABLE)); NextToken(); break; case TIDENT : case TNUMBER : { m_aNodeStack.push(new SmTextNode(m_aCurToken, m_aCurToken.eType == TNUMBER ? FNT_NUMBER : FNT_VARIABLE)); if (!bGroupNumberIdent) { NextToken(); } else { // Some people want to be able to write "x_2n" for "x_{2n}" // although e.g. LaTeX or AsciiMath interpret that as "x_2 n". // The tokenizer skips whitespaces so we need some additional // work to distinguish from "x_2 n". // See https://issues.apache.org/ooo/show_bug.cgi?id=11752 and // https://www.libreoffice.org/bugzilla/show_bug.cgi?id=55853 sal_Int32 nBufLen = m_aBufferString.getLength(); CharClass aCC(SM_MOD()->GetSysLocale().GetLanguageTag()); sal_Int32 nTokens = 1; // We need to be careful to call NextToken() only after having // tested for a whitespace separator (otherwise it will be // skipped!) bool moveToNextToken = true; while (m_nBufferIndex < nBufLen && aCC.getType(m_aBufferString, m_nBufferIndex) != UnicodeType::SPACE_SEPARATOR) { NextToken(); if (m_aCurToken.eType != TNUMBER && m_aCurToken.eType != TIDENT) { // Neither a number nor an indentifier. We just moved to // the next token, so no need to do that again. moveToNextToken = false; break; } m_aNodeStack.push(new SmTextNode(m_aCurToken, m_aCurToken.eType == TNUMBER ? FNT_NUMBER : FNT_VARIABLE)); nTokens++; } if (moveToNextToken) NextToken(); if (nTokens > 1) { // We have several concatenated identifiers and numbers. // Let's group them into one SmExpressionNode. SmNodeArray nodeArray; nodeArray.resize(nTokens); while (nTokens > 0) { nodeArray[nTokens-1] = lcl_popOrZero(m_aNodeStack); nTokens--; } SmExpressionNode* pNode = new SmExpressionNode(SmToken()); pNode->SetSubNodes(nodeArray); m_aNodeStack.push(pNode); } } break; } case TLEFTARROW : case TRIGHTARROW : case TUPARROW : case TDOWNARROW : case TCIRC : case TDRARROW : case TDLARROW : case TDLRARROW : case TEXISTS : case TNOTEXISTS : case TFORALL : case TPARTIAL : case TNABLA : case TTOWARD : case TDOTSAXIS : case TDOTSDIAG : case TDOTSDOWN : case TDOTSLOW : case TDOTSUP : case TDOTSVERT : m_aNodeStack.push(new SmMathSymbolNode(m_aCurToken)); NextToken(); break; case TSETN : case TSETZ : case TSETQ : case TSETR : case TSETC : case THBAR : case TLAMBDABAR : case TBACKEPSILON : case TALEPH : case TIM : case TRE : case TWP : case TEMPTYSET : case TINFINITY : m_aNodeStack.push(new SmMathIdentifierNode(m_aCurToken)); NextToken(); break; case TPLACE: m_aNodeStack.push(new SmPlaceNode(m_aCurToken)); NextToken(); break; case TSPECIAL: Special(); break; case TBINOM: Binom(); break; case TSTACK: Stack(); break; case TMATRIX: Matrix(); break; default: if (TokenInGroup(TGLBRACES)) { Brace(); } else if (TokenInGroup(TGOPER)) { Operator(); } else if (TokenInGroup(TGUNOPER)) { UnOper(); } else if ( TokenInGroup(TGATTRIBUT) || TokenInGroup(TGFONTATTR)) { SmStructureNodeArray aArray; bool bIsAttr; sal_uInt16 n = 0; while (true == (bIsAttr = TokenInGroup(TGATTRIBUT)) || TokenInGroup(TGFONTATTR)) { aArray.resize(n + 1); if (bIsAttr) Attribut(); else FontAttribut(); SmNode* pTmp = lcl_popOrZero(m_aNodeStack); // check if casting in following line is ok OSL_ENSURE(pTmp && !pTmp->IsVisible(), "Sm : Ooops..."); aArray[n] = (SmStructureNode *) pTmp; n++; } Power(); SmNode *pFirstNode = lcl_popOrZero(m_aNodeStack); while (n > 0) { aArray[n - 1]->SetSubNodes(0, pFirstNode); pFirstNode = aArray[n - 1]; n--; } m_aNodeStack.push(pFirstNode); } else if (TokenInGroup(TGFUNCTION)) { Function(); } else Error(PE_UNEXPECTED_CHAR); } } void SmParser::Escape() { NextToken(); switch (m_aCurToken.eType) { case TLPARENT : case TRPARENT : case TLBRACKET : case TRBRACKET : case TLDBRACKET : case TRDBRACKET : case TLBRACE : case TLGROUP : case TRBRACE : case TRGROUP : case TLANGLE : case TRANGLE : case TLCEIL : case TRCEIL : case TLFLOOR : case TRFLOOR : case TLLINE : case TRLINE : case TLDLINE : case TRDLINE : break; default: Error(PE_UNEXPECTED_TOKEN); } SmNode *pNode = new SmMathSymbolNode(m_aCurToken); m_aNodeStack.push(pNode); NextToken(); } void SmParser::Operator() { if (TokenInGroup(TGOPER)) { SmStructureNode *pSNode = new SmOperNode(m_aCurToken); // put operator on top of stack Oper(); if (TokenInGroup(TGLIMIT) || TokenInGroup(TGPOWER)) SubSup(m_aCurToken.nGroup); SmNode *pOperator = lcl_popOrZero(m_aNodeStack); // get argument Power(); pSNode->SetSubNodes(pOperator, lcl_popOrZero(m_aNodeStack)); m_aNodeStack.push(pSNode); } } void SmParser::Oper() { SmTokenType eType (m_aCurToken.eType); SmNode *pNode = NULL; switch (eType) { case TSUM : case TPROD : case TCOPROD : case TINT : case TIINT : case TIIINT : case TLINT : case TLLINT : case TLLLINT : pNode = new SmMathSymbolNode(m_aCurToken); break; case TLIM : case TLIMSUP : case TLIMINF : { const sal_Char* pLim = 0; switch (eType) { case TLIM : pLim = "lim"; break; case TLIMSUP : pLim = "lim sup"; break; case TLIMINF : pLim = "lim inf"; break; default: break; } if( pLim ) m_aCurToken.aText = OUString::createFromAscii(pLim); pNode = new SmTextNode(m_aCurToken, FNT_TEXT); } break; case TOVERBRACE : case TUNDERBRACE : pNode = new SmMathSymbolNode(m_aCurToken); break; case TOPER : NextToken(); OSL_ENSURE(m_aCurToken.eType == TSPECIAL, "Sm: wrong token"); pNode = new SmGlyphSpecialNode(m_aCurToken); break; default : SAL_WARN("starmath", "unknown case"); } m_aNodeStack.push(pNode); NextToken(); } void SmParser::UnOper() { OSL_ENSURE(TokenInGroup(TGUNOPER), "Sm: wrong token"); SmToken aNodeToken = m_aCurToken; SmTokenType eType = m_aCurToken.eType; bool bIsPostfix = eType == TFACT; SmStructureNode *pSNode; SmNode *pOper = 0, *pExtra = 0, *pArg; switch (eType) { case TABS : case TSQRT : /* Dynamic integrals are handled as unary operators so we can wrap the symbol together with the body in a upper level node and make proper graphic arrangements */ case TINTD: NextToken(); break; case TNROOT : NextToken(); Power(); pExtra = lcl_popOrZero(m_aNodeStack); break; case TUOPER : NextToken(); //Let the glyph know what it is... m_aCurToken.eType = TUOPER; m_aCurToken.nGroup = TGUNOPER; GlyphSpecial(); pOper = lcl_popOrZero(m_aNodeStack); break; case TPLUS : case TMINUS : case TPLUSMINUS : case TMINUSPLUS : case TNEG : case TFACT : OpSubSup(); pOper = lcl_popOrZero(m_aNodeStack); break; default : Error(PE_UNOPER_EXPECTED); } // get argument Power(); pArg = lcl_popOrZero(m_aNodeStack); if (eType == TABS) { pSNode = new SmBraceNode(aNodeToken); pSNode->SetScaleMode(SCALE_HEIGHT); // build nodes for left & right lines // (text, group, level of the used token are of no interrest here) // we'll use row & column of the keyword for abs aNodeToken.eType = TABS; aNodeToken.cMathChar = MS_VERTLINE; SmNode* pLeft = new SmMathSymbolNode(aNodeToken); aNodeToken.cMathChar = MS_VERTLINE; SmNode* pRight = new SmMathSymbolNode(aNodeToken); pSNode->SetSubNodes(pLeft, pArg, pRight); } else if (eType == TSQRT || eType == TNROOT) { pSNode = new SmRootNode(aNodeToken); pOper = new SmRootSymbolNode(aNodeToken); pSNode->SetSubNodes(pExtra, pOper, pArg); } else if(eType == TINTD) { pSNode = new SmDynIntegralNode(aNodeToken); pOper = new SmDynIntegralSymbolNode(aNodeToken); pSNode->SetSubNodes(pOper, pArg); } else { pSNode = new SmUnHorNode(aNodeToken); if (bIsPostfix) pSNode->SetSubNodes(pArg, pOper); else // prefix operator pSNode->SetSubNodes(pOper, pArg); } m_aNodeStack.push(pSNode); } void SmParser::Attribut() { OSL_ENSURE(TokenInGroup(TGATTRIBUT), "Sm: wrong token group"); SmStructureNode *pSNode = new SmAttributNode(m_aCurToken); SmNode *pAttr; SmScaleMode eScaleMode = SCALE_NONE; // get appropriate node for the attribute itself switch (m_aCurToken.eType) { case TUNDERLINE : case TOVERLINE : case TOVERSTRIKE : pAttr = new SmRectangleNode(m_aCurToken); eScaleMode = SCALE_WIDTH; break; case TWIDEVEC : case TWIDEHAT : case TWIDETILDE : pAttr = new SmMathSymbolNode(m_aCurToken); eScaleMode = SCALE_WIDTH; break; default : pAttr = new SmMathSymbolNode(m_aCurToken); } NextToken(); pSNode->SetSubNodes(pAttr, 0); pSNode->SetScaleMode(eScaleMode); m_aNodeStack.push(pSNode); } void SmParser::FontAttribut() { OSL_ENSURE(TokenInGroup(TGFONTATTR), "Sm: wrong token group"); switch (m_aCurToken.eType) { case TITALIC : case TNITALIC : case TBOLD : case TNBOLD : case TPHANTOM : m_aNodeStack.push(new SmFontNode(m_aCurToken)); NextToken(); break; case TSIZE : FontSize(); break; case TFONT : Font(); break; case TCOLOR : Color(); break; default : SAL_WARN("starmath", "unknown case"); } } void SmParser::Color() { OSL_ENSURE(m_aCurToken.eType == TCOLOR, "Sm : Ooops..."); // last color rules, get that one SmToken aToken; do { NextToken(); if (TokenInGroup(TGCOLOR)) { aToken = m_aCurToken; NextToken(); } else Error(PE_COLOR_EXPECTED); } while (m_aCurToken.eType == TCOLOR); m_aNodeStack.push(new SmFontNode(aToken)); } void SmParser::Font() { OSL_ENSURE(m_aCurToken.eType == TFONT, "Sm : Ooops..."); // last font rules, get that one SmToken aToken; do { NextToken(); if (TokenInGroup(TGFONT)) { aToken = m_aCurToken; NextToken(); } else Error(PE_FONT_EXPECTED); } while (m_aCurToken.eType == TFONT); m_aNodeStack.push(new SmFontNode(aToken)); } // gets number used as arguments in Math formulas (e.g. 'size' command) // Format: no negative numbers, must start with a digit, no exponent notation, ... static bool lcl_IsNumber(const OUString& rText) { bool bPoint = false; const sal_Unicode* pBuffer = rText.getStr(); for(sal_Int32 nPos = 0; nPos < rText.getLength(); nPos++, pBuffer++) { const sal_Unicode cChar = *pBuffer; if(cChar == '.') { if(bPoint) return false; else bPoint = true; } else if ( !rtl::isAsciiDigit( cChar ) ) return false; } return true; } void SmParser::FontSize() { OSL_ENSURE(m_aCurToken.eType == TSIZE, "Sm : Ooops..."); sal_uInt16 Type; SmFontNode *pFontNode = new SmFontNode(m_aCurToken); NextToken(); switch (m_aCurToken.eType) { case TNUMBER: Type = FNTSIZ_ABSOLUT; break; case TPLUS: Type = FNTSIZ_PLUS; break; case TMINUS: Type = FNTSIZ_MINUS; break; case TMULTIPLY: Type = FNTSIZ_MULTIPLY; break; case TDIVIDEBY: Type = FNTSIZ_DIVIDE; break; default: delete pFontNode; Error(PE_SIZE_EXPECTED); return; } if (Type != FNTSIZ_ABSOLUT) { NextToken(); if (m_aCurToken.eType != TNUMBER) { delete pFontNode; Error(PE_SIZE_EXPECTED); return; } } // get number argument Fraction aValue( 1L ); if (lcl_IsNumber( m_aCurToken.aText )) { double fTmp = OUString(m_aCurToken.aText).toDouble(); if (fTmp != 0.0) { aValue = fTmp; //!! keep the numerator and denominator from being to large //!! otherwise ongoing multiplications may result in overflows //!! (for example in SmNode::SetFontSize the font size calculated //!! may become 0 because of this!!! Happens e.g. for ftmp = 2.9 with Linux //!! or ftmp = 1.11111111111111111... (11/9) on every platform.) if (aValue.GetDenominator() > 1000) { long nNum = aValue.GetNumerator(); long nDenom = aValue.GetDenominator(); while (nDenom > 1000) { nNum /= 10; nDenom /= 10; } aValue = Fraction( nNum, nDenom ); } } } NextToken(); pFontNode->SetSizeParameter(aValue, Type); m_aNodeStack.push(pFontNode); } void SmParser::Brace() { OSL_ENSURE(m_aCurToken.eType == TLEFT || TokenInGroup(TGLBRACES), "Sm: kein Klammer Ausdruck"); SmStructureNode *pSNode = new SmBraceNode(m_aCurToken); SmNode *pBody = 0, *pLeft = 0, *pRight = 0; SmScaleMode eScaleMode = SCALE_NONE; SmParseError eError = PE_NONE; if (m_aCurToken.eType == TLEFT) { NextToken(); eScaleMode = SCALE_HEIGHT; // check for left bracket if (TokenInGroup(TGLBRACES) || TokenInGroup(TGRBRACES)) { pLeft = new SmMathSymbolNode(m_aCurToken); NextToken(); Bracebody(true); pBody = lcl_popOrZero(m_aNodeStack); if (m_aCurToken.eType == TRIGHT) { NextToken(); // check for right bracket if (TokenInGroup(TGLBRACES) || TokenInGroup(TGRBRACES)) { pRight = new SmMathSymbolNode(m_aCurToken); NextToken(); } else eError = PE_RBRACE_EXPECTED; } else eError = PE_RIGHT_EXPECTED; } else eError = PE_LBRACE_EXPECTED; } else { if (TokenInGroup(TGLBRACES)) { pLeft = new SmMathSymbolNode(m_aCurToken); NextToken(); Bracebody(false); pBody = lcl_popOrZero(m_aNodeStack); SmTokenType eExpectedType = TUNKNOWN; switch (pLeft->GetToken().eType) { case TLPARENT : eExpectedType = TRPARENT; break; case TLBRACKET : eExpectedType = TRBRACKET; break; case TLBRACE : eExpectedType = TRBRACE; break; case TLDBRACKET : eExpectedType = TRDBRACKET; break; case TLLINE : eExpectedType = TRLINE; break; case TLDLINE : eExpectedType = TRDLINE; break; case TLANGLE : eExpectedType = TRANGLE; break; case TLFLOOR : eExpectedType = TRFLOOR; break; case TLCEIL : eExpectedType = TRCEIL; break; default : SAL_WARN("starmath", "unknown case"); } if (m_aCurToken.eType == eExpectedType) { pRight = new SmMathSymbolNode(m_aCurToken); NextToken(); } else eError = PE_PARENT_MISMATCH; } else eError = PE_LBRACE_EXPECTED; } if (eError == PE_NONE) { OSL_ENSURE(pLeft, "Sm: NULL pointer"); OSL_ENSURE(pRight, "Sm: NULL pointer"); pSNode->SetSubNodes(pLeft, pBody, pRight); pSNode->SetScaleMode(eScaleMode); m_aNodeStack.push(pSNode); } else { delete pSNode; delete pBody; delete pLeft; delete pRight; Error(eError); } } void SmParser::Bracebody(bool bIsLeftRight) { SmStructureNode *pBody = new SmBracebodyNode(m_aCurToken); SmNodeArray aNodes; sal_uInt16 nNum = 0; // get body if any if (bIsLeftRight) { do { if (m_aCurToken.eType == TMLINE) { m_aNodeStack.push(new SmMathSymbolNode(m_aCurToken)); NextToken(); nNum++; } else if (m_aCurToken.eType != TRIGHT) { Align(); nNum++; if (m_aCurToken.eType != TMLINE && m_aCurToken.eType != TRIGHT) Error(PE_RIGHT_EXPECTED); } } while (m_aCurToken.eType != TEND && m_aCurToken.eType != TRIGHT); } else { do { if (m_aCurToken.eType == TMLINE) { m_aNodeStack.push(new SmMathSymbolNode(m_aCurToken)); NextToken(); nNum++; } else if (!TokenInGroup(TGRBRACES)) { Align(); nNum++; if (m_aCurToken.eType != TMLINE && !TokenInGroup(TGRBRACES)) Error(PE_RBRACE_EXPECTED); } } while (m_aCurToken.eType != TEND && !TokenInGroup(TGRBRACES)); } // build argument vector in parsing order aNodes.resize(nNum); for (sal_uInt16 i = 0; i < nNum; i++) { aNodes[nNum - 1 - i] = lcl_popOrZero(m_aNodeStack); } pBody->SetSubNodes(aNodes); pBody->SetScaleMode(bIsLeftRight ? SCALE_HEIGHT : SCALE_NONE); m_aNodeStack.push(pBody); } void SmParser::Function() { switch (m_aCurToken.eType) { case TFUNC: NextToken(); // skip "FUNC"-statement // fall through case TSIN : case TCOS : case TTAN : case TCOT : case TASIN : case TACOS : case TATAN : case TACOT : case TSINH : case TCOSH : case TTANH : case TCOTH : case TASINH : case TACOSH : case TATANH : case TACOTH : case TLN : case TLOG : case TEXP : m_aNodeStack.push(new SmTextNode(m_aCurToken, FNT_FUNCTION)); NextToken(); break; default: Error(PE_FUNC_EXPECTED); } } void SmParser::Binom() { SmNodeArray ExpressionArray; SmStructureNode *pSNode = new SmTableNode(m_aCurToken); NextToken(); Sum(); Sum(); ExpressionArray.resize(2); for (int i = 0; i < 2; i++) { ExpressionArray[2 - (i + 1)] = lcl_popOrZero(m_aNodeStack); } pSNode->SetSubNodes(ExpressionArray); m_aNodeStack.push(pSNode); } void SmParser::Stack() { SmNodeArray ExpressionArray; NextToken(); if (m_aCurToken.eType == TLGROUP) { sal_uInt16 n = 0; do { NextToken(); Align(); n++; } while (m_aCurToken.eType == TPOUND); ExpressionArray.resize(n); for (sal_uInt16 i = 0; i < n; i++) { ExpressionArray[n - (i + 1)] = lcl_popOrZero(m_aNodeStack); } if (m_aCurToken.eType != TRGROUP) Error(PE_RGROUP_EXPECTED); NextToken(); //We need to let the table node know it context //it's used in SmNodeToTextVisitor SmToken aTok = m_aCurToken; aTok.eType = TSTACK; SmStructureNode *pSNode = new SmTableNode(aTok); pSNode->SetSubNodes(ExpressionArray); m_aNodeStack.push(pSNode); } else Error(PE_LGROUP_EXPECTED); } void SmParser::Matrix() { SmNodeArray ExpressionArray; NextToken(); if (m_aCurToken.eType == TLGROUP) { sal_uInt16 c = 0; do { NextToken(); Align(); c++; } while (m_aCurToken.eType == TPOUND); sal_uInt16 r = 1; while (m_aCurToken.eType == TDPOUND) { NextToken(); for (sal_uInt16 i = 0; i < c; i++) { Align(); if (i < (c - 1)) { if (m_aCurToken.eType == TPOUND) { NextToken(); } else Error(PE_POUND_EXPECTED); } } r++; } long nRC = r * c; ExpressionArray.resize(nRC); for (sal_uInt16 i = 0; i < (nRC); i++) { ExpressionArray[(nRC) - (i + 1)] = lcl_popOrZero(m_aNodeStack); } if (m_aCurToken.eType != TRGROUP) Error(PE_RGROUP_EXPECTED); NextToken(); SmMatrixNode *pMNode = new SmMatrixNode(m_aCurToken); pMNode->SetSubNodes(ExpressionArray); pMNode->SetRowCol(r, c); m_aNodeStack.push(pMNode); } else Error(PE_LGROUP_EXPECTED); } void SmParser::Special() { bool bReplace = false; OUString &rName = m_aCurToken.aText; OUString aNewName; // conversion of symbol names for 6.0 (XML) file format // (name change on import / export. // UI uses localized names XML file format does not.) if( rName.startsWith("%") ) { if (IsImportSymbolNames()) { const SmLocalizedSymbolData &rLSD = SM_MOD()->GetLocSymbolData(); aNewName = rLSD.GetUiSymbolName(rName.copy(1)); bReplace = true; } else if (IsExportSymbolNames()) { const SmLocalizedSymbolData &rLSD = SM_MOD()->GetLocSymbolData(); aNewName = rLSD.GetExportSymbolName(rName.copy(1)); bReplace = true; } } if (!aNewName.isEmpty()) aNewName = "%" + aNewName; if (bReplace && !aNewName.isEmpty() && rName != aNewName) { Replace(GetTokenIndex(), rName.getLength(), aNewName); rName = aNewName; } // add symbol name to list of used symbols const OUString aSymbolName(m_aCurToken.aText.copy(1)); if (!aSymbolName.isEmpty()) AddToUsedSymbols( aSymbolName ); m_aNodeStack.push(new SmSpecialNode(m_aCurToken)); NextToken(); } void SmParser::GlyphSpecial() { m_aNodeStack.push(new SmGlyphSpecialNode(m_aCurToken)); NextToken(); } void SmParser::Error(SmParseError eError) { SmStructureNode *pSNode = new SmExpressionNode(m_aCurToken); SmErrorNode *pErr = new SmErrorNode(eError, m_aCurToken); pSNode->SetSubNodes(pErr, 0); //! put a structure node on the stack (instead of the error node itself) //! because sometimes such a node is expected in order to attach some //! subnodes m_aNodeStack.push(pSNode); AddError(eError, pSNode); NextToken(); } // end gramar SmParser::SmParser() : m_nCurError( 0 ) , m_nBufferIndex( 0 ) , m_nTokenIndex( 0 ) , m_Row( 0 ) , m_nColOff( 0 ) , m_aDotLoc( LanguageTag::convertToLocale( LANGUAGE_ENGLISH_US ) ) { bImportSymNames = m_bExportSymNames = false; m_nLang = Application::GetSettings().GetUILanguageTag().getLanguageType(); } SmNode *SmParser::Parse(const OUString &rBuffer) { ClearUsedSymbols(); m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); m_nBufferIndex = 0; m_nTokenIndex = 0; m_Row = 1; m_nColOff = 0; m_nCurError = -1; for ( size_t i = 0, n = m_aErrDescList.size(); i < n; ++i ) delete m_aErrDescList[ i ]; m_aErrDescList.clear(); while ( !m_aNodeStack.empty() ) m_aNodeStack.pop(); SetLanguage( Application::GetSettings().GetUILanguageTag().getLanguageType() ); NextToken(); Table(); SmNode* result = lcl_popOrZero(m_aNodeStack); return result; } SmNode *SmParser::ParseExpression(const OUString &rBuffer) { m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); m_nBufferIndex = 0; m_nTokenIndex = 0; m_Row = 1; m_nColOff = 0; m_nCurError = -1; for ( size_t i = 0, n = m_aErrDescList.size(); i < n; ++i ) delete m_aErrDescList[ i ]; m_aErrDescList.clear(); while ( !m_aNodeStack.empty() ) m_aNodeStack.pop(); SetLanguage( Application::GetSettings().GetUILanguageTag().getLanguageType() ); NextToken(); Expression(); SmNode* result = lcl_popOrZero(m_aNodeStack); return result; } size_t SmParser::AddError(SmParseError Type, SmNode *pNode) { SmErrorDesc *pErrDesc = new SmErrorDesc; pErrDesc->Type = Type; pErrDesc->pNode = pNode; pErrDesc->Text = SM_RESSTR(RID_ERR_IDENT); sal_uInt16 nRID; switch (Type) { case PE_UNEXPECTED_CHAR: nRID = RID_ERR_UNEXPECTEDCHARACTER; break; case PE_LGROUP_EXPECTED: nRID = RID_ERR_LGROUPEXPECTED; break; case PE_RGROUP_EXPECTED: nRID = RID_ERR_RGROUPEXPECTED; break; case PE_LBRACE_EXPECTED: nRID = RID_ERR_LBRACEEXPECTED; break; case PE_RBRACE_EXPECTED: nRID = RID_ERR_RBRACEEXPECTED; break; case PE_FUNC_EXPECTED: nRID = RID_ERR_FUNCEXPECTED; break; case PE_UNOPER_EXPECTED: nRID = RID_ERR_UNOPEREXPECTED; break; case PE_BINOPER_EXPECTED: nRID = RID_ERR_BINOPEREXPECTED; break; case PE_SYMBOL_EXPECTED: nRID = RID_ERR_SYMBOLEXPECTED; break; case PE_IDENTIFIER_EXPECTED: nRID = RID_ERR_IDENTEXPECTED; break; case PE_POUND_EXPECTED: nRID = RID_ERR_POUNDEXPECTED; break; case PE_COLOR_EXPECTED: nRID = RID_ERR_COLOREXPECTED; break; case PE_RIGHT_EXPECTED: nRID = RID_ERR_RIGHTEXPECTED; break; default: nRID = RID_ERR_UNKNOWN; } pErrDesc->Text += SM_RESSTR(nRID); m_aErrDescList.push_back( pErrDesc ); return m_aErrDescList.size()-1; } const SmErrorDesc *SmParser::NextError() { if ( !m_aErrDescList.empty() ) if (m_nCurError > 0) return m_aErrDescList[ --m_nCurError ]; else { m_nCurError = 0; return m_aErrDescList[ m_nCurError ]; } else return NULL; } const SmErrorDesc *SmParser::PrevError() { if ( !m_aErrDescList.empty() ) if (m_nCurError < (int) (m_aErrDescList.size() - 1)) return m_aErrDescList[ ++m_nCurError ]; else { m_nCurError = (int) (m_aErrDescList.size() - 1); return m_aErrDescList[ m_nCurError ]; } else return NULL; } const SmErrorDesc *SmParser::GetError(size_t i) { if ( i < m_aErrDescList.size() ) return m_aErrDescList[ i ]; if ( (size_t)m_nCurError < m_aErrDescList.size() ) return m_aErrDescList[ m_nCurError ]; return NULL; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */