/** * @brief Get a node and a property from an expression * @param expression Expression tree to analyse * @param context Call context * @param[out] property A node property * @return A node (else nullptr, if no node found) and a property (else nullptr if no/wrong property defined) */ uiNode_t* UI_GetNodeFromExpression (uiAction_t *expression, const uiCallContext_t *context, const value_t **property) { if (property != nullptr) *property = nullptr; switch (expression->type & EA_HIGHT_MASK) { case EA_VALUE: switch (expression->type) { case EA_VALUE_VAR: { uiValue_t *variable = UI_GetVariable(context, expression->d.terminal.d1.integer); switch (variable->type) { case EA_VALUE_NODE: return variable->value.node; default: break; } } break; case EA_VALUE_PATHNODE: case EA_VALUE_PATHNODE_WITHINJECTION: { uiNode_t *node; const value_t *propertyTmp; const char *path = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_PATHNODE_WITHINJECTION) path = UI_GenInjectedString(path, false, context); UI_ReadNodePath(path, context->source, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Node '%s' wasn't found; nullptr returned\n", path); return nullptr; } if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' contain a property. Property ignored.\n", path); return node; } case EA_VALUE_PATHPROPERTY: case EA_VALUE_PATHPROPERTY_WITHINJECTION: { uiNode_t *node; const value_t *propertyTmp; const char *path = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_PATHPROPERTY_WITHINJECTION) path = UI_GenInjectedString(path, false, context); UI_ReadNodePath(path, context->source, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Node '%s' wasn't found; nullptr returned\n", path); return nullptr; } if (property == nullptr) { if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' contain a property. Property ignored.\n", path); } else { *property = propertyTmp; } return node; } case EA_VALUE_THIS: return context->source; case EA_VALUE_PARENT: return context->source->parent; case EA_VALUE_WINDOW: return context->source->root; default: break; } case EA_OPERATOR_UNARY: switch (expression->type) { case EA_OPERATOR_PATHPROPERTYFROM: { uiNode_t *relativeTo = UI_GetNodeFromExpression(expression->d.nonTerminal.left, context, nullptr); uiNode_t *node; const value_t *propertyTmp; const char* path = expression->d.terminal.d2.constString; UI_ReadNodePath(path, relativeTo, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Path '%s' from node '%s' found no node; nullptr returned\n", path, UI_GetPath(relativeTo)); return nullptr; } if (property == nullptr) { if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' from node '%s' found no node; nullptr returned\n", path, UI_GetPath(relativeTo)); } else { *property = propertyTmp; } return node; } default: break; } default: break; } return nullptr; }
/** * @return A float value, else 0 */ float UI_GetFloatFromExpression (uiAction_t *expression, const uiCallContext_t *context) { switch (expression->type & EA_HIGHT_MASK) { case EA_VALUE: switch (expression->type) { case EA_VALUE_VAR: { uiValue_t *variable = UI_GetVariable(context, expression->d.terminal.d1.integer); switch (variable->type) { case EA_VALUE_STRING: if (variable->value.string == nullptr) { Com_Printf("UI_GetFloatFromExpression: String variable not initialized. '0' returned"); return 0; } return atof(variable->value.string); case EA_VALUE_FLOAT: return variable->value.number; case EA_VALUE_CVAR: { cvar_t *cvar = variable->value.cvar; if (cvar == nullptr) { Com_Printf("UI_GetFloatFromExpression: Cvar variable not initialized. '0' returned"); return 0; } return cvar->value; } default: Com_Printf("UI_GetFloatFromExpression: Unsupported variable type: %i. '0' returned", variable->type); return 0; } } case EA_VALUE_STRING: case EA_VALUE_STRING_WITHINJECTION: { const char* string = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_STRING_WITHINJECTION) string = UI_GenInjectedString(string, false, context); return atof(string); } case EA_VALUE_FLOAT: return expression->d.terminal.d1.number; case EA_VALUE_CVARNAME: case EA_VALUE_CVARNAME_WITHINJECTION: { cvar_t *cvar = nullptr; const char *cvarName = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_CVARNAME_WITHINJECTION) cvarName = UI_GenInjectedString(cvarName, false, context); cvar = Cvar_Get(cvarName, "", 0, "Cvar from UI script expression"); return cvar->value; } case EA_VALUE_PATHPROPERTY: case EA_VALUE_PATHPROPERTY_WITHINJECTION: { uiNode_t *node; const value_t *property; node = UI_GetNodeFromExpression(expression, context, &property); if (!node) { Com_Printf("UI_GetFloatFromParam: Node wasn't found; '0'\n"); return 0; } if (!property) { Com_Printf("UI_GetFloatFromParam: Property wasn't found; '0' returned\n"); return 0; } return UI_GetFloatFromNodeProperty(node, property); } case EA_VALUE_PARAM: { const int paramId = expression->d.terminal.d1.integer; const char *string = UI_GetParam(context, paramId); if (string[0] == '\0') { Com_Printf("UI_GetFloatFromParam: Param '%i' is out of range, or empty; '0' returned\n", paramId); return 0; } return atof(string); } case EA_VALUE_PARAMCOUNT: return UI_GetParamNumber(context); } break; case EA_OPERATOR_FLOAT2FLOAT: { const float value1 = UI_GetFloatFromExpression(expression->d.nonTerminal.left, context); const float value2 = UI_GetFloatFromExpression(expression->d.nonTerminal.right, context); switch (expression->type) { case EA_OPERATOR_ADD: return value1 + value2; case EA_OPERATOR_SUB: return value1 - value2; case EA_OPERATOR_MUL: return value1 * value2; case EA_OPERATOR_DIV: if (value2 == 0) { Com_Printf("UI_GetFloatFromExpression: Div by 0. '0' returned"); return 0; } else return value1 / value2; case EA_OPERATOR_MOD: { const int v1 = value1; const int v2 = value2; /** @todo do we have some check to do? */ return v1 % v2; } } } break; case EA_OPERATOR_UNARY: switch (expression->type) { case EA_OPERATOR_PATHPROPERTYFROM: { uiNode_t *node; const value_t *property; node = UI_GetNodeFromExpression(expression, context, &property); return UI_GetFloatFromNodeProperty(node, property); } default: Com_Error(ERR_FATAL, "UI_GetFloatFromExpression: (EA_OPERATOR_UNARY) Invalid expression type %i", expression->type); } } Com_Printf("UI_GetFloatFromExpression: Unsupported expression type: %i. '0' returned", expression->type); return 0; }
/** * @return A string, else an empty string * @todo this should not work very well, because too much va are used * then we should locally cache values, or manage a temporary string structure */ const char* UI_GetStringFromExpression (uiAction_t *expression, const uiCallContext_t *context) { switch (expression->type & EA_HIGHT_MASK) { case EA_VALUE: switch (expression->type) { case EA_VALUE_VAR: { uiValue_t *variable = UI_GetVariable(context, expression->d.terminal.d1.integer); switch (variable->type) { case EA_VALUE_STRING: if (variable->value.string == nullptr) { Com_Printf("UI_GetStringFromExpression: String variable not initialized. Empty string returned"); return ""; } return variable->value.string; case EA_VALUE_FLOAT: { const float number = variable->value.number; const int integer = number; /** @todo should we add a delta? */ if (number == integer) return va("%i", integer); else return va("%f", number); } case EA_VALUE_CVAR: { cvar_t *cvar = variable->value.cvar; if (cvar == nullptr) { Com_Printf("UI_GetStringFromExpression: Cvar variable not initialized. Empty string returned"); return ""; } return cvar->string; } default: Com_Printf("UI_GetStringFromExpression: Unsupported variable type: %i. Empty string returned", variable->type); return ""; } } case EA_VALUE_STRING: case EA_VALUE_STRING_WITHINJECTION: { const char* string = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_STRING_WITHINJECTION) string = UI_GenInjectedString(string, false, context); return string; } case EA_VALUE_FLOAT: { const float number = expression->d.terminal.d1.number; const int integer = number; /** @todo should we add a delta? */ if (number == integer) return va("%i", integer); else return va("%f", number); } case EA_VALUE_CVARNAME: case EA_VALUE_CVARNAME_WITHINJECTION: { cvar_t *cvar = nullptr; const char *cvarName = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_CVARNAME_WITHINJECTION) cvarName = UI_GenInjectedString(cvarName, false, context); cvar = Cvar_Get(cvarName, "", 0, "Cvar from UI script expression"); return cvar->string; } case EA_VALUE_PATHPROPERTY: case EA_VALUE_PATHPROPERTY_WITHINJECTION: { uiNode_t *node; const value_t *property; const char* string; node = UI_GetNodeFromExpression(expression, context, &property); if (!node) { Com_Printf("UI_GetStringFromExpression: Node wasn't found; Empty string returned\n"); return ""; } if (!property) { Com_Printf("UI_GetStringFromExpression: Property wasn't found; Empty string returned\n"); return ""; } string = UI_GetStringFromNodeProperty(node, property); if (string == nullptr) { Com_Printf("UI_GetStringFromExpression: String getter for '%s@%s' property do not exists; '' returned\n", UI_Node_GetWidgetName(node), property->string); return ""; } return string; } break; case EA_VALUE_PARAM: return UI_GetParam(context, expression->d.terminal.d1.integer); case EA_VALUE_PARAMCOUNT: return va("%i", UI_GetParamNumber(context)); } break; case EA_OPERATOR_UNARY: switch (expression->type) { case EA_OPERATOR_PATHPROPERTYFROM: { uiNode_t *node; const value_t *property; node = UI_GetNodeFromExpression(expression, context, &property); return UI_GetStringFromNodeProperty(node, property); } default: Com_Error(ERR_FATAL, "UI_GetFloatFromExpression: (EA_OPERATOR_UNARY) Invalid expression type %i", expression->type); } case EA_OPERATOR_BOOLEAN2BOOLEAN: case EA_OPERATOR_FLOAT2BOOLEAN: case EA_OPERATOR_STRING2BOOLEAN: { const bool v = UI_GetBooleanFromExpression(expression, context); return (v)?"1":"0"; } case EA_OPERATOR_FLOAT2FLOAT: { const float number = UI_GetFloatFromExpression(expression, context); const int integer = number; /** @todo should we add a delta? */ if (number == integer) return va("%i", integer); else return va("%f", number); } } Com_Printf("UI_GetStringFromExpression: Unsupported expression type: %i", expression->type); return ""; }
/** * @brief Execute an action from a source * @param[in] context Context node * @param[in] action Action to execute */ static void UI_ExecuteAction (const uiAction_t* action, uiCallContext_t* context) { switch (action->type) { case EA_NULL: /* do nothing */ break; case EA_CMD: /* execute a command */ if (action->d.terminal.d1.constString) Cbuf_AddText("%s\n", UI_GenInjectedString(action->d.terminal.d1.constString, true, context)); break; case EA_CALL: case EA_LISTENER: UI_ExecuteCallAction(action, context); break; case EA_POPVARS: { const int number = action->d.terminal.d1.integer; assert(number <= context->varNumber); for (int i = 0; i < number; i++) { const int varId = context->varPosition + context->varNumber - i - 1; UI_ReleaseVariable(&(ui_global.variableStack[varId])); } context->varNumber -= number; } break; case EA_PUSHVARS: #ifdef DEBUG /* check sanity */ /** @todo check var slots should be empty */ #endif context->varNumber += action->d.terminal.d1.integer; if (context->varNumber >= UI_MAX_VARIABLESTACK) Com_Error(ERR_FATAL, "UI_ExecuteAction: Variable stack full. UI_MAX_VARIABLESTACK hit."); break; case EA_ASSIGN: UI_ExecuteSetAction(action, context); break; case EA_DELETE: { const char* cvarname = action->d.nonTerminal.left->d.terminal.d1.constString; Cvar_Delete(cvarname); break; } case EA_WHILE: { int loop = 0; while (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) { UI_ExecuteActions(action->d.nonTerminal.right, context); if (context->breakLoop) { context->breakLoop = false; break; } if (loop > 1000) { Com_Printf("UI_ExecuteAction: Infinite loop. Force breaking 'while'\n"); break; } loop++; } break; } /* expected usage in .ufo scripts: forchildin ( *node:someNode ) { ... *node:child <-- reference the child being iterated ... } */ case EA_FORCHILDIN: { uiNode_t* root; root = UI_GetNodeFromExpression(action->d.nonTerminal.left, context, nullptr); if (!root) { break; } int loop = 0; for (uiNode_t* node = root->firstChild; node; node = node->next, loop++) { /* associate the child node with the call context so it can be referenced inside the script block */ context->tagNode = node; /* execute the script block inside the forchildin loop */ UI_ExecuteActions(action->d.nonTerminal.right, context); /* clean the tag node */ context->tagNode = nullptr; if (context->breakLoop) { context->breakLoop = false; break; } if (loop > 1000) { Com_Printf("UI_ExecuteAction: Infinite loop. Force breaking 'forchildin'\n"); break; } } break; } case EA_BREAK: { /* flag to break, the caller must check this flag before processing the next action in the list */ Com_Printf("BREAK: break statement found\n"); context->breakLoop = true; break; } case EA_IF: if (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) { UI_ExecuteActions(action->d.nonTerminal.right, context); return; } action = action->next; while (action && action->type == EA_ELIF) { if (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) { UI_ExecuteActions(action->d.nonTerminal.right, context); return; } action = action->next; } if (action && action->type == EA_ELSE) { UI_ExecuteActions(action->d.nonTerminal.right, context); } break; /** @todo Skipping actions like that is a bad way. the function should return the next action, * or we should move IF, ELSE, ELIF... in a IF block and not interleave it with default actions */ case EA_ELSE: case EA_ELIF: /* previous EA_IF execute this action */ break; default: Com_Error(ERR_FATAL, "UI_ExecuteAction: Unknown action type %i", action->type); } }