Ensure correct expression type for static expressions in menu parsing but do not enforce when permissive

This commit is contained in:
Jan 2023-08-24 20:12:57 +02:00
parent aea76d0b42
commit 4829a4206b
6 changed files with 114 additions and 39 deletions

View File

@ -751,7 +751,8 @@ namespace IW4
continue;
}
assert(false);
// Do not consider this a mistake since the games menus do this by mistake and it should be able to compile them anyway
// But the game should also not know what to do with this i guess
expressionIsStatic = false;
}

View File

@ -753,7 +753,8 @@ namespace IW5
continue;
}
assert(false);
// Do not consider this a mistake since the games menus do this by mistake and it should be able to compile them anyway
// But the game should also not know what to do with this i guess
expressionIsStatic = false;
}

View File

@ -0,0 +1,27 @@
#include "MenuFileCommonOperations.h"
#include "Parsing/ParsingException.h"
using namespace menu;
void MenuFileCommonOperations::EnsureIsNumericExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression)
{
if (!state->m_permissive_mode && expression.IsStatic())
{
const auto staticValue = expression.EvaluateStatic();
if (staticValue.m_type != SimpleExpressionValue::Type::INT && staticValue.m_type != SimpleExpressionValue::Type::DOUBLE)
throw ParsingException(pos, "Expression is expected to be numeric. Use permissive mode to compile anyway.");
}
}
void MenuFileCommonOperations::EnsureIsStringExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression)
{
if (!state->m_permissive_mode && expression.IsStatic())
{
const auto staticValue = expression.EvaluateStatic();
if (staticValue.m_type != SimpleExpressionValue::Type::STRING)
throw ParsingException(pos, "Expression is expected to be string. Use permissive mode to compile anyway.");
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "MenuFileParserState.h"
#include "Parsing/TokenPos.h"
#include "Parsing/Simple/Expression/ISimpleExpression.h"
namespace menu
{
class MenuFileCommonOperations
{
public:
static void EnsureIsNumericExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression);
static void EnsureIsStringExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression);
};
}

View File

@ -10,6 +10,7 @@
#include "Generic/GenericKeywordPropertySequence.h"
#include "Generic/GenericMenuEventHandlerSetPropertySequence.h"
#include "Generic/GenericStringPropertySequence.h"
#include "Parsing/Menu/MenuFileCommonOperations.h"
#include "Parsing/Menu/Matcher/MenuExpressionMatchers.h"
#include "Parsing/Menu/Matcher/MenuMatcherFactory.h"
@ -663,12 +664,14 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
{
state->m_current_item->m_border_size = value;
}));
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_visible_expression = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("disabled", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("disabled", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_disabled_expression = std::move(value);
}));
AddSequence(std::make_unique<GenericIntPropertySequence>("ownerdraw", [](const MenuFileParserState* state, const TokenPos&, const int value)
@ -803,96 +806,114 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
state->m_current_item->m_game_message_window_mode = value;
}));
AddSequence(std::make_unique<SequenceDecodeEffect>());
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "disabled"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "disabled"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_disabled_expression = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "text"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "text"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value);
state->m_current_item->m_text_expression = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value);
state->m_current_item->m_material_expression = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
{
state->m_current_item->m_material_expression = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_rect_x_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_rect_y_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_rect_w_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_rect_h_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_forecolor_expressions.m_r_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_forecolor_expressions.m_g_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_forecolor_expressions.m_b_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_forecolor_expressions.m_a_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_forecolor_expressions.m_rgb_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_glowcolor_expressions.m_r_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_glowcolor_expressions.m_g_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_glowcolor_expressions.m_b_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_glowcolor_expressions.m_a_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_glowcolor_expressions.m_rgb_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_backcolor_expressions.m_r_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_backcolor_expressions.m_g_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_backcolor_expressions.m_b_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_backcolor_expressions.m_a_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_backcolor_expressions.m_rgb_exp = std::move(value);
}));
@ -901,8 +922,9 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>("hasFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr<CommonEventHandlerSet>& {
return state->m_current_item->m_has_focus;
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "textaligny"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "textaligny"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_text_align_y_expression = std::move(value);
}));
}
@ -964,6 +986,7 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "elementheight"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos);
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_item->m_list_box_features->m_element_height_expression = std::move(value);
}));
}

View File

@ -10,6 +10,7 @@
#include "Generic/GenericKeywordPropertySequence.h"
#include "Generic/GenericMenuEventHandlerSetPropertySequence.h"
#include "Generic/GenericStringPropertySequence.h"
#include "Parsing/Menu/MenuFileCommonOperations.h"
#include "Parsing/Menu/Matcher/MenuMatcherFactory.h"
#include "Parsing/Menu/Domain/CommonMenuTypes.h"
#include "Parsing/Menu/Matcher/MenuExpressionMatchers.h"
@ -254,8 +255,9 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
{
state->m_current_menu->m_style = value;
}));
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_menu->m_visible_expression = std::move(value);
}));
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>("onOpen", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr<CommonEventHandlerSet>& {
@ -318,28 +320,34 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
{
state->m_current_menu->m_sound_loop = value;
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_menu->m_rect_x_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_menu->m_rect_y_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_menu->m_rect_w_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value);
state->m_current_menu->m_rect_h_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "openSound"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "openSound"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value);
state->m_current_menu->m_open_sound_exp = std::move(value);
}));
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "closeSound"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "closeSound"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value);
state->m_current_menu->m_close_sound_exp = std::move(value);
}));
AddSequence(std::make_unique<GenericKeywordPropertySequence>("popup", [](const MenuFileParserState* state, const TokenPos&)
@ -396,8 +404,9 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive
if (featureLevel == FeatureLevel::IW5)
{
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "soundLoop"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr<ISimpleExpression> value)
AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "soundLoop"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr<ISimpleExpression> value)
{
MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value);
state->m_current_menu->m_sound_loop_exp = std::move(value);
}));
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>("onFocusDueToClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr<CommonEventHandlerSet>& {