1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-16 12:46:33 +00:00

Account for numeric precision and infinities. Also pretend to be more like GLSL

This commit is contained in:
Evil Eye 2025-08-18 19:52:28 +02:00
parent 3f2fd06514
commit a6c942b33a
4 changed files with 121 additions and 26 deletions

View file

@ -135,6 +135,17 @@ namespace
EXPECT_EQ(std::get<Integer>(token).value, 123); EXPECT_EQ(std::get<Integer>(token).value, 123);
} }
TEST(LexerTest, float_suffix_should_be_float)
{
Lexer lexer(R"(
123f
)");
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<Float>(token));
EXPECT_FLOAT_EQ(std::get<Float>(token).value, 123.f);
}
TEST(LexerTest, simple_string) TEST(LexerTest, simple_string)
{ {
Lexer lexer(R"( Lexer lexer(R"(

View file

@ -90,6 +90,56 @@ namespace
technique { passes = main; } technique { passes = main; }
)" }; )" };
constexpr VFS::Path::NormalizedView invalidNumberInfPath("shaders/invalid_number_inf.omwfx");
TestingOpenMW::VFSTestFile invalidNumberInf{ R"(
uniform_vec4 uVec4 {
step = inf;
}
fragment main { }
technique { passes = main; }
)" };
constexpr VFS::Path::NormalizedView invalidNumberNegativeInfPath("shaders/invalid_number_negative_inf.omwfx");
TestingOpenMW::VFSTestFile invalidNumberNegativeInf{ R"(
uniform_vec4 uVec4 {
step = -inf;
}
fragment main { }
technique { passes = main; }
)" };
constexpr VFS::Path::NormalizedView invalidNumberUnsignedLongPath("shaders/invalid_number_ulong.omwfx");
TestingOpenMW::VFSTestFile invalidNumberUnsignedLong{ R"(
uniform_vec4 uVec4 {
step = 18446744073709551615;
}
fragment main { }
technique { passes = main; }
)" };
constexpr VFS::Path::NormalizedView invalidNumberHexFloatPath("shaders/invalid_number_hex.omwfx");
TestingOpenMW::VFSTestFile invalidNumberHexFloat{ R"(
uniform_vec4 uVec4 {
step = 0x1.fffffep+12;
}
fragment main { }
technique { passes = main; }
)" };
constexpr VFS::Path::NormalizedView invalidNumberDoublePath("shaders/invalid_number_double.omwfx");
TestingOpenMW::VFSTestFile invalidNumberDouble{ R"(
uniform_vec4 uVec4 {
step = 1.79769e+50;
}
fragment main { }
technique { passes = main; }
)" };
using namespace testing; using namespace testing;
using namespace Fx; using namespace Fx;
@ -106,6 +156,11 @@ namespace
{ uniformPropertiesPath, &uniformProperties }, { uniformPropertiesPath, &uniformProperties },
{ missingSamplerSourcePath, &missingSamplerSource }, { missingSamplerSourcePath, &missingSamplerSource },
{ repeatedSharedBlockPath, &repeatedSharedBlock }, { repeatedSharedBlockPath, &repeatedSharedBlock },
{ invalidNumberInfPath, &invalidNumberInf },
{ invalidNumberNegativeInfPath, &invalidNumberNegativeInf },
{ invalidNumberUnsignedLongPath, &invalidNumberUnsignedLong },
{ invalidNumberHexFloatPath, &invalidNumberHexFloat },
{ invalidNumberDoublePath, &invalidNumberDouble },
})) }))
, mImageManager(mVFS.get(), 0) , mImageManager(mVFS.get(), 0)
{ {
@ -117,6 +172,17 @@ namespace
*mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true); *mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true);
mTechnique->compile(); mTechnique->compile();
} }
void expectFailure(const std::string& name, std::string_view errorString)
{
internal::CaptureStdout();
compile(name);
std::string output = internal::GetCapturedStdout();
Log(Debug::Error) << output;
EXPECT_THAT(output, HasSubstr(errorString));
}
}; };
TEST_F(TechniqueTest, technique_properties) TEST_F(TechniqueTest, technique_properties)
@ -183,23 +249,24 @@ namespace
TEST_F(TechniqueTest, fail_with_missing_source_for_sampler) TEST_F(TechniqueTest, fail_with_missing_source_for_sampler)
{ {
internal::CaptureStdout(); expectFailure("missing_sampler_source", "sampler_1d 'mysampler1d' requires a filename");
compile("missing_sampler_source");
std::string output = internal::GetCapturedStdout();
Log(Debug::Error) << output;
EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename"));
} }
TEST_F(TechniqueTest, fail_with_repeated_shared_block) TEST_F(TechniqueTest, fail_with_repeated_shared_block)
{ {
internal::CaptureStdout(); expectFailure("repeated_shared_block", "repeated 'shared' block");
}
compile("repeated_shared_block"); TEST_F(TechniqueTest, fail_with_invalid_float)
{
expectFailure("invalid_number_inf", "expected float value");
expectFailure("invalid_number_negative_inf", "expected float value");
expectFailure("invalid_number_hex", "expected float value");
}
std::string output = internal::GetCapturedStdout(); TEST_F(TechniqueTest, fail_with_out_of_range)
Log(Debug::Error) << output; {
EXPECT_THAT(output, HasSubstr("repeated 'shared' block")); expectFailure("invalid_number_ulong", "number out of range");
expectFailure("invalid_number_double", "number out of range");
} }
} }

View file

@ -1,13 +1,11 @@
#include "lexer.hpp" #include "lexer.hpp"
#include <cctype> #include <cctype>
#include <cmath>
#include <cstdlib>
#include <format> #include <format>
#ifndef __cpp_lib_to_chars #include <components/misc/strings/algorithm.hpp>
#include <cstdlib>
#else
#include <charconv>
#endif
namespace Fx namespace Fx
{ {
@ -294,23 +292,42 @@ namespace Fx
Token Lexer::scanNumber() Token Lexer::scanNumber()
{ {
double buffer; double buffer;
#ifndef __cpp_lib_to_chars
char* endPtr = nullptr; char* endPtr = nullptr;
buffer = std::strtod(mHead, &endPtr); buffer = std::strtod(mHead, &endPtr);
if (endPtr == nullptr || endPtr == mHead) if (endPtr == nullptr || endPtr == mHead)
#else
const auto [endPtr, ec] = std::from_chars(mHead, mTail, buffer);
if (ec != std::errc())
#endif
error("critical error while parsing number"); error("critical error while parsing number");
bool isDefinitelyAFloat = false;
// GLSL allows floats to end on f/F
if (endPtr != mTail && Misc::StringUtils::toLower(*endPtr) == 'f')
{
isDefinitelyAFloat = true;
++endPtr;
}
std::string_view literal(mHead, endPtr); std::string_view literal(mHead, endPtr);
mHead = endPtr; mHead = endPtr;
if (literal.find('.') != std::string_view::npos) // Disallow -inf, -nan, and values that cannot be represented as doubles
return Float{ static_cast<float>(buffer) }; if (!std::isfinite(buffer))
return Literal{ literal };
// Disallow hex notation (not allowed in GLSL so confusing to partially allow)
if (Misc::StringUtils::ciStartsWith(literal, "0x") || Misc::StringUtils::ciStartsWith(literal, "-0x"))
return Literal{ literal };
return Integer{ static_cast<int>(buffer) }; constexpr std::string_view floatCharacters = ".eE";
if (!isDefinitelyAFloat && literal.find_first_of(floatCharacters) == std::string_view::npos)
{
// This is supposed to be an int, but that doesn't mean it can fit in one
int intValue = static_cast<int>(buffer);
if (intValue != buffer)
error("number out of range");
return Integer{ intValue };
}
float floatValue = static_cast<float>(buffer);
if (!std::isfinite(floatValue))
error("number out of range");
return Float{ floatValue };
} }
} }
} }

View file

@ -758,7 +758,7 @@ namespace Fx
if (named) if (named)
{ {
expect<Lexer::Literal>("name is required for preceeding block decleration"); expect<Lexer::Literal>("name is required for preceeding block declaration");
mBlockName = std::get<Lexer::Literal>(mToken).value; mBlockName = std::get<Lexer::Literal>(mToken).value;