/** * @brief unittest to check back slash entity conversion */ TEST_F(ParserTest, ParserWithEntity) { const char* string = "\n\taaaa \" \\n \\t \\\" \""; const char* cursor = string; const char* token; token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aaaa"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, " \n \t \" "); }
/** * @brief unittest around default use of parser */ TEST_F(ParserTest, ParserWithUnParse) { const char* string = "aaaaa\n\tbbbbb \"ccccc\""; const char* cursor = string; const char* token; token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aaaaa"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aaaaa"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aaaaa"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "bbbbb"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "bbbbb"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "bbbbb"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "ccccc"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "ccccc"); Com_UnParseLastToken(); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "ccccc"); }
/** * @brief unittest around default use of parser */ TEST_F(ParserTest, Parser) { const char* string = "aa \t\n {\"bbb(bbb bbb)bbb {\"{a}\n/* foooo { } \n { } */\n// fooooo\naaaa"; const char* cursor = string; const char* token; token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aa"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "{"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "bbb(bbb bbb)bbb {"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "{"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "a"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "}"); token = Com_Parse(&cursor); ASSERT_NE(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "aaaa"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_EOF); ASSERT_STREQ(token, "\0"); }
/** * @brief Parse a property value * @todo don't read the next token (need to change the script language) */ static bool UI_ParseProperty (void* object, const value_t* property, const char* objectName, const char** text, const char** token) { const char* errhead = "UI_ParseProperty: unexpected end of file (object"; static const char* notWellFormedValue = "UI_ParseProperty: \"%s\" is not a well formed node name (it must be quoted, uppercase const, a number, or prefixed with '*')\n"; size_t bytes; int result; const int specialType = property->type & V_UI_MASK; if (property->type == V_NULL) { return false; } switch (specialType) { case V_NOT_UI: /* common type */ *token = Com_EParse(text, errhead, objectName); if (!*text) return false; if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf(notWellFormedValue, *token); return false; } if (property->type == V_TRANSLATION_STRING) { /* selectbox values are static arrays */ char* const target = Com_GetValue<char[]>(object, property); const char* translatableToken = *token; assert(property->size); if (translatableToken[0] == '_') translatableToken++; Q_strncpyz(target, translatableToken, property->size); } else { result = Com_ParseValue(object, *token, property->type, property->ofs, property->size, &bytes); if (result != RESULT_OK) { Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError()); return false; } } break; case V_UI_REF: *token = Com_EParse(text, errhead, objectName); if (!*text) return false; if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf(notWellFormedValue, *token); return false; } /* a reference to data is handled like this */ ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK)); Com_GetValue<byte*>(object, property) = ui_global.curadata; /** @todo check for the moment its not a cvar */ assert((*token)[0] != '*'); /* sanity check */ if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) { Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string); return false; } result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size, &bytes); if (result != RESULT_OK) { Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError()); return false; } ui_global.curadata += bytes; break; case V_UI_CVAR: /* common type */ *token = Com_EParse(text, errhead, objectName); if (!*text) return false; if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf(notWellFormedValue, *token); return false; } /* references are parsed as string */ if ((*token)[0] == '*') { /* a reference to data */ ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, V_STRING); Com_GetValue<byte*>(object, property) = ui_global.curadata; /* sanity check */ if (strlen(*token) > MAX_VAR - 1) { Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string); return false; } result = Com_ParseValue(ui_global.curadata, *token, V_STRING, 0, 0, &bytes); if (result != RESULT_OK) { Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError()); return false; } ui_global.curadata += bytes; } else { /* a reference to data */ ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t)(property->type & V_BASETYPEMASK)); Com_GetValue<byte*>(object, property) = ui_global.curadata; /* sanity check */ if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) { Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string); return false; } result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t)(property->type & V_BASETYPEMASK), 0, property->size, &bytes); if (result != RESULT_OK) { Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError()); return false; } ui_global.curadata += bytes; } break; case V_UI: switch ((int)property->type) { case V_UI_ACTION: result = UI_ParseEventProperty(static_cast<uiNode_t*>(object), property, text, token, errhead); if (!result) return false; break; case V_UI_EXCLUDERECT: result = UI_ParseExcludeRect(static_cast<uiNode_t*>(object), text, token, errhead); if (!result) return false; break; case V_UI_SPRITEREF: { *token = Com_EParse(text, errhead, objectName); if (!*text) return false; uiSprite_t const*& sprite = Com_GetValue<uiSprite_t const*>(object, property); sprite = UI_GetSpriteByName(*token); if (!sprite) { Com_Printf("UI_ParseProperty: sprite '%s' not found (object %s)\n", *token, objectName); } } break; case V_UI_IF: { *token = Com_EParse(text, errhead, objectName); if (!*text) return false; uiAction_t*& expression = Com_GetValue<uiAction_t*>(object, property); expression = UI_AllocStaticStringCondition(*token); if (!expression) return false; } break; case V_UI_DATAID: { *token = Com_EParse(text, errhead, objectName); if (!*text) return false; int& dataId = Com_GetValue<int>(object, property); dataId = UI_GetDataIDByName(*token); if (dataId < 0) { Com_Printf("UI_ParseProperty: Could not find shared data ID '%s' (%s@%s)\n", *token, objectName, property->string); return false; } } break; default: Com_Printf("UI_ParseProperty: unknown property type '%d' (0x%X) (%s@%s)\n", property->type, property->type, objectName, property->string); return false; } break; default: Com_Printf("UI_ParseProperties: unknown property type '%d' (0x%X) (%s@%s)\n", property->type, property->type, objectName, property->string); return false; } return true; }
/** * @brief Parse a window * @sa CL_ParseClientData * @code * window windowName { * } * @endcode */ bool UI_ParseWindow (const char* type, const char* name, const char** text) { const char* errhead = "UI_ParseWindow: unexpected end of file (window"; uiNode_t* window; const char* token; int i; if (!Q_streq(type, "window")) { Com_Error(ERR_FATAL, "UI_ParseWindow: '%s %s' is not a window node\n", type, name); return false; /* never reached */ } if (!UI_TokenIsName(name, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf("UI_ParseWindow: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name); return false; } if (UI_TokenIsReserved(name)) { Com_Printf("UI_ParseWindow: \"%s\" is a reserved token, we can't call a node with it (node \"%s\")\n", name, name); return false; } /* search for windows with same name */ for (i = 0; i < ui_global.numWindows; i++) if (!strncmp(name, ui_global.windows[i]->name, sizeof(ui_global.windows[i]->name))) break; if (i < ui_global.numWindows) { Com_Printf("UI_ParseWindow: %s \"%s\" with same name found, second ignored\n", type, name); } if (ui_global.numWindows >= UI_MAX_WINDOWS) { Com_Error(ERR_FATAL, "UI_ParseWindow: max windows exceeded (%i) - ignore '%s'\n", UI_MAX_WINDOWS, name); return false; /* never reached */ } /* get window body */ token = Com_Parse(text); /* does this window inherit data from another window? */ if (Q_streq(token, "extends")) { uiNode_t* superWindow; token = Com_Parse(text); superWindow = UI_GetWindow(token); if (superWindow == nullptr) Sys_Error("Could not get the super window \"%s\"", token); window = UI_CloneNode(superWindow, nullptr, true, name, false); token = Com_Parse(text); } else { window = UI_AllocNode(name, type, false); window->root = window; } UI_InsertWindow(window); /* parse it's body */ bool result = UI_ParseNodeBody(window, text, &token, errhead); if (!result) { Com_Error(ERR_FATAL, "UI_ParseWindow: window \"%s\" has a bad body\n", window->name); } UI_Node_Loaded(window); return true; }
/** * @brief parse a node * @sa UI_ParseNodeProperties * @todo we can think about merging UI_ParseNodeProperties here * @note first token already read * @note dont read more than the need token (last right token is '}' of end of node) */ static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead) { uiNode_t* node = nullptr; uiBehaviour_t* behaviour; uiNode_t* component = nullptr; /* get the behaviour */ behaviour = UI_GetNodeBehaviour(*token); if (!behaviour) { component = UI_GetComponent(*token); } if (behaviour == nullptr && component == nullptr) { Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exist (%s)\n", *token, UI_GetPath(parent)); return nullptr; } /* get the name */ *token = Com_EParse(text, errhead, ""); if (!*text) return nullptr; if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token); return nullptr; } if (UI_TokenIsReserved(*token)) { Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token); return nullptr; } /* test if node already exists */ /* Already existing node should only come from inherited node, we should not have 2 definitions of the same node into the same window. */ if (parent) node = UI_GetNode(parent, *token); /* reuse a node */ if (node) { const uiBehaviour_t* test = (behaviour != nullptr) ? behaviour : (component != nullptr) ? component->behaviour : nullptr; if (node->behaviour != test) { Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node)); return nullptr; } Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node)); /* else initialize a component */ } else if (component) { node = UI_CloneNode(component, nullptr, true, *token, false); if (parent) { if (parent->root) UI_UpdateRoot(node, parent->root); UI_AppendNode(parent, node); } /* else initialize a new node */ } else { node = UI_AllocNode(*token, behaviour->name, false); node->parent = parent; if (parent) node->root = parent->root; /** @todo move it into caller */ if (parent) UI_AppendNode(parent, node); } /* get body */ const bool result = UI_ParseNodeBody(node, text, token, errhead); if (!result) return nullptr; /* validate properties */ UI_Node_Loaded(node); return node; }
/** * @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); }
/** * @brief unittest around default use of parser */ TEST_F(ParserTest, ParserWithFunctionScriptToken) { const char* string = "aa \t\n aa,({\"bbb(bbb bbb)bbb {\"{a}\n/* foooo { } \n { } */\n// fooooo\naaaa)"; const char* cursor = string; const char* token; token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_WORD); ASSERT_STREQ(token, "aa"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_WORD); ASSERT_STREQ(token, "aa"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_COMMA); ASSERT_STREQ(token, ","); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_BEGIN_LIST); ASSERT_STREQ(token, "("); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_BEGIN_BLOCK); ASSERT_STREQ(token, "{"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_QUOTED_WORD); ASSERT_STREQ(token, "bbb(bbb bbb)bbb {"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_BEGIN_BLOCK); ASSERT_STREQ(token, "{"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_WORD); ASSERT_STREQ(token, "a"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_END_BLOCK); ASSERT_STREQ(token, "}"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_WORD); ASSERT_STREQ(token, "aaaa"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_END_LIST); ASSERT_STREQ(token, ")"); token = Com_Parse(&cursor); ASSERT_EQ(Com_GetType(&cursor), TT_EOF); ASSERT_STREQ(token, "\0"); }