/[svn]/linuxsampler/trunk/src/network/lscp.y
ViewVC logotype

Diff of /linuxsampler/trunk/src/network/lscp.y

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 2525 by schoenebeck, Mon Feb 24 17:52:51 2014 UTC revision 3787 by schoenebeck, Mon Jun 8 11:55:18 2020 UTC
# Line 3  Line 3 
3   *   LinuxSampler - modular, streaming capable sampler                     *   *   LinuxSampler - modular, streaming capable sampler                     *
4   *                                                                         *   *                                                                         *
5   *   Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck   *   *   Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck   *
6   *   Copyright (C) 2005 - 2014 Christian Schoenebeck                       *   *   Copyright (C) 2005 - 2020 Christian Schoenebeck                       *
7   *                                                                         *   *                                                                         *
8   *   This program is free software; you can redistribute it and/or modify  *   *   This program is free software; you can redistribute it and/or modify  *
9   *   it under the terms of the GNU General Public License as published by  *   *   it under the terms of the GNU General Public License as published by  *
# Line 34  Line 34 
34  #include "lscpparser.h"  #include "lscpparser.h"
35  #include "lscpserver.h"  #include "lscpserver.h"
36  #include "lscpevent.h"  #include "lscpevent.h"
37  #include "lscpsymbols.h"  
38    #if AC_APPLE_UNIVERSAL_BUILD
39    # include "lscp.tab.h"
40    #else
41    # include "lscpsymbols.h"
42    #endif
43    
44  #include <algorithm>  #include <algorithm>
45  #include "lscp.h"  #include "lscp.h"
46    
# Line 43  namespace LinuxSampler { Line 49  namespace LinuxSampler {
49  // to save us typing work in the rules action definitions  // to save us typing work in the rules action definitions
50  #define LSCPSERVER ((yyparse_param_t*) yyparse_param)->pServer  #define LSCPSERVER ((yyparse_param_t*) yyparse_param)->pServer
51  #define SESSION_PARAM ((yyparse_param_t*) yyparse_param)  #define SESSION_PARAM ((yyparse_param_t*) yyparse_param)
52  #define INCREMENT_LINE { SESSION_PARAM->iLine++; SESSION_PARAM->iColumn = 0; sParsed.clear(); }  #define INCREMENT_LINE { SESSION_PARAM->onNextLine(); sParsed.clear(); }
53    
54  // clears input buffer  // clears input buffer
55  void restart(yyparse_param_t* pparam, int& yychar);  void restart(yyparse_param_t* pparam, int& yychar);
# Line 444  set_instruction       :  AUDIO_OUTPUT_DE Line 450  set_instruction       :  AUDIO_OUTPUT_DE
450                        |  ECHO SP boolean                                                                  { $$ = LSCPSERVER->SetEcho((yyparse_param_t*) yyparse_param, $3);  }                        |  ECHO SP boolean                                                                  { $$ = LSCPSERVER->SetEcho((yyparse_param_t*) yyparse_param, $3);  }
451                        |  SHELL SP INTERACT SP boolean                                                     { $$ = LSCPSERVER->SetShellInteract((yyparse_param_t*) yyparse_param, $5); }                        |  SHELL SP INTERACT SP boolean                                                     { $$ = LSCPSERVER->SetShellInteract((yyparse_param_t*) yyparse_param, $5); }
452                        |  SHELL SP AUTO_CORRECT SP boolean                                                 { $$ = LSCPSERVER->SetShellAutoCorrect((yyparse_param_t*) yyparse_param, $5); }                        |  SHELL SP AUTO_CORRECT SP boolean                                                 { $$ = LSCPSERVER->SetShellAutoCorrect((yyparse_param_t*) yyparse_param, $5); }
453                          |  SHELL SP DOC SP boolean                                                          { $$ = LSCPSERVER->SetShellDoc((yyparse_param_t*) yyparse_param, $5); }
454                        |  VOLUME SP volume_value                                                           { $$ = LSCPSERVER->SetGlobalVolume($3);                            }                        |  VOLUME SP volume_value                                                           { $$ = LSCPSERVER->SetGlobalVolume($3);                            }
455                        |  VOICES SP number                                                                 { $$ = LSCPSERVER->SetGlobalMaxVoices($3);                         }                        |  VOICES SP number                                                                 { $$ = LSCPSERVER->SetGlobalMaxVoices($3);                         }
456                        |  STREAMS SP number                                                                { $$ = LSCPSERVER->SetGlobalMaxStreams($3);                        }                        |  STREAMS SP number                                                                { $$ = LSCPSERVER->SetGlobalMaxStreams($3);                        }
# Line 1281  NAME                  :  'N''A''M''E' Line 1288  NAME                  :  'N''A''M''E'
1288  ECHO                  :  'E''C''H''O'  ECHO                  :  'E''C''H''O'
1289                        ;                        ;
1290    
1291    DOC                   :  'D''O''C'
1292                          ;
1293    
1294  QUIT                  :  'Q''U''I''T'  QUIT                  :  'Q''U''I''T'
1295                        ;                        ;
1296    
# Line 1450  inline static int _yyDefaultReduce(std:: Line 1460  inline static int _yyDefaultReduce(std::
1460    
1461  static bool yyValid(std::vector<YYTYPE_INT16>& stack, char ch);  static bool yyValid(std::vector<YYTYPE_INT16>& stack, char ch);
1462    
1463    /**
1464     * A set of parser symbol stacks. This type is used for the recursive algorithms
1465     * in a) yyAutoComplete() and b) walkAndFillExpectedSymbols() for detecting
1466     * endless recursions.
1467     *
1468     * This unique container is used to keep track of all previous parser states
1469     * (stacks), for detecting a parser symbol stack that has already been
1470     * encountered before. Because if yyAutoComplete() or
1471     * walkAndFillExpectedSymbols() reach the exactly same parser symbol stack
1472     * again, that means there is an endless recursion in that part of the grammar
1473     * tree branch and shall not be evaluated any further, since it would end up in
1474     * an endless loop of the algorithm otherwise.
1475     *
1476     * This solution consumes a lot of memory, but unfortunately there is no other
1477     * easy way to solve it. With our grammar and today's usual memory heap size &
1478     * memory stack size in modern devices, it should be fine though.
1479     */
1480    typedef std::set< std::vector<YYTYPE_INT16> > YYStackHistory;
1481    
1482    /*
1483     * YYTERROR macro was removed in Bison 3.6.0, we need it in function below.
1484     */
1485    #ifndef YYTERROR
1486    # define YYTERROR YYSYMBOL_YYerror
1487    #endif
1488    
1489  #define DEBUG_BISON_SYNTAX_ERROR_WALKER 0  #define DEBUG_BISON_SYNTAX_ERROR_WALKER 0
1490    
1491  /**  /**
# Line 1471  static bool yyValid(std::vector<YYTYPE_I Line 1507  static bool yyValid(std::vector<YYTYPE_I
1507   * @param expectedSymbols - will be filled with next expected grammar symbols   * @param expectedSymbols - will be filled with next expected grammar symbols
1508   * @param nextExpectedChars - just for internal purpose, due to the recursive   * @param nextExpectedChars - just for internal purpose, due to the recursive
1509   *                            implementation of this function, do supply an   *                            implementation of this function, do supply an
1510   *                            empty character for this argument   *                            empty string for this argument
1511   * @param depth - just for internal debugging purposes   * @param history - only for internal purpose, keeps a history of all previous
1512     *                  parser symbol stacks (just for avoiding endless recursion in
1513     *                  this recursive algorithm), do supply an empty history
1514     * @param depth - just for internal debugging purposes, do not supply it
1515   */   */
1516  static void walkAndFillExpectedSymbols(  static void walkAndFillExpectedSymbols(
1517      std::vector<YYTYPE_INT16>& stack,      std::vector<YYTYPE_INT16>& stack,
1518      std::map<String,BisonSymbolInfo>& expectedSymbols,      std::map<String,BisonSymbolInfo>& expectedSymbols,
1519      String& nextExpectedChars, int depth = 0)      String& nextExpectedChars, YYStackHistory& history, int depth = 0)
1520  {  {
1521  #if DEBUG_BISON_SYNTAX_ERROR_WALKER  #if DEBUG_BISON_SYNTAX_ERROR_WALKER
1522      printf("\n");      printf("\n");
# Line 1490  static void walkAndFillExpectedSymbols( Line 1529  static void walkAndFillExpectedSymbols(
1529  #endif  #endif
1530      startLabel:      startLabel:
1531    
1532        // detect endless recursion
1533        if (history.count(stack)) return;
1534        history.insert(stack);
1535    
1536      if (stack.empty()) {      if (stack.empty()) {
1537  #if DEBUG_BISON_SYNTAX_ERROR_WALKER  #if DEBUG_BISON_SYNTAX_ERROR_WALKER
1538          for (int i = 0; i < depth; ++i) printf("\t");          for (int i = 0; i < depth; ++i) printf("\t");
# Line 1584  static void walkAndFillExpectedSymbols( Line 1627  static void walkAndFillExpectedSymbols(
1627              // the token is valid, "stackCopy" has been reduced accordingly              // the token is valid, "stackCopy" has been reduced accordingly
1628              // and now do recurse ...              // and now do recurse ...
1629              nextExpectedChars += _tokenName(token);              nextExpectedChars += _tokenName(token);
1630              nextExpectedCharsLen = nextExpectedChars.size();              nextExpectedCharsLen = (int)nextExpectedChars.size();
1631              walkAndFillExpectedSymbols( //FIXME: could cause stack overflow (should be a loop instead), is probably fine with our current grammar though              walkAndFillExpectedSymbols( //FIXME: could cause stack overflow (should be a loop instead), is probably fine with our current grammar though
1632                  stackCopy, expectedSymbols, nextExpectedChars, depth + 1                  stackCopy, expectedSymbols, nextExpectedChars, history, depth + 1
1633              );              );
1634              nextExpectedChars.resize(nextExpectedCharsLen); // restore 'nextExpectedChars'              nextExpectedChars.resize(nextExpectedCharsLen); // restore 'nextExpectedChars'
1635              continue;              continue;
# Line 1627  static void walkAndFillExpectedSymbols( Line 1670  static void walkAndFillExpectedSymbols(
1670    
1671          // "shift" / push the new state on the symbol stack and call this          // "shift" / push the new state on the symbol stack and call this
1672          // function recursively, and restore the stack after the recurse return          // function recursively, and restore the stack after the recurse return
1673          stackSize = stack.size();          stackSize = (int)stack.size();
1674          nextExpectedCharsLen = nextExpectedChars.size();          nextExpectedCharsLen = (int)nextExpectedChars.size();
1675          stack.push_back(action);          stack.push_back(action);
1676          nextExpectedChars += _tokenName(token);          nextExpectedChars += _tokenName(token);
1677          walkAndFillExpectedSymbols( //FIXME: could cause stack overflow (should be a loop instead), is probably fine with our current grammar though          walkAndFillExpectedSymbols( //FIXME: could cause stack overflow (should be a loop instead), is probably fine with our current grammar though
1678              stack, expectedSymbols, nextExpectedChars, depth + 1              stack, expectedSymbols, nextExpectedChars, history, depth + 1
1679          );          );
1680          stack.resize(stackSize); // restore stack          stack.resize(stackSize); // restore stack
1681          nextExpectedChars.resize(nextExpectedCharsLen); // restore 'nextExpectedChars'          nextExpectedChars.resize(nextExpectedCharsLen); // restore 'nextExpectedChars'
# Line 1673  static void walkAndFillExpectedSymbols( Line 1716  static void walkAndFillExpectedSymbols(
1716  #endif  #endif
1717  }  }
1718    
1719    /**
1720     * Just a convenience wrapper on top of the actual walkAndFillExpectedSymbols()
1721     * implementation above, which can be called with less parameters than the
1722     * implementing function above actually requires.
1723     */
1724    static void walkAndFillExpectedSymbols(
1725        std::vector<YYTYPE_INT16>& stack,
1726        std::map<String,BisonSymbolInfo>& expectedSymbols)
1727    {
1728        String nextExpectedChars;
1729        YYStackHistory history;
1730    
1731        walkAndFillExpectedSymbols(
1732            stack, expectedSymbols, nextExpectedChars, history
1733        );
1734    }
1735    
1736  #define DEBUG_PUSH_PARSE 0  #define DEBUG_PUSH_PARSE 0
1737    
1738  /**  /**
1739   * Implements parsing exactly one character (given by @a c), continueing at the   * Implements parsing exactly one character (given by @a ch), continueing at the
1740   * parser position reflected by @a stack. The @a stack will hold the new parser   * parser position reflected by @a stack. The @a stack will hold the new parser
1741   * state after this call.   * state after this call.
1742   *   *
# Line 1746  static bool yyPushParse(std::vector<YYTY Line 1806  static bool yyPushParse(std::vector<YYTY
1806   * The @a stack will reflect the new parser state after this call.   * The @a stack will reflect the new parser state after this call.
1807   *   *
1808   * This is just a wrapper ontop of yyPushParse() which converts parser   * This is just a wrapper ontop of yyPushParse() which converts parser
1809   * exceptions thrown by yyPushParse() into negative return value.   * exceptions thrown by yyPushParse() into @c false return value.
1810   */   */
1811  static bool yyValid(std::vector<YYTYPE_INT16>& stack, char ch) {  static bool yyValid(std::vector<YYTYPE_INT16>& stack, char ch) {
1812      try {      try {
# Line 1812  static std::set<String> yyExpectedSymbol Line 1872  static std::set<String> yyExpectedSymbol
1872      yyparse_param_t* param = GetCurrentYaccSession();      yyparse_param_t* param = GetCurrentYaccSession();
1873      YYTYPE_INT16* ss = (*param->ppStackBottom);      YYTYPE_INT16* ss = (*param->ppStackBottom);
1874      YYTYPE_INT16* sp = (*param->ppStackTop);      YYTYPE_INT16* sp = (*param->ppStackTop);
1875      int iStackSize   = sp - ss + 1;      int iStackSize   = int(sp - ss + 1);
1876      // copy and wrap parser's symbol stack into a convenient STL container      // copy and wrap parser's symbol stack into a convenient STL container
1877      std::vector<YYTYPE_INT16> stack;      std::vector<YYTYPE_INT16> stack;
1878      for (int i = 0; i < iStackSize; ++i) {      for (int i = 0; i < iStackSize; ++i) {
1879          stack.push_back(ss[i]);          stack.push_back(ss[i]);
1880      }      }
     String notUsedHere;  
1881      // do the actual parser work      // do the actual parser work
1882      walkAndFillExpectedSymbols(stack, expectedSymbols, notUsedHere);      walkAndFillExpectedSymbols(stack, expectedSymbols);
1883    
1884      // convert expectedSymbols to the result set      // convert expectedSymbols to the result set
1885      std::set<String> result;      std::set<String> result;
# Line 1832  static std::set<String> yyExpectedSymbol Line 1891  static std::set<String> yyExpectedSymbol
1891  #define DEBUG_YY_AUTO_COMPLETE 0  #define DEBUG_YY_AUTO_COMPLETE 0
1892    
1893  /**  /**
  * A set of parser symbol stacks. This type is used in yyAutoComplete() to keep  
  * track of all previous parser states, for detecting a parser symbol stack that  
  * has already been before. Because if yyAutoComplete() reaches the exactly same  
  * parser symbol stack again, it means there is an endless recursion in that  
  * part of the grammar tree branch and shall not be evaluated any further,  
  * because it would end up in an endless loop otherwise.  
  *  
  * This solution consumes a lot of memory, but unfortunately there is no other  
  * easy way to solve it. With our grammar and today's memory heap & memory stack  
  * it should be fine though.  
  */  
 typedef std::set< std::vector<YYTYPE_INT16> > YYStackHistory;  
   
 /**  
1894   * Generates and returns an auto completion string for the current parser   * Generates and returns an auto completion string for the current parser
1895   * state given by @a stack. That means, this function will return the longest   * state given by @a stack. That means, this function will return the longest
1896   * sequence of characters that is uniqueley expected to be sent next by the LSCP   * sequence of characters that is uniqueley expected to be sent next by the LSCP
# Line 1868  typedef std::set< std::vector<YYTYPE_INT Line 1913  typedef std::set< std::vector<YYTYPE_INT
1913   * @param stack - current Bison (yacc) symbol stack to create auto completion for   * @param stack - current Bison (yacc) symbol stack to create auto completion for
1914   * @param history - only for internal purpose, keeps a history of all previous   * @param history - only for internal purpose, keeps a history of all previous
1915   *                  parser symbol stacks (just for avoiding endless recursion in   *                  parser symbol stacks (just for avoiding endless recursion in
1916   *                  this auto completion algorithm)   *                  this auto completion algorithm), do supply an empty history
1917   * @param depth - just for internal debugging purposes   * @param depth - just for internal debugging purposes, do not supply anything
1918   * @returns auto completion for current, given parser state   * @returns auto completion for current, given parser state
1919   */   */
1920  static String yyAutoComplete(std::vector<YYTYPE_INT16>& stack, YYStackHistory& history, int depth = 0) {  static String yyAutoComplete(std::vector<YYTYPE_INT16>& stack, YYStackHistory& history, int depth = 0) {
1921      std::map<String,BisonSymbolInfo> expectedSymbols;      std::map<String,BisonSymbolInfo> expectedSymbols;
1922      String notUsedHere;      walkAndFillExpectedSymbols(stack, expectedSymbols);
     walkAndFillExpectedSymbols(stack, expectedSymbols, notUsedHere);  
1923      if (expectedSymbols.size() == 1) {      if (expectedSymbols.size() == 1) {
1924          String name          = expectedSymbols.begin()->first;          String name          = expectedSymbols.begin()->first;
1925          BisonSymbolInfo info = expectedSymbols.begin()->second;          BisonSymbolInfo info = expectedSymbols.begin()->second;
# Line 1944  static String yyAutoComplete(std::vector Line 1988  static String yyAutoComplete(std::vector
1988          // (from the left)          // (from the left)
1989          String sCommon;          String sCommon;
1990          for (int i = 0; true; ++i) {          for (int i = 0; true; ++i) {
1991              char c;              char c = '\0';
1992              for (std::map<String,BisonSymbolInfo>::const_iterator it = expectedSymbols.begin();              for (std::map<String,BisonSymbolInfo>::const_iterator it = expectedSymbols.begin();
1993                   it != expectedSymbols.end(); ++it)                   it != expectedSymbols.end(); ++it)
1994              {              {
# Line 1978  static String yyAutoComplete(std::vector Line 2022  static String yyAutoComplete(std::vector
2022    
2023  /**  /**
2024   * Just a convenience wrapper on top of the actual yyAutoComplete()   * Just a convenience wrapper on top of the actual yyAutoComplete()
2025   * implementation. See description above for details.   * implementation. See its description above for details.
2026   */   */
2027  static String yyAutoComplete(std::vector<YYTYPE_INT16>& stack) {  static String yyAutoComplete(std::vector<YYTYPE_INT16>& stack) {
2028      YYStackHistory history;      YYStackHistory history;
# Line 2009  namespace LinuxSampler { Line 2053  namespace LinuxSampler {
2053   * application will only have to show the results of the LSCP server's   * application will only have to show the results of the LSCP server's
2054   * evaluation to the user on the screen.   * evaluation to the user on the screen.
2055   *   *
2056     * @param line - the current command line to be evaluated by LSCP parser
2057     * @param param = reentrant parser session parameters
2058     * @param possibilities - whether all possibilities shall be shown
2059   * @returns LSCP shell response line to be returned to the client   * @returns LSCP shell response line to be returned to the client
2060   */   */
2061  String lscpParserProcessShellInteraction(String& line, yyparse_param_t* param) {  String lscpParserProcessShellInteraction(String& line, yyparse_param_t* param, bool possibilities) {
2062      // first, determine how many characters (starting from the left) of the      // first, determine how many characters (starting from the left) of the
2063      // given input line are already syntactically correct      // given input line are already syntactically correct
2064      std::vector<YYTYPE_INT16> stack;      std::vector<YYTYPE_INT16> stack;
# Line 2022  String lscpParserProcessShellInteraction Line 2069  String lscpParserProcessShellInteraction
2069      // if auto correction is enabled, apply the auto corrected string to      // if auto correction is enabled, apply the auto corrected string to
2070      // intput/output string 'line'      // intput/output string 'line'
2071      if (param->bShellAutoCorrect) {      if (param->bShellAutoCorrect) {
2072          int nMin = (n < line.length()) ? n : line.length();          int nMin = int( (n < line.length()) ? n : line.length() );
2073          line.replace(0, nMin, l.substr(0, nMin));          line.replace(0, nMin, l.substr(0, nMin));
2074      }      }
2075    
2076        ssize_t cursorPos = line.size() + param->iCursorOffset;
2077        if (cursorPos < 0) cursorPos = 0;
2078    
2079      // generate an info string that will be sent to the LSCP shell for letting      // generate an info string that will be sent to the LSCP shell for letting
2080      // it know which part is correct, which one is wrong, where is the cursor, etc.      // it know which part is correct, which one is wrong, where is the cursor, etc.
2081      String result = line;      String result = line;
2082      result.insert(n <= result.length() ? n : result.length(), LSCP_SHK_GOOD_FRONT);      result.insert(n <= result.length() ? n : result.length(), LSCP_SHK_GOOD_FRONT);
2083        result.insert(cursorPos <= n ? cursorPos : cursorPos + String(LSCP_SHK_GOOD_FRONT).length(), LSCP_SHK_CURSOR);
2084      int code = (n > line.length()) ? LSCP_SHU_COMPLETE : (n < line.length()) ?      int code = (n > line.length()) ? LSCP_SHU_COMPLETE : (n < line.length()) ?
2085                 LSCP_SHU_SYNTAX_ERR : LSCP_SHU_INCOMPLETE;                 LSCP_SHU_SYNTAX_ERR : LSCP_SHU_INCOMPLETE;
2086      result = "SHU:" + ToString(code) + ":" + result + LSCP_SHK_CURSOR;      result = "SHU:" + ToString(code) + ":" + result;
2087      //if (n > line.length()) result += " [OK]";      //if (n > line.length()) result += " [OK]";
2088    
2089      // get a clean parser stack to the last valid parse position      // get a clean parser stack to the last valid parse position
# Line 2044  String lscpParserProcessShellInteraction Line 2095  String lscpParserProcessShellInteraction
2095      if (!l.empty()) yyValidCharacters(stack, l, param->bShellAutoCorrect);      if (!l.empty()) yyValidCharacters(stack, l, param->bShellAutoCorrect);
2096    
2097      // generate auto completion suggestion (based on the current parser stack)      // generate auto completion suggestion (based on the current parser stack)
2098      String sSuggestion = yyAutoComplete(stack);      std::vector<YYTYPE_INT16> stackCopy = stack; // make a copy, since yyAutoComplete() might alter the stack
2099        String sSuggestion = yyAutoComplete(stackCopy);
2100      if (!sSuggestion.empty()) result += LSCP_SHK_SUGGEST_BACK + sSuggestion;      if (!sSuggestion.empty()) result += LSCP_SHK_SUGGEST_BACK + sSuggestion;
2101    
2102        if (possibilities) {
2103            // append all possible terminals and non-terminals according to
2104            // current parser state
2105            std::map<String,BisonSymbolInfo> expectedSymbols;
2106            walkAndFillExpectedSymbols(stack, expectedSymbols);
2107    
2108            // pretend to LSCP shell that the following terminal symbols were
2109            // non-terminal symbols (since they are not human visible for auto
2110            // completion on the shell's screen)
2111            std::set<String> specialNonTerminals;
2112            specialNonTerminals.insert("SP");
2113            specialNonTerminals.insert("CR");
2114            specialNonTerminals.insert("LF");
2115    
2116            String sPossibilities;
2117            int iNonTerminals = 0;
2118            int iTerminals    = 0;
2119            for (std::map<String,BisonSymbolInfo>::const_iterator it = expectedSymbols.begin();
2120                 it != expectedSymbols.end(); ++it)
2121            {
2122                if (!sPossibilities.empty()) sPossibilities += " | ";
2123                if (it->second.isTerminalSymbol && !specialNonTerminals.count(it->first)) {
2124                    sPossibilities += it->first;
2125                    iTerminals++;
2126                } else {
2127                    sPossibilities += "<" + it->first + ">";
2128                    iNonTerminals++;
2129                }
2130            }
2131            if (!sPossibilities.empty() && (iNonTerminals || iTerminals > 1)) {
2132                result += LSCP_SHK_POSSIBILITIES_BACK + sPossibilities;
2133            }
2134        }
2135    
2136  #if DEBUG_SHELL_INTERACTION  #if DEBUG_SHELL_INTERACTION
2137      printf("%s\n", result.c_str());      printf("%s\n", result.c_str());
2138  #endif  #endif

Legend:
Removed from v.2525  
changed lines
  Added in v.3787

  ViewVC Help
Powered by ViewVC