12void TclParser::DEBUG_PRINT(
const std::string& s)
14 std::cout << std::string(2 * level,
' ') << s <<
'\n';
17static constexpr std::string_view type2string(
int type)
22 case TCL_TOKEN_SIMPLE_WORD:
24 case TCL_TOKEN_EXPAND_WORD:
30 case TCL_TOKEN_COMMAND:
32 case TCL_TOKEN_VARIABLE:
34 case TCL_TOKEN_SUB_EXPR:
36 case TCL_TOKEN_OPERATOR:
45static constexpr bool inRange(
char c,
char low,
char high)
48 return t <= unsigned(high - low);
51static bool isNumber(std::string_view str)
53 if (str.starts_with(
'-') || str.starts_with(
'+')) {
56 if (str.starts_with(
"0x") || str.starts_with(
"0X")) {
59 return inRange(c,
'0',
'9') ||
60 inRange(c,
'a',
'f') ||
65 [](
char c) {
return inRange(c,
'0',
'9'); });
72 , colors(input.size(),
'.')
75 parse(parseStr.data(), narrow<int>(parseStr.size()), COMMAND);
78void TclParser::parse(
const char* p,
int size, ParseType type)
80 ScopedAssign sa1(offset, offset + narrow<int>(p - parseStr.data()));
82 last.push_back(offset);
92 int parseStatus = (type == EXPRESSION)
93 ? Tcl_ParseExpr(interp, parseStr.data(), int(parseStr.size()), &parseInfo)
94 : Tcl_ParseCommand(interp, parseStr.data(), int(parseStr.size()), 1, &parseInfo);
95 if (parseStatus == TCL_OK)
break;
96 Tcl_FreeParse(&parseInfo);
99 bool allowComplete = ((offset + parseStr.size()) >= colors.size()) &&
101 Tcl_Obj* resObj = Tcl_GetObjResult(interp);
103 const char* resStr = Tcl_GetStringFromObj(resObj, &resLen);
104 std::string_view error(resStr, resLen);
106 if (allowComplete && error.starts_with(
"missing close-brace")) {
108 }
else if (allowComplete && error.starts_with(
"missing close-bracket")) {
110 }
else if (allowComplete && error.starts_with(
"missing \"")) {
112 }
else if (allowComplete && error.starts_with(
"unbalanced open paren")) {
114 }
else if (allowComplete && error.starts_with(
"missing operand")) {
121 }
else if (allowComplete && error.starts_with(
"missing )")) {
125 setColors(parseStr.data(),
int(parseStr.size()),
'E');
126 if ((offset + size) <
int(colors.size())) last.pop_back();
131 if (type == EXPRESSION) {
134 if (parseInfo.commentSize) {
135 DEBUG_PRINT(
"COMMENT: " + std::string_view(parseInfo.commentStart, parseInfo.commentSize));
136 setColors(parseInfo.commentStart, parseInfo.commentSize,
'c');
138 DEBUG_PRINT(
"COMMAND: " + std::string_view(parseInfo.commandStart, parseInfo.commandSize));
140 printTokens({parseInfo.tokenPtr, size_t(parseInfo.numTokens)});
145 if ((offset + size) < int(colors.size())) last.pop_back();
147 const char* nextStart = parseInfo.commandStart + parseInfo.commandSize;
148 Tcl_FreeParse(&parseInfo);
150 if (type == COMMAND) {
152 auto nextSize = int((parseStr.data() + parseStr.size()) - nextStart);
154 parse(nextStart, nextSize, type);
159void TclParser::printTokens(std::span<const Tcl_Token> tokens)
164 for (
size_t i = 0; i < tokens.size(); ) {
165 const Tcl_Token& token = tokens[i];
166 std::string_view tokenStr(token.start, token.size);
167 DEBUG_PRINT(type2string(token.type) +
" -> " + tokenStr);
168 switch (token.type) {
169 case TCL_TOKEN_VARIABLE:
170 assert(token.numComponents >= 1);
171 setColors(tokens[i + 1].start - 1, tokens[i + 1].size + 1,
'v');
174 case TCL_TOKEN_SIMPLE_WORD:
175 if (*token.start ==
'"') {
176 setColors(token.start, token.size,
'l');
178 if ((i == 0) &&
isProc(interp, tokenStr)) {
179 setColors(token.start, token.size,
'p');
182 case TCL_TOKEN_EXPAND_WORD:
183 setColors(token.start, 3,
'o');
185 case TCL_TOKEN_OPERATOR:
187 setColors(token.start, token.size,
'o');
190 if (isNumber(tokenStr) || (*token.start ==
'"')) {
192 setColors(token.start, token.size,
'l');
196 if (token.type == TCL_TOKEN_COMMAND) {
197 parse(token.start + 1, token.size - 2, COMMAND);
198 }
else if (token.type == TCL_TOKEN_SIMPLE_WORD) {
199 ParseType subType = guessSubType(tokens, i);
200 if (subType != OTHER) {
201 parse(tokens[i + 1].start, tokens[i + 1].size, subType);
204 printTokens(tokens.subspan(++i, token.numComponents));
205 i += token.numComponents;
209TclParser::ParseType TclParser::guessSubType(std::span<const Tcl_Token> tokens,
size_t i)
212 if ((i >= 1) && (tokens[i - 1].type == TCL_TOKEN_TEXT)) {
213 std::string_view prevText(tokens[i - 1].start, tokens[i - 1].size);
214 if (prevText ==
one_of(
"if",
"elseif",
"expr")) {
220 if (*tokens[i].start ==
'{') {
230 auto command =
tmpStrCat(
"openmsx::is_command_name {", str,
'}');
231 if (Tcl_Eval(interp, command.c_str()) != TCL_OK)
return false;
233 if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), &result)
234 != TCL_OK)
return false;
238void TclParser::setColors(
const char* p,
int size,
char c)
240 int start = narrow<int>(p - parseStr.data()) + offset;
241 int stop = std::min(start + size,
int(colors.size()));
242 for (
auto i :
xrange(start, stop)) {
Assign new value to some variable and restore the original value when this object goes out of scope.
TclParser(Tcl_Interp *interp, std::string_view input)
Input: Tcl interpreter and command to parse
static bool isProc(Tcl_Interp *interp, std::string_view str)
Is the given string a valid Tcl command.
constexpr bool all_of(InputRange &&range, UnaryPredicate pred)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)