/** * @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(UI_GenInjectedString(action->d.terminal.d1.constString, true, context)); break; case EA_CALL: case EA_LISTENER: UI_ExecuteCallAction(action, context); break; case EA_POPVARS: { int i; const int number = action->d.terminal.d1.integer; assert(number <= context->varNumber); for (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 (loop > 1000) { Com_Printf("UI_ExecuteAction: Infinite loop. Force breaking 'while'\n"); break; } loop++; } 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); } }
/** * @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); } }