mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-16 16:16:34 +00:00
Account for numeric precision and infinities. Also pretend to be more like GLSL
This commit is contained in:
parent
3f2fd06514
commit
a6c942b33a
4 changed files with 121 additions and 26 deletions
|
@ -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"(
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue