mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-15 23:46:36 +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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Lexer lexer(R"(
|
||||
|
|
|
@ -90,6 +90,56 @@ namespace
|
|||
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 Fx;
|
||||
|
||||
|
@ -106,6 +156,11 @@ namespace
|
|||
{ uniformPropertiesPath, &uniformProperties },
|
||||
{ missingSamplerSourcePath, &missingSamplerSource },
|
||||
{ repeatedSharedBlockPath, &repeatedSharedBlock },
|
||||
{ invalidNumberInfPath, &invalidNumberInf },
|
||||
{ invalidNumberNegativeInfPath, &invalidNumberNegativeInf },
|
||||
{ invalidNumberUnsignedLongPath, &invalidNumberUnsignedLong },
|
||||
{ invalidNumberHexFloatPath, &invalidNumberHexFloat },
|
||||
{ invalidNumberDoublePath, &invalidNumberDouble },
|
||||
}))
|
||||
, mImageManager(mVFS.get(), 0)
|
||||
{
|
||||
|
@ -117,6 +172,17 @@ namespace
|
|||
*mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true);
|
||||
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)
|
||||
|
@ -183,23 +249,24 @@ namespace
|
|||
|
||||
TEST_F(TechniqueTest, fail_with_missing_source_for_sampler)
|
||||
{
|
||||
internal::CaptureStdout();
|
||||
|
||||
compile("missing_sampler_source");
|
||||
|
||||
std::string output = internal::GetCapturedStdout();
|
||||
Log(Debug::Error) << output;
|
||||
EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename"));
|
||||
expectFailure("missing_sampler_source", "sampler_1d 'mysampler1d' requires a filename");
|
||||
}
|
||||
|
||||
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();
|
||||
Log(Debug::Error) << output;
|
||||
EXPECT_THAT(output, HasSubstr("repeated 'shared' block"));
|
||||
TEST_F(TechniqueTest, fail_with_out_of_range)
|
||||
{
|
||||
expectFailure("invalid_number_ulong", "number out of range");
|
||||
expectFailure("invalid_number_double", "number out of range");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
#include "lexer.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <format>
|
||||
|
||||
#ifndef __cpp_lib_to_chars
|
||||
#include <cstdlib>
|
||||
#else
|
||||
#include <charconv>
|
||||
#endif
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
|
||||
namespace Fx
|
||||
{
|
||||
|
@ -294,23 +292,42 @@ namespace Fx
|
|||
Token Lexer::scanNumber()
|
||||
{
|
||||
double buffer;
|
||||
#ifndef __cpp_lib_to_chars
|
||||
char* endPtr = nullptr;
|
||||
buffer = std::strtod(mHead, &endPtr);
|
||||
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");
|
||||
|
||||
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);
|
||||
mHead = endPtr;
|
||||
|
||||
if (literal.find('.') != std::string_view::npos)
|
||||
return Float{ static_cast<float>(buffer) };
|
||||
// Disallow -inf, -nan, and values that cannot be represented as doubles
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue