/** * @brief Allocate and initialize a command action * @param[in] command A command for the action * @return An initialised action */ uiAction_t* UI_AllocStaticCommandAction (const char *command) { uiAction_t* action = UI_AllocStaticAction(); action->type = EA_CMD; action->d.terminal.d1.constString = command; return action; }
/** * @brief Parse actions and return action list * @return The first element from a list of action * @sa ea_t * @todo need cleanup, compute action out of the final memory; reduce number of var */ static uiAction_t* UI_ParseActionList (uiNode_t* node, const char** text, const char** token) { const char* errhead = "UI_ParseActionList: unexpected end of file (in event)"; uiAction_t* firstAction; uiAction_t* lastAction; uiAction_t* action; lastAction = nullptr; firstAction = nullptr; /* prevent bad position */ if ((*token)[0] != '{') { Com_Printf("UI_ParseActionList: token \"{\" expected, but \"%s\" found (in event) (node: %s)\n", *token, UI_GetPath(node)); return nullptr; } while (true) { bool result; int type = EA_NULL; /* get new token */ *token = Com_EParse(text, errhead, nullptr); if (!*token) return nullptr; if ((*token)[0] == '}') break; type = UI_GetActionTokenType(*token, EA_ACTION); /* setter form */ if (type == EA_NULL && (*token)[0] == '*') type = EA_ASSIGN; /* unknown, we break the parsing */ if (type == EA_NULL) { Com_Printf("UI_ParseActionList: unknown token \"%s\" ignored (in event) (node: %s)\n", *token, UI_GetPath(node)); return nullptr; } /* add the action */ action = UI_AllocStaticAction(); /** @todo better to append the action after initialization */ if (lastAction) lastAction->next = action; if (!firstAction) firstAction = action; action->type = type; /* decode action */ switch (action->type) { case EA_CMD: /* get parameter values */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; /* get the value */ action->d.terminal.d1.constString = UI_AllocStaticString(*token, 0); break; case EA_ASSIGN: result = UI_ParseSetAction(node, action, text, token, errhead); if (!result) return nullptr; break; case EA_CALL: result = UI_ParseCallAction(node, action, text, token, errhead); if (!result) return nullptr; break; case EA_DELETE: { uiAction_t* expression; expression = UI_ParseExpression(text); if (expression == nullptr) return nullptr; if (expression->type != EA_VALUE_CVARNAME) { Com_Printf("UI_ParseActionList: \"delete\" keyword only support cvarname (node: %s)\n", UI_GetPath(node)); return nullptr; } action->d.nonTerminal.left = expression; break; } case EA_ELIF: /* check previous action */ if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) { Com_Printf("UI_ParseActionList: 'elif' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node)); return nullptr; } /* then it execute EA_IF, no break */ case EA_WHILE: case EA_IF: { uiAction_t* expression; /* get the condition */ expression = UI_ParseExpression(text); if (expression == nullptr) return nullptr; action->d.nonTerminal.left = expression; /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { switch (action->type) { case EA_IF: Com_Printf("UI_ParseActionList: block expected after \"if\" (node: %s)\n", UI_GetPath(node)); break; case EA_ELIF: Com_Printf("UI_ParseActionList: block expected after \"elif\" (node: %s)\n", UI_GetPath(node)); break; case EA_WHILE: Com_Printf("UI_ParseActionList: block expected after \"while\" (node: %s)\n", UI_GetPath(node)); break; default: Com_Printf("UI_ParseActionList: cannot determine statement type (node: %s)\n", UI_GetPath(node)); } return nullptr; } break; } case EA_FORCHILDIN: { uiAction_t* expression; /* get the node */ expression = UI_ParseExpression(text); if (expression == nullptr) { return nullptr; } action->d.nonTerminal.left = expression; /* check the type */ type = action->d.nonTerminal.left->type; if (type != EA_VALUE_PATHNODE && type != EA_VALUE_PATHNODE_WITHINJECTION) { Com_Printf("UI_ParseActionList: Node property expected. Type '%x' found\n", type); return nullptr; } /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { Com_Printf("UI_ParseActionList: block expected after \"forchildin\" (node: %s)\n", UI_GetPath(node)); return nullptr; } break; } case EA_ELSE: /* check previous action */ if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) { Com_Printf("UI_ParseActionList: 'else' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node)); return nullptr; } /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.left = nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { Com_Printf("UI_ParseActionList: block expected after \"else\" (node: %s)\n", UI_GetPath(node)); return nullptr; } break; default: assert(false); } /* step */ lastAction = action; } assert((*token)[0] == '}'); /* return non nullptr value */ if (firstAction == nullptr) { firstAction = UI_AllocStaticAction(); } return firstAction; }
/** * @brief Parser for setter command */ static bool UI_ParseSetAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead) { const value_t* property; int type; uiAction_t* localAction; assert((*token)[0] == '*'); Com_UnParseLastToken(); action->d.nonTerminal.left = UI_ParseExpression(text); type = action->d.nonTerminal.left->type; if (type != EA_VALUE_CVARNAME && type != EA_VALUE_CVARNAME_WITHINJECTION && type != EA_VALUE_PATHPROPERTY && type != EA_VALUE_PATHPROPERTY_WITHINJECTION) { Com_Printf("UI_ParseSetAction: Cvar or Node property expected. Type '%i' found\n", type); return false; } /* must use "equal" char between name and value */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return false; if (!Q_streq(*token, "=")) { Com_Printf("UI_ParseSetAction: Assign sign '=' expected between variable and value. '%s' found in node %s.\n", *token, UI_GetPath(node)); return false; } /* get the value */ if (type == EA_VALUE_CVARNAME || type == EA_VALUE_CVARNAME_WITHINJECTION) { action->d.nonTerminal.right = UI_ParseExpression(text); return true; } property = (const value_t*) action->d.nonTerminal.left->d.terminal.d2.data; *token = Com_EParse(text, errhead, nullptr); if (!*text) return false; if (Q_streq(*token, "{")) { uiAction_t* actionList; if (property != nullptr && property->type != V_UI_ACTION) { Com_Printf("UI_ParseSetAction: Property %s@%s do not expect code block.\n", UI_GetPath(node), property->string); return false; } actionList = UI_ParseActionList(node, text, token); if (actionList == nullptr) return false; localAction = UI_AllocStaticAction(); localAction->type = EA_VALUE_RAW; localAction->d.terminal.d1.data = actionList; localAction->d.terminal.d2.integer = V_UI_ACTION; action->d.nonTerminal.right = localAction; return true; } if (Q_streq(*token, "(")) { Com_UnParseLastToken(); action->d.nonTerminal.right = UI_ParseExpression(text); return true; } /* @todo everything should come from UI_ParseExpression */ if (UI_IsInjectedString(*token)) { localAction = UI_AllocStaticAction(); localAction->type = EA_VALUE_STRING_WITHINJECTION; localAction->d.terminal.d1.data = UI_AllocStaticString(*token, 0); action->d.nonTerminal.right = localAction; return true; } localAction = UI_AllocStaticAction(); UI_InitRawActionValue(localAction, node, property, *token); action->d.nonTerminal.right = localAction; return true; }
uiAction_t *UI_ParseExpression (const char **text) { const char* token; token = Com_Parse(text); if (*text == nullptr) return nullptr; if (Q_streq(token, "(")) { uiAction_t *expression; uiAction_t *e; e = UI_ParseExpression(text); token = Com_Parse(text); if (*text == nullptr) return nullptr; /* unary operator or unneed "( ... )" */ if (Q_streq(token, ")")) return e; /* then its an operator */ expression = UI_AllocStaticAction(); expression->d.nonTerminal.left = e; expression->type = UI_GetActionTokenType(token, EA_BINARYOPERATOR); if (expression->type == EA_nullptr) { Com_Printf("UI_ParseExpression: Invalid 'expression' statement. Unknown '%s' operator\n", token); return nullptr; } e = UI_ParseExpression(text); expression->d.nonTerminal.right = e; token = Com_Parse(text); if (*text == nullptr) return nullptr; if (!Q_streq(token, ")")) { Com_Printf("UI_ParseExpression: Token ')' expected\n"); return nullptr; } return expression; } else { const int type = UI_GetActionTokenType(token, EA_UNARYOPERATOR); if (type == EA_nullptr) { Com_UnParseLastToken(); return UI_ParseValueExpression(text); } else { uiAction_t *expression = UI_AllocStaticAction(); uiAction_t *e; e = UI_ParseExpression(text); expression->type = type; expression->d.nonTerminal.left = e; if (expression->type == EA_OPERATOR_EXISTS) { switch (e->type) { case EA_VALUE_CVARNAME: case EA_VALUE_CVARNAME_WITHINJECTION: case EA_VALUE_PATHNODE: case EA_VALUE_PATHNODE_WITHINJECTION: break; default: Com_Printf("UI_ParseExpression: Cvar or Node path expected, but type %d found\n", e->type); return nullptr; } } return expression; } } }
/** * @brief Read a value from the stream and init an action with it * @return An initialized action else nullptr */ static uiAction_t *UI_ParseValueExpression (const char **text) { const char* token; uiAction_t *expression = UI_AllocStaticAction(); token = Com_Parse(text); if (*text == nullptr) { Com_Printf("UI_ParseTerminalExpression: Token expected\n"); return nullptr; } /* it is a const string (or an injection tag for compatibility) */ if (Com_GetType(text) == TT_QUOTED_WORD || token[0] == '<') { expression->d.terminal.d1.constString = UI_AllocStaticString(token, 0); if (UI_IsInjectedString(token)) expression->type = EA_VALUE_STRING_WITHINJECTION; else expression->type = EA_VALUE_STRING; return expression; } /* it is a param */ if (!Q_strncasecmp(token, "param", 5)) { if (!Q_strcasecmp(token, "paramcount")) { expression->type = EA_VALUE_PARAMCOUNT; return expression; } else if (token[5] >= '1' && token[5] <= '9') { int i; if (sscanf(token + 5, "%i", &i) == 1) { /* token range 1-9 already avoid 0 */ assert(i != 0); /** @todo when it is possible, we must check range of param id */ expression->type = EA_VALUE_PARAM; expression->d.terminal.d1.integer = i; return expression; } } } /* it is a cvarname */ if (char const* const cvarName = Q_strstart(token, "*cvar:")) { expression->d.terminal.d1.constString = UI_AllocStaticString(cvarName, 0); if (UI_IsInjectedString(cvarName)) expression->type = EA_VALUE_CVARNAME_WITHINJECTION; else expression->type = EA_VALUE_CVARNAME; return expression; } /* it is a node property or it is a OLD syntax node property */ /** @todo We MUST remove the OLD code as fast as possible */ if (char const* path = Q_strstart(token, "*")) { const char *propertyName; #if 0 /* it looks useless, an unused cache */ const value_t *property; #endif char const* const relativeToNode = Q_strstart(path, "node:"); if (relativeToNode) path = relativeToNode; if (UI_IsInjectedString(path)) expression->type = EA_VALUE_PATHPROPERTY_WITHINJECTION; else expression->type = EA_VALUE_PATHPROPERTY; if (!relativeToNode) { Com_Printf("UI_ParseExpression: Old syntax, please prefix '%s' with \"*node:root.\" \n", token); path = va("root.%s", path); } expression->d.terminal.d1.constString = UI_AllocStaticString(path, 0); /* get property name */ propertyName = strchr(path, '@'); if (propertyName == nullptr) { if (expression->type == EA_VALUE_PATHPROPERTY_WITHINJECTION) expression->type = EA_VALUE_PATHNODE_WITHINJECTION; else expression->type = EA_VALUE_PATHNODE; return expression; } propertyName++; return expression; } /* unsigned and signed number */ if ((token[0] >= '0' && token[0] <= '9') || (token[0] == '-' && token[1] >= '0' && token[1] <= '9')) { /** @todo use a better check - e.g. Com_ParseValue with V_INT or V_FLOAT */ float f = atof(token); expression->d.terminal.d1.number = f; expression->type = EA_VALUE_FLOAT; return expression; } /* boolean */ if (Q_streq(token, "true")) { expression->d.terminal.d1.number = 1.0; expression->type = EA_VALUE_FLOAT; return expression; } if (Q_streq(token, "false")) { expression->d.terminal.d1.number = 0.0; expression->type = EA_VALUE_FLOAT; return expression; } Com_Error(ERR_FATAL, "UI_ParseValueExpression: Token \"%s\" unknown. String must use quotes, cvar and nodes must use prefix.\n", token); }