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 * |
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 |
|
|
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); |
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); } |
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 |
|
|
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 |
/** |
/** |
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"); |
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"); |
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; |
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' |
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 |
* |
* |
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 { |
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; |
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 |
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; |
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 |
{ |
{ |
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; |
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; |
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 |
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 |