From 47e54c4f7dff2d573b1fece23690d3abb4e4d966 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 31 Aug 2024 11:14:30 +0200 Subject: [PATCH 01/72] Set version to 0.11.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66a900c7..fe84adbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.10.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.11.0 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) From eea2f639adc83f7011803af6d2d77c78b3bb2850 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 2 Sep 2024 19:54:00 +0200 Subject: [PATCH 02/72] Treat stopped projects with active threads as running Resolves: #507 --- src/engine/internal/engine.cpp | 7 ++++++- src/engine/internal/engine.h | 1 + test/engine/engine_test.cpp | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 448103e4..c7b0bb7c 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -95,6 +95,7 @@ void Engine::clear() m_edgeActivatedHatValues.clear(); m_running = false; + m_frameActivity = false; m_unsupportedBlocks.clear(); } @@ -421,6 +422,7 @@ void Engine::stop() m_running = false; } + m_frameActivity = false; deleteClones(); stopSounds(); m_stopped(); @@ -578,6 +580,9 @@ void Engine::step() } } + // Check running threads (must be done here) + m_frameActivity = !m_threads.empty(); + m_redrawRequested = false; // Step threads @@ -737,7 +742,7 @@ void Engine::eventLoop(bool untilProjectStops) bool Engine::isRunning() const { - return m_running; + return m_running || m_frameActivity; } double Engine::fps() const diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 218dc9ba..67c10f52 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -277,6 +277,7 @@ class Engine : public IEngine bool m_spriteFencingEnabled = true; bool m_running = false; + bool m_frameActivity = false; bool m_redrawRequested = false; sigslot::signal<> m_aboutToRedraw; sigslot::signal m_threadAboutToStop; diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 86670a44..88302960 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -2018,6 +2018,8 @@ TEST(EngineTest, StopAllBypass) ASSERT_VAR(stage, "j"); ASSERT_EQ(GET_VAR(stage, "j")->value().toInt(), 5); + ASSERT_TRUE(engine->isRunning()); + engine->step(); ASSERT_FALSE(engine->isRunning()); } From 7396c0b4cef54f0d3cff7cf22e496cc6efa34fd3 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:05:46 +0200 Subject: [PATCH 03/72] Add touchingColor() with mask param to target handlers --- include/scratchcpp/ispritehandler.h | 3 +++ include/scratchcpp/istagehandler.h | 3 +++ test/mocks/spritehandlermock.h | 1 + test/mocks/stagehandlermock.h | 1 + 4 files changed, 8 insertions(+) diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h index 9bfb4307..af760ce4 100644 --- a/include/scratchcpp/ispritehandler.h +++ b/include/scratchcpp/ispritehandler.h @@ -92,6 +92,9 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler /*! Used to check whether the sprite touches the given color. */ virtual bool touchingColor(const Value &color) const = 0; + + /*! Used to check whether the mask part of the sprite touches the given color. */ + virtual bool touchingColor(const Value &color, const Value &mask) const = 0; }; } // namespace libscratchcpp diff --git a/include/scratchcpp/istagehandler.h b/include/scratchcpp/istagehandler.h index 0b397e17..6b51ce26 100644 --- a/include/scratchcpp/istagehandler.h +++ b/include/scratchcpp/istagehandler.h @@ -69,6 +69,9 @@ class LIBSCRATCHCPP_EXPORT IStageHandler /*! Used to check whether the stage touches the given color. */ virtual bool touchingColor(const Value &color) const = 0; + + /*! Used to check whether the mask part of the stage touches the given color. */ + virtual bool touchingColor(const Value &color, const Value &mask) const = 0; }; } // namespace libscratchcpp diff --git a/test/mocks/spritehandlermock.h b/test/mocks/spritehandlermock.h index d944f807..ef4c61c4 100644 --- a/test/mocks/spritehandlermock.h +++ b/test/mocks/spritehandlermock.h @@ -38,4 +38,5 @@ class SpriteHandlerMock : public ISpriteHandler MOCK_METHOD(bool, touchingClones, (const std::vector &), (const, override)); MOCK_METHOD(bool, touchingPoint, (double, double), (const, override)); MOCK_METHOD(bool, touchingColor, (const Value &), (const, override)); + MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override)); }; diff --git a/test/mocks/stagehandlermock.h b/test/mocks/stagehandlermock.h index a7e15055..4702328d 100644 --- a/test/mocks/stagehandlermock.h +++ b/test/mocks/stagehandlermock.h @@ -29,4 +29,5 @@ class StageHandlerMock : public IStageHandler MOCK_METHOD(bool, touchingClones, (const std::vector &), (const, override)); MOCK_METHOD(bool, touchingPoint, (double, double), (const, override)); MOCK_METHOD(bool, touchingColor, (const Value &), (const, override)); + MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override)); }; From 1a412326ac881da735cef88907e0f0fd4e78fe20 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:23:41 +0200 Subject: [PATCH 04/72] Add touchingColor() with mask param to Target --- include/scratchcpp/sprite.h | 1 + include/scratchcpp/stage.h | 1 + include/scratchcpp/target.h | 1 + src/scratch/sprite.cpp | 9 +++++++++ src/scratch/stage.cpp | 9 +++++++++ src/scratch/target.cpp | 6 ++++++ test/mocks/targetmock.h | 1 + test/scratch_classes/sprite_test.cpp | 7 +++++++ test/scratch_classes/stage_test.cpp | 7 +++++++ test/scratch_classes/target_test.cpp | 1 + 10 files changed, 43 insertions(+) diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h index 328c6371..af6ec39a 100644 --- a/include/scratchcpp/sprite.h +++ b/include/scratchcpp/sprite.h @@ -82,6 +82,7 @@ class LIBSCRATCHCPP_EXPORT Sprite bool touchingPoint(double x, double y) const override; bool touchingColor(const Value &color) const override; + bool touchingColor(const Value &color, const Value &mask) const override; void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override; diff --git a/include/scratchcpp/stage.h b/include/scratchcpp/stage.h index 0d37c862..39d969bd 100644 --- a/include/scratchcpp/stage.h +++ b/include/scratchcpp/stage.h @@ -56,6 +56,7 @@ class LIBSCRATCHCPP_EXPORT Stage : public Target bool touchingPoint(double x, double y) const override; bool touchingColor(const Value &color) const override; + bool touchingColor(const Value &color, const Value &mask) const override; void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override; diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h index cb8f7c1b..82a92eec 100644 --- a/include/scratchcpp/target.h +++ b/include/scratchcpp/target.h @@ -101,6 +101,7 @@ class LIBSCRATCHCPP_EXPORT Target virtual bool touchingPoint(double x, double y) const; bool touchingEdge() const; virtual bool touchingColor(const Value &color) const; + virtual bool touchingColor(const Value &color, const Value &mask) const; double graphicsEffectValue(IGraphicsEffect *effect) const; virtual void setGraphicsEffectValue(IGraphicsEffect *effect, double value); diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp index d711ca7c..42d67557 100644 --- a/src/scratch/sprite.cpp +++ b/src/scratch/sprite.cpp @@ -503,6 +503,15 @@ bool Sprite::touchingColor(const Value &color) const return impl->iface->touchingColor(color); } +/*! Overrides Target#touchingColor(). */ +bool Sprite::touchingColor(const Value &color, const Value &mask) const +{ + if (!impl->iface) + return false; + + return impl->iface->touchingColor(color, mask); +} + /*! Overrides Target#setGraphicsEffectValue(). */ void Sprite::setGraphicsEffectValue(IGraphicsEffect *effect, double value) { diff --git a/src/scratch/stage.cpp b/src/scratch/stage.cpp index d798451e..ac312ffa 100644 --- a/src/scratch/stage.cpp +++ b/src/scratch/stage.cpp @@ -185,6 +185,15 @@ bool Stage::touchingColor(const Value &color) const return impl->iface->touchingColor(color); } +/*! Overrides Target#touchingColor(). */ +bool Stage::touchingColor(const Value &color, const Value &mask) const +{ + if (!impl->iface) + return false; + + return impl->iface->touchingColor(color, mask); +} + /*! Overrides Target#setGraphicsEffectValue(). */ void Stage::setGraphicsEffectValue(IGraphicsEffect *effect, double value) { diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index 1e9f4b46..b1027233 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -520,6 +520,12 @@ bool Target::touchingColor(const Value &color) const return false; } +/*! Returns true if the mask part of the Target is touching the given color (RGB triplet). */ +bool Target::touchingColor(const Value &color, const Value &mask) const +{ + return false; +} + /*! Returns the value of the given graphics effect. */ double Target::graphicsEffectValue(IGraphicsEffect *effect) const { diff --git a/test/mocks/targetmock.h b/test/mocks/targetmock.h index 5df18e09..20f3bd7a 100644 --- a/test/mocks/targetmock.h +++ b/test/mocks/targetmock.h @@ -28,6 +28,7 @@ class TargetMock : public Target MOCK_METHOD(bool, touchingPoint, (double, double), (const, override)); MOCK_METHOD(bool, touchingColor, (const Value &), (const, override)); + MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override)); MOCK_METHOD(void, setGraphicsEffectValue, (IGraphicsEffect *, double), (override)); MOCK_METHOD(void, clearGraphicsEffects, (), (override)); diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp index ea79c577..9c00e4d7 100644 --- a/test/scratch_classes/sprite_test.cpp +++ b/test/scratch_classes/sprite_test.cpp @@ -825,6 +825,7 @@ TEST(SpriteTest, TouchingColor) { Sprite sprite; ASSERT_FALSE(sprite.touchingColor(0)); + ASSERT_FALSE(sprite.touchingColor(0, 0)); SpriteHandlerMock iface; EXPECT_CALL(iface, init); @@ -836,6 +837,12 @@ TEST(SpriteTest, TouchingColor) EXPECT_CALL(iface, touchingColor(v2)).WillOnce(Return(true)); ASSERT_TRUE(sprite.touchingColor(v2)); + + EXPECT_CALL(iface, touchingColor(v1, v2)).WillOnce(Return(false)); + ASSERT_FALSE(sprite.touchingColor(v1, v2)); + + EXPECT_CALL(iface, touchingColor(v2, v1)).WillOnce(Return(true)); + ASSERT_TRUE(sprite.touchingColor(v2, v1)); } TEST(SpriteTest, GraphicsEffects) diff --git a/test/scratch_classes/stage_test.cpp b/test/scratch_classes/stage_test.cpp index 78d3763b..ee69c187 100644 --- a/test/scratch_classes/stage_test.cpp +++ b/test/scratch_classes/stage_test.cpp @@ -222,6 +222,7 @@ TEST(SpriteTest, TouchingColor) { Stage stage; ASSERT_FALSE(stage.touchingColor(0)); + ASSERT_FALSE(stage.touchingColor(0, 0)); StageHandlerMock iface; EXPECT_CALL(iface, init); @@ -233,6 +234,12 @@ TEST(SpriteTest, TouchingColor) EXPECT_CALL(iface, touchingColor(v2)).WillOnce(Return(true)); ASSERT_TRUE(stage.touchingColor(v2)); + + EXPECT_CALL(iface, touchingColor(v1, v2)).WillOnce(Return(false)); + ASSERT_FALSE(stage.touchingColor(v1, v2)); + + EXPECT_CALL(iface, touchingColor(v2, v1)).WillOnce(Return(true)); + ASSERT_TRUE(stage.touchingColor(v2, v1)); } TEST(StageTest, GraphicsEffects) diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp index e71771d2..edf559a2 100644 --- a/test/scratch_classes/target_test.cpp +++ b/test/scratch_classes/target_test.cpp @@ -716,6 +716,7 @@ TEST(TargetTest, TouchingColor) Target target; Value v; ASSERT_FALSE(target.touchingColor(v)); + ASSERT_FALSE(target.touchingColor(v, v)); } TEST(TargetTest, GraphicsEffects) From 4d57b321110744e2d7c1402c224b065b1d7cd9e1 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:59:46 +0200 Subject: [PATCH 05/72] Implement sensing_coloristouchingcolor block --- src/blocks/sensingblocks.cpp | 15 ++++++ src/blocks/sensingblocks.h | 4 ++ test/blocks/sensing_blocks_test.cpp | 75 +++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index fee64bc0..0a198a97 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -32,6 +32,7 @@ void SensingBlocks::registerBlocks(IEngine *engine) // Blocks engine->addCompileFunction(this, "sensing_touchingobject", &compileTouchingObject); engine->addCompileFunction(this, "sensing_touchingcolor", &compileTouchingColor); + engine->addCompileFunction(this, "sensing_coloristouchingcolor", &compileColorIsTouchingColor); engine->addCompileFunction(this, "sensing_distanceto", &compileDistanceTo); engine->addCompileFunction(this, "sensing_askandwait", &compileAskAndWait); engine->addCompileFunction(this, "sensing_answer", &compileAnswer); @@ -60,6 +61,7 @@ void SensingBlocks::registerBlocks(IEngine *engine) // Inputs engine->addInput(this, "TOUCHINGOBJECTMENU", TOUCHINGOBJECTMENU); engine->addInput(this, "COLOR", COLOR); + engine->addInput(this, "COLOR2", COLOR2); engine->addInput(this, "DISTANCETOMENU", DISTANCETOMENU); engine->addInput(this, "QUESTION", QUESTION); engine->addInput(this, "KEY_OPTION", KEY_OPTION); @@ -139,6 +141,13 @@ void SensingBlocks::compileTouchingColor(Compiler *compiler) compiler->addFunctionCall(&touchingColor); } +void SensingBlocks::compileColorIsTouchingColor(Compiler *compiler) +{ + compiler->addInput(COLOR2); // target color + compiler->addInput(COLOR); // mask color + compiler->addFunctionCall(&colorIsTouchingColor); +} + void SensingBlocks::compileDistanceTo(Compiler *compiler) { Input *input = compiler->input(DISTANCETOMENU); @@ -525,6 +534,12 @@ unsigned int SensingBlocks::touchingColor(VirtualMachine *vm) return 0; } +unsigned int SensingBlocks::colorIsTouchingColor(VirtualMachine *vm) +{ + vm->replaceReturnValue(vm->target()->touchingColor(*vm->getInput(0, 2), *vm->getInput(1, 2)), 2); + return 1; +} + unsigned int SensingBlocks::keyPressed(VirtualMachine *vm) { vm->replaceReturnValue(vm->engine()->keyPressed(vm->getInput(0, 1)->toString()), 1); diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index e281827f..1d7e1903 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -22,6 +22,7 @@ class SensingBlocks : public IBlockSection { TOUCHINGOBJECTMENU, COLOR, + COLOR2, DISTANCETOMENU, QUESTION, KEY_OPTION, @@ -64,6 +65,7 @@ class SensingBlocks : public IBlockSection static void compileTouchingObject(Compiler *compiler); static void compileTouchingColor(Compiler *compiler); + static void compileColorIsTouchingColor(Compiler *compiler); static void compileDistanceTo(Compiler *compiler); static void compileAskAndWait(Compiler *compiler); static void compileAnswer(Compiler *compiler); @@ -95,6 +97,8 @@ class SensingBlocks : public IBlockSection static unsigned int touchingColor(VirtualMachine *vm); + static unsigned int colorIsTouchingColor(VirtualMachine *vm); + static unsigned int keyPressed(VirtualMachine *vm); static unsigned int mouseDown(VirtualMachine *vm); static unsigned int mouseX(VirtualMachine *vm); diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp index ecc61337..d5afbde1 100644 --- a/test/blocks/sensing_blocks_test.cpp +++ b/test/blocks/sensing_blocks_test.cpp @@ -127,6 +127,7 @@ TEST_F(SensingBlocksTest, RegisterBlocks) // Blocks EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_touchingobject", &SensingBlocks::compileTouchingObject)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_touchingcolor", &SensingBlocks::compileTouchingColor)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_coloristouchingcolor", &SensingBlocks::compileColorIsTouchingColor)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_distanceto", &SensingBlocks::compileDistanceTo)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_askandwait", &SensingBlocks::compileAskAndWait)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_answer", &SensingBlocks::compileAnswer)); @@ -155,6 +156,7 @@ TEST_F(SensingBlocksTest, RegisterBlocks) // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOUCHINGOBJECTMENU", SensingBlocks::TOUCHINGOBJECTMENU)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "COLOR", SensingBlocks::COLOR)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "COLOR2", SensingBlocks::COLOR2)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DISTANCETOMENU", SensingBlocks::DISTANCETOMENU)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "QUESTION", SensingBlocks::QUESTION)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "KEY_OPTION", SensingBlocks::KEY_OPTION)); @@ -454,6 +456,79 @@ TEST_F(SensingBlocksTest, TouchingColorImpl) ASSERT_TRUE(vm.getInput(0, 1)->toBool()); } +TEST_F(SensingBlocksTest, ColorIsTouchingColor) +{ + Compiler compiler(&m_engineMock); + + // color (#FF00FF) is touching color (#FFFF00) + auto block1 = std::make_shared("a", "sensing_coloristouchingcolor"); + addValueInput(block1, "COLOR", SensingBlocks::COLOR, "#FF00FF"); + addValueInput(block1, "COLOR2", SensingBlocks::COLOR2, "#FFFF00"); + + // color (null block) is touching color (null block) + auto block2 = std::make_shared("b", "sensing_coloristouchingcolor"); + addDropdownInput(block2, "COLOR", SensingBlocks::COLOR, "", createNullBlock("c")); + addDropdownInput(block2, "COLOR2", SensingBlocks::COLOR2, "", createNullBlock("d")); + + compiler.init(); + + EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::colorIsTouchingColor)).WillOnce(Return(1)); + compiler.setBlock(block1); + SensingBlocks::compileColorIsTouchingColor(&compiler); + + EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::colorIsTouchingColor)).WillOnce(Return(1)); + compiler.setBlock(block2); + SensingBlocks::compileColorIsTouchingColor(&compiler); + + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 1, vm::OP_NULL, vm::OP_NULL, vm::OP_EXEC, 1, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "#FFFF00", "#FF00FF" })); +} + +TEST_F(SensingBlocksTest, ColorIsTouchingColorImpl) +{ + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &SensingBlocks::colorIsTouchingColor }; + static Value constValues[] = { "#FF00FF", "#FFFF00", 1946195606, 238 }; + + TargetMock target; + Sprite sprite; + VirtualMachine vm(&target, nullptr, nullptr); + vm.setFunctions(functions); + vm.setConstValues(constValues); + + EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(false)); + vm.setBytecode(bytecode1); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 1); + ASSERT_FALSE(vm.getInput(0, 1)->toBool()); + + EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(true)); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 1); + ASSERT_TRUE(vm.getInput(0, 1)->toBool()); + + EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(false)); + vm.reset(); + vm.setBytecode(bytecode2); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 1); + ASSERT_FALSE(vm.getInput(0, 1)->toBool()); + + EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(true)); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 1); + ASSERT_TRUE(vm.getInput(0, 1)->toBool()); +} + TEST_F(SensingBlocksTest, DistanceTo) { Compiler compiler(&m_engineMock); From 694f1bf62158e80b39bb14b8dd967f974166784c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:21:19 +0200 Subject: [PATCH 06/72] Add getVisibleTargets() method to IEngine Resolves: #533 --- include/scratchcpp/iengine.h | 3 +++ src/engine/internal/engine.cpp | 27 +++++++++++++++++++++++++++ src/engine/internal/engine.h | 1 + test/engine/engine_test.cpp | 20 +++++++++++++++++--- test/mocks/enginemock.h | 1 + 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index aa5aac94..221d9bb2 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -331,6 +331,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Returns the target at index. */ virtual Target *targetAt(int index) const = 0; + /*! Gets visible targets sorted by layer order and stores them in the given vector. */ + virtual void getVisibleTargets(std::vector &dst) const = 0; + /*! * Returns the index of the target with the given name. * \note Using "Stage" will return the index of the sprite with this name, or nullptr if it doesn't exist. diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index c7b0bb7c..4a0c8407 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -1227,6 +1227,33 @@ Target *Engine::targetAt(int index) const return m_targets[index].get(); } +void Engine::getVisibleTargets(std::vector &dst) const +{ + dst.clear(); + + for (auto target : m_targets) { + assert(target); + + if (target->isStage()) + dst.push_back(target.get()); + else { + Sprite *sprite = static_cast(target.get()); + + if (sprite->visible()) + dst.push_back(target.get()); + + const auto &clones = sprite->clones(); + + for (auto clone : clones) { + if (clone->visible()) + dst.push_back(clone.get()); + } + } + } + + std::sort(dst.begin(), dst.end(), [](Target *t1, Target *t2) { return t1->layerOrder() > t2->layerOrder(); }); +} + int Engine::findTarget(const std::string &targetName) const { auto it = std::find_if(m_targets.begin(), m_targets.end(), [targetName](std::shared_ptr target) { diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 67c10f52..1b59ed3d 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -138,6 +138,7 @@ class Engine : public IEngine const std::vector> &targets() const override; void setTargets(const std::vector> &newTargets) override; Target *targetAt(int index) const override; + void getVisibleTargets(std::vector &dst) const override; int findTarget(const std::string &targetName) const override; void moveSpriteToFront(Sprite *sprite) override; diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 88302960..25d601ab 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -1411,16 +1411,21 @@ TEST(EngineTest, Targets) Engine engine; ASSERT_TRUE(engine.targets().empty()); - auto t1 = std::make_shared(); + auto t1 = std::make_shared(); t1->setName("Sprite1"); - auto t2 = std::make_shared(); + t1->setVisible(true); + t1->setLayerOrder(3); + auto t2 = std::make_shared(); auto block1 = std::make_shared("", ""); auto block2 = std::make_shared("", ""); t2->setName("Sprite2"); + t2->setVisible(false); + t2->setLayerOrder(1); t2->addBlock(block1); t2->addBlock(block2); auto t3 = std::make_shared(); t3->setName("Stage"); + t3->setLayerOrder(0); engine.setTargets({ t1, t2, t3 }); ASSERT_EQ(engine.targets(), std::vector>({ t1, t2, t3 })); @@ -1436,16 +1441,25 @@ TEST(EngineTest, Targets) ASSERT_EQ(engine.findTarget("Stage"), -1); ASSERT_EQ(engine.findTarget("_stage_"), 2); - auto t4 = std::make_shared(); + auto t4 = std::make_shared(); t4->setName("Stage"); + t4->setVisible(true); + t4->setLayerOrder(2); engine.setTargets({ t1, t2, t4 }); ASSERT_EQ(engine.findTarget("Stage"), 2); ASSERT_EQ(engine.findTarget("_stage_"), -1); + std::vector visibleTargets; + engine.getVisibleTargets(visibleTargets); + ASSERT_EQ(visibleTargets, std::vector({ t1.get(), t4.get() })); + engine.setTargets({ t1, t2, t3, t4 }); ASSERT_EQ(engine.findTarget("Stage"), 3); ASSERT_EQ(engine.findTarget("_stage_"), 2); + engine.getVisibleTargets(visibleTargets); + ASSERT_EQ(visibleTargets, std::vector({ t1.get(), t4.get(), t3.get() })); + ASSERT_EQ(t1->engine(), &engine); ASSERT_EQ(t2->engine(), &engine); ASSERT_EQ(t3->engine(), &engine); diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index deddb32f..cf7641ff 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -118,6 +118,7 @@ class EngineMock : public IEngine MOCK_METHOD(const std::vector> &, targets, (), (const, override)); MOCK_METHOD(void, setTargets, (const std::vector> &), (override)); MOCK_METHOD(Target *, targetAt, (int), (const, override)); + MOCK_METHOD(void, getVisibleTargets, (std::vector &), (const, override)); MOCK_METHOD(int, findTarget, (const std::string &), (const, override)); MOCK_METHOD(void, moveSpriteToFront, (Sprite * sprite), (override)); From 85f96a7fce457870a0b64c9243912867720174b0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:54:16 +0200 Subject: [PATCH 07/72] fix #571: Get processed bubble text before passing to interfaces --- src/scratch/sprite.cpp | 5 +++-- src/scratch/stage.cpp | 5 +++-- .../target_interfaces/ispritehandler_test.cpp | 19 +++++++++++++++++++ test/target_interfaces/istagehandler_test.cpp | 19 +++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp index 42d67557..47bc1016 100644 --- a/src/scratch/sprite.cpp +++ b/src/scratch/sprite.cpp @@ -557,8 +557,9 @@ void Sprite::setBubbleType(BubbleType type) void Sprite::setBubbleText(const std::string &text) { Target::setBubbleText(impl->visible ? text : ""); + const std::string &finalText = bubbleText(); - if (impl->visible && !text.empty()) { + if (impl->visible && !finalText.empty()) { IEngine *eng = engine(); if (eng) @@ -566,7 +567,7 @@ void Sprite::setBubbleText(const std::string &text) } if (impl->iface) - impl->iface->onBubbleTextChanged(impl->visible ? text : ""); + impl->iface->onBubbleTextChanged(impl->visible ? finalText : ""); } Target *Sprite::dataSource() const diff --git a/src/scratch/stage.cpp b/src/scratch/stage.cpp index ac312ffa..dc08a729 100644 --- a/src/scratch/stage.cpp +++ b/src/scratch/stage.cpp @@ -233,8 +233,9 @@ void Stage::setBubbleType(BubbleType type) void Stage::setBubbleText(const std::string &text) { Target::setBubbleText(text); + const std::string &finalText = bubbleText(); - if (!text.empty()) { + if (!finalText.empty()) { IEngine *eng = engine(); if (eng) @@ -242,7 +243,7 @@ void Stage::setBubbleText(const std::string &text) } if (impl->iface) - impl->iface->onBubbleTextChanged(text); + impl->iface->onBubbleTextChanged(finalText); } bool Stage::touchingClones(const std::vector &clones) const diff --git a/test/target_interfaces/ispritehandler_test.cpp b/test/target_interfaces/ispritehandler_test.cpp index 752593e9..077d44e0 100644 --- a/test/target_interfaces/ispritehandler_test.cpp +++ b/test/target_interfaces/ispritehandler_test.cpp @@ -200,6 +200,25 @@ TEST_F(ISpriteHandlerTest, BubbleText) EXPECT_CALL(m_handler, onBubbleTextChanged("test")); EXPECT_CALL(m_engine, requestRedraw()); m_sprite.setBubbleText("test"); + + // The text should be processed in Target::setBubbleText() (#571) + std::string longstr = + "EY8OUNzAqwgh7NRGk5TzCP3dkAhJy9TX" + "Y9mqKElPjdQpKddYqjyCwUk2hx6YgVZV" + "6BOdmZGxDMs8Hjv8W9G6j4gTxAWdOkzs" + "8Ih80xzEDbvLilWsDwoB6FxH2kVVI4xs" + "IXOETNQ6QMsCKLWc5XjHk2BS9nYvDGpJ" + "uEmp9zIzFGT1kRSrOlU3ZwnN1YtvqFx" + "3hkWVNtJ71dQ0PJHhOVQPUy19V01SPu3" + "KIIS2wdSUVAc4RYMzepSveghzWbdcizy" + "Tm1KKAj4svu9YoL8b9vsolG8gKunvKO7" + "MurRKSeUbECELnJEKV6683xCq7RvmjAu" + "2djZ54apiQc1lTixWns5GoG0SVNuFzHl" + "q97qUiqiMecjVFM51YVif7c1Stip52Hl"; + + EXPECT_CALL(m_handler, onBubbleTextChanged(longstr.substr(0, 330))); + EXPECT_CALL(m_engine, requestRedraw()); + m_sprite.setBubbleText(longstr); } TEST_F(ISpriteHandlerTest, BoundingRect) diff --git a/test/target_interfaces/istagehandler_test.cpp b/test/target_interfaces/istagehandler_test.cpp index 242211ec..43038a22 100644 --- a/test/target_interfaces/istagehandler_test.cpp +++ b/test/target_interfaces/istagehandler_test.cpp @@ -83,6 +83,25 @@ TEST_F(IStageHandlerTest, BubbleText) EXPECT_CALL(m_handler, onBubbleTextChanged("test")); EXPECT_CALL(m_engine, requestRedraw()); m_stage.setBubbleText("test"); + + // The text should be processed in Target::setBubbleText() (#571) + std::string longstr = + "EY8OUNzAqwgh7NRGk5TzCP3dkAhJy9TX" + "Y9mqKElPjdQpKddYqjyCwUk2hx6YgVZV" + "6BOdmZGxDMs8Hjv8W9G6j4gTxAWdOkzs" + "8Ih80xzEDbvLilWsDwoB6FxH2kVVI4xs" + "IXOETNQ6QMsCKLWc5XjHk2BS9nYvDGpJ" + "uEmp9zIzFGT1kRSrOlU3ZwnN1YtvqFx" + "3hkWVNtJ71dQ0PJHhOVQPUy19V01SPu3" + "KIIS2wdSUVAc4RYMzepSveghzWbdcizy" + "Tm1KKAj4svu9YoL8b9vsolG8gKunvKO7" + "MurRKSeUbECELnJEKV6683xCq7RvmjAu" + "2djZ54apiQc1lTixWns5GoG0SVNuFzHl" + "q97qUiqiMecjVFM51YVif7c1Stip52Hl"; + + EXPECT_CALL(m_handler, onBubbleTextChanged(longstr.substr(0, 330))); + EXPECT_CALL(m_engine, requestRedraw()); + m_stage.setBubbleText(longstr); } TEST_F(IStageHandlerTest, BoundingRect) From ba14cf873213360d0cdfbe28a4e4c006a8c7ab0c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:05:09 +0200 Subject: [PATCH 08/72] fix #478: Format bubble text according to Scratch 2.0 --- src/scratch/target.cpp | 15 ++++++++++++- test/scratch_classes/target_test.cpp | 32 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index b1027233..5da5129e 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -576,8 +576,21 @@ const std::string &Target::bubbleText() const */ void Target::setBubbleText(const std::string &text) { + // https://github.com/scratchfoundation/scratch-vm/blob/7313ce5199f8a3da7850085d0f7f6a3ca2c89bf6/src/blocks/scratch3_looks.js#L251-L257 + const Value v(text); + std::string converted = text; + + // Non-integers should be rounded to 2 decimal places (no more, no less), unless they're small enough that rounding would display them as 0.00. + if (v.isValidNumber()) { + const double num = v.toDouble(); + + if (std::abs(num) >= 0.01 && (v % 1).toDouble() != 0) + converted = Value(std::round(num * 100) / 100).toString(); + } + + // Limit the length of the string size_t limit = 330; - impl->bubbleText = text.substr(0, limit); + impl->bubbleText = converted.substr(0, limit); } /*! Returns the engine. */ diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp index edf559a2..3b39ed28 100644 --- a/test/scratch_classes/target_test.cpp +++ b/test/scratch_classes/target_test.cpp @@ -781,6 +781,38 @@ TEST(TargetTest, BubbleText) target.setBubbleText(longstr); ASSERT_EQ(target.bubbleText().length(), 330); ASSERT_EQ(target.bubbleText(), longstr.substr(0, 330)); + + // Integers should be left unchanged + target.setBubbleText("8"); + ASSERT_EQ(target.bubbleText(), "8"); + + target.setBubbleText("-52"); + ASSERT_EQ(target.bubbleText(), "-52"); + + target.setBubbleText("0"); + ASSERT_EQ(target.bubbleText(), "0"); + + // Non-integers should be rounded to 2 decimal places (no more, no less), unless they're small enough that rounding would display them as 0.00 (#478) + target.setBubbleText("8.324"); + ASSERT_EQ(target.bubbleText(), "8.32"); + + target.setBubbleText("-52.576"); + ASSERT_EQ(target.bubbleText(), "-52.58"); + + target.setBubbleText("3.5"); + ASSERT_EQ(target.bubbleText(), "3.5"); + + target.setBubbleText("0.015"); + ASSERT_EQ(target.bubbleText(), "0.02"); + + target.setBubbleText("-0.015"); + ASSERT_EQ(target.bubbleText(), "-0.02"); + + target.setBubbleText("0.005"); + ASSERT_EQ(target.bubbleText(), "0.005"); + + target.setBubbleText("-0.005"); + ASSERT_EQ(target.bubbleText(), "-0.005"); } TEST(TargetTest, Engine) From c8218075716bca57b2104ec598c52bfaeb858dfb Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:08:02 +0200 Subject: [PATCH 09/72] Drop switch costume/backdrop optimizations They make the code too complex --- src/blocks/looksblocks.cpp | 141 +----- src/blocks/looksblocks.h | 4 - test/blocks/looks_blocks_test.cpp | 764 ++---------------------------- 3 files changed, 42 insertions(+), 867 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index fb55f39d..7d4b07fd 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -347,36 +347,8 @@ void LooksBlocks::compileSwitchCostumeTo(Compiler *compiler) if (!target) return; - Input *input = compiler->input(COSTUME); - - if (input->pointsToDropdownMenu()) { - std::string value = input->selectedMenuItem(); - int index = target->findCostume(value); - - if (index == -1) { - if (value == "next costume") - compiler->addFunctionCall(&nextCostume); - else if (value == "previous costume") - compiler->addFunctionCall(&previousCostume); - else { - Value v(value); - - if (v.type() == Value::Type::Integer) { - compiler->addConstValue(v.toLong() - 1); - compiler->addFunctionCall(&switchCostumeToByIndex); - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchCostumeTo); - } - } - } else { - compiler->addConstValue(index); - compiler->addFunctionCall(&switchCostumeToByIndex); - } - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchCostumeTo); - } + compiler->addInput(COSTUME); + compiler->addFunctionCall(&switchCostumeTo); } void LooksBlocks::compileNextCostume(Compiler *compiler) @@ -386,85 +358,14 @@ void LooksBlocks::compileNextCostume(Compiler *compiler) void LooksBlocks::compileSwitchBackdropTo(Compiler *compiler) { - Stage *stage = compiler->engine()->stage(); - - if (!stage) - return; - - Input *input = compiler->input(BACKDROP); - - if (input->pointsToDropdownMenu()) { - std::string value = input->selectedMenuItem(); - int index = stage->findCostume(value); - - if (index == -1) { - if (value == "next backdrop") - compiler->addFunctionCall(&nextBackdrop); - else if (value == "previous backdrop") - compiler->addFunctionCall(&previousBackdrop); - else if (value == "random backdrop") - compiler->addFunctionCall(&randomBackdrop); - else { - Value v(value); - - if (v.type() == Value::Type::Integer) { - compiler->addConstValue(v.toLong() - 1); - compiler->addFunctionCall(&switchBackdropToByIndex); - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchBackdropTo); - } - } - } else { - compiler->addConstValue(index); - compiler->addFunctionCall(&switchBackdropToByIndex); - } - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchBackdropTo); - } + compiler->addInput(BACKDROP); + compiler->addFunctionCall(&switchBackdropTo); } void LooksBlocks::compileSwitchBackdropToAndWait(Compiler *compiler) { - Stage *stage = compiler->engine()->stage(); - - if (!stage) - return; - - Input *input = compiler->input(BACKDROP); - - if (input->pointsToDropdownMenu()) { - std::string value = input->selectedMenuItem(); - int index = stage->findCostume(value); - - if (index == -1) { - if (value == "next backdrop") - compiler->addFunctionCall(&nextBackdropAndWait); - else if (value == "previous backdrop") - compiler->addFunctionCall(&previousBackdropAndWait); - else if (value == "random backdrop") - compiler->addFunctionCall(&randomBackdropAndWait); - else { - Value v(value); - - if (v.type() == Value::Type::Integer) { - compiler->addConstValue(v.toLong() - 1); - compiler->addFunctionCall(&switchBackdropToByIndexAndWait); - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchBackdropToAndWait); - } - } - } else { - compiler->addConstValue(index); - compiler->addFunctionCall(&switchBackdropToByIndexAndWait); - } - } else { - compiler->addInput(input); - compiler->addFunctionCall(&switchBackdropToAndWait); - } - + compiler->addInput(BACKDROP); + compiler->addFunctionCall(&switchBackdropToAndWait); compiler->addFunctionCall(&backdropNumber); compiler->addFunctionCall(&checkBackdropScripts); } @@ -932,14 +833,6 @@ void LooksBlocks::setCostumeByIndex(Target *target, long index) target->setCostumeIndex(index); } -unsigned int LooksBlocks::switchCostumeToByIndex(VirtualMachine *vm) -{ - if (Target *target = vm->target()) - setCostumeByIndex(target, vm->getInput(0, 1)->toLong()); - - return 1; -} - unsigned int LooksBlocks::switchCostumeTo(VirtualMachine *vm) { Target *target = vm->target(); @@ -990,12 +883,6 @@ void LooksBlocks::startBackdropScripts(VirtualMachine *vm, bool wait) } } -void LooksBlocks::switchBackdropToByIndexImpl(VirtualMachine *vm) -{ - if (Stage *stage = vm->engine()->stage()) - setCostumeByIndex(stage, vm->getInput(0, 1)->toLong()); -} - void LooksBlocks::switchBackdropToImpl(VirtualMachine *vm) { Stage *stage = vm->engine()->stage(); @@ -1047,14 +934,6 @@ void LooksBlocks::randomBackdropImpl(VirtualMachine *vm) } } -unsigned int LooksBlocks::switchBackdropToByIndex(VirtualMachine *vm) -{ - switchBackdropToByIndexImpl(vm); - startBackdropScripts(vm, false); - - return 1; -} - unsigned int LooksBlocks::switchBackdropTo(VirtualMachine *vm) { switchBackdropToImpl(vm); @@ -1063,14 +942,6 @@ unsigned int LooksBlocks::switchBackdropTo(VirtualMachine *vm) return 1; } -unsigned int LooksBlocks::switchBackdropToByIndexAndWait(VirtualMachine *vm) -{ - switchBackdropToByIndexImpl(vm); - startBackdropScripts(vm, true); - - return 1; -} - unsigned int LooksBlocks::switchBackdropToAndWait(VirtualMachine *vm) { switchBackdropToImpl(vm); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 60d54fc5..5d3e147d 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -129,21 +129,17 @@ class LooksBlocks : public IBlockSection static unsigned int size(VirtualMachine *vm); static void setCostumeByIndex(Target *target, long index); - static unsigned int switchCostumeToByIndex(VirtualMachine *vm); static unsigned int switchCostumeTo(VirtualMachine *vm); static unsigned int nextCostume(VirtualMachine *vm); static unsigned int previousCostume(VirtualMachine *vm); static void startBackdropScripts(VirtualMachine *vm, bool wait); - static void switchBackdropToByIndexImpl(VirtualMachine *vm); static void switchBackdropToImpl(VirtualMachine *vm); static void nextBackdropImpl(VirtualMachine *vm); static void previousBackdropImpl(VirtualMachine *vm); static void randomBackdropImpl(VirtualMachine *vm); - static unsigned int switchBackdropToByIndex(VirtualMachine *vm); static unsigned int switchBackdropTo(VirtualMachine *vm); - static unsigned int switchBackdropToByIndexAndWait(VirtualMachine *vm); static unsigned int switchBackdropToAndWait(VirtualMachine *vm); static unsigned int nextBackdrop(VirtualMachine *vm); static unsigned int nextBackdropAndWait(VirtualMachine *vm); diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 950187b7..71012c78 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1426,159 +1426,24 @@ TEST_F(LooksBlocksTest, SwitchCostumeTo) auto block1 = std::make_shared("a", "looks_switchcostumeto"); addDropdownInput(block1, "COSTUME", LooksBlocks::COSTUME, "costume2"); - // switch costume to (0) - auto block2 = std::make_shared("b", "looks_switchcostumeto"); - addDropdownInput(block2, "COSTUME", LooksBlocks::COSTUME, "0"); - - // switch costume to (1) - auto block3 = std::make_shared("b", "looks_switchcostumeto"); - addDropdownInput(block3, "COSTUME", LooksBlocks::COSTUME, "1"); - - // switch costume to (2) - auto block4 = std::make_shared("b", "looks_switchcostumeto"); - addDropdownInput(block4, "COSTUME", LooksBlocks::COSTUME, "2"); - - // switch costume to (4) - auto block5 = std::make_shared("b", "looks_switchcostumeto"); - addDropdownInput(block5, "COSTUME", LooksBlocks::COSTUME, "4"); - - // switch costume to (3) - there's a costume with this name - auto block6 = std::make_shared("c", "looks_switchcostumeto"); - addDropdownInput(block6, "COSTUME", LooksBlocks::COSTUME, "3"); - - // switch costume to (next costume) - auto block7 = std::make_shared("d", "looks_switchcostumeto"); - addDropdownInput(block7, "COSTUME", LooksBlocks::COSTUME, "next costume"); - - // switch costume to (next costume) - there's a costume with this name - auto block8 = std::make_shared("d", "looks_switchcostumeto"); - addDropdownInput(block8, "COSTUME", LooksBlocks::COSTUME, "next costume"); - - // switch costume to (previous costume) - auto block9 = std::make_shared("e", "looks_switchcostumeto"); - addDropdownInput(block9, "COSTUME", LooksBlocks::COSTUME, "previous costume"); - - // switch costume to (previous costume) - there's a costume with this name - auto block10 = std::make_shared("f", "looks_switchcostumeto"); - addDropdownInput(block10, "COSTUME", LooksBlocks::COSTUME, "previous costume"); - // switch costume to (null block) - auto block11 = std::make_shared("g", "looks_switchcostumeto"); - addDropdownInput(block11, "COSTUME", LooksBlocks::COSTUME, "", createNullBlock("h")); + auto block2 = std::make_shared("b", "looks_switchcostumeto"); + addDropdownInput(block2, "COSTUME", LooksBlocks::COSTUME, "", createNullBlock("c")); compiler.init(); - // Test without any costumes first compiler.setBlock(block1); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(0)); LooksBlocks::compileSwitchCostumeTo(&compiler); - target.addCostume(std::make_shared("costume1", "c1", "svg")); - target.addCostume(std::make_shared("costume2", "c2", "svg")); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block1); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - target.addCostume(std::make_shared("costume3", "c3", "svg")); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(0)); compiler.setBlock(block2); LooksBlocks::compileSwitchCostumeTo(&compiler); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); - compiler.setBlock(block3); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); - compiler.setBlock(block4); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); - compiler.setBlock(block5); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - target.addCostume(std::make_shared("3", "c4", "svg")); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block6); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::nextCostume)).WillOnce(Return(1)); - compiler.setBlock(block7); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - target.addCostume(std::make_shared("next costume", "c5", "svg")); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block8); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::previousCostume)).WillOnce(Return(2)); - compiler.setBlock(block9); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - target.addCostume(std::make_shared("previous costume", "c6", "svg")); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block10); - LooksBlocks::compileSwitchCostumeTo(&compiler); - - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchCostumeTo)).WillOnce(Return(3)); - compiler.setBlock(block11); - LooksBlocks::compileSwitchCostumeTo(&compiler); - compiler.end(); - ASSERT_EQ( - compiler.bytecode(), - std::vector( - { vm::OP_START, - vm::OP_CONST, - 0, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 1, - vm::OP_EXEC, - 0, - vm::OP_CONST, - 2, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 3, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 4, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 5, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 6, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 1, - vm::OP_CONST, - 7, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 2, - vm::OP_CONST, - 8, - vm::OP_EXEC, - 0, - vm::OP_NULL, - vm::OP_EXEC, - 3, - vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ "costume2", 1, "0", "1", "2", "4", 3, 4, 5 })); + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "costume2" })); } TEST_F(LooksBlocksTest, SwitchCostumeToImpl) @@ -1591,12 +1456,8 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 1, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::switchCostumeTo, &LooksBlocks::switchCostumeToByIndex }; - static Value constValues[] = { "costume2", 0, 1, 2, 3, "next costume", "previous costume", -1, 0, 5, 6 }; + static BlockFunc functions[] = { &LooksBlocks::switchCostumeTo }; + static Value constValues[] = { "costume2", 0, 1, 2, 3, "next costume", "previous costume" }; Target target; target.addCostume(std::make_shared("costume1", "c1", "svg")); @@ -1718,36 +1579,6 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(target.costumeIndex(), 5); - - // -1 (index) - target.setCostumeIndex(1); - - vm.setBytecode(bytecode9); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(target.costumeIndex(), 5); - - // 0 (index) - vm.setBytecode(bytecode10); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(target.costumeIndex(), 0); - - // 5 (index) - vm.setBytecode(bytecode11); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(target.costumeIndex(), 5); - - // 6 (index) - vm.setBytecode(bytecode12); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(target.costumeIndex(), 0); } TEST_F(LooksBlocksTest, NextCostume) @@ -1834,204 +1665,30 @@ TEST_F(LooksBlocksTest, PreviousCostume) TEST_F(LooksBlocksTest, SwitchBackdropTo) { Target target; - Stage stage; Compiler compiler(&m_engineMock, &target); // switch backdrop to (backdrop2) auto block1 = std::make_shared("a", "looks_switchbackdropto"); addDropdownInput(block1, "BACKDROP", LooksBlocks::BACKDROP, "backdrop2"); - // switch backdrop to (0) - auto block2 = std::make_shared("b", "looks_switchbackdropto"); - addDropdownInput(block2, "BACKDROP", LooksBlocks::BACKDROP, "0"); - - // switch backdrop to (1) - auto block3 = std::make_shared("b", "looks_switchbackdropto"); - addDropdownInput(block3, "BACKDROP", LooksBlocks::BACKDROP, "1"); - - // switch backdrop to (2) - auto block4 = std::make_shared("b", "looks_switchbackdropto"); - addDropdownInput(block4, "BACKDROP", LooksBlocks::BACKDROP, "2"); - - // switch backdrop to (4) - auto block5 = std::make_shared("b", "looks_switchbackdropto"); - addDropdownInput(block5, "BACKDROP", LooksBlocks::BACKDROP, "4"); - - // switch backdrop to (3) - there's a backdrop with this name - auto block6 = std::make_shared("c", "looks_switchbackdropto"); - addDropdownInput(block6, "BACKDROP", LooksBlocks::BACKDROP, "3"); - - // switch backdrop to (next backdrop) - auto block7 = std::make_shared("d", "looks_switchbackdropto"); - addDropdownInput(block7, "BACKDROP", LooksBlocks::BACKDROP, "next backdrop"); - - // switch backdrop to (next backdrop) - there's a backdrop with this name - auto block8 = std::make_shared("d", "looks_switchbackdropto"); - addDropdownInput(block8, "BACKDROP", LooksBlocks::BACKDROP, "next backdrop"); - - // switch backdrop to (previous backdrop) - auto block9 = std::make_shared("e", "looks_switchbackdropto"); - addDropdownInput(block9, "BACKDROP", LooksBlocks::BACKDROP, "previous backdrop"); - - // switch backdrop to (previous backdrop) - there's a backdrop with this name - auto block10 = std::make_shared("f", "looks_switchbackdropto"); - addDropdownInput(block10, "BACKDROP", LooksBlocks::BACKDROP, "previous backdrop"); - - // switch backdrop to (random backdrop) - auto block11 = std::make_shared("g", "looks_switchbackdropto"); - addDropdownInput(block11, "BACKDROP", LooksBlocks::BACKDROP, "random backdrop"); - - // switch backdrop to (random backdrop) - there's a backdrop with this name - auto block12 = std::make_shared("h", "looks_switchbackdropto"); - addDropdownInput(block12, "BACKDROP", LooksBlocks::BACKDROP, "random backdrop"); - // switch backdrop to (null block) - auto block13 = std::make_shared("i", "looks_switchbackdropto"); - addDropdownInput(block13, "BACKDROP", LooksBlocks::BACKDROP, "", createNullBlock("j")); + auto block2 = std::make_shared("b", "looks_switchbackdropto"); + addDropdownInput(block2, "BACKDROP", LooksBlocks::BACKDROP, "", createNullBlock("c")); compiler.init(); - // Test without any backdrops first - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(0)); compiler.setBlock(block1); LooksBlocks::compileSwitchBackdropTo(&compiler); - stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); - stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block1); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(0)); compiler.setBlock(block2); LooksBlocks::compileSwitchBackdropTo(&compiler); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); - compiler.setBlock(block3); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); - compiler.setBlock(block4); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); - compiler.setBlock(block5); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - stage.addCostume(std::make_shared("3", "b4", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block6); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::nextBackdrop)).WillOnce(Return(1)); - compiler.setBlock(block7); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - stage.addCostume(std::make_shared("next backdrop", "b5", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block8); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::previousBackdrop)).WillOnce(Return(2)); - compiler.setBlock(block9); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - stage.addCostume(std::make_shared("previous backdrop", "b6", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block10); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::randomBackdrop)).WillOnce(Return(3)); - compiler.setBlock(block11); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndex)).WillOnce(Return(0)); - compiler.setBlock(block12); - LooksBlocks::compileSwitchBackdropTo(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropTo)).WillOnce(Return(4)); - compiler.setBlock(block13); - LooksBlocks::compileSwitchBackdropTo(&compiler); - compiler.end(); - ASSERT_EQ( - compiler.bytecode(), - std::vector( - { vm::OP_START, - vm::OP_CONST, - 0, - vm::OP_EXEC, - 4, - vm::OP_CONST, - 1, - vm::OP_EXEC, - 0, - vm::OP_CONST, - 2, - vm::OP_EXEC, - 4, - vm::OP_CONST, - 3, - vm::OP_EXEC, - 4, - vm::OP_CONST, - 4, - vm::OP_EXEC, - 4, - vm::OP_CONST, - 5, - vm::OP_EXEC, - 4, - vm::OP_CONST, - 6, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 1, - vm::OP_CONST, - 7, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 2, - vm::OP_CONST, - 8, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 3, - vm::OP_CONST, - 9, - vm::OP_EXEC, - 0, - vm::OP_NULL, - vm::OP_EXEC, - 4, - vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ "backdrop2", 1, "0", "1", "2", "4", 3, 4, 5, 6 })); + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "backdrop2" })); } TEST_F(LooksBlocksTest, SwitchBackdropToImpl) @@ -2044,12 +1701,8 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 1, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::switchBackdropTo, &LooksBlocks::switchBackdropToByIndex }; - static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop", -1, 0, 5, 6 }; + static BlockFunc functions[] = { &LooksBlocks::switchBackdropTo }; + static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop" }; Target target; @@ -2203,349 +1856,55 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); - - // -1 (index) - stage.setCostumeIndex(1); - - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - vm.setBytecode(bytecode9); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 5); - - // 0 (index) - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - vm.setBytecode(bytecode10); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); - - // 5 (index) - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - vm.setBytecode(bytecode11); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 5); - - // 6 (index) - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - vm.setBytecode(bytecode12); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); } TEST_F(LooksBlocksTest, SwitchBackdropToAndWait) { Target target; - Stage stage; Compiler compiler(&m_engineMock, &target); // switch backdrop to (backdrop2) and wait auto block1 = std::make_shared("a", "looks_switchbackdroptoandwait"); addDropdownInput(block1, "BACKDROP", LooksBlocks::BACKDROP, "backdrop2"); - // switch backdrop to (0) and wait - auto block2 = std::make_shared("b", "looks_switchbackdroptoandwait"); - addDropdownInput(block2, "BACKDROP", LooksBlocks::BACKDROP, "0"); - - // switch backdrop to (1) and wait - auto block3 = std::make_shared("b", "looks_switchbackdroptoandwait"); - addDropdownInput(block3, "BACKDROP", LooksBlocks::BACKDROP, "1"); - - // switch backdrop to (2) and wait - auto block4 = std::make_shared("b", "looks_switchbackdroptoandwait"); - addDropdownInput(block4, "BACKDROP", LooksBlocks::BACKDROP, "2"); - - // switch backdrop to (4) and wait - auto block5 = std::make_shared("b", "looks_switchbackdroptoandwait"); - addDropdownInput(block5, "BACKDROP", LooksBlocks::BACKDROP, "4"); - - // switch backdrop to (3) and wait - there's a backdrop with this name - auto block6 = std::make_shared("c", "looks_switchbackdroptoandwait"); - addDropdownInput(block6, "BACKDROP", LooksBlocks::BACKDROP, "3"); - - // switch backdrop to (next backdrop) and wait - auto block7 = std::make_shared("d", "looks_switchbackdroptoandwait"); - addDropdownInput(block7, "BACKDROP", LooksBlocks::BACKDROP, "next backdrop"); - - // switch backdrop to (next backdrop) and wait - there's a backdrop with this name - auto block8 = std::make_shared("d", "looks_switchbackdroptoandwait"); - addDropdownInput(block8, "BACKDROP", LooksBlocks::BACKDROP, "next backdrop"); - - // switch backdrop to (previous backdrop) and wait - auto block9 = std::make_shared("e", "looks_switchbackdroptoandwait"); - addDropdownInput(block9, "BACKDROP", LooksBlocks::BACKDROP, "previous backdrop"); - - // switch backdrop to (previous backdrop) and wait - there's a backdrop with this name - auto block10 = std::make_shared("f", "looks_switchbackdroptoandwait"); - addDropdownInput(block10, "BACKDROP", LooksBlocks::BACKDROP, "previous backdrop"); - - // switch backdrop to (random backdrop) and wait - auto block11 = std::make_shared("g", "looks_switchbackdroptoandwait"); - addDropdownInput(block11, "BACKDROP", LooksBlocks::BACKDROP, "random backdrop"); - - // switch backdrop to (random backdrop) and wait - there's a backdrop with this name - auto block12 = std::make_shared("h", "looks_switchbackdroptoandwait"); - addDropdownInput(block12, "BACKDROP", LooksBlocks::BACKDROP, "random backdrop"); - // switch backdrop to (null block) and wait - auto block13 = std::make_shared("i", "looks_switchbackdroptoandwait"); - addDropdownInput(block13, "BACKDROP", LooksBlocks::BACKDROP, "", createNullBlock("j")); + auto block2 = std::make_shared("b", "looks_switchbackdroptoandwait"); + addDropdownInput(block2, "BACKDROP", LooksBlocks::BACKDROP, "", createNullBlock("c")); compiler.init(); - // Test without any backdrops first - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(1)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(2)); compiler.setBlock(block1); LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); - stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndexAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block1); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(1)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(2)); compiler.setBlock(block2); LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block3); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block4); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block5); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - stage.addCostume(std::make_shared("3", "b4", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndexAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block6); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::nextBackdropAndWait)).WillOnce(Return(1)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block7); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - stage.addCostume(std::make_shared("next backdrop", "b5", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndexAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block8); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::previousBackdropAndWait)).WillOnce(Return(2)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block9); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - stage.addCostume(std::make_shared("previous backdrop", "b6", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndexAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block10); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::randomBackdropAndWait)).WillOnce(Return(3)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block11); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToByIndexAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block12); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(4)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(6)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(5)); - compiler.setBlock(block13); - LooksBlocks::compileSwitchBackdropToAndWait(&compiler); - compiler.end(); ASSERT_EQ( compiler.bytecode(), - std::vector( - { vm::OP_START, - vm::OP_CONST, - 0, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 1, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 2, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 3, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 4, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 5, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 6, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_EXEC, - 1, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 7, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_EXEC, - 2, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 8, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_EXEC, - 3, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_CONST, - 9, - vm::OP_EXEC, - 0, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_NULL, - vm::OP_EXEC, - 4, - vm::OP_EXEC, - 6, - vm::OP_EXEC, - 5, - vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ "backdrop2", 1, "0", "1", "2", "4", 3, 4, 5, 6 })); + std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "backdrop2" })); } TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) { - static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait, &LooksBlocks::switchBackdropToByIndexAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; - static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop", -1, 0, 5, 6 }; + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; + static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop" }; Target target; @@ -2750,57 +2109,6 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); - - // -1 (index) - stage.setCostumeIndex(1); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(true)); - vm.setBytecode(bytecode9); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(stage.costumeIndex(), 5); - ASSERT_FALSE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(false)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_TRUE(vm.atEnd()); - - // 0 (index) - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); - vm.setBytecode(bytecode10); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); - ASSERT_TRUE(vm.atEnd()); - - // 5 (index) - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(false)); - vm.setBytecode(bytecode11); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 5); - - // 6 (index) - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); - vm.setBytecode(bytecode12); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); } TEST_F(LooksBlocksTest, NextBackdrop) From 20e78b8ab3db2e44d04ab4c134b0fcbf065dee6e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:30:00 +0200 Subject: [PATCH 10/72] fix #522: Fix switch costume/backdrop block behavior --- src/blocks/looksblocks.cpp | 72 +++++--- test/blocks/looks_blocks_test.cpp | 276 ++++++++++++++++++++++++++---- 2 files changed, 292 insertions(+), 56 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 7d4b07fd..6361a5aa 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -835,26 +835,41 @@ void LooksBlocks::setCostumeByIndex(Target *target, long index) unsigned int LooksBlocks::switchCostumeTo(VirtualMachine *vm) { + // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L389-L413 Target *target = vm->target(); if (!target) return 1; const Value *name = vm->getInput(0, 1); - std::string nameStr = name->toString(); - int index = target->findCostume(nameStr); - if (index == -1) { - if (nameStr == "next costume") + if (!name->isString()) { + // Numbers should be treated as costume indices, always + if (name->isNaN() || name->isInfinity() || name->isNegativeInfinity()) + target->setCostumeIndex(0); + else + setCostumeByIndex(target, name->toLong() - 1); + } else { + // Strings should be treated as costume names, where possible + const int costumeIndex = target->findCostume(name->toString()); + std::string nameStr = name->toString(); + + auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); }); + bool isWhiteSpace = (it == nameStr.end()); + + if (costumeIndex != -1) { + setCostumeByIndex(target, costumeIndex); + } else if (nameStr == "next costume") { nextCostume(vm); - else if (nameStr == "previous costume") + } else if (nameStr == "previous costume") { previousCostume(vm); - else { - if (name->isValidNumber()) - setCostumeByIndex(target, name->toLong() - 1); + // Try to cast the string to a number (and treat it as a costume index) + // Pure whitespace should not be treated as a number + // Note: isNaN will cast the string to a number before checking if it's NaN + } else if (!(name->isNaN() || isWhiteSpace)) { + target->setCostumeIndex(name->toInt() - 1); } - } else - setCostumeByIndex(target, index); + } return 1; } @@ -885,28 +900,43 @@ void LooksBlocks::startBackdropScripts(VirtualMachine *vm, bool wait) void LooksBlocks::switchBackdropToImpl(VirtualMachine *vm) { + // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L423-L462 Stage *stage = vm->engine()->stage(); if (!stage) return; const Value *name = vm->getInput(0, 1); - std::string nameStr = name->toString(); - int index = stage->findCostume(nameStr); - if (index == -1) { - if (nameStr == "next backdrop") + if (!name->isString()) { + // Numbers should be treated as costume indices, always + if (name->isNaN() || name->isInfinity() || name->isNegativeInfinity()) + stage->setCostumeIndex(0); + else + setCostumeByIndex(stage, name->toLong() - 1); + } else { + // Strings should be treated as costume names, where possible + const int costumeIndex = stage->findCostume(name->toString()); + std::string nameStr = name->toString(); + + auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); }); + bool isWhiteSpace = (it == nameStr.end()); + + if (costumeIndex != -1) { + setCostumeByIndex(stage, costumeIndex); + } else if (nameStr == "next backdrop") { nextBackdropImpl(vm); - else if (nameStr == "previous backdrop") + } else if (nameStr == "previous backdrop") { previousBackdropImpl(vm); - else if (nameStr == "random backdrop") { + } else if (nameStr == "random backdrop") { randomBackdropImpl(vm); - } else { - if (name->isValidNumber()) - setCostumeByIndex(stage, name->toLong() - 1); + // Try to cast the string to a number (and treat it as a costume index) + // Pure whitespace should not be treated as a number + // Note: isNaN will cast the string to a number before checking if it's NaN + } else if (!(name->isNaN() || isWhiteSpace)) { + stage->setCostumeIndex(name->toInt() - 1); } - } else - setCostumeByIndex(stage, index); + } } void LooksBlocks::nextBackdropImpl(VirtualMachine *vm) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 71012c78..24f8aede 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1453,11 +1453,19 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchCostumeTo }; - static Value constValues[] = { "costume2", 0, 1, 2, 3, "next costume", "previous costume" }; + static Value constValues[] = { + "costume2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next costume", "previous costume" + }; Target target; target.addCostume(std::make_shared("costume1", "c1", "svg")); @@ -1513,21 +1521,84 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) target.setCostumeIndex(1); - // "2" + // 2 target.addCostume(std::make_shared("2", "c3", "svg")); target.addCostume(std::make_shared("test", "c4", "svg")); target.setCostumeIndex(0); + vm.setBytecode(bytecode4); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 1); + + // "2" + target.setCostumeIndex(0); + vm.setBytecode(bytecode6); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(target.costumeIndex(), 2); - // "next costume" + // "3" + target.setCostumeIndex(0); + vm.setBytecode(bytecode7); vm.run(); + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 2); + + // NaN + target.setCostumeIndex(2); + + vm.setBytecode(bytecode8); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 0); + + // Infinity + target.setCostumeIndex(2); + + vm.setBytecode(bytecode9); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 0); + + // -Infinity + target.setCostumeIndex(2); + + vm.setBytecode(bytecode10); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 0); + + // "" + target.setCostumeIndex(2); + + vm.setBytecode(bytecode11); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 2); + + // " " + target.setCostumeIndex(2); + + vm.setBytecode(bytecode12); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.costumeIndex(), 2); + + // "next costume" + vm.setBytecode(bytecode13); + vm.run(); + ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(target.costumeIndex(), 3); @@ -1552,7 +1623,7 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) ASSERT_EQ(target.costumeIndex(), 4); // "previous costume" - vm.setBytecode(bytecode8); + vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -1698,11 +1769,19 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropTo }; - static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop" }; + static Value constValues[] = { + "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop" + }; Target target; @@ -1755,31 +1834,88 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) // 2 EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + stage.addCostume(std::make_shared("2", "b3", "svg")); + stage.addCostume(std::make_shared("test", "b4", "svg")); + stage.setCostumeIndex(0); + vm.setBytecode(bytecode4); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); - // 3 + // "2" + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + stage.setCostumeIndex(0); + + vm.setBytecode(bytecode6); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // "3" + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + stage.setCostumeIndex(0); + + vm.setBytecode(bytecode7); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // NaN EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - vm.setBytecode(bytecode5); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode8); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); - stage.setCostumeIndex(1); + // Infinity + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + stage.setCostumeIndex(2); - // "2" - stage.addCostume(std::make_shared("2", "b3", "svg")); - stage.addCostume(std::make_shared("test", "b4", "svg")); - stage.setCostumeIndex(0); + vm.setBytecode(bytecode9); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 0); + + // -Infinity + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode10); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 0); + // "" EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - vm.setBytecode(bytecode6); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode11); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // " " + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode12); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -1788,7 +1924,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) // "next backdrop" EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); - vm.setBytecode(bytecode7); + vm.setBytecode(bytecode13); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -1823,7 +1959,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) // "previous backdrop" EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); - vm.setBytecode(bytecode8); + vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -1900,11 +2036,19 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; - static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "next backdrop", "previous backdrop" }; + static Value constValues[] = { + "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop" + }; Target target; @@ -1980,33 +2124,95 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + stage.addCostume(std::make_shared("2", "b3", "svg")); + stage.addCostume(std::make_shared("test", "b4", "svg")); + stage.setCostumeIndex(0); + vm.setBytecode(bytecode4); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); - // 3 + // "2" + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + stage.setCostumeIndex(0); + + vm.setBytecode(bytecode6); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // "3" + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + stage.setCostumeIndex(0); + + vm.setBytecode(bytecode7); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // NaN EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); - vm.setBytecode(bytecode5); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode8); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); - stage.setCostumeIndex(1); + // Infinity + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + stage.setCostumeIndex(2); - // "2" - stage.addCostume(std::make_shared("2", "b3", "svg")); - stage.addCostume(std::make_shared("test", "b4", "svg")); - stage.setCostumeIndex(0); + vm.setBytecode(bytecode9); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 0); + // -Infinity + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode10); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 0); + + // "" EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); - vm.setBytecode(bytecode6); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode11); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 2); + + // " " + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + stage.setCostumeIndex(2); + + vm.setBytecode(bytecode12); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -2016,7 +2222,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(true)); - vm.setBytecode(bytecode7); + vm.setBytecode(bytecode13); vm.run(); ASSERT_EQ(vm.registerCount(), 1); @@ -2063,7 +2269,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(true)); - vm.setBytecode(bytecode8); + vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 1); From 8c473d0ca7004e03b5488035553190592529aaa0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:01:01 +0200 Subject: [PATCH 11/72] Add test for random backdrop in switch backdrop block --- test/blocks/looks_blocks_test.cpp | 83 ++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 24f8aede..ef094000 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1778,9 +1778,11 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropTo }; static Value constValues[] = { - "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop" + "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", + "random backdrop" }; Target target; @@ -1992,6 +1994,42 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); + + // random backdrop + RandomGeneratorMock rng; + LooksBlocks::rng = &rng; + + EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); + EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(3)); + stage.setCostumeIndex(0); + vm.setBytecode(bytecode15); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 3); + + EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); + EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(5)); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 5); + + stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); + + EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast())); + EXPECT_CALL(rng, randint).Times(0); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 6); + + LooksBlocks::rng = RandomGenerator::instance().get(); } TEST_F(LooksBlocksTest, SwitchBackdropToAndWait) @@ -2045,9 +2083,11 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; static Value constValues[] = { - "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop" + "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", + "random backdrop" }; Target target; @@ -2315,6 +2355,45 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); + + // random backdrop + RandomGeneratorMock rng; + LooksBlocks::rng = &rng; + + EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(3)); + stage.setCostumeIndex(0); + vm.setBytecode(bytecode15); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 3); + + EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(5)); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 5); + + stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); + + EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast())); + EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(6)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(rng, randint).Times(0); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(stage.costumeIndex(), 6); + + LooksBlocks::rng = RandomGenerator::instance().get(); } TEST_F(LooksBlocksTest, NextBackdrop) From 96800f9b02d9491b7ec10de42fed66584b6bf73e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 7 Sep 2024 10:25:26 +0200 Subject: [PATCH 12/72] VirtualMachine: Fix a typo in docs --- include/scratchcpp/virtualmachine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/scratchcpp/virtualmachine.h b/include/scratchcpp/virtualmachine.h index 58541495..ac642837 100644 --- a/include/scratchcpp/virtualmachine.h +++ b/include/scratchcpp/virtualmachine.h @@ -76,7 +76,7 @@ enum Opcode OP_STR_CONTAINS, /*!< Stores true in the last register if the string stored in the second last register contains the substring in the last register. */ OP_EXEC, /*!< Calls the function with the index in the argument. */ OP_INIT_PROCEDURE, /*!< Initializes the list of procedure (custom block) arguments. */ - OP_CALL_PROCEDURE, /*! Calls the procedure (custom block) with the index in the argument. */ + OP_CALL_PROCEDURE, /*!< Calls the procedure (custom block) with the index in the argument. */ OP_ADD_ARG, /*!< Adds a procedure (custom block) argument with the value from the last register. */ OP_READ_ARG, /*!< Reads the procedure (custom block) argument with the index in the argument and stores the value in the last register. */ OP_BREAK_FRAME, /*!< Breaks current frame at the end of the loop. */ From 77e0a7e8136dc73836d4cdd30ab9c1b770d67e9c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 7 Sep 2024 15:56:58 +0200 Subject: [PATCH 13/72] Remove unused functions from looks blocks --- src/blocks/looksblocks.cpp | 24 ----- src/blocks/looksblocks.h | 3 - test/blocks/looks_blocks_test.cpp | 167 ------------------------------ 3 files changed, 194 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 6361a5aa..e2961a7d 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -988,14 +988,6 @@ unsigned int LooksBlocks::nextBackdrop(VirtualMachine *vm) return 0; } -unsigned int LooksBlocks::nextBackdropAndWait(VirtualMachine *vm) -{ - nextBackdropImpl(vm); - startBackdropScripts(vm, true); - - return 0; -} - unsigned int LooksBlocks::previousBackdrop(VirtualMachine *vm) { previousBackdropImpl(vm); @@ -1004,14 +996,6 @@ unsigned int LooksBlocks::previousBackdrop(VirtualMachine *vm) return 0; } -unsigned int LooksBlocks::previousBackdropAndWait(VirtualMachine *vm) -{ - previousBackdropImpl(vm); - startBackdropScripts(vm, true); - - return 0; -} - unsigned int LooksBlocks::randomBackdrop(VirtualMachine *vm) { randomBackdropImpl(vm); @@ -1020,14 +1004,6 @@ unsigned int LooksBlocks::randomBackdrop(VirtualMachine *vm) return 0; } -unsigned int LooksBlocks::randomBackdropAndWait(VirtualMachine *vm) -{ - randomBackdropImpl(vm); - startBackdropScripts(vm, true); - - return 0; -} - unsigned int LooksBlocks::checkBackdropScripts(VirtualMachine *vm) { if (Stage *stage = vm->engine()->stage()) { diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 5d3e147d..ed80f1b3 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -142,11 +142,8 @@ class LooksBlocks : public IBlockSection static unsigned int switchBackdropTo(VirtualMachine *vm); static unsigned int switchBackdropToAndWait(VirtualMachine *vm); static unsigned int nextBackdrop(VirtualMachine *vm); - static unsigned int nextBackdropAndWait(VirtualMachine *vm); static unsigned int previousBackdrop(VirtualMachine *vm); - static unsigned int previousBackdropAndWait(VirtualMachine *vm); static unsigned int randomBackdrop(VirtualMachine *vm); - static unsigned int randomBackdropAndWait(VirtualMachine *vm); static unsigned int checkBackdropScripts(VirtualMachine *vm); static unsigned int goToFront(VirtualMachine *vm); diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index ef094000..9e6c7b66 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -2454,59 +2454,6 @@ TEST_F(LooksBlocksTest, NextBackdropImpl) ASSERT_EQ(stage.costumeIndex(), 0); } -TEST_F(LooksBlocksTest, NextBackdropAndWait) -{ - static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::nextBackdropAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; - - Target target; - - Stage stage; - stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); - stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); - stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); - stage.setCostumeIndex(0); - - VirtualMachine vm(&target, &m_engineMock, nullptr); - vm.setBytecode(bytecode); - vm.setFunctions(functions); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(true)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(stage.costumeIndex(), 1); - ASSERT_FALSE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_TRUE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 2); - ASSERT_TRUE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); -} - TEST_F(LooksBlocksTest, PreviousBackdrop) { static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }; @@ -2548,59 +2495,6 @@ TEST_F(LooksBlocksTest, PreviousBackdrop) ASSERT_EQ(stage.costumeIndex(), 2); } -TEST_F(LooksBlocksTest, PreviousBackdropAndWait) -{ - static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::previousBackdropAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; - - Target target; - - Stage stage; - stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); - stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); - stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); - stage.setCostumeIndex(2); - - VirtualMachine vm(&target, &m_engineMock, nullptr); - vm.setBytecode(bytecode); - vm.setFunctions(functions); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(true)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(stage.costumeIndex(), 1); - ASSERT_FALSE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_TRUE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 0); - ASSERT_TRUE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 2); -} - TEST_F(LooksBlocksTest, RandomBackdrop) { static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }; @@ -2650,67 +2544,6 @@ TEST_F(LooksBlocksTest, RandomBackdrop) LooksBlocks::rng = RandomGenerator::instance().get(); } -TEST_F(LooksBlocksTest, RandomBackdropAndWait) -{ - static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::randomBackdropAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; - - Target target; - - Stage stage; - - VirtualMachine vm(&target, &m_engineMock, nullptr); - vm.setBytecode(bytecode); - vm.setFunctions(functions); - - RandomGeneratorMock rng; - LooksBlocks::rng = &rng; - - EXPECT_CALL(rng, randint).Times(0); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning).Times(0); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - - stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); - stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); - stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); - - EXPECT_CALL(rng, randint(0, 2)).WillOnce(Return(1)); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(true)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(stage.costumeIndex(), 1); - ASSERT_FALSE(vm.atEnd()); - - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_TRUE(vm.atEnd()); - - stage.addCostume(std::make_shared("backdrop4", "b4", "svg")); - - EXPECT_CALL(rng, randint(0, 3)).WillOnce(Return(2)); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(stage.costumeIndex(), 2); - ASSERT_TRUE(vm.atEnd()); - - LooksBlocks::rng = RandomGenerator::instance().get(); -} - TEST_F(LooksBlocksTest, GoToFrontBack) { Compiler compiler(&m_engineMock); From 16999e7a772e81f1a53db0cc11b31e0ec591bb83 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 7 Sep 2024 16:06:40 +0200 Subject: [PATCH 14/72] Engine: Add backdrop broadcast map --- src/engine/internal/engine.cpp | 23 +++++++++++++++++++++++ src/engine/internal/engine.h | 1 + 2 files changed, 24 insertions(+) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 4a0c8407..593ac57f 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -1160,7 +1160,30 @@ void Engine::addBroadcastScript(std::shared_ptr whenReceivedBlock, int fi void Engine::addBackdropChangeScript(std::shared_ptr hatBlock, int fieldId) { + Stage *stage = this->stage(); + + if (!stage) + return; + + // TODO: This assumes the first field holds the broadcast pointer, maybe this is not the best way (e. g. if an extension uses this method) + assert(hatBlock->fieldAt(0)); + const std::string &backdropName = hatBlock->fieldAt(0)->value().toString(); + auto backdrop = stage->costumeAt(stage->findCostume(backdropName)); + Broadcast *broadcast = backdrop->broadcast(); + assert(broadcast->isBackdropBroadcast()); + Script *script = m_scripts[hatBlock].get(); + auto it = m_backdropBroadcastMap.find(broadcast); + + if (it != m_backdropBroadcastMap.cend()) { + auto &scripts = it->second; + auto scriptIt = std::find(scripts.begin(), scripts.end(), script); + + if (scriptIt == scripts.end()) + scripts.push_back(script); + } else + m_backdropBroadcastMap[broadcast] = { script }; + addHatToMap(m_backdropChangeHats, script); addHatField(script, HatField::Backdrop, fieldId); } diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 1b59ed3d..0bde8d2a 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -237,6 +237,7 @@ class Engine : public IEngine std::vector> m_targets; std::vector> m_broadcasts; std::unordered_map> m_broadcastMap; + std::unordered_map> m_backdropBroadcastMap; std::vector> m_monitors; std::vector m_extensions; std::vector m_executableTargets; // sorted by layer (reverse order of execution) From 83dd69447ed601e0f86808e7d37b93001bc7eacc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 7 Sep 2024 17:17:59 +0200 Subject: [PATCH 15/72] fix #563: Refactor broadcasts to use promise API --- include/scratchcpp/iengine.h | 6 +- src/blocks/eventblocks.cpp | 60 +--- src/blocks/eventblocks.h | 3 - src/blocks/looksblocks.cpp | 18 +- src/blocks/looksblocks.h | 1 - src/engine/internal/engine.cpp | 66 +++- src/engine/internal/engine.h | 9 +- test/blocks/event_blocks_test.cpp | 78 +---- test/blocks/looks_blocks_test.cpp | 294 ++++++++---------- test/engine/engine_test.cpp | 24 +- test/mocks/enginemock.h | 6 +- .../563_broadcast_stops_wait_blocks.sb3 | Bin 0 -> 1781 bytes 12 files changed, 246 insertions(+), 319 deletions(-) create mode 100644 test/regtest_projects/563_broadcast_stops_wait_blocks.sb3 diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index 221d9bb2..e099e446 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -61,13 +61,13 @@ class LIBSCRATCHCPP_EXPORT IEngine virtual VirtualMachine *startScript(std::shared_ptr topLevelBlock, Target *) = 0; /*! Starts the scripts of the broadcast with the given index. */ - virtual void broadcast(int index) = 0; + virtual void broadcast(int index, VirtualMachine *sender) = 0; /*! Starts the scripts of the given broadcast. */ - virtual void broadcastByPtr(Broadcast *broadcast) = 0; + virtual void broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) = 0; /*! Starts the "when backdrop switches to" scripts for the given backdrop broadcast. */ - virtual void startBackdropScripts(Broadcast *broadcast) = 0; + virtual void startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) = 0; /*! Stops the given script. */ virtual void stopScript(VirtualMachine *vm) = 0; diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index e4c61902..25631e54 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -113,26 +113,8 @@ void EventBlocks::compileBroadcast(Compiler *compiler) void EventBlocks::compileBroadcastAndWait(Compiler *compiler) { - auto input = compiler->input(BROADCAST_INPUT); - - if (input->type() != Input::Type::ObscuredShadow) { - std::vector broadcasts = compiler->engine()->findBroadcasts(input->primaryValue()->value().toString()); - - for (int index : broadcasts) { - compiler->addConstValue(index); - compiler->addFunctionCall(&broadcastByIndexAndWait); - } - - for (int index : broadcasts) { - compiler->addConstValue(index); - compiler->addFunctionCall(&checkBroadcastByIndex); - } - } else { - compiler->addInput(input); - compiler->addFunctionCall(&broadcastAndWait); - compiler->addInput(input); - compiler->addFunctionCall(&checkBroadcast); - } + compiler->addInput(BROADCAST_INPUT); + compiler->addFunctionCall(&broadcastAndWait); } void EventBlocks::compileWhenBroadcastReceived(Compiler *compiler) @@ -210,14 +192,14 @@ unsigned int EventBlocks::broadcast(VirtualMachine *vm) std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); for (int index : broadcasts) - vm->engine()->broadcast(index); + vm->engine()->broadcast(index, vm); return 1; } unsigned int EventBlocks::broadcastByIndex(VirtualMachine *vm) { - vm->engine()->broadcast(vm->getInput(0, 1)->toLong()); + vm->engine()->broadcast(vm->getInput(0, 1)->toLong(), vm); return 1; } @@ -226,40 +208,10 @@ unsigned int EventBlocks::broadcastAndWait(VirtualMachine *vm) std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); for (int index : broadcasts) - vm->engine()->broadcast(index); - - return 1; -} + vm->engine()->broadcast(index, vm); -unsigned int EventBlocks::broadcastByIndexAndWait(VirtualMachine *vm) -{ - vm->engine()->broadcast(vm->getInput(0, 1)->toLong()); - return 1; -} - -unsigned int EventBlocks::checkBroadcast(VirtualMachine *vm) -{ - bool running = false; - - std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); + vm->promise(); - for (int index : broadcasts) { - if (vm->engine()->broadcastRunning(index)) { - running = true; - break; - } - } - - if (running) - vm->stop(true, true, true); - - return 1; -} - -unsigned int EventBlocks::checkBroadcastByIndex(VirtualMachine *vm) -{ - if (vm->engine()->broadcastRunning(vm->getInput(0, 1)->toLong())) - vm->stop(true, true, true); return 1; } diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index 0c5e5fd6..76547109 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -58,9 +58,6 @@ class EventBlocks : public IBlockSection static unsigned int broadcast(VirtualMachine *vm); static unsigned int broadcastByIndex(VirtualMachine *vm); static unsigned int broadcastAndWait(VirtualMachine *vm); - static unsigned int broadcastByIndexAndWait(VirtualMachine *vm); - static unsigned int checkBroadcast(VirtualMachine *vm); - static unsigned int checkBroadcastByIndex(VirtualMachine *vm); static unsigned int whenLoudnessGreaterThanPredicate(VirtualMachine *vm); static unsigned int whenTimerGreaterThanPredicate(VirtualMachine *vm); diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index e2961a7d..066c368d 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -366,8 +366,6 @@ void LooksBlocks::compileSwitchBackdropToAndWait(Compiler *compiler) { compiler->addInput(BACKDROP); compiler->addFunctionCall(&switchBackdropToAndWait); - compiler->addFunctionCall(&backdropNumber); - compiler->addFunctionCall(&checkBackdropScripts); } void LooksBlocks::compileNextBackdrop(Compiler *compiler) @@ -894,7 +892,7 @@ void LooksBlocks::startBackdropScripts(VirtualMachine *vm, bool wait) { if (Stage *stage = vm->engine()->stage()) { if (stage->costumes().size() > 0) - vm->engine()->startBackdropScripts(stage->currentCostume()->broadcast()); + vm->engine()->startBackdropScripts(stage->currentCostume()->broadcast(), vm); } } @@ -976,6 +974,7 @@ unsigned int LooksBlocks::switchBackdropToAndWait(VirtualMachine *vm) { switchBackdropToImpl(vm); startBackdropScripts(vm, true); + vm->promise(); return 1; } @@ -1004,19 +1003,6 @@ unsigned int LooksBlocks::randomBackdrop(VirtualMachine *vm) return 0; } -unsigned int LooksBlocks::checkBackdropScripts(VirtualMachine *vm) -{ - if (Stage *stage = vm->engine()->stage()) { - long index = vm->getInput(0, 1)->toLong() - 1; - assert(stage->costumes().size() == 0 || index >= 0); - - if ((stage->costumes().size() > 0) && vm->engine()->broadcastByPtrRunning(stage->costumeAt(index)->broadcast())) - vm->stop(true, true, true); - } - - return 1; -} - unsigned int LooksBlocks::goToFront(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index ed80f1b3..2c3adc2a 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -144,7 +144,6 @@ class LooksBlocks : public IBlockSection static unsigned int nextBackdrop(VirtualMachine *vm); static unsigned int previousBackdrop(VirtualMachine *vm); static unsigned int randomBackdrop(VirtualMachine *vm); - static unsigned int checkBackdropScripts(VirtualMachine *vm); static unsigned int goToFront(VirtualMachine *vm); static unsigned int goToBack(VirtualMachine *vm); diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 593ac57f..050a28cf 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -433,22 +433,24 @@ VirtualMachine *Engine::startScript(std::shared_ptr topLevelBlock, Target return pushThread(topLevelBlock, target).get(); } -void Engine::broadcast(int index) +void Engine::broadcast(int index, VirtualMachine *sender) { if (index < 0 || index >= m_broadcasts.size()) return; - broadcastByPtr(m_broadcasts[index].get()); + broadcastByPtr(m_broadcasts[index].get(), sender); } -void Engine::broadcastByPtr(Broadcast *broadcast) +void Engine::broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) { startHats(HatType::BroadcastReceived, { { HatField::BroadcastOption, broadcast } }, nullptr); + addBroadcastPromise(broadcast, sender); } -void Engine::startBackdropScripts(Broadcast *broadcast) +void Engine::startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) { startHats(HatType::BackdropChanged, { { HatField::Backdrop, broadcast->name() } }, nullptr); + addBroadcastPromise(broadcast, sender); } void Engine::stopScript(VirtualMachine *vm) @@ -583,6 +585,48 @@ void Engine::step() // Check running threads (must be done here) m_frameActivity = !m_threads.empty(); + // Resolve stopped broadcast scripts + std::vector resolved; + + for (const auto &[broadcast, senderThread] : m_broadcastSenders) { + std::unordered_map> *broadcastMap = nullptr; + + if (broadcast->isBackdropBroadcast()) { + // This broadcast belongs to a backdrop + assert(m_broadcastMap.find(broadcast) == m_broadcastMap.cend()); + broadcastMap = &m_backdropBroadcastMap; + } else { + // This is a regular broadcast + assert(m_backdropBroadcastMap.find(broadcast) == m_backdropBroadcastMap.cend()); + broadcastMap = &m_broadcastMap; + } + + bool found = false; + + if (broadcastMap->find(broadcast) != broadcastMap->cend()) { + const auto &scripts = (*broadcastMap)[broadcast]; + + for (auto script : scripts) { + if (std::find_if(m_threads.begin(), m_threads.end(), [script](std::shared_ptr thread) { return thread->script() == script; }) != m_threads.end()) { + found = true; + break; + } + } + } + + if (!found) { + VirtualMachine *th = senderThread; + + if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr thread) { return thread.get() == th; }) != m_threads.end()) + th->resolvePromise(); + + resolved.push_back(broadcast); + } + } + + for (Broadcast *broadcast : resolved) + m_broadcastSenders.erase(broadcast); + m_redrawRequested = false; // Step threads @@ -1896,6 +1940,20 @@ void Engine::addRunningScript(std::shared_ptr vm) m_threads.push_back(vm); } +void Engine::addBroadcastPromise(Broadcast *broadcast, VirtualMachine *sender) +{ + assert(broadcast); + assert(sender); + + // Resolve broadcast promise if it's already running + auto it = m_broadcastSenders.find(broadcast); + + if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr thread) { return thread.get() == it->second; }) != m_threads.end()) + it->second->resolvePromise(); + + m_broadcastSenders[broadcast] = sender; +} + std::shared_ptr Engine::pushThread(std::shared_ptr block, Target *target) { // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L1649-L1661 diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 0bde8d2a..099faa96 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -35,9 +35,9 @@ class Engine : public IEngine void start() override; void stop() override; VirtualMachine *startScript(std::shared_ptr topLevelBlock, Target *target) override; - void broadcast(int index) override; - void broadcastByPtr(Broadcast *broadcast) override; - void startBackdropScripts(Broadcast *broadcast) override; + void broadcast(int index, VirtualMachine *sender) override; + void broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) override; + void startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) override; void stopScript(VirtualMachine *vm) override; void stopTarget(Target *target, VirtualMachine *exceptScript) override; void initClone(std::shared_ptr clone) override; @@ -220,6 +220,8 @@ class Engine : public IEngine void updateFrameDuration(); void addRunningScript(std::shared_ptr vm); + void addBroadcastPromise(Broadcast *broadcast, VirtualMachine *sender); + std::shared_ptr pushThread(std::shared_ptr block, Target *target); void stopThread(VirtualMachine *thread); std::shared_ptr restartThread(std::shared_ptr thread); @@ -238,6 +240,7 @@ class Engine : public IEngine std::vector> m_broadcasts; std::unordered_map> m_broadcastMap; std::unordered_map> m_backdropBroadcastMap; + std::unordered_map m_broadcastSenders; // used for resolving broadcast promises std::vector> m_monitors; std::vector m_extensions; std::vector m_executableTargets; // sorted by layer (reverse order of execution) diff --git a/test/blocks/event_blocks_test.cpp b/test/blocks/event_blocks_test.cpp index 5a6db769..aeb31012 100644 --- a/test/blocks/event_blocks_test.cpp +++ b/test/blocks/event_blocks_test.cpp @@ -402,15 +402,15 @@ TEST_F(EventBlocksTest, BroadcastImpl) vm.setConstValues(constValues); EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 1, 4 }))); - EXPECT_CALL(m_engineMock, broadcast(1)); - EXPECT_CALL(m_engineMock, broadcast(4)); + EXPECT_CALL(m_engineMock, broadcast(1, &vm)); + EXPECT_CALL(m_engineMock, broadcast(4, &vm)); vm.setBytecode(bytecode1); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - EXPECT_CALL(m_engineMock, broadcast(2)).Times(1); + EXPECT_CALL(m_engineMock, broadcast(2, &vm)).Times(1); vm.setBytecode(bytecode2); vm.run(); @@ -432,11 +432,7 @@ TEST_F(EventBlocksTest, BroadcastAndWait) notBlock->setCompileFunction(&OperatorBlocks::compileNot); addObscuredInput(block2, "BROADCAST_INPUT", EventBlocks::BROADCAST_INPUT, notBlock); - EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 0, 3 }))); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcastByIndexAndWait)).Times(2).WillRepeatedly(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::checkBroadcastByIndex)).Times(2).WillRepeatedly(Return(1)); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcastAndWait)).WillOnce(Return(2)); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::checkBroadcast)).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcastAndWait)).Times(2).WillRepeatedly(Return(0)); compiler.init(); compiler.setBlock(block1); @@ -445,23 +441,16 @@ TEST_F(EventBlocksTest, BroadcastAndWait) EventBlocks::compileBroadcastAndWait(&compiler); compiler.end(); - ASSERT_EQ( - compiler.bytecode(), - std::vector( - { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_CONST, 2, vm::OP_EXEC, 1, vm::OP_CONST, 3, vm::OP_EXEC, 1, - vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 3, vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ 0, 3, 0, 3 })); + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "test" })); ASSERT_TRUE(compiler.variables().empty()); ASSERT_TRUE(compiler.lists().empty()); } TEST_F(EventBlocksTest, BroadcastAndWaitImpl) { - static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 1, vm::OP_HALT }; - static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 3, vm::OP_HALT }; - static BlockFunc functions[] = { &EventBlocks::broadcastAndWait, &EventBlocks::checkBroadcast, &EventBlocks::broadcastByIndexAndWait, &EventBlocks::checkBroadcastByIndex }; + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &EventBlocks::broadcastAndWait }; static Value constValues[] = { "test", 3 }; VirtualMachine vm(nullptr, &m_engineMock, nullptr); @@ -469,62 +458,25 @@ TEST_F(EventBlocksTest, BroadcastAndWaitImpl) vm.setConstValues(constValues); EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 1, 4 }))); - EXPECT_CALL(m_engineMock, broadcast(1)); - EXPECT_CALL(m_engineMock, broadcast(4)); + EXPECT_CALL(m_engineMock, broadcast(1, &vm)); + EXPECT_CALL(m_engineMock, broadcast(4, &vm)); - vm.setBytecode(bytecode1); + vm.setBytecode(bytecode); vm.run(); ASSERT_EQ(vm.registerCount(), 0); + ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 2, 3 }))); - EXPECT_CALL(m_engineMock, broadcastRunning(2)).WillOnce(Return(true)); - - vm.setBytecode(bytecode2); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(vm.atEnd(), false); - - EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 2, 3 }))); - EXPECT_CALL(m_engineMock, broadcastRunning(2)).WillOnce(Return(true)); - - vm.reset(); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(vm.atEnd(), false); - - EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 2, 3 }))); - EXPECT_CALL(m_engineMock, broadcastRunning(2)).WillOnce(Return(false)); - EXPECT_CALL(m_engineMock, broadcastRunning(3)).WillOnce(Return(false)); - - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(vm.atEnd(), true); - - EXPECT_CALL(m_engineMock, broadcast(3)).Times(1); - - vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); + ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, broadcastRunning(3)).WillOnce(Return(true)); - - vm.setBytecode(bytecode4); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 1); - ASSERT_EQ(vm.atEnd(), false); - - EXPECT_CALL(m_engineMock, broadcastRunning(3)).WillOnce(Return(false)); - + vm.resolvePromise(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(vm.atEnd(), true); + ASSERT_TRUE(vm.atEnd()); } TEST_F(EventBlocksTest, WhenBroadcastReceived) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 9e6c7b66..f17349cb 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1791,14 +1791,14 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); stage.setCostumeIndex(0); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); VirtualMachine vm(&target, &m_engineMock, nullptr); vm.setFunctions(functions); vm.setConstValues(constValues); // "backdrop2" - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.setBytecode(bytecode1); vm.run(); @@ -1806,8 +1806,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 1); // 0 - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.setBytecode(bytecode2); vm.run(); @@ -1816,8 +1815,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.setCostumeIndex(0); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1825,8 +1823,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 1); // 1 - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.setBytecode(bytecode3); vm.run(); @@ -1834,8 +1831,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 0); // 2 - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); stage.addCostume(std::make_shared("2", "b3", "svg")); stage.addCostume(std::make_shared("test", "b4", "svg")); stage.setCostumeIndex(0); @@ -1847,8 +1843,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 1); // "2" - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(0); vm.setBytecode(bytecode6); @@ -1858,8 +1853,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 2); // "3" - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(0); vm.setBytecode(bytecode7); @@ -1869,8 +1863,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 2); // NaN - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode8); @@ -1880,8 +1873,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 0); // Infinity - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode9); @@ -1891,8 +1883,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 0); // -Infinity - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode10); @@ -1902,8 +1893,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 0); // "" - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode11); @@ -1913,8 +1903,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 2); // " " - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode12); @@ -1924,24 +1913,21 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 2); // "next backdrop" - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); vm.setBytecode(bytecode13); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1950,8 +1936,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.addCostume(std::make_shared("next backdrop", "b5", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1959,16 +1944,14 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(stage.costumeIndex(), 4); // "previous backdrop" - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1977,8 +1960,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.setCostumeIndex(0); - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1987,8 +1969,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.addCostume(std::make_shared("previous backdrop", "b6", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -1999,8 +1980,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) RandomGeneratorMock rng; LooksBlocks::rng = &rng; - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(3)); stage.setCostumeIndex(0); vm.setBytecode(bytecode15); @@ -2009,8 +1989,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); - EXPECT_CALL(m_engineMock, stage()).Times(3).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast(), &vm)); EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(5)); vm.reset(); vm.run(); @@ -2020,8 +1999,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast(), &vm)); EXPECT_CALL(rng, randint).Times(0); vm.reset(); vm.run(); @@ -2048,43 +2026,37 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWait) compiler.init(); EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(1)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(2)); compiler.setBlock(block1); LooksBlocks::compileSwitchBackdropToAndWait(&compiler); EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::switchBackdropToAndWait)).WillOnce(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::backdropNumber)).WillOnce(Return(1)); - EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::checkBackdropScripts)).WillOnce(Return(2)); compiler.setBlock(block2); LooksBlocks::compileSwitchBackdropToAndWait(&compiler); compiler.end(); - ASSERT_EQ( - compiler.bytecode(), - std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT })); + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_HALT })); ASSERT_EQ(compiler.constValues(), std::vector({ "backdrop2" })); } TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) { - static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }; - static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait, &LooksBlocks::backdropNumber, &LooksBlocks::checkBackdropScripts }; + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode8[] = { vm::OP_START, vm::OP_CONST, 7, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode9[] = { vm::OP_START, vm::OP_CONST, 8, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode10[] = { vm::OP_START, vm::OP_CONST, 9, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode11[] = { vm::OP_START, vm::OP_CONST, 10, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode12[] = { vm::OP_START, vm::OP_CONST, 11, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait }; static Value constValues[] = { "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", "random backdrop" @@ -2096,42 +2068,37 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) stage.addCostume(std::make_shared("backdrop1", "b1", "svg")); stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); stage.setCostumeIndex(0); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); VirtualMachine vm(&target, &m_engineMock, nullptr); vm.setFunctions(functions); vm.setConstValues(constValues); // "backdrop2" - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.setBytecode(bytecode1); vm.run(); - ASSERT_EQ(vm.registerCount(), 1); + ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + vm.resolvePromise(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_TRUE(vm.atEnd()); // 0 - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.setBytecode(bytecode2); vm.run(); - ASSERT_EQ(vm.registerCount(), 1); + ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + vm.resolvePromise(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -2139,219 +2106,217 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) stage.setCostumeIndex(0); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); ASSERT_TRUE(vm.atEnd()); // 1 - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.setBytecode(bytecode3); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); ASSERT_TRUE(vm.atEnd()); // 2 - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); stage.addCostume(std::make_shared("2", "b3", "svg")); stage.addCostume(std::make_shared("test", "b4", "svg")); stage.setCostumeIndex(0); vm.setBytecode(bytecode4); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); // "2" - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(0); vm.setBytecode(bytecode6); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); // "3" - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(0); vm.setBytecode(bytecode7); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); // NaN - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode8); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); // Infinity - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode9); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); // -Infinity - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode10); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); // "" - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode11); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); // " " - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); stage.setCostumeIndex(2); vm.setBytecode(bytecode12); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); // "next backdrop" - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); + vm.resolvePromise(); vm.setBytecode(bytecode13); vm.run(); - ASSERT_EQ(vm.registerCount(), 1); + ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(false)); + vm.run(); + vm.resolvePromise(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_TRUE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(0)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(1)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); stage.addCostume(std::make_shared("next backdrop", "b5", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(4)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 4); // "previous backdrop" - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); + vm.resolvePromise(); vm.setBytecode(bytecode14); vm.run(); - ASSERT_EQ(vm.registerCount(), 1); + ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); ASSERT_FALSE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).WillOnce(Return(&stage)); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(false)); stage.setCostumeIndex(2); // checkBackdropScripts() should still check index 3 even after changing the backdrop vm.run(); + vm.resolvePromise(); + vm.run(); stage.setCostumeIndex(3); ASSERT_EQ(vm.registerCount(), 0); ASSERT_TRUE(vm.atEnd()); - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(2)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); stage.setCostumeIndex(0); - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(4)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(4)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 4); stage.addCostume(std::make_shared("previous backdrop", "b6", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast(), &vm)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); @@ -2360,35 +2325,36 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) RandomGeneratorMock rng; LooksBlocks::rng = &rng; - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(3)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(3)->broadcast(), &vm)); EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(3)); stage.setCostumeIndex(0); + vm.resolvePromise(); vm.setBytecode(bytecode15); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 3); - EXPECT_CALL(m_engineMock, stage()).Times(5).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(5)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(5)->broadcast(), &vm)); EXPECT_CALL(rng, randint(0, 5)).WillOnce(Return(5)); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 5); stage.addCostume(std::make_shared("random backdrop", "b7", "svg")); - EXPECT_CALL(m_engineMock, stage()).Times(4).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast())); - EXPECT_CALL(m_engineMock, broadcastByPtrRunning(stage.costumeAt(6)->broadcast())).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(6)->broadcast(), &vm)); EXPECT_CALL(rng, randint).Times(0); vm.reset(); vm.run(); + vm.resolvePromise(); + vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 6); @@ -2425,28 +2391,26 @@ TEST_F(LooksBlocksTest, NextBackdropImpl) stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); stage.setCostumeIndex(0); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); VirtualMachine vm(&target, &m_engineMock, nullptr); vm.setBytecode(bytecode); vm.setFunctions(functions); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 2); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -2466,28 +2430,26 @@ TEST_F(LooksBlocksTest, PreviousBackdrop) stage.addCostume(std::make_shared("backdrop2", "b2", "svg")); stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); stage.setCostumeIndex(2); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); VirtualMachine vm(&target, &m_engineMock, nullptr); vm.setBytecode(bytecode); vm.setFunctions(functions); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 1); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(0)->broadcast(), &vm)); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(stage.costumeIndex(), 0); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -2503,6 +2465,7 @@ TEST_F(LooksBlocksTest, RandomBackdrop) Target target; Stage stage; + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); VirtualMachine vm(&target, &m_engineMock, nullptr); vm.setBytecode(bytecode); @@ -2512,7 +2475,6 @@ TEST_F(LooksBlocksTest, RandomBackdrop) LooksBlocks::rng = &rng; EXPECT_CALL(rng, randint).Times(0); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); vm.run(); ASSERT_EQ(vm.registerCount(), 0); @@ -2522,8 +2484,7 @@ TEST_F(LooksBlocksTest, RandomBackdrop) stage.addCostume(std::make_shared("backdrop3", "b3", "svg")); EXPECT_CALL(rng, randint(0, 2)).WillOnce(Return(1)); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(1)->broadcast(), &vm)); vm.reset(); vm.run(); @@ -2533,8 +2494,7 @@ TEST_F(LooksBlocksTest, RandomBackdrop) stage.addCostume(std::make_shared("backdrop4", "b4", "svg")); EXPECT_CALL(rng, randint(0, 3)).WillOnce(Return(2)); - EXPECT_CALL(m_engineMock, stage()).Times(2).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast())); + EXPECT_CALL(m_engineMock, startBackdropScripts(stage.costumeAt(2)->broadcast(), &vm)); vm.reset(); vm.run(); diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 25d601ab..ca503321 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -2254,14 +2254,15 @@ TEST(EngineTest, StopBeforeStarting) Stage *stage = engine->stage(); ASSERT_TRUE(stage); - engine->broadcast(0); + VirtualMachine fakeThread; + engine->broadcast(0, &fakeThread); engine->step(); ASSERT_VAR(stage, "test"); ASSERT_FALSE(GET_VAR(stage, "test")->value().toBool()); GET_VAR(stage, "test")->setValue(true); - engine->broadcast(0); + engine->broadcast(0, &fakeThread); engine->start(); engine->step(); ASSERT_VAR(stage, "test"); @@ -2366,3 +2367,22 @@ TEST(EngineTest, DuplicateVariableOrListIDs) ASSERT_VAR(stage, "passed"); ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); } + +TEST(EngineTest, BroadcastStopsWaitBlocks) +{ + // Regtest for #563 + Project p("regtest_projects/563_broadcast_stops_wait_blocks.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "broadcast_passed"); + ASSERT_TRUE(GET_VAR(stage, "broadcast_passed")->value().toBool()); + ASSERT_VAR(stage, "backdrop_passed"); + ASSERT_TRUE(GET_VAR(stage, "backdrop_passed")->value().toBool()); +} diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index cf7641ff..03aca227 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -17,9 +17,9 @@ class EngineMock : public IEngine MOCK_METHOD(void, start, (), (override)); MOCK_METHOD(void, stop, (), (override)); MOCK_METHOD(VirtualMachine *, startScript, (std::shared_ptr, Target *), (override)); - MOCK_METHOD(void, broadcast, (int), (override)); - MOCK_METHOD(void, broadcastByPtr, (Broadcast *), (override)); - MOCK_METHOD(void, startBackdropScripts, (Broadcast *), (override)); + MOCK_METHOD(void, broadcast, (int, VirtualMachine *), (override)); + MOCK_METHOD(void, broadcastByPtr, (Broadcast *, VirtualMachine *), (override)); + MOCK_METHOD(void, startBackdropScripts, (Broadcast *, VirtualMachine *), (override)); MOCK_METHOD(void, stopScript, (VirtualMachine *), (override)); MOCK_METHOD(void, stopTarget, (Target *, VirtualMachine *), (override)); MOCK_METHOD(void, initClone, (std::shared_ptr), (override)); diff --git a/test/regtest_projects/563_broadcast_stops_wait_blocks.sb3 b/test/regtest_projects/563_broadcast_stops_wait_blocks.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..6c850f7b96f4d48814ddac934f01cbce4ba067a4 GIT binary patch literal 1781 zcma)7X*3%M7Y$h?Vn*yb+Kjf;zKf+rtEvDv+1Y90JesmP3G%?-mR~62m0Zxk-W?E;S>1c9^A+1ZsXI;azqLYwA60UvVp}{j z5YFcaQ?=4|laSvkJ?c|M8xg&G0$#)Qsoh(1*Fy#`wU}H5OJ9h$QqFd-ubibtCr0li zvUazZn4L|w+olQ2==X_B#X=vzgocN>7ku@ryc1M%JMDS}DED!hBQzoOYWxYO^tiNpS*>_0_ zH2=A#+n2DuA8lj0xRN@I3`MTmf?VhnmK3|;cFNoQ+dNeN#u`M*qtHr{fHupkEmQY4 z^aIjJM~=T#7e{g3WlsK7cNIj_iiLfKAAF6-*etUQ9akKCgsFjX(-ft-p9J2H*(Q;T z{ki`35;6e~8fpd{?$h&z>}(ZUah`p4Y526mKBD!}`DUA3mlxA7!d=p3XyvpzlbhH> zYLuTbIcN+_8^}`4Q~b{@O~1(F_X2odII;~3ek#L3x$DpD^FwD(of>dgz)=rEKZ?Y* zlKpNi6hO}&0Nt<#0(M#9KIQ2?x{D0)xJ84b_>4REmUU;iVl$!eH!aVSavU<+OYe?DmMkoEKKRr%#Hc zsk|&_=~(ux1?5~rt@%Rot}b{FCb}{;ep!X6IddZ5cjwulu5%IWo@rSen`Hq`z=zEQ zg{X8WBtf8@Rl(x3bU-+?i@K~|rf*S6>tOD_5buJ)O1LVzrjTmZmer#| z^RcjC^zaa+R_U1f-Mr|54i5n~*4=~Z`*P38oiNgP@c{p_&mC{sr)Z;G*@}_6Cqw)Y zmpXGCAM+~yjcr6elCFu^OoPB|>2iH&eAO3T$q}SGj2SKa7#M>P5Q2* zl+Zz!9Ge3g6M>2(iHqx)RdbUBQh#PV6g)rggZYR1W)CC8sQa$V*oWFa0s@VVP)4SK zhA0!GpunIYe`8}~LsOz5F#tsjv@rG4C&dLvpq;_}F#bR`jQAw1_q!>=FO3;3qu6{bk8lS0TRmk0dsP}%?NZ}s Date: Sat, 7 Sep 2024 17:24:03 +0200 Subject: [PATCH 16/72] Drop broadcast block optimization --- src/blocks/eventblocks.cpp | 21 ++------------------- src/blocks/eventblocks.h | 1 - test/blocks/event_blocks_test.cpp | 18 ++++-------------- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 25631e54..9945e337 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -96,19 +96,8 @@ void EventBlocks::compileWhenStageClicked(Compiler *compiler) void EventBlocks::compileBroadcast(Compiler *compiler) { - auto input = compiler->input(BROADCAST_INPUT); - - if (input->type() != Input::Type::ObscuredShadow) { - std::vector broadcasts = compiler->engine()->findBroadcasts(input->primaryValue()->value().toString()); - - for (int index : broadcasts) { - compiler->addConstValue(index); - compiler->addFunctionCall(&broadcastByIndex); - } - } else { - compiler->addInput(input); - compiler->addFunctionCall(&broadcast); - } + compiler->addInput(BROADCAST_INPUT); + compiler->addFunctionCall(&broadcast); } void EventBlocks::compileBroadcastAndWait(Compiler *compiler) @@ -197,12 +186,6 @@ unsigned int EventBlocks::broadcast(VirtualMachine *vm) return 1; } -unsigned int EventBlocks::broadcastByIndex(VirtualMachine *vm) -{ - vm->engine()->broadcast(vm->getInput(0, 1)->toLong(), vm); - return 1; -} - unsigned int EventBlocks::broadcastAndWait(VirtualMachine *vm) { std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index 76547109..f09b3f2b 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -56,7 +56,6 @@ class EventBlocks : public IBlockSection static unsigned int whenTouchingObjectPredicate(VirtualMachine *vm); static unsigned int broadcast(VirtualMachine *vm); - static unsigned int broadcastByIndex(VirtualMachine *vm); static unsigned int broadcastAndWait(VirtualMachine *vm); static unsigned int whenLoudnessGreaterThanPredicate(VirtualMachine *vm); diff --git a/test/blocks/event_blocks_test.cpp b/test/blocks/event_blocks_test.cpp index aeb31012..a03b4977 100644 --- a/test/blocks/event_blocks_test.cpp +++ b/test/blocks/event_blocks_test.cpp @@ -373,9 +373,7 @@ TEST_F(EventBlocksTest, Broadcast) notBlock->setCompileFunction(&OperatorBlocks::compileNot); addObscuredInput(block2, "BROADCAST_INPUT", EventBlocks::BROADCAST_INPUT, notBlock); - EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 0, 3 }))); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcastByIndex)).Times(2).WillRepeatedly(Return(0)); - EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcast)).WillOnce(Return(1)); + EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::broadcast)).Times(2).WillRepeatedly(Return(0)); compiler.init(); compiler.setBlock(block1); @@ -384,8 +382,8 @@ TEST_F(EventBlocksTest, Broadcast) EventBlocks::compileBroadcast(&compiler); compiler.end(); - ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 1, vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ 0, 3 })); + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "test" })); ASSERT_TRUE(compiler.variables().empty()); ASSERT_TRUE(compiler.lists().empty()); } @@ -393,8 +391,7 @@ TEST_F(EventBlocksTest, Broadcast) TEST_F(EventBlocksTest, BroadcastImpl) { static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; - static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 1, vm::OP_HALT }; - static BlockFunc functions[] = { &EventBlocks::broadcast, &EventBlocks::broadcastByIndex }; + static BlockFunc functions[] = { &EventBlocks::broadcast }; static Value constValues[] = { "test", 2 }; VirtualMachine vm(nullptr, &m_engineMock, nullptr); @@ -409,13 +406,6 @@ TEST_F(EventBlocksTest, BroadcastImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - - EXPECT_CALL(m_engineMock, broadcast(2, &vm)).Times(1); - - vm.setBytecode(bytecode2); - vm.run(); - - ASSERT_EQ(vm.registerCount(), 0); } TEST_F(EventBlocksTest, BroadcastAndWait) From 537ff52a7500d17d07df7ea31c7023358824e344 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 7 Sep 2024 18:00:21 +0200 Subject: [PATCH 17/72] Remove broadcast running methods from IEngine --- include/scratchcpp/iengine.h | 6 ---- src/engine/internal/engine.cpp | 51 ---------------------------------- src/engine/internal/engine.h | 3 -- test/mocks/enginemock.h | 3 -- 4 files changed, 63 deletions(-) diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index e099e446..3cdd5295 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -207,12 +207,6 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Toggles sprite fencing. */ virtual void setSpriteFencingEnabled(bool enable) = 0; - /*! Returns true if there are any running script of the broadcast with the given index. */ - virtual bool broadcastRunning(unsigned int index) = 0; - - /*! Returns true if there are any running script of the given broadcast. */ - virtual bool broadcastByPtrRunning(Broadcast *broadcast) = 0; - /*! * Call this from a block implementation to force a redraw (screen refresh). * \note This has no effect in "run without screen refresh" custom blocks. diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 050a28cf..098ed2a3 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -960,57 +960,6 @@ void Engine::setSpriteFencingEnabled(bool enable) m_spriteFencingEnabled = enable; } -bool Engine::broadcastRunning(unsigned int index) -{ - if (index < 0 || index >= m_broadcasts.size()) - return false; - - return broadcastByPtrRunning(m_broadcasts[index].get()); -} - -bool Engine::broadcastByPtrRunning(Broadcast *broadcast) -{ - if (broadcast->isBackdropBroadcast()) { - // This broadcast belongs to a backdrop - assert(m_broadcastMap.find(broadcast) == m_broadcastMap.cend()); - - for (auto thread : m_threads) { - if (!thread->atEnd()) { - Script *script = thread->script(); - auto topBlock = script->topBlock(); - - const auto &scripts = m_backdropChangeHats[script->target()]; - auto scriptIt = std::find(scripts.begin(), scripts.end(), script); - auto scriptFieldMapIt = m_scriptHatFields.find(script); - - if (scriptFieldMapIt != m_scriptHatFields.cend()) { - const auto &fieldMap = scriptFieldMapIt->second; - auto fieldIt = fieldMap.find(HatField::Backdrop); - assert(fieldIt != fieldMap.cend()); - assert(topBlock->findFieldById(fieldIt->second)); - - if ((scriptIt != scripts.end()) && (topBlock->findFieldById(fieldIt->second)->value().toString() == broadcast->name())) - return true; - } - } - } - } else { - // This is a regular broadcast - assert(m_broadcastMap.find(broadcast) != m_broadcastMap.cend()); - const auto &scripts = m_broadcastMap[broadcast]; - - for (auto thread : m_threads) { - if (!thread->atEnd()) { - auto it = std::find_if(scripts.begin(), scripts.end(), [thread](Script *script) { return thread->script() == script; }); - - if (it != scripts.end()) - return true; - } - } - } - return false; -} - void Engine::requestRedraw() { m_redrawRequested = true; diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 099faa96..6ffee1da 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -98,9 +98,6 @@ class Engine : public IEngine bool spriteFencingEnabled() const override; void setSpriteFencingEnabled(bool enable) override; - bool broadcastRunning(unsigned int index) override; - bool broadcastByPtrRunning(Broadcast *broadcast) override; - void requestRedraw() override; ITimer *timer() const override; diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index 03aca227..df3e8f68 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -80,9 +80,6 @@ class EngineMock : public IEngine MOCK_METHOD(bool, spriteFencingEnabled, (), (const, override)); MOCK_METHOD(void, setSpriteFencingEnabled, (bool), (override)); - MOCK_METHOD(bool, broadcastRunning, (unsigned int), (override)); - MOCK_METHOD(bool, broadcastByPtrRunning, (Broadcast *), (override)); - MOCK_METHOD(void, requestRedraw, (), (override)); MOCK_METHOD(ITimer *, timer, (), (const, override)); From fea9558e5bed51d0f5c3ed550caba3bea784b16a Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:29:09 +0200 Subject: [PATCH 18/72] Add support for local var monitors used by clones Resolves: #423 --- src/blocks/variableblocks.cpp | 21 ++++++++-- test/blocks/variable_blocks_test.cpp | 61 +++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/blocks/variableblocks.cpp b/src/blocks/variableblocks.cpp index 668bd56b..7bc4b82f 100644 --- a/src/blocks/variableblocks.cpp +++ b/src/blocks/variableblocks.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -108,8 +109,14 @@ unsigned int VariableBlocks::showGlobalVariable(VirtualMachine *vm) unsigned int VariableBlocks::showVariable(VirtualMachine *vm) { if (Target *target = vm->target()) { - int index = target->findVariableById(vm->getInput(0, 1)->toString()); - setVarVisible(target->variableAt(index), true); + if (!target->isStage() && static_cast(target)->isClone()) { + Sprite *sprite = static_cast(target)->cloneSprite(); // use clone root variable + int index = sprite->findVariableById(vm->getInput(0, 1)->toString()); + setVarVisible(sprite->variableAt(index), true); + } else { + int index = target->findVariableById(vm->getInput(0, 1)->toString()); + setVarVisible(target->variableAt(index), true); + } } return 1; @@ -128,8 +135,14 @@ unsigned int VariableBlocks::hideGlobalVariable(VirtualMachine *vm) unsigned int VariableBlocks::hideVariable(VirtualMachine *vm) { if (Target *target = vm->target()) { - int index = target->findVariableById(vm->getInput(0, 1)->toString()); - setVarVisible(target->variableAt(index), false); + if (!target->isStage() && static_cast(target)->isClone()) { + Sprite *sprite = static_cast(target)->cloneSprite(); // use clone root variable + int index = sprite->findVariableById(vm->getInput(0, 1)->toString()); + setVarVisible(sprite->variableAt(index), false); + } else { + int index = target->findVariableById(vm->getInput(0, 1)->toString()); + setVarVisible(target->variableAt(index), false); + } } return 1; diff --git a/test/blocks/variable_blocks_test.cpp b/test/blocks/variable_blocks_test.cpp index 079397ff..f772476a 100644 --- a/test/blocks/variable_blocks_test.cpp +++ b/test/blocks/variable_blocks_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -271,8 +272,12 @@ TEST_F(VariableBlocksTest, ShowVariableImpl) Stage stage; stage.addVariable(var1); - Target target; - target.addVariable(var2); + Sprite sprite; + sprite.addVariable(var2); + + Engine fakeEngine; + sprite.setEngine(&fakeEngine); + auto clone = sprite.clone(); // Global VirtualMachine vm1(&stage, &m_engineMock, nullptr); @@ -299,7 +304,7 @@ TEST_F(VariableBlocksTest, ShowVariableImpl) monitor1.setVisible(false); // Local - VirtualMachine vm2(&target, &m_engineMock, nullptr); + VirtualMachine vm2(&sprite, &m_engineMock, nullptr); vm2.setBytecode(bytecode3); vm2.setFunctions(functions); vm2.setConstValues(constValues); @@ -316,6 +321,26 @@ TEST_F(VariableBlocksTest, ShowVariableImpl) ASSERT_EQ(vm2.registerCount(), 0); ASSERT_FALSE(monitor1.visible()); ASSERT_TRUE(monitor2.visible()); + + // Local - clone + monitor2.setVisible(false); + VirtualMachine vm3(clone.get(), &m_engineMock, nullptr); + vm3.setBytecode(bytecode3); + vm3.setFunctions(functions); + vm3.setConstValues(constValues); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_FALSE(monitor1.visible()); + ASSERT_FALSE(monitor2.visible()); + + vm3.reset(); + vm3.setBytecode(bytecode4); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_FALSE(monitor1.visible()); + ASSERT_TRUE(monitor2.visible()); } TEST_F(VariableBlocksTest, HideVariable) @@ -374,8 +399,12 @@ TEST_F(VariableBlocksTest, HideVariableImpl) Stage stage; stage.addVariable(var1); - Target target; - target.addVariable(var2); + Sprite sprite; + sprite.addVariable(var2); + + Engine fakeEngine; + sprite.setEngine(&fakeEngine); + auto clone = sprite.clone(); // Global VirtualMachine vm1(&stage, &m_engineMock, nullptr); @@ -402,7 +431,7 @@ TEST_F(VariableBlocksTest, HideVariableImpl) monitor1.setVisible(true); // Local - VirtualMachine vm2(&target, &m_engineMock, nullptr); + VirtualMachine vm2(&sprite, &m_engineMock, nullptr); vm2.setBytecode(bytecode3); vm2.setFunctions(functions); vm2.setConstValues(constValues); @@ -419,4 +448,24 @@ TEST_F(VariableBlocksTest, HideVariableImpl) ASSERT_EQ(vm2.registerCount(), 0); ASSERT_TRUE(monitor1.visible()); ASSERT_FALSE(monitor2.visible()); + + // Local - clone + monitor2.setVisible(true); + VirtualMachine vm3(clone.get(), &m_engineMock, nullptr); + vm3.setBytecode(bytecode3); + vm3.setFunctions(functions); + vm3.setConstValues(constValues); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_TRUE(monitor1.visible()); + ASSERT_TRUE(monitor2.visible()); + + vm3.reset(); + vm3.setBytecode(bytecode4); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_TRUE(monitor1.visible()); + ASSERT_FALSE(monitor2.visible()); } From 400c1f22e3a3a4a320b5d21d7a300bdd64ebbb2b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:36:23 +0200 Subject: [PATCH 19/72] Add support for local list monitors used by clones --- src/blocks/listblocks.cpp | 21 ++++++++--- test/blocks/list_blocks_test.cpp | 61 ++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/blocks/listblocks.cpp b/src/blocks/listblocks.cpp index 03705deb..cabe63cb 100644 --- a/src/blocks/listblocks.cpp +++ b/src/blocks/listblocks.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -162,8 +163,14 @@ unsigned int ListBlocks::showGlobalList(VirtualMachine *vm) unsigned int ListBlocks::showList(VirtualMachine *vm) { if (Target *target = vm->target()) { - int index = target->findListById(vm->getInput(0, 1)->toString()); - setListVisible(target->listAt(index), true); + if (!target->isStage() && static_cast(target)->isClone()) { + Sprite *sprite = static_cast(target)->cloneSprite(); // use clone root list + int index = sprite->findListById(vm->getInput(0, 1)->toString()); + setListVisible(sprite->listAt(index), true); + } else { + int index = target->findListById(vm->getInput(0, 1)->toString()); + setListVisible(target->listAt(index), true); + } } return 1; @@ -182,8 +189,14 @@ unsigned int ListBlocks::hideGlobalList(VirtualMachine *vm) unsigned int ListBlocks::hideList(VirtualMachine *vm) { if (Target *target = vm->target()) { - int index = target->findListById(vm->getInput(0, 1)->toString()); - setListVisible(target->listAt(index), false); + if (!target->isStage() && static_cast(target)->isClone()) { + Sprite *sprite = static_cast(target)->cloneSprite(); // use clone root list + int index = sprite->findListById(vm->getInput(0, 1)->toString()); + setListVisible(sprite->listAt(index), false); + } else { + int index = target->findListById(vm->getInput(0, 1)->toString()); + setListVisible(target->listAt(index), false); + } } return 1; diff --git a/test/blocks/list_blocks_test.cpp b/test/blocks/list_blocks_test.cpp index 40225cbc..7c02be84 100644 --- a/test/blocks/list_blocks_test.cpp +++ b/test/blocks/list_blocks_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -498,8 +499,12 @@ TEST_F(ListBlocksTest, ShowListImpl) Stage stage; stage.addList(list1); - Target target; - target.addList(list2); + Sprite sprite; + sprite.addList(list2); + + Engine fakeEngine; + sprite.setEngine(&fakeEngine); + auto clone = sprite.clone(); // Global VirtualMachine vm1(&stage, &m_engineMock, nullptr); @@ -526,7 +531,7 @@ TEST_F(ListBlocksTest, ShowListImpl) monitor1.setVisible(false); // Local - VirtualMachine vm2(&target, &m_engineMock, nullptr); + VirtualMachine vm2(&sprite, &m_engineMock, nullptr); vm2.setBytecode(bytecode3); vm2.setFunctions(functions); vm2.setConstValues(constValues); @@ -543,6 +548,26 @@ TEST_F(ListBlocksTest, ShowListImpl) ASSERT_EQ(vm2.registerCount(), 0); ASSERT_FALSE(monitor1.visible()); ASSERT_TRUE(monitor2.visible()); + + // Local - clone + monitor2.setVisible(false); + VirtualMachine vm3(clone.get(), &m_engineMock, nullptr); + vm3.setBytecode(bytecode3); + vm3.setFunctions(functions); + vm3.setConstValues(constValues); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_FALSE(monitor1.visible()); + ASSERT_FALSE(monitor2.visible()); + + vm3.reset(); + vm3.setBytecode(bytecode4); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_FALSE(monitor1.visible()); + ASSERT_TRUE(monitor2.visible()); } TEST_F(ListBlocksTest, HideList) @@ -601,8 +626,12 @@ TEST_F(ListBlocksTest, HideListImpl) Stage stage; stage.addList(list1); - Target target; - target.addList(list2); + Sprite sprite; + sprite.addList(list2); + + Engine fakeEngine; + sprite.setEngine(&fakeEngine); + auto clone = sprite.clone(); // Global VirtualMachine vm1(&stage, &m_engineMock, nullptr); @@ -629,7 +658,7 @@ TEST_F(ListBlocksTest, HideListImpl) monitor1.setVisible(true); // Local - VirtualMachine vm2(&target, &m_engineMock, nullptr); + VirtualMachine vm2(&sprite, &m_engineMock, nullptr); vm2.setBytecode(bytecode3); vm2.setFunctions(functions); vm2.setConstValues(constValues); @@ -646,4 +675,24 @@ TEST_F(ListBlocksTest, HideListImpl) ASSERT_EQ(vm2.registerCount(), 0); ASSERT_TRUE(monitor1.visible()); ASSERT_FALSE(monitor2.visible()); + + // Local - clone + monitor2.setVisible(true); + VirtualMachine vm3(clone.get(), &m_engineMock, nullptr); + vm3.setBytecode(bytecode3); + vm3.setFunctions(functions); + vm3.setConstValues(constValues); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_TRUE(monitor1.visible()); + ASSERT_TRUE(monitor2.visible()); + + vm3.reset(); + vm3.setBytecode(bytecode4); + vm3.run(); + + ASSERT_EQ(vm3.registerCount(), 0); + ASSERT_TRUE(monitor1.visible()); + ASSERT_FALSE(monitor2.visible()); } From 6f177e63e0e6855579aa58bc0c2473fd2ed91dcd Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:56:26 +0200 Subject: [PATCH 20/72] Move value data out of Value class * move to ValueData struct that can be used from LLVM IR --- include/scratchcpp/value.h | 654 ++++++++++++++-------------- src/scratch/inputvalue.cpp | 8 +- test/scratch_classes/value_test.cpp | 108 ++--- 3 files changed, 391 insertions(+), 379 deletions(-) diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index 94baeb55..169fba15 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -16,6 +16,34 @@ namespace libscratchcpp { +enum class ValueType +{ + Integer = 0, + Double = 1, + Bool = 2, + String = 3, + Infinity = -1, + NegativeInfinity = -2, + NaN = -3 +}; + +extern "C" +{ + /*! \brief The ValueData struct holds the data of Value. It's used in compiled Scratch code for better performance. */ + struct ValueData + { + union + { + long intValue; + double doubleValue; + bool boolValue; + std::string *stringValue; + }; + + ValueType type; + }; +} + /*! \brief The Value class represents a Scratch value. */ class LIBSCRATCHCPP_EXPORT Value { @@ -27,85 +55,77 @@ class LIBSCRATCHCPP_EXPORT Value NaN }; - enum class Type - { - Integer = 0, - Double = 1, - Bool = 2, - String = 3, - Infinity = -1, - NegativeInfinity = -2, - NaN = -3 - }; - /*! Constructs a number Value. */ - Value(float numberValue) : - m_type(Type::Double) + Value(float numberValue) { if (isInf(numberValue)) - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (isNegativeInf(numberValue)) - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (std::isnan(numberValue)) - m_type = Type::NaN; - else - m_doubleValue = floatToDouble(numberValue); + m_data.type = ValueType::NaN; + else { + m_data.type = ValueType::Double; + m_data.doubleValue = floatToDouble(numberValue); + } } /*! Constructs a number Value. */ - Value(double numberValue) : - m_type(Type::Double) + Value(double numberValue) { if (isInf(numberValue)) - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (isNegativeInf(numberValue)) - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (std::isnan(numberValue)) - m_type = Type::NaN; - else - m_doubleValue = numberValue; + m_data.type = ValueType::NaN; + else { + m_data.type = ValueType::Double; + m_data.doubleValue = numberValue; + } } /*! Constructs a number Value. */ - Value(int numberValue = 0) : - m_type(Type::Integer) + Value(int numberValue = 0) { - m_intValue = numberValue; + m_data.type = ValueType::Integer; + m_data.intValue = numberValue; } /*! Constructs a number Value. */ - Value(size_t numberValue) : - m_type(Type::Integer) + Value(size_t numberValue) { - m_intValue = numberValue; + m_data.type = ValueType::Integer; + m_data.intValue = numberValue; } /*! Constructs a number Value. */ - Value(long numberValue) : - m_type(Type::Integer) + Value(long numberValue) { - m_intValue = numberValue; + m_data.type = ValueType::Integer; + m_data.intValue = numberValue; } /*! Constructs a boolean Value. */ - Value(bool boolValue) : - m_type(Type::Bool) + Value(bool boolValue) { - m_boolValue = boolValue; + m_data.type = ValueType::Bool; + m_data.boolValue = boolValue; } /*! Constructs a string Value. */ - Value(const std::string &stringValue) : - m_type(Type::String) + Value(const std::string &stringValue) { if (stringValue == "Infinity") - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (stringValue == "-Infinity") - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (stringValue == "NaN") - m_type = Type::NaN; - else - new (&m_stringValue) std::string(stringValue); + m_data.type = ValueType::NaN; + else { + m_data.type = ValueType::String; + m_data.stringValue = new std::string(stringValue); + } } /*! Constructs a string Value. */ @@ -118,33 +138,33 @@ class LIBSCRATCHCPP_EXPORT Value Value(SpecialValue specialValue) { if (specialValue == SpecialValue::Infinity) - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (specialValue == SpecialValue::NegativeInfinity) - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (specialValue == SpecialValue::NaN) - m_type = Type::NaN; + m_data.type = ValueType::NaN; else { - m_type = Type::Integer; - m_intValue = 0; + m_data.type = ValueType::Integer; + m_data.intValue = 0; } } Value(const Value &v) { - m_type = v.m_type; + m_data.type = v.m_data.type; - switch (m_type) { - case Type::Integer: - m_intValue = v.m_intValue; + switch (m_data.type) { + case ValueType::Integer: + m_data.intValue = v.m_data.intValue; break; - case Type::Double: - m_doubleValue = v.m_doubleValue; + case ValueType::Double: + m_data.doubleValue = v.m_data.doubleValue; break; - case Type::Bool: - m_boolValue = v.m_boolValue; + case ValueType::Bool: + m_data.boolValue = v.m_data.boolValue; break; - case Type::String: - new (&m_stringValue) std::string(v.m_stringValue); + case ValueType::String: + m_data.stringValue = new std::string(*v.m_data.stringValue); break; default: break; @@ -153,25 +173,25 @@ class LIBSCRATCHCPP_EXPORT Value ~Value() { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; } /*! Returns the type of the value. */ - Type type() const { return m_type; } + ValueType type() const { return m_data.type; } /*! Returns true if the value is infinity. */ bool isInfinity() const { - switch (m_type) { - case Type::Infinity: + switch (m_data.type) { + case ValueType::Infinity: return true; - case Type::Integer: - return isInf(m_intValue); - case Type::Double: - return isInf(m_doubleValue); - case Type::String: - return m_stringValue == "Infinity"; + case ValueType::Integer: + return isInf(m_data.intValue); + case ValueType::Double: + return isInf(m_data.doubleValue); + case ValueType::String: + return *m_data.stringValue == "Infinity"; default: return false; } @@ -180,15 +200,15 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns true if the value is negative infinity. */ bool isNegativeInfinity() const { - switch (m_type) { - case Type::NegativeInfinity: + switch (m_data.type) { + case ValueType::NegativeInfinity: return true; - case Type::Integer: - return isNegativeInf(-m_intValue); - case Type::Double: - return isNegativeInf(-m_doubleValue); - case Type::String: - return m_stringValue == "-Infinity"; + case ValueType::Integer: + return isNegativeInf(-m_data.intValue); + case ValueType::Double: + return isNegativeInf(-m_data.doubleValue); + case ValueType::String: + return *m_data.stringValue == "-Infinity"; default: return false; } @@ -197,21 +217,21 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns true if the value is NaN (Not a Number). */ bool isNaN() const { - switch (m_type) { - case Type::NaN: + switch (m_data.type) { + case ValueType::NaN: return true; - case Type::Double: - assert(!std::isnan(m_doubleValue)); - return std::isnan(m_doubleValue); - case Type::String: - return m_stringValue == "NaN"; + case ValueType::Double: + assert(!std::isnan(m_data.doubleValue)); + return std::isnan(m_data.doubleValue); + case ValueType::String: + return *m_data.stringValue == "NaN"; default: return false; } } /*! Returns true if the value is a number. */ - bool isNumber() const { return m_type == Type::Integer || m_type == Type::Double; } + bool isNumber() const { return m_data.type == ValueType::Integer || m_data.type == ValueType::Double; } /*! Returns true if the value is a number or can be converted to a number. */ bool isValidNumber() const @@ -222,15 +242,15 @@ class LIBSCRATCHCPP_EXPORT Value if (isInfinity() || isNegativeInfinity()) return true; - assert(m_type != Type::Infinity && m_type != Type::NegativeInfinity); + assert(m_data.type != ValueType::Infinity && m_data.type != ValueType::NegativeInfinity); - switch (m_type) { - case Type::Integer: - case Type::Double: - case Type::Bool: + switch (m_data.type) { + case ValueType::Integer: + case ValueType::Double: + case ValueType::Bool: return true; - case Type::String: - return m_stringValue.empty() || checkString(m_stringValue) > 0; + case ValueType::String: + return m_data.stringValue->empty() || checkString(*m_data.stringValue) > 0; default: return false; } @@ -240,30 +260,30 @@ class LIBSCRATCHCPP_EXPORT Value bool isInt() const { // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L157-L181 - switch (m_type) { - case Type::Integer: - case Type::Bool: - case Type::Infinity: - case Type::NegativeInfinity: - case Type::NaN: + switch (m_data.type) { + case ValueType::Integer: + case ValueType::Bool: + case ValueType::Infinity: + case ValueType::NegativeInfinity: + case ValueType::NaN: return true; - case Type::Double: { + case ValueType::Double: { double intpart; - std::modf(m_doubleValue, &intpart); - return m_doubleValue == intpart; + std::modf(m_data.doubleValue, &intpart); + return m_data.doubleValue == intpart; } - case Type::String: - return m_stringValue.find('.') == std::string::npos; + case ValueType::String: + return m_data.stringValue->find('.') == std::string::npos; } return false; } /*! Returns true if the value is a boolean. */ - bool isBool() const { return m_type == Type::Bool; } + bool isBool() const { return m_data.type == ValueType::Bool; } /*! Returns true if the value is a string. */ - bool isString() const { return m_type == Type::String; } + bool isString() const { return m_data.type == ValueType::String; } /*! Returns the int representation of the value. */ int toInt() const { return toLong(); } @@ -271,18 +291,18 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns the long representation of the value. */ long toLong() const { - if (static_cast(m_type) < 0) + if (static_cast(m_data.type) < 0) return 0; else { - switch (m_type) { - case Type::Integer: - return m_intValue; - case Type::Double: - return m_doubleValue; - case Type::Bool: - return m_boolValue; - case Type::String: - return stringToLong(m_stringValue); + switch (m_data.type) { + case ValueType::Integer: + return m_data.intValue; + case ValueType::Double: + return m_data.doubleValue; + case ValueType::Bool: + return m_data.boolValue; + case ValueType::String: + return stringToLong(*m_data.stringValue); default: return 0; } @@ -292,25 +312,25 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns the double representation of the value. */ double toDouble() const { - if (static_cast(m_type) < 0) { - switch (m_type) { - case Type::Infinity: + if (static_cast(m_data.type) < 0) { + switch (m_data.type) { + case ValueType::Infinity: return std::numeric_limits::infinity(); - case Type::NegativeInfinity: + case ValueType::NegativeInfinity: return -std::numeric_limits::infinity(); default: return 0; } } else { - switch (m_type) { - case Type::Double: - return m_doubleValue; - case Type::Integer: - return m_intValue; - case Type::Bool: - return m_boolValue; - case Type::String: - return stringToDouble(m_stringValue); + switch (m_data.type) { + case ValueType::Double: + return m_data.doubleValue; + case ValueType::Integer: + return m_data.intValue; + case ValueType::Bool: + return m_data.boolValue; + case ValueType::String: + return stringToDouble(*m_data.stringValue); default: return 0; } @@ -320,19 +340,19 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns the boolean representation of the value. */ bool toBool() const { - switch (m_type) { - case Type::Bool: - return m_boolValue; - case Type::Integer: - return m_intValue != 0; - case Type::Double: - return m_doubleValue != 0; - case Type::String: - return !m_stringValue.empty() && !stringsEqual(m_stringValue, "false") && m_stringValue != "0"; - case Type::Infinity: - case Type::NegativeInfinity: + switch (m_data.type) { + case ValueType::Bool: + return m_data.boolValue; + case ValueType::Integer: + return m_data.intValue != 0; + case ValueType::Double: + return m_data.doubleValue != 0; + case ValueType::String: + return !m_data.stringValue->empty() && !stringsEqual(*m_data.stringValue, "false") && *m_data.stringValue != "0"; + case ValueType::Infinity: + case ValueType::NegativeInfinity: return true; - case Type::NaN: + case ValueType::NaN: return false; default: return false; @@ -342,26 +362,26 @@ class LIBSCRATCHCPP_EXPORT Value /*! Returns the string representation of the value. */ std::string toString() const { - if (static_cast(m_type) < 0) { - switch (m_type) { - case Type::Infinity: + if (static_cast(m_data.type) < 0) { + switch (m_data.type) { + case ValueType::Infinity: return "Infinity"; - case Type::NegativeInfinity: + case ValueType::NegativeInfinity: return "-Infinity"; default: return "NaN"; } } else { - switch (m_type) { - case Type::String: - return m_stringValue; - case Type::Integer: - return std::to_string(m_intValue); - case Type::Double: { - return doubleToString(m_doubleValue); + switch (m_data.type) { + case ValueType::String: + return *m_data.stringValue; + case ValueType::Integer: + return std::to_string(m_data.intValue); + case ValueType::Double: { + return doubleToString(m_data.doubleValue); } - case Type::Bool: - return m_boolValue ? "true" : "false"; + case ValueType::Bool: + return m_data.boolValue ? "true" : "false"; default: return ""; } @@ -374,18 +394,18 @@ class LIBSCRATCHCPP_EXPORT Value /*! Adds the given value to the value. */ void add(const Value &v) { - if ((static_cast(m_type) < 0) || (static_cast(v.m_type) < 0)) { - if ((m_type == Type::Infinity && v.m_type == Type::NegativeInfinity) || (m_type == Type::NegativeInfinity && v.m_type == Type::Infinity)) - m_type = Type::NaN; - else if (m_type == Type::Infinity || v.m_type == Type::Infinity) - m_type = Type::Infinity; - else if (m_type == Type::NegativeInfinity || v.m_type == Type::NegativeInfinity) - m_type = Type::NegativeInfinity; + if ((static_cast(m_data.type) < 0) || (static_cast(v.m_data.type) < 0)) { + if ((m_data.type == ValueType::Infinity && v.m_data.type == ValueType::NegativeInfinity) || (m_data.type == ValueType::NegativeInfinity && v.m_data.type == ValueType::Infinity)) + m_data.type = ValueType::NaN; + else if (m_data.type == ValueType::Infinity || v.m_data.type == ValueType::Infinity) + m_data.type = ValueType::Infinity; + else if (m_data.type == ValueType::NegativeInfinity || v.m_data.type == ValueType::NegativeInfinity) + m_data.type = ValueType::NegativeInfinity; return; } - if (m_type == Type::Integer && v.m_type == Type::Integer) - m_intValue += v.m_intValue; + if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) + m_data.intValue += v.m_data.intValue; else *this = toDouble() + v.toDouble(); } @@ -393,18 +413,18 @@ class LIBSCRATCHCPP_EXPORT Value /*! Subtracts the given value from the value. */ void subtract(const Value &v) { - if ((static_cast(m_type) < 0) || (static_cast(v.m_type) < 0)) { - if ((m_type == Type::Infinity && v.m_type == Type::Infinity) || (m_type == Type::NegativeInfinity && v.m_type == Type::NegativeInfinity)) - m_type = Type::NaN; - else if (m_type == Type::Infinity || v.m_type == Type::NegativeInfinity) - m_type = Type::Infinity; - else if (m_type == Type::NegativeInfinity || v.m_type == Type::Infinity) - m_type = Type::NegativeInfinity; + if ((static_cast(m_data.type) < 0) || (static_cast(v.m_data.type) < 0)) { + if ((m_data.type == ValueType::Infinity && v.m_data.type == ValueType::Infinity) || (m_data.type == ValueType::NegativeInfinity && v.m_data.type == ValueType::NegativeInfinity)) + m_data.type = ValueType::NaN; + else if (m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) + m_data.type = ValueType::Infinity; + else if (m_data.type == ValueType::NegativeInfinity || v.m_data.type == ValueType::Infinity) + m_data.type = ValueType::NegativeInfinity; return; } - if (m_type == Type::Integer && v.m_type == Type::Integer) - m_intValue -= v.m_intValue; + if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) + m_data.intValue -= v.m_data.intValue; else *this = toDouble() - v.toDouble(); } @@ -412,23 +432,26 @@ class LIBSCRATCHCPP_EXPORT Value /*! Multiplies the given value with the value. */ void multiply(const Value &v) { - Type t1 = m_type, t2 = v.m_type; - if ((static_cast(t1) < 0 && t1 != Type::NaN) || (static_cast(t2) < 0 && t2 != Type::NaN)) { - if (t1 == Type::Infinity || t1 == Type::NegativeInfinity || t2 == Type::Infinity || t2 == Type::NegativeInfinity) { - bool mode = (t1 == Type::Infinity || t2 == Type::Infinity); - const Value &value = ((t1 == Type::Infinity && (t2 == Type::Infinity || t2 == Type::NegativeInfinity)) || (t2 != Type::Infinity && t2 != Type::NegativeInfinity)) ? v : *this; + ValueType t1 = m_data.type, t2 = v.m_data.type; + if ((static_cast(t1) < 0 && t1 != ValueType::NaN) || (static_cast(t2) < 0 && t2 != ValueType::NaN)) { + if (t1 == ValueType::Infinity || t1 == ValueType::NegativeInfinity || t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity) { + bool mode = (t1 == ValueType::Infinity || t2 == ValueType::Infinity); + const Value &value = + ((t1 == ValueType::Infinity && (t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity)) || (t2 != ValueType::Infinity && t2 != ValueType::NegativeInfinity)) ? + v : + *this; if (value > 0) - m_type = mode ? Type::Infinity : Type::NegativeInfinity; + m_data.type = mode ? ValueType::Infinity : ValueType::NegativeInfinity; else if (value < 0) - m_type = mode ? Type::NegativeInfinity : Type::Infinity; + m_data.type = mode ? ValueType::NegativeInfinity : ValueType::Infinity; else - m_type = Type::NaN; + m_data.type = ValueType::NaN; return; } } - if (m_type == Type::Integer && v.m_type == Type::Integer) - m_intValue *= v.m_intValue; + if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) + m_data.intValue *= v.m_data.intValue; else *this = toDouble() * v.toDouble(); } @@ -437,19 +460,19 @@ class LIBSCRATCHCPP_EXPORT Value void divide(const Value &v) { if ((toDouble() == 0) && (v.toDouble() == 0)) { - m_type = Type::NaN; + m_data.type = ValueType::NaN; return; } else if (v.toDouble() == 0) { - m_type = *this > 0 ? Type::Infinity : Type::NegativeInfinity; + m_data.type = *this > 0 ? ValueType::Infinity : ValueType::NegativeInfinity; return; - } else if ((m_type == Type::Infinity || m_type == Type::NegativeInfinity) && (v.m_type == Type::Infinity || v.m_type == Type::NegativeInfinity)) { - m_type = Type::NaN; + } else if ((m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity) && (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity)) { + m_data.type = ValueType::NaN; return; - } else if (m_type == Type::Infinity || m_type == Type::NegativeInfinity) { + } else if (m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity) { if (v.toDouble() < 0) - m_type = m_type == Type::Infinity ? Type::NegativeInfinity : Type::Infinity; + m_data.type = m_data.type == ValueType::Infinity ? ValueType::NegativeInfinity : ValueType::Infinity; return; - } else if (v.m_type == Type::Infinity || v.m_type == Type::NegativeInfinity) { + } else if (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) { *this = 0; return; } @@ -459,10 +482,10 @@ class LIBSCRATCHCPP_EXPORT Value /*! Replaces the value with modulo of the value and the given value. */ void mod(const Value &v) { - if ((v == 0) || (m_type == Type::Infinity || m_type == Type::NegativeInfinity)) { - m_type = Type::NaN; + if ((v == 0) || (m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity)) { + m_data.type = ValueType::NaN; return; - } else if (v.m_type == Type::Infinity || v.m_type == Type::NegativeInfinity) { + } else if (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) { return; } if (*this < 0 || v < 0) @@ -473,18 +496,18 @@ class LIBSCRATCHCPP_EXPORT Value const Value &operator=(float v) { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; if (isInf(v)) - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (isNegativeInf(v)) - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (std::isnan(v)) - m_type = Type::NaN; + m_data.type = ValueType::NaN; else { - m_type = Type::Double; - m_doubleValue = floatToDouble(v); + m_data.type = ValueType::Double; + m_data.doubleValue = floatToDouble(v); } return *this; @@ -492,74 +515,74 @@ class LIBSCRATCHCPP_EXPORT Value const Value &operator=(double v) { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; if (isInf(v)) - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; else if (isNegativeInf(v)) - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; else if (std::isnan(v)) - m_type = Type::NaN; + m_data.type = ValueType::NaN; else { - m_type = Type::Double; - m_doubleValue = v; + m_data.type = ValueType::Double; + m_data.doubleValue = v; } return *this; } const Value &operator=(int v) { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::Integer; - m_intValue = v; + m_data.type = ValueType::Integer; + m_data.intValue = v; return *this; } const Value &operator=(long v) { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::Integer; - m_intValue = v; + m_data.type = ValueType::Integer; + m_data.intValue = v; return *this; } const Value &operator=(bool v) { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::Bool; - m_boolValue = v; + m_data.type = ValueType::Bool; + m_data.boolValue = v; return *this; } const Value &operator=(const std::string &v) { if (v == "Infinity") { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::Infinity; + m_data.type = ValueType::Infinity; } else if (v == "-Infinity") { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::NegativeInfinity; + m_data.type = ValueType::NegativeInfinity; } else if (v == "NaN") { - if (m_type == Type::String) - m_stringValue.~basic_string(); + if (m_data.type == ValueType::String) + delete m_data.stringValue; - m_type = Type::NaN; - } else if (m_type == Type::String) - m_stringValue = v; + m_data.type = ValueType::NaN; + } else if (m_data.type == ValueType::String) + m_data.stringValue->assign(v); else { - new (&m_stringValue) std::string(v); - m_type = Type::String; + m_data.stringValue = new std::string(v); + m_data.type = ValueType::String; } return *this; @@ -569,52 +592,34 @@ class LIBSCRATCHCPP_EXPORT Value const Value &operator=(const Value &v) { - switch (v.m_type) { - case Type::Integer: - if (m_type == Type::String) - m_stringValue.~basic_string(); - - m_intValue = v.m_intValue; + switch (v.m_data.type) { + case ValueType::Integer: + m_data.intValue = v.m_data.intValue; break; - case Type::Double: - if (m_type == Type::String) - m_stringValue.~basic_string(); - - m_doubleValue = v.m_doubleValue; + case ValueType::Double: + m_data.doubleValue = v.m_data.doubleValue; break; - case Type::Bool: - if (m_type == Type::String) - m_stringValue.~basic_string(); - - m_boolValue = v.m_boolValue; + case ValueType::Bool: + m_data.boolValue = v.m_data.boolValue; break; - case Type::String: - if (m_type == Type::String) - m_stringValue = v.m_stringValue; - else - new (&m_stringValue) std::string(v.m_stringValue); + case ValueType::String: + m_data.stringValue = new std::string(*v.m_data.stringValue); break; default: break; } - m_type = v.m_type; + m_data.type = v.m_data.type; return *this; } private: - union - { - long m_intValue; - double m_doubleValue; - bool m_boolValue; - std::string m_stringValue; - }; + ValueData m_data; // 0 - is string // 1 - is long @@ -638,26 +643,26 @@ class LIBSCRATCHCPP_EXPORT Value if (ok) *ok = true; - switch (m_type) { - case Type::Integer: - return m_intValue; + switch (m_data.type) { + case ValueType::Integer: + return m_data.intValue; - case Type::Double: - return m_doubleValue; + case ValueType::Double: + return m_data.doubleValue; - case Type::Bool: - return m_boolValue; + case ValueType::Bool: + return m_data.boolValue; - case Type::String: - return stringToDouble(m_stringValue, ok); + case ValueType::String: + return stringToDouble(*m_data.stringValue, ok); - case Type::Infinity: + case ValueType::Infinity: return std::numeric_limits::infinity(); - case Type::NegativeInfinity: + case ValueType::NegativeInfinity: return -std::numeric_limits::infinity(); - case Type::NaN: + case ValueType::NaN: if (ok) *ok = false; @@ -672,11 +677,14 @@ class LIBSCRATCHCPP_EXPORT Value } } - Type m_type; - friend bool operator==(const Value &v1, const Value &v2) { // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L121-L150 + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue == v2.m_data.intValue; + else if (v1.m_data.type == ValueType::Double && v2.m_data.type == ValueType::Double) + return v1.m_data.doubleValue == v2.m_data.doubleValue; + bool ok; double n1 = v1.getNumber(&ok); double n2; @@ -691,10 +699,10 @@ class LIBSCRATCHCPP_EXPORT Value } // Handle the special case of Infinity - if ((static_cast(v1.m_type) < 0) && (static_cast(v2.m_type) < 0)) { - assert(v1.m_type != Type::NaN); - assert(v2.m_type != Type::NaN); - return v1.m_type == v2.m_type; + if ((static_cast(v1.m_data.type) < 0) && (static_cast(v2.m_data.type) < 0)) { + assert(v1.m_data.type != ValueType::NaN); + assert(v2.m_data.type != ValueType::NaN); + return v1.m_data.type == v2.m_data.type; } // Compare as numbers @@ -705,38 +713,38 @@ class LIBSCRATCHCPP_EXPORT Value friend bool operator>(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_type) < 0) || (static_cast(v2.m_type) < 0)) { - if (v1.m_type == Type::Infinity) { - return v2.m_type != Type::Infinity; - } else if (v1.m_type == Type::NegativeInfinity) + if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { + if (v1.m_data.type == ValueType::Infinity) { + return v2.m_data.type != ValueType::Infinity; + } else if (v1.m_data.type == ValueType::NegativeInfinity) return false; - else if (v2.m_type == Type::Infinity) + else if (v2.m_data.type == ValueType::Infinity) return false; - else if (v2.m_type == Type::NegativeInfinity) + else if (v2.m_data.type == ValueType::NegativeInfinity) return true; } - if (v1.m_type == Type::Integer && v2.m_type == Type::Integer) - return v1.m_intValue > v2.m_intValue; + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue > v2.m_data.intValue; else return v1.toDouble() > v2.toDouble(); } friend bool operator<(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_type) < 0) || (static_cast(v2.m_type) < 0)) { - if (v1.m_type == Type::Infinity) { + if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { + if (v1.m_data.type == ValueType::Infinity) { return false; - } else if (v1.m_type == Type::NegativeInfinity) - return v2.m_type != Type::NegativeInfinity; - else if (v2.m_type == Type::Infinity) - return v1.m_type != Type::Infinity; - else if (v2.m_type == Type::NegativeInfinity) + } else if (v1.m_data.type == ValueType::NegativeInfinity) + return v2.m_data.type != ValueType::NegativeInfinity; + else if (v2.m_data.type == ValueType::Infinity) + return v1.m_data.type != ValueType::Infinity; + else if (v2.m_data.type == ValueType::NegativeInfinity) return false; } - if (v1.m_type == Type::Integer && v2.m_type == Type::Integer) - return v1.m_intValue < v2.m_intValue; + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue < v2.m_data.intValue; else return v1.toDouble() < v2.toDouble(); } @@ -747,45 +755,48 @@ class LIBSCRATCHCPP_EXPORT Value friend Value operator+(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_type) < 0) || (static_cast(v2.m_type) < 0)) { - if ((v1.m_type == Type::Infinity && v2.m_type == Type::NegativeInfinity) || (v1.m_type == Type::NegativeInfinity && v2.m_type == Type::Infinity)) + if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { + if ((v1.m_data.type == ValueType::Infinity && v2.m_data.type == ValueType::NegativeInfinity) || + (v1.m_data.type == ValueType::NegativeInfinity && v2.m_data.type == ValueType::Infinity)) return Value(SpecialValue::NaN); - else if (v1.m_type == Type::Infinity || v2.m_type == Type::Infinity) + else if (v1.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::Infinity) return Value(SpecialValue::Infinity); - else if (v1.m_type == Type::NegativeInfinity || v2.m_type == Type::NegativeInfinity) + else if (v1.m_data.type == ValueType::NegativeInfinity || v2.m_data.type == ValueType::NegativeInfinity) return Value(SpecialValue::NegativeInfinity); } - if (v1.m_type == Type::Integer && v2.m_type == Type::Integer) - return v1.m_intValue + v2.m_intValue; + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue + v2.m_data.intValue; else return v1.toDouble() + v2.toDouble(); } friend Value operator-(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_type) < 0) || (static_cast(v2.m_type) < 0)) { - if ((v1.m_type == Type::Infinity && v2.m_type == Type::Infinity) || (v1.m_type == Type::NegativeInfinity && v2.m_type == Type::NegativeInfinity)) + if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { + if ((v1.m_data.type == ValueType::Infinity && v2.m_data.type == ValueType::Infinity) || + (v1.m_data.type == ValueType::NegativeInfinity && v2.m_data.type == ValueType::NegativeInfinity)) return Value(SpecialValue::NaN); - else if (v1.m_type == Type::Infinity || v2.m_type == Type::NegativeInfinity) + else if (v1.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) return Value(SpecialValue::Infinity); - else if (v1.m_type == Type::NegativeInfinity || v2.m_type == Type::Infinity) + else if (v1.m_data.type == ValueType::NegativeInfinity || v2.m_data.type == ValueType::Infinity) return Value(SpecialValue::NegativeInfinity); } - if (v1.m_type == Type::Integer && v2.m_type == Type::Integer) - return v1.m_intValue - v2.m_intValue; + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue - v2.m_data.intValue; else return v1.toDouble() - v2.toDouble(); } friend Value operator*(const Value &v1, const Value &v2) { - Type t1 = v1.m_type, t2 = v2.m_type; - if ((static_cast(t1) < 0 && t1 != Type::NaN) || (static_cast(t2) < 0 && t2 != Type::NaN)) { - if (t1 == Type::Infinity || t1 == Type::NegativeInfinity || t2 == Type::Infinity || t2 == Type::NegativeInfinity) { - bool mode = (t1 == Type::Infinity || t2 == Type::Infinity); - const Value &value = ((t1 == Type::Infinity && (t2 == Type::Infinity || t2 == Type::NegativeInfinity)) || (t2 != Type::Infinity && t2 != Type::NegativeInfinity)) ? v2 : v1; + ValueType t1 = v1.m_data.type, t2 = v2.m_data.type; + if ((static_cast(t1) < 0 && t1 != ValueType::NaN) || (static_cast(t2) < 0 && t2 != ValueType::NaN)) { + if (t1 == ValueType::Infinity || t1 == ValueType::NegativeInfinity || t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity) { + bool mode = (t1 == ValueType::Infinity || t2 == ValueType::Infinity); + const Value &value = + ((t1 == ValueType::Infinity && (t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity)) || (t2 != ValueType::Infinity && t2 != ValueType::NegativeInfinity)) ? v2 : v1; if (value > 0) return Value(mode ? SpecialValue::Infinity : SpecialValue::NegativeInfinity); else if (value < 0) @@ -795,8 +806,8 @@ class LIBSCRATCHCPP_EXPORT Value } } - if (v1.m_type == Type::Integer && v2.m_type == Type::Integer) - return v1.m_intValue * v2.m_intValue; + if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) + return v1.m_data.intValue * v2.m_data.intValue; else return v1.toDouble() * v2.toDouble(); } @@ -807,14 +818,15 @@ class LIBSCRATCHCPP_EXPORT Value return Value(SpecialValue::NaN); else if (v2.toDouble() == 0) return v1 > 0 ? Value(SpecialValue::Infinity) : Value(SpecialValue::NegativeInfinity); - else if ((v1.m_type == Type::Infinity || v1.m_type == Type::NegativeInfinity) && (v2.m_type == Type::Infinity || v2.m_type == Type::NegativeInfinity)) { + else if ((v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity) && + (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity)) { return Value(SpecialValue::NaN); - } else if (v1.m_type == Type::Infinity || v1.m_type == Type::NegativeInfinity) { + } else if (v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity) { if (v2.toDouble() < 0) - return v1.m_type == Type::Infinity ? Value(SpecialValue::NegativeInfinity) : Value(SpecialValue::Infinity); + return v1.m_data.type == ValueType::Infinity ? Value(SpecialValue::NegativeInfinity) : Value(SpecialValue::Infinity); else - return v1.m_type == Type::Infinity ? Value(SpecialValue::Infinity) : Value(SpecialValue::NegativeInfinity); - } else if (v2.m_type == Type::Infinity || v2.m_type == Type::NegativeInfinity) { + return v1.m_data.type == ValueType::Infinity ? Value(SpecialValue::Infinity) : Value(SpecialValue::NegativeInfinity); + } else if (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) { return 0; } return v1.toDouble() / v2.toDouble(); @@ -823,9 +835,9 @@ class LIBSCRATCHCPP_EXPORT Value friend Value operator%(const Value &v1, const Value &v2) { - if ((v2 == 0) || (v1.m_type == Type::Infinity || v1.m_type == Type::NegativeInfinity)) + if ((v2 == 0) || (v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity)) return Value(SpecialValue::NaN); - else if (v2.m_type == Type::Infinity || v2.m_type == Type::NegativeInfinity) { + else if (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) { return v1.toDouble(); } if (v1 < 0 || v2 < 0) diff --git a/src/scratch/inputvalue.cpp b/src/scratch/inputvalue.cpp index cb883b0d..46d4738e 100644 --- a/src/scratch/inputvalue.cpp +++ b/src/scratch/inputvalue.cpp @@ -13,10 +13,10 @@ using namespace libscratchcpp; -static const std::map VALUE_TYPE_MAP = { - { Value::Type::Integer, InputValue::Type::Number }, { Value::Type::Double, InputValue::Type::Number }, { Value::Type::Bool, InputValue::Type::String }, - { Value::Type::String, InputValue::Type::String }, { Value::Type::Infinity, InputValue::Type::String }, { Value::Type::NegativeInfinity, InputValue::Type::String }, - { Value::Type::NaN, InputValue::Type::String } +static const std::map VALUE_TYPE_MAP = { + { ValueType::Integer, InputValue::Type::Number }, { ValueType::Double, InputValue::Type::Number }, { ValueType::Bool, InputValue::Type::String }, + { ValueType::String, InputValue::Type::String }, { ValueType::Infinity, InputValue::Type::String }, { ValueType::NegativeInfinity, InputValue::Type::String }, + { ValueType::NaN, InputValue::Type::String } }; /*! Constructs InputValue with the given type. */ diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 69ea1fa0..695e5da6 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -9,7 +9,7 @@ TEST(ValueTest, DefaultConstructor) { Value v; ASSERT_EQ(v.toInt(), 0); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -25,7 +25,7 @@ TEST(ValueTest, FloatConstructor) { Value v(3.14f); ASSERT_EQ(v.toDouble(), 3.14); - ASSERT_EQ(v.type(), Value::Type::Double); + ASSERT_EQ(v.type(), ValueType::Double); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -38,7 +38,7 @@ TEST(ValueTest, FloatConstructor) { Value v(std::numeric_limits::infinity()); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -50,7 +50,7 @@ TEST(ValueTest, FloatConstructor) { Value v(-std::numeric_limits::infinity()); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -62,7 +62,7 @@ TEST(ValueTest, FloatConstructor) { Value v(std::numeric_limits::quiet_NaN()); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -78,7 +78,7 @@ TEST(ValueTest, DoubleConstructor) { Value v(static_cast(3.14)); ASSERT_EQ(v.toDouble(), 3.14); - ASSERT_EQ(v.type(), Value::Type::Double); + ASSERT_EQ(v.type(), ValueType::Double); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -92,7 +92,7 @@ TEST(ValueTest, DoubleConstructor) { Value v(static_cast(-5.0)); ASSERT_EQ(v.toDouble(), -5.0); - ASSERT_EQ(v.type(), Value::Type::Double); + ASSERT_EQ(v.type(), ValueType::Double); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -105,7 +105,7 @@ TEST(ValueTest, DoubleConstructor) { Value v(std::numeric_limits::infinity()); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -117,7 +117,7 @@ TEST(ValueTest, DoubleConstructor) { Value v(-std::numeric_limits::infinity()); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -129,7 +129,7 @@ TEST(ValueTest, DoubleConstructor) { Value v(std::numeric_limits::quiet_NaN()); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -144,7 +144,7 @@ TEST(ValueTest, IntConstructor) { Value v(static_cast(55)); ASSERT_EQ(v.toInt(), 55); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -159,7 +159,7 @@ TEST(ValueTest, SizeTConstructor) { Value v(static_cast(100)); ASSERT_EQ(v.toLong(), 100); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -174,7 +174,7 @@ TEST(ValueTest, LongConstructor) { Value v(999999999999999999L); ASSERT_EQ(v.toLong(), 999999999999999999L); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -190,7 +190,7 @@ TEST(ValueTest, BoolConstructor) { Value v(true); ASSERT_EQ(v.toBool(), true); - ASSERT_EQ(v.type(), Value::Type::Bool); + ASSERT_EQ(v.type(), ValueType::Bool); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -204,7 +204,7 @@ TEST(ValueTest, BoolConstructor) { Value v(false); ASSERT_EQ(v.toBool(), false); - ASSERT_EQ(v.type(), Value::Type::Bool); + ASSERT_EQ(v.type(), ValueType::Bool); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -221,7 +221,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("test")); ASSERT_EQ(v.toString(), "test"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -235,7 +235,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("532")); ASSERT_EQ(v.toString(), "532"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -250,7 +250,7 @@ TEST(ValueTest, StdStringConstructor) Value v(std::string("532.15")); ASSERT_EQ(v.toString(), "532.15"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -264,7 +264,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("1 2 3")); ASSERT_EQ(v.toString(), "1 2 3"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -279,7 +279,7 @@ TEST(ValueTest, StdStringConstructor) Value v(std::string("")); ASSERT_EQ(v.toString(), ""); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -294,7 +294,7 @@ TEST(ValueTest, StdStringConstructor) Value v(std::string(" ")); ASSERT_EQ(v.toString(), " "); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -307,7 +307,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("Infinity")); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -319,7 +319,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("-Infinity")); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -331,7 +331,7 @@ TEST(ValueTest, StdStringConstructor) { Value v(std::string("NaN")); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -347,7 +347,7 @@ TEST(ValueTest, CStringConstructor) { Value v("test"); ASSERT_EQ(v.toString(), "test"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -360,7 +360,7 @@ TEST(ValueTest, CStringConstructor) { Value v("Infinity"); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -372,7 +372,7 @@ TEST(ValueTest, CStringConstructor) { Value v("-Infinity"); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -384,7 +384,7 @@ TEST(ValueTest, CStringConstructor) { Value v("NaN"); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -398,7 +398,7 @@ TEST(ValueTest, CStringConstructor) TEST(ValueTest, InfinityConstructor) { Value v(Value::SpecialValue::Infinity); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -412,7 +412,7 @@ TEST(ValueTest, InfinityConstructor) TEST(ValueTest, NegativeInfinityConstructor) { Value v(Value::SpecialValue::NegativeInfinity); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -426,7 +426,7 @@ TEST(ValueTest, NegativeInfinityConstructor) TEST(ValueTest, NaNConstructor) { Value v(Value::SpecialValue::NaN); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -541,7 +541,7 @@ TEST(ValueTest, FloatAssignment) Value v; v = 3.14f; ASSERT_EQ(v.toDouble(), 3.14); - ASSERT_EQ(v.type(), Value::Type::Double); + ASSERT_EQ(v.type(), ValueType::Double); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -551,7 +551,7 @@ TEST(ValueTest, FloatAssignment) ASSERT_FALSE(v.isString()); v = std::numeric_limits::infinity(); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -561,7 +561,7 @@ TEST(ValueTest, FloatAssignment) ASSERT_FALSE(v.isString()); v = -std::numeric_limits::infinity(); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -571,7 +571,7 @@ TEST(ValueTest, FloatAssignment) ASSERT_FALSE(v.isString()); v = -std::numeric_limits::quiet_NaN(); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -586,7 +586,7 @@ TEST(ValueTest, DoubleAssignment) Value v; v = static_cast(3.14); ASSERT_EQ(v.toDouble(), 3.14); - ASSERT_EQ(v.type(), Value::Type::Double); + ASSERT_EQ(v.type(), ValueType::Double); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -596,7 +596,7 @@ TEST(ValueTest, DoubleAssignment) ASSERT_FALSE(v.isString()); v = std::numeric_limits::infinity(); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -606,7 +606,7 @@ TEST(ValueTest, DoubleAssignment) ASSERT_FALSE(v.isString()); v = -std::numeric_limits::infinity(); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -616,7 +616,7 @@ TEST(ValueTest, DoubleAssignment) ASSERT_FALSE(v.isString()); v = -std::numeric_limits::quiet_NaN(); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -631,7 +631,7 @@ TEST(ValueTest, IntAssignment) Value v; v = static_cast(55); ASSERT_EQ(v.toInt(), 55); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -646,7 +646,7 @@ TEST(ValueTest, LongAssignment) Value v; v = 999999999999999999L; ASSERT_EQ(v.toLong(), 999999999999999999L); - ASSERT_EQ(v.type(), Value::Type::Integer); + ASSERT_EQ(v.type(), ValueType::Integer); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -662,7 +662,7 @@ TEST(ValueTest, BoolAssignment) Value v; v = true; ASSERT_EQ(v.toBool(), true); - ASSERT_EQ(v.type(), Value::Type::Bool); + ASSERT_EQ(v.type(), ValueType::Bool); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -676,7 +676,7 @@ TEST(ValueTest, BoolAssignment) Value v; v = false; ASSERT_EQ(v.toBool(), false); - ASSERT_EQ(v.type(), Value::Type::Bool); + ASSERT_EQ(v.type(), ValueType::Bool); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -693,7 +693,7 @@ TEST(ValueTest, StdStringAssignment) Value v; v = std::string("test"); ASSERT_EQ(v.toString(), "test"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -707,7 +707,7 @@ TEST(ValueTest, StdStringAssignment) Value v; v = std::string("Infinity"); ASSERT_EQ(v.toString(), "Infinity"); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -721,7 +721,7 @@ TEST(ValueTest, StdStringAssignment) Value v; v = std::string("-Infinity"); ASSERT_EQ(v.toString(), "-Infinity"); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -735,7 +735,7 @@ TEST(ValueTest, StdStringAssignment) Value v; v = std::string("NaN"); ASSERT_EQ(v.toString(), "NaN"); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -752,7 +752,7 @@ TEST(ValueTest, CStringAssignment) Value v; v = "test"; ASSERT_EQ(v.toString(), "test"); - ASSERT_EQ(v.type(), Value::Type::String); + ASSERT_EQ(v.type(), ValueType::String); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -766,7 +766,7 @@ TEST(ValueTest, CStringAssignment) Value v; v = "Infinity"; ASSERT_EQ(v.toString(), "Infinity"); - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -780,7 +780,7 @@ TEST(ValueTest, CStringAssignment) Value v; v = "-Infinity"; ASSERT_EQ(v.toString(), "-Infinity"); - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -794,7 +794,7 @@ TEST(ValueTest, CStringAssignment) Value v; v = "NaN"; ASSERT_EQ(v.toString(), "NaN"); - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); @@ -809,7 +809,7 @@ TEST(ValueTest, InfinityAssignment) { Value v; v = Value::SpecialValue::Infinity; - ASSERT_EQ(v.type(), Value::Type::Infinity); + ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -823,7 +823,7 @@ TEST(ValueTest, NegativeInfinityAssignment) { Value v; v = Value::SpecialValue::NegativeInfinity; - ASSERT_EQ(v.type(), Value::Type::NegativeInfinity); + ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); @@ -837,7 +837,7 @@ TEST(ValueTest, NaNAssignment) { Value v; v = Value::SpecialValue::NaN; - ASSERT_EQ(v.type(), Value::Type::NaN); + ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_TRUE(v.isNaN()); From 845c1fd4b7e7cd4bdf9eeb6bb02d9d779f4f7fbf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:26:26 +0200 Subject: [PATCH 21/72] Move value implementation to C-like functions --- include/scratchcpp/value.h | 1007 ++--------------- include/scratchcpp/value_functions.h | 52 + include/scratchcpp/valuedata.h | 47 + src/blocks/operatorblocks.cpp | 8 +- src/engine/virtualmachine_p.cpp | 18 +- src/scratch/CMakeLists.txt | 3 +- src/scratch/value.cpp | 9 - src/scratch/value_functions.cpp | 619 ++++++++++ src/scratch/value_functions_p.h | 384 +++++++ test/blocks/list_blocks_test.cpp | 4 +- test/blocks/looks_blocks_test.cpp | 10 +- test/blocks/operator_blocks_test.cpp | 8 +- test/scratch_classes/value_test.cpp | 60 +- test/virtual_machine/virtual_machine_test.cpp | 24 +- 14 files changed, 1247 insertions(+), 1006 deletions(-) create mode 100644 include/scratchcpp/value_functions.h create mode 100644 include/scratchcpp/valuedata.h delete mode 100644 src/scratch/value.cpp create mode 100644 src/scratch/value_functions.cpp create mode 100644 src/scratch/value_functions_p.h diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index 169fba15..6f425893 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -2,7 +2,6 @@ #pragma once -#include #include #include #include @@ -11,743 +10,216 @@ #include #include -#include "global.h" +#include "value_functions.h" -namespace libscratchcpp -{ - -enum class ValueType -{ - Integer = 0, - Double = 1, - Bool = 2, - String = 3, - Infinity = -1, - NegativeInfinity = -2, - NaN = -3 -}; +// NOTE: Put all logic in value functions so we can keep using this class in unit tests -extern "C" +namespace libscratchcpp { - /*! \brief The ValueData struct holds the data of Value. It's used in compiled Scratch code for better performance. */ - struct ValueData - { - union - { - long intValue; - double doubleValue; - bool boolValue; - std::string *stringValue; - }; - - ValueType type; - }; -} /*! \brief The Value class represents a Scratch value. */ class LIBSCRATCHCPP_EXPORT Value { public: - enum class SpecialValue - { - Infinity, - NegativeInfinity, - NaN - }; - /*! Constructs a number Value. */ Value(float numberValue) { - if (isInf(numberValue)) - m_data.type = ValueType::Infinity; - else if (isNegativeInf(numberValue)) - m_data.type = ValueType::NegativeInfinity; - else if (std::isnan(numberValue)) - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::Double; - m_data.doubleValue = floatToDouble(numberValue); - } + value_init(&m_data); + value_assign_float(&m_data, numberValue); } /*! Constructs a number Value. */ Value(double numberValue) { - if (isInf(numberValue)) - m_data.type = ValueType::Infinity; - else if (isNegativeInf(numberValue)) - m_data.type = ValueType::NegativeInfinity; - else if (std::isnan(numberValue)) - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::Double; - m_data.doubleValue = numberValue; - } + value_init(&m_data); + value_assign_double(&m_data, numberValue); } /*! Constructs a number Value. */ Value(int numberValue = 0) { - m_data.type = ValueType::Integer; - m_data.intValue = numberValue; + value_init(&m_data); + value_assign_int(&m_data, numberValue); } /*! Constructs a number Value. */ Value(size_t numberValue) { - m_data.type = ValueType::Integer; - m_data.intValue = numberValue; + value_init(&m_data); + value_assign_size_t(&m_data, numberValue); } /*! Constructs a number Value. */ Value(long numberValue) { - m_data.type = ValueType::Integer; - m_data.intValue = numberValue; + value_init(&m_data); + value_assign_long(&m_data, numberValue); } /*! Constructs a boolean Value. */ Value(bool boolValue) { - m_data.type = ValueType::Bool; - m_data.boolValue = boolValue; + value_init(&m_data); + value_assign_bool(&m_data, boolValue); } /*! Constructs a string Value. */ Value(const std::string &stringValue) { - if (stringValue == "Infinity") - m_data.type = ValueType::Infinity; - else if (stringValue == "-Infinity") - m_data.type = ValueType::NegativeInfinity; - else if (stringValue == "NaN") - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::String; - m_data.stringValue = new std::string(stringValue); - } + value_init(&m_data); + value_assign_string(&m_data, stringValue); } /*! Constructs a string Value. */ - Value(const char *stringValue) : - Value(std::string(stringValue)) + Value(const char *stringValue) { + value_init(&m_data); + value_assign_cstring(&m_data, stringValue); } /*! Constructs a special Value. */ Value(SpecialValue specialValue) { - if (specialValue == SpecialValue::Infinity) - m_data.type = ValueType::Infinity; - else if (specialValue == SpecialValue::NegativeInfinity) - m_data.type = ValueType::NegativeInfinity; - else if (specialValue == SpecialValue::NaN) - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::Integer; - m_data.intValue = 0; - } + value_init(&m_data); + value_assign_special(&m_data, specialValue); } Value(const Value &v) { - m_data.type = v.m_data.type; - - switch (m_data.type) { - case ValueType::Integer: - m_data.intValue = v.m_data.intValue; - break; - case ValueType::Double: - m_data.doubleValue = v.m_data.doubleValue; - break; - case ValueType::Bool: - m_data.boolValue = v.m_data.boolValue; - break; - case ValueType::String: - m_data.stringValue = new std::string(*v.m_data.stringValue); - break; - default: - break; - } + value_init(&m_data); + value_assign_copy(&m_data, &v.m_data); } - ~Value() - { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - } + ~Value() { value_free(&m_data); } /*! Returns the type of the value. */ ValueType type() const { return m_data.type; } /*! Returns true if the value is infinity. */ - bool isInfinity() const - { - switch (m_data.type) { - case ValueType::Infinity: - return true; - case ValueType::Integer: - return isInf(m_data.intValue); - case ValueType::Double: - return isInf(m_data.doubleValue); - case ValueType::String: - return *m_data.stringValue == "Infinity"; - default: - return false; - } - } + bool isInfinity() const { return value_isInfinity(&m_data); } /*! Returns true if the value is negative infinity. */ - bool isNegativeInfinity() const - { - switch (m_data.type) { - case ValueType::NegativeInfinity: - return true; - case ValueType::Integer: - return isNegativeInf(-m_data.intValue); - case ValueType::Double: - return isNegativeInf(-m_data.doubleValue); - case ValueType::String: - return *m_data.stringValue == "-Infinity"; - default: - return false; - } - } + bool isNegativeInfinity() const { return value_isNegativeInfinity(&m_data); } /*! Returns true if the value is NaN (Not a Number). */ - bool isNaN() const - { - switch (m_data.type) { - case ValueType::NaN: - return true; - case ValueType::Double: - assert(!std::isnan(m_data.doubleValue)); - return std::isnan(m_data.doubleValue); - case ValueType::String: - return *m_data.stringValue == "NaN"; - default: - return false; - } - } + bool isNaN() const { return value_isNaN(&m_data); } /*! Returns true if the value is a number. */ - bool isNumber() const { return m_data.type == ValueType::Integer || m_data.type == ValueType::Double; } + bool isNumber() const { return value_isNumber(&m_data); } /*! Returns true if the value is a number or can be converted to a number. */ - bool isValidNumber() const - { - if (isNaN()) - return false; - - if (isInfinity() || isNegativeInfinity()) - return true; - - assert(m_data.type != ValueType::Infinity && m_data.type != ValueType::NegativeInfinity); - - switch (m_data.type) { - case ValueType::Integer: - case ValueType::Double: - case ValueType::Bool: - return true; - case ValueType::String: - return m_data.stringValue->empty() || checkString(*m_data.stringValue) > 0; - default: - return false; - } - } + bool isValidNumber() const { return value_isValidNumber(&m_data); } /*! Returns true if this value represents a round integer. */ - bool isInt() const - { - // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L157-L181 - switch (m_data.type) { - case ValueType::Integer: - case ValueType::Bool: - case ValueType::Infinity: - case ValueType::NegativeInfinity: - case ValueType::NaN: - return true; - case ValueType::Double: { - double intpart; - std::modf(m_data.doubleValue, &intpart); - return m_data.doubleValue == intpart; - } - case ValueType::String: - return m_data.stringValue->find('.') == std::string::npos; - } - - return false; - } + bool isInt() const { return value_isInt(&m_data); } /*! Returns true if the value is a boolean. */ - bool isBool() const { return m_data.type == ValueType::Bool; } + bool isBool() const { return value_isBool(&m_data); } /*! Returns true if the value is a string. */ - bool isString() const { return m_data.type == ValueType::String; } + bool isString() const { return value_isString(&m_data); } /*! Returns the int representation of the value. */ - int toInt() const { return toLong(); } + int toInt() const { return value_toInt(&m_data); } /*! Returns the long representation of the value. */ - long toLong() const - { - if (static_cast(m_data.type) < 0) - return 0; - else { - switch (m_data.type) { - case ValueType::Integer: - return m_data.intValue; - case ValueType::Double: - return m_data.doubleValue; - case ValueType::Bool: - return m_data.boolValue; - case ValueType::String: - return stringToLong(*m_data.stringValue); - default: - return 0; - } - } - } + long toLong() const { return value_toLong(&m_data); } /*! Returns the double representation of the value. */ - double toDouble() const - { - if (static_cast(m_data.type) < 0) { - switch (m_data.type) { - case ValueType::Infinity: - return std::numeric_limits::infinity(); - case ValueType::NegativeInfinity: - return -std::numeric_limits::infinity(); - default: - return 0; - } - } else { - switch (m_data.type) { - case ValueType::Double: - return m_data.doubleValue; - case ValueType::Integer: - return m_data.intValue; - case ValueType::Bool: - return m_data.boolValue; - case ValueType::String: - return stringToDouble(*m_data.stringValue); - default: - return 0; - } - } - }; + double toDouble() const { return value_toDouble(&m_data); }; /*! Returns the boolean representation of the value. */ - bool toBool() const - { - switch (m_data.type) { - case ValueType::Bool: - return m_data.boolValue; - case ValueType::Integer: - return m_data.intValue != 0; - case ValueType::Double: - return m_data.doubleValue != 0; - case ValueType::String: - return !m_data.stringValue->empty() && !stringsEqual(*m_data.stringValue, "false") && *m_data.stringValue != "0"; - case ValueType::Infinity: - case ValueType::NegativeInfinity: - return true; - case ValueType::NaN: - return false; - default: - return false; - } - }; + bool toBool() const { return value_toBool(&m_data); }; /*! Returns the string representation of the value. */ std::string toString() const { - if (static_cast(m_data.type) < 0) { - switch (m_data.type) { - case ValueType::Infinity: - return "Infinity"; - case ValueType::NegativeInfinity: - return "-Infinity"; - default: - return "NaN"; - } - } else { - switch (m_data.type) { - case ValueType::String: - return *m_data.stringValue; - case ValueType::Integer: - return std::to_string(m_data.intValue); - case ValueType::Double: { - return doubleToString(m_data.doubleValue); - } - case ValueType::Bool: - return m_data.boolValue ? "true" : "false"; - default: - return ""; - } - } + std::string ret; + value_toString(&m_data, &ret); + return ret; }; /*! Returns the UTF-16 representation of the value. */ - std::u16string toUtf16() const; - - /*! Adds the given value to the value. */ - void add(const Value &v) + std::u16string toUtf16() { - if ((static_cast(m_data.type) < 0) || (static_cast(v.m_data.type) < 0)) { - if ((m_data.type == ValueType::Infinity && v.m_data.type == ValueType::NegativeInfinity) || (m_data.type == ValueType::NegativeInfinity && v.m_data.type == ValueType::Infinity)) - m_data.type = ValueType::NaN; - else if (m_data.type == ValueType::Infinity || v.m_data.type == ValueType::Infinity) - m_data.type = ValueType::Infinity; - else if (m_data.type == ValueType::NegativeInfinity || v.m_data.type == ValueType::NegativeInfinity) - m_data.type = ValueType::NegativeInfinity; - return; - } - - if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) - m_data.intValue += v.m_data.intValue; - else - *this = toDouble() + v.toDouble(); + std::u16string ret; + value_toUtf16(&m_data, &ret); + return ret; } - /*! Subtracts the given value from the value. */ - void subtract(const Value &v) - { - if ((static_cast(m_data.type) < 0) || (static_cast(v.m_data.type) < 0)) { - if ((m_data.type == ValueType::Infinity && v.m_data.type == ValueType::Infinity) || (m_data.type == ValueType::NegativeInfinity && v.m_data.type == ValueType::NegativeInfinity)) - m_data.type = ValueType::NaN; - else if (m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) - m_data.type = ValueType::Infinity; - else if (m_data.type == ValueType::NegativeInfinity || v.m_data.type == ValueType::Infinity) - m_data.type = ValueType::NegativeInfinity; - return; - } + /*! Adds the given value to the value. */ + void add(const Value &v) { value_add(&m_data, &v.m_data, &m_data); } - if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) - m_data.intValue -= v.m_data.intValue; - else - *this = toDouble() - v.toDouble(); - } + /*! Subtracts the given value from the value. */ + void subtract(const Value &v) { value_subtract(&m_data, &v.m_data, &m_data); } /*! Multiplies the given value with the value. */ - void multiply(const Value &v) - { - ValueType t1 = m_data.type, t2 = v.m_data.type; - if ((static_cast(t1) < 0 && t1 != ValueType::NaN) || (static_cast(t2) < 0 && t2 != ValueType::NaN)) { - if (t1 == ValueType::Infinity || t1 == ValueType::NegativeInfinity || t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity) { - bool mode = (t1 == ValueType::Infinity || t2 == ValueType::Infinity); - const Value &value = - ((t1 == ValueType::Infinity && (t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity)) || (t2 != ValueType::Infinity && t2 != ValueType::NegativeInfinity)) ? - v : - *this; - if (value > 0) - m_data.type = mode ? ValueType::Infinity : ValueType::NegativeInfinity; - else if (value < 0) - m_data.type = mode ? ValueType::NegativeInfinity : ValueType::Infinity; - else - m_data.type = ValueType::NaN; - return; - } - } - - if (m_data.type == ValueType::Integer && v.m_data.type == ValueType::Integer) - m_data.intValue *= v.m_data.intValue; - else - *this = toDouble() * v.toDouble(); - } + void multiply(const Value &v) { value_multiply(&m_data, &v.m_data, &m_data); } /*! Divides the value by the given value. */ - void divide(const Value &v) - { - if ((toDouble() == 0) && (v.toDouble() == 0)) { - m_data.type = ValueType::NaN; - return; - } else if (v.toDouble() == 0) { - m_data.type = *this > 0 ? ValueType::Infinity : ValueType::NegativeInfinity; - return; - } else if ((m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity) && (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity)) { - m_data.type = ValueType::NaN; - return; - } else if (m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity) { - if (v.toDouble() < 0) - m_data.type = m_data.type == ValueType::Infinity ? ValueType::NegativeInfinity : ValueType::Infinity; - return; - } else if (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) { - *this = 0; - return; - } - *this = toDouble() / v.toDouble(); - } + void divide(const Value &v) { value_divide(&m_data, &v.m_data, &m_data); } /*! Replaces the value with modulo of the value and the given value. */ - void mod(const Value &v) - { - if ((v == 0) || (m_data.type == ValueType::Infinity || m_data.type == ValueType::NegativeInfinity)) { - m_data.type = ValueType::NaN; - return; - } else if (v.m_data.type == ValueType::Infinity || v.m_data.type == ValueType::NegativeInfinity) { - return; - } - if (*this < 0 || v < 0) - *this = fmod(v.toDouble() + fmod(toDouble(), -v.toDouble()), v.toDouble()); - else - *this = fmod(toDouble(), v.toDouble()); - } + void mod(const Value &v) { value_mod(&m_data, &v.m_data, &m_data); } const Value &operator=(float v) { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - if (isInf(v)) - m_data.type = ValueType::Infinity; - else if (isNegativeInf(v)) - m_data.type = ValueType::NegativeInfinity; - else if (std::isnan(v)) - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::Double; - m_data.doubleValue = floatToDouble(v); - } - + value_assign_float(&m_data, v); return *this; } const Value &operator=(double v) { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - if (isInf(v)) - m_data.type = ValueType::Infinity; - else if (isNegativeInf(v)) - m_data.type = ValueType::NegativeInfinity; - else if (std::isnan(v)) - m_data.type = ValueType::NaN; - else { - m_data.type = ValueType::Double; - m_data.doubleValue = v; - } + value_assign_double(&m_data, v); return *this; } const Value &operator=(int v) { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::Integer; - m_data.intValue = v; + value_assign_int(&m_data, v); return *this; } const Value &operator=(long v) { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::Integer; - m_data.intValue = v; + value_assign_long(&m_data, v); return *this; } const Value &operator=(bool v) { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::Bool; - m_data.boolValue = v; + value_assign_bool(&m_data, v); return *this; } const Value &operator=(const std::string &v) { - if (v == "Infinity") { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::Infinity; - } else if (v == "-Infinity") { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::NegativeInfinity; - } else if (v == "NaN") { - if (m_data.type == ValueType::String) - delete m_data.stringValue; - - m_data.type = ValueType::NaN; - } else if (m_data.type == ValueType::String) - m_data.stringValue->assign(v); - else { - m_data.stringValue = new std::string(v); - m_data.type = ValueType::String; - } - + value_assign_string(&m_data, v); return *this; } - const Value &operator=(const char *v) { return (*this = std::string(v)); } + const Value &operator=(const char *v) + { + value_assign_cstring(&m_data, v); + return *this; + } const Value &operator=(const Value &v) { - switch (v.m_data.type) { - case ValueType::Integer: - m_data.intValue = v.m_data.intValue; - break; - - case ValueType::Double: - m_data.doubleValue = v.m_data.doubleValue; - break; - - case ValueType::Bool: - m_data.boolValue = v.m_data.boolValue; - break; - - case ValueType::String: - m_data.stringValue = new std::string(*v.m_data.stringValue); - break; - - default: - break; - } - - m_data.type = v.m_data.type; - + value_assign_copy(&m_data, &v.m_data); return *this; } private: ValueData m_data; - // 0 - is string - // 1 - is long - // 2 - is double - static int checkString(const std::string &str) - { - bool ok; - - if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) { - stringToLong(str, &ok); - return ok ? 1 : 0; - } else { - stringToDouble(str, &ok); - return ok ? 2 : 0; - } - } - - double getNumber(bool *ok = nullptr) const - { - // Equivalent to JavaScript Number(), *ok == false means NaN - if (ok) - *ok = true; - - switch (m_data.type) { - case ValueType::Integer: - return m_data.intValue; - - case ValueType::Double: - return m_data.doubleValue; - - case ValueType::Bool: - return m_data.boolValue; - - case ValueType::String: - return stringToDouble(*m_data.stringValue, ok); - - case ValueType::Infinity: - return std::numeric_limits::infinity(); - - case ValueType::NegativeInfinity: - return -std::numeric_limits::infinity(); - - case ValueType::NaN: - if (ok) - *ok = false; - - return 0; - - default: - assert(false); // this should never happen - if (ok) - *ok = false; - - return 0; - } - } - - friend bool operator==(const Value &v1, const Value &v2) - { - // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L121-L150 - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue == v2.m_data.intValue; - else if (v1.m_data.type == ValueType::Double && v2.m_data.type == ValueType::Double) - return v1.m_data.doubleValue == v2.m_data.doubleValue; - - bool ok; - double n1 = v1.getNumber(&ok); - double n2; - - if (ok) - n2 = v2.getNumber(&ok); - - if (!ok) { - // At least one argument can't be converted to a number - // Scratch compares strings as case insensitive - return stringsEqual(v1.toUtf16(), v2.toUtf16()); - } + friend bool operator==(const Value &v1, const Value &v2) { return value_equals(&v1.m_data, &v2.m_data); } - // Handle the special case of Infinity - if ((static_cast(v1.m_data.type) < 0) && (static_cast(v2.m_data.type) < 0)) { - assert(v1.m_data.type != ValueType::NaN); - assert(v2.m_data.type != ValueType::NaN); - return v1.m_data.type == v2.m_data.type; - } + friend bool operator!=(const Value &v1, const Value &v2) { return !value_equals(&v1.m_data, &v2.m_data); } - // Compare as numbers - return n1 == n2; - } - - friend bool operator!=(const Value &v1, const Value &v2) { return !(v1 == v2); } - - friend bool operator>(const Value &v1, const Value &v2) - { - if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { - if (v1.m_data.type == ValueType::Infinity) { - return v2.m_data.type != ValueType::Infinity; - } else if (v1.m_data.type == ValueType::NegativeInfinity) - return false; - else if (v2.m_data.type == ValueType::Infinity) - return false; - else if (v2.m_data.type == ValueType::NegativeInfinity) - return true; - } - - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue > v2.m_data.intValue; - else - return v1.toDouble() > v2.toDouble(); - } + friend bool operator>(const Value &v1, const Value &v2) { return value_greater(&v1.m_data, &v2.m_data); } - friend bool operator<(const Value &v1, const Value &v2) - { - if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { - if (v1.m_data.type == ValueType::Infinity) { - return false; - } else if (v1.m_data.type == ValueType::NegativeInfinity) - return v2.m_data.type != ValueType::NegativeInfinity; - else if (v2.m_data.type == ValueType::Infinity) - return v1.m_data.type != ValueType::Infinity; - else if (v2.m_data.type == ValueType::NegativeInfinity) - return false; - } - - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue < v2.m_data.intValue; - else - return v1.toDouble() < v2.toDouble(); - } + friend bool operator<(const Value &v1, const Value &v2) { return value_lower(&v1.m_data, &v2.m_data); } friend bool operator>=(const Value &v1, const Value &v2) { return v1 > v2 || v1 == v2; } @@ -755,355 +227,38 @@ class LIBSCRATCHCPP_EXPORT Value friend Value operator+(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { - if ((v1.m_data.type == ValueType::Infinity && v2.m_data.type == ValueType::NegativeInfinity) || - (v1.m_data.type == ValueType::NegativeInfinity && v2.m_data.type == ValueType::Infinity)) - return Value(SpecialValue::NaN); - else if (v1.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::Infinity) - return Value(SpecialValue::Infinity); - else if (v1.m_data.type == ValueType::NegativeInfinity || v2.m_data.type == ValueType::NegativeInfinity) - return Value(SpecialValue::NegativeInfinity); - } - - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue + v2.m_data.intValue; - else - return v1.toDouble() + v2.toDouble(); + Value ret; + value_add(&v1.m_data, &v2.m_data, &ret.m_data); + return ret; } friend Value operator-(const Value &v1, const Value &v2) { - if ((static_cast(v1.m_data.type) < 0) || (static_cast(v2.m_data.type) < 0)) { - if ((v1.m_data.type == ValueType::Infinity && v2.m_data.type == ValueType::Infinity) || - (v1.m_data.type == ValueType::NegativeInfinity && v2.m_data.type == ValueType::NegativeInfinity)) - return Value(SpecialValue::NaN); - else if (v1.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) - return Value(SpecialValue::Infinity); - else if (v1.m_data.type == ValueType::NegativeInfinity || v2.m_data.type == ValueType::Infinity) - return Value(SpecialValue::NegativeInfinity); - } - - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue - v2.m_data.intValue; - else - return v1.toDouble() - v2.toDouble(); + Value ret; + value_subtract(&v1.m_data, &v2.m_data, &ret.m_data); + return ret; } friend Value operator*(const Value &v1, const Value &v2) { - ValueType t1 = v1.m_data.type, t2 = v2.m_data.type; - if ((static_cast(t1) < 0 && t1 != ValueType::NaN) || (static_cast(t2) < 0 && t2 != ValueType::NaN)) { - if (t1 == ValueType::Infinity || t1 == ValueType::NegativeInfinity || t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity) { - bool mode = (t1 == ValueType::Infinity || t2 == ValueType::Infinity); - const Value &value = - ((t1 == ValueType::Infinity && (t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity)) || (t2 != ValueType::Infinity && t2 != ValueType::NegativeInfinity)) ? v2 : v1; - if (value > 0) - return Value(mode ? SpecialValue::Infinity : SpecialValue::NegativeInfinity); - else if (value < 0) - return Value(mode ? SpecialValue::NegativeInfinity : SpecialValue::Infinity); - else - return Value(SpecialValue::NaN); - } - } - - if (v1.m_data.type == ValueType::Integer && v2.m_data.type == ValueType::Integer) - return v1.m_data.intValue * v2.m_data.intValue; - else - return v1.toDouble() * v2.toDouble(); + Value ret; + value_multiply(&v1.m_data, &v2.m_data, &ret.m_data); + return ret; } friend Value operator/(const Value &v1, const Value &v2) { - if ((v1 == 0) && (v2 == 0)) - return Value(SpecialValue::NaN); - else if (v2.toDouble() == 0) - return v1 > 0 ? Value(SpecialValue::Infinity) : Value(SpecialValue::NegativeInfinity); - else if ((v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity) && - (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity)) { - return Value(SpecialValue::NaN); - } else if (v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity) { - if (v2.toDouble() < 0) - return v1.m_data.type == ValueType::Infinity ? Value(SpecialValue::NegativeInfinity) : Value(SpecialValue::Infinity); - else - return v1.m_data.type == ValueType::Infinity ? Value(SpecialValue::Infinity) : Value(SpecialValue::NegativeInfinity); - } else if (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) { - return 0; - } - return v1.toDouble() / v2.toDouble(); - } - - friend Value operator%(const Value &v1, const Value &v2) - { - - if ((v2 == 0) || (v1.m_data.type == ValueType::Infinity || v1.m_data.type == ValueType::NegativeInfinity)) - return Value(SpecialValue::NaN); - else if (v2.m_data.type == ValueType::Infinity || v2.m_data.type == ValueType::NegativeInfinity) { - return v1.toDouble(); - } - if (v1 < 0 || v2 < 0) - return fmod(v2.toDouble() + fmod(v1.toDouble(), -v2.toDouble()), v2.toDouble()); - else - return fmod(v1.toDouble(), v2.toDouble()); - } - - static bool stringsEqual(std::u16string s1, std::u16string s2) - { - std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); - std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); - return (s1.compare(s2) == 0); - } - - static bool stringsEqual(std::string s1, std::string s2) - { - std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); - std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); - return (s1.compare(s2) == 0); - } - - static long hexToDec(const std::string &s) - { - static const std::string digits = "0123456789abcdef"; - - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } - - std::istringstream stream(s); - long ret; - stream >> std::hex >> ret; + Value ret; + value_divide(&v1.m_data, &v2.m_data, &ret.m_data); return ret; } - static long octToDec(const std::string &s) + friend Value operator%(const Value &v1, const Value &v2) { - static const std::string digits = "01234567"; - - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } - - std::istringstream stream(s); - long ret; - stream >> std::oct >> ret; + Value ret; + value_mod(&v1.m_data, &v2.m_data, &ret.m_data); return ret; } - - static double binToDec(const std::string &s) - { - static const std::string digits = "01"; - - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } - - return std::stoi(s, 0, 2); - } - - static double stringToDouble(const std::string &s, bool *ok = nullptr) - { - if (ok) - *ok = false; - - if (s == "Infinity") { - if (ok) - *ok = true; - return std::numeric_limits::infinity(); - } else if (s == "-Infinity") { - if (ok) - *ok = true; - return -std::numeric_limits::infinity(); - } else if (s == "NaN") { - if (ok) - *ok = true; - return 0; - } - - if (s.size() >= 2 && s[0] == '0') { - std::string sub = s.substr(2, s.size() - 2); - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); - - if (s[1] == 'x' || s[1] == 'X') { - return hexToDec(sub); - } else if (s[1] == 'o' || s[1] == 'O') { - return octToDec(sub); - } else if (s[1] == 'b' || s[1] == 'B') { - return binToDec(sub); - } - } - - static const std::string digits = "0123456789.eE+-"; - const std::string *stringPtr = &s; - bool customStr = false; - - if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) { - std::string *localPtr = new std::string(s); - stringPtr = localPtr; - customStr = true; - - while (!localPtr->empty() && (localPtr->at(0) == ' ')) - localPtr->erase(0, 1); - - while (!localPtr->empty() && (localPtr->back() == ' ')) - localPtr->pop_back(); - } - - for (char c : *stringPtr) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } - - try { - if (ok) - *ok = true; - - // Set locale to C to avoid conversion issues - std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); - std::setlocale(LC_NUMERIC, "C"); - - double ret = std::stod(*stringPtr); - - // Restore old locale - std::setlocale(LC_NUMERIC, oldLocale.c_str()); - - if (customStr) - delete stringPtr; - - return ret; - } catch (...) { - if (ok) - *ok = false; - return 0; - } - } - - static long stringToLong(const std::string &s, bool *ok = nullptr) - { - if (ok) - *ok = false; - - if (s == "Infinity") { - if (ok) - *ok = true; - return std::numeric_limits::infinity(); - } else if (s == "-Infinity") { - if (ok) - *ok = true; - return -std::numeric_limits::infinity(); - } else if (s == "NaN") { - if (ok) - *ok = true; - return 0; - } - - if (s.size() >= 2 && s[0] == '0') { - std::string sub = s.substr(2, s.size() - 2); - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); - - if (s[1] == 'x' || s[1] == 'X') { - return hexToDec(sub); - } else if (s[1] == 'o' || s[1] == 'O') { - return octToDec(sub); - } else if (s[1] == 'b' || s[1] == 'B') { - return binToDec(sub); - } - } - - static const std::string digits = "0123456789.eE+-"; - - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } - - try { - if (ok) - *ok = true; - return std::stol(s); - } catch (...) { - if (ok) - *ok = false; - return 0; - } - } - - static std::string doubleToString(double v) - { - std::stringstream stream; - - if (v != 0) { - const int exponent = std::log10(std::abs(v)); - - if (exponent > 20) - stream << std::scientific << std::setprecision(digitCount(v / std::pow(10, exponent + 1)) - 1) << v; - else - stream << std::setprecision(std::max(16u, digitCount(v))) << v; - } else - stream << std::setprecision(std::max(16u, digitCount(v))) << v; - - std::string s = stream.str(); - std::size_t index; - - for (int i = 0; i < 2; i++) { - if (i == 0) - index = s.find("e+"); - else - index = s.find("e-"); - - if (index != std::string::npos) { - while ((s.size() >= index + 3) && (s[index + 2] == '0')) - s.erase(index + 2, 1); - } - } - - return s; - } - - static double floatToDouble(float v) - { - unsigned int digits = digitCount(v); - double f = std::pow(10, digits); - return std::round(v * f) / f; - } - - template - static unsigned int digitCount(T v) - { - const T epsilon = 0.0000001; - T intpart; - unsigned int i = 1, j = 0; - - if (std::abs(std::modf(v, &intpart)) >= epsilon) { - T tmp_intpart; - - while (std::abs(std::modf(v * std::pow(10, i), &tmp_intpart)) >= epsilon) - i++; - } - - while (std::abs(intpart / pow(10, j)) >= 1.0) - j++; - - return i + j; - } - - template - static bool isInf(T v) - { - return v > 0 && std::isinf(v); - } - - template - static bool isNegativeInf(T v) - { - return v < 0 && std::isinf(v); - } }; } // namespace libscratchcpp diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h new file mode 100644 index 00000000..f5707166 --- /dev/null +++ b/include/scratchcpp/value_functions.h @@ -0,0 +1,52 @@ +#pragma once + +#include "valuedata.h" + +namespace libscratchcpp +{ + +extern "C" +{ + LIBSCRATCHCPP_EXPORT void value_free(ValueData *v); + + LIBSCRATCHCPP_EXPORT void value_init(ValueData *v); + + LIBSCRATCHCPP_EXPORT void value_assign_float(ValueData *v, float numberValue); + LIBSCRATCHCPP_EXPORT void value_assign_double(ValueData *v, double numberValue); + LIBSCRATCHCPP_EXPORT void value_assign_int(ValueData *v, int numberValue); + LIBSCRATCHCPP_EXPORT void value_assign_size_t(ValueData *v, size_t numberValue); + LIBSCRATCHCPP_EXPORT void value_assign_long(ValueData *v, long numberValue); + LIBSCRATCHCPP_EXPORT void value_assign_bool(ValueData *v, bool boolValue); + LIBSCRATCHCPP_EXPORT void value_assign_string(ValueData *v, const std::string &stringValue); + LIBSCRATCHCPP_EXPORT void value_assign_cstring(ValueData *v, const char *stringValue); + LIBSCRATCHCPP_EXPORT void value_assign_special(ValueData *v, SpecialValue specialValue); + LIBSCRATCHCPP_EXPORT void value_assign_copy(ValueData *v, const ValueData *another); + + LIBSCRATCHCPP_EXPORT bool value_isInfinity(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isNegativeInfinity(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isNaN(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isNumber(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isValidNumber(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isInt(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isBool(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_isString(const ValueData *v); + + LIBSCRATCHCPP_EXPORT long value_toLong(const ValueData *v); + LIBSCRATCHCPP_EXPORT int value_toInt(const ValueData *v); + LIBSCRATCHCPP_EXPORT double value_toDouble(const ValueData *v); + LIBSCRATCHCPP_EXPORT bool value_toBool(const ValueData *v); + LIBSCRATCHCPP_EXPORT void value_toString(const ValueData *v, std::string *dst); + LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst); + + LIBSCRATCHCPP_EXPORT void value_add(const ValueData *v1, const ValueData *v2, ValueData *dst); + LIBSCRATCHCPP_EXPORT void value_subtract(const ValueData *v1, const ValueData *v2, ValueData *dst); + LIBSCRATCHCPP_EXPORT void value_multiply(const ValueData *v1, const ValueData *v2, ValueData *dst); + LIBSCRATCHCPP_EXPORT void value_divide(const ValueData *v1, const ValueData *v2, ValueData *dst); + LIBSCRATCHCPP_EXPORT void value_mod(const ValueData *v1, const ValueData *v2, ValueData *dst); + + LIBSCRATCHCPP_EXPORT bool value_equals(const ValueData *v1, const ValueData *v2); + LIBSCRATCHCPP_EXPORT bool value_greater(const ValueData *v1, const ValueData *v2); + LIBSCRATCHCPP_EXPORT bool value_lower(const ValueData *v1, const ValueData *v2); +} + +} // namespace libscratchcpp diff --git a/include/scratchcpp/valuedata.h b/include/scratchcpp/valuedata.h new file mode 100644 index 00000000..aef7881f --- /dev/null +++ b/include/scratchcpp/valuedata.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "global.h" + +namespace libscratchcpp +{ + +enum class LIBSCRATCHCPP_EXPORT SpecialValue +{ + Infinity, + NegativeInfinity, + NaN +}; + +enum class LIBSCRATCHCPP_EXPORT ValueType +{ + Integer = 0, + Double = 1, + Bool = 2, + String = 3, + Infinity = -1, + NegativeInfinity = -2, + NaN = -3 +}; + +extern "C" +{ + /*! \brief The ValueData struct holds the data of Value. It's used in compiled Scratch code for better performance. */ + struct LIBSCRATCHCPP_EXPORT ValueData + { + union + { + long intValue; + double doubleValue; + bool boolValue; + std::string *stringValue; + }; + + ValueType type; + }; +} + +} // namespace libscratchcpp diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp index 58851344..b713ae6a 100644 --- a/src/blocks/operatorblocks.cpp +++ b/src/blocks/operatorblocks.cpp @@ -256,9 +256,9 @@ unsigned int OperatorBlocks::op_ln(VirtualMachine *vm) { const Value &v = *vm->getInput(0, 1); if (v < 0) - vm->replaceReturnValue(Value(Value::SpecialValue::NaN), 1); + vm->replaceReturnValue(Value(SpecialValue::NaN), 1); else if (v == 0 || v.isNaN()) - vm->replaceReturnValue(Value(Value::SpecialValue::NegativeInfinity), 1); + vm->replaceReturnValue(Value(SpecialValue::NegativeInfinity), 1); else if (!v.isInfinity()) vm->replaceReturnValue(std::log(v.toDouble()), 1); return 0; @@ -268,9 +268,9 @@ unsigned int OperatorBlocks::op_log(VirtualMachine *vm) { const Value &v = *vm->getInput(0, 1); if (v < 0) - vm->replaceReturnValue(Value(Value::SpecialValue::NaN), 1); + vm->replaceReturnValue(Value(SpecialValue::NaN), 1); else if (v == 0 || v.isNaN()) - vm->replaceReturnValue(Value(Value::SpecialValue::NegativeInfinity), 1); + vm->replaceReturnValue(Value(SpecialValue::NegativeInfinity), 1); else if (!v.isInfinity()) vm->replaceReturnValue(std::log10(v.toDouble()), 1); return 0; diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp index 6f2c0e4f..43ac22d2 100644 --- a/src/engine/virtualmachine_p.cpp +++ b/src/engine/virtualmachine_p.cpp @@ -460,7 +460,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value *v = READ_REG(0, 1); if (v->isNegativeInfinity()) - REPLACE_RET_VALUE(Value(Value::SpecialValue::Infinity), 1); + REPLACE_RET_VALUE(Value(SpecialValue::Infinity), 1); else if (!v->isInfinity()) REPLACE_RET_VALUE(std::abs(v->toDouble()), 1); DISPATCH(); @@ -486,7 +486,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value &v = *READ_REG(0, 1); if (v < 0) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else if (!v.isInfinity()) REPLACE_RET_VALUE(std::sqrt(v.toDouble()), 1); DISPATCH(); @@ -496,7 +496,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value *v = READ_REG(0, 1); if (v->isInfinity() || v->isNegativeInfinity()) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else REPLACE_RET_VALUE(std::sin(v->toDouble() * pi / 180), 1); DISPATCH(); @@ -506,7 +506,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value *v = READ_REG(0, 1); if (v->isInfinity() || v->isNegativeInfinity()) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else REPLACE_RET_VALUE(std::cos(v->toDouble() * pi / 180), 1); DISPATCH(); @@ -516,7 +516,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value *v = READ_REG(0, 1); if (v->isInfinity() || v->isNegativeInfinity()) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else { long mod; if (v->toLong() < 0) @@ -524,9 +524,9 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) else mod = v->toLong() % 360; if (mod == 90) - REPLACE_RET_VALUE(Value(Value::SpecialValue::Infinity), 1); + REPLACE_RET_VALUE(Value(SpecialValue::Infinity), 1); else if (mod == 270) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NegativeInfinity), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NegativeInfinity), 1); else REPLACE_RET_VALUE(std::tan(v->toDouble() * pi / 180), 1); } @@ -537,7 +537,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value &v = *READ_REG(0, 1); if (v < -1 || v > 1) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else REPLACE_RET_VALUE(std::asin(v.toDouble()) * 180 / pi, 1); DISPATCH(); @@ -547,7 +547,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) { const Value &v = *READ_REG(0, 1); if (v < -1 || v > 1) - REPLACE_RET_VALUE(Value(Value::SpecialValue::NaN), 1); + REPLACE_RET_VALUE(Value(SpecialValue::NaN), 1); else REPLACE_RET_VALUE(std::acos(v.toDouble()) * 180 / pi, 1); DISPATCH(); diff --git a/src/scratch/CMakeLists.txt b/src/scratch/CMakeLists.txt index d317335a..42e3221c 100644 --- a/src/scratch/CMakeLists.txt +++ b/src/scratch/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(scratchcpp PRIVATE - value.cpp + value_functions.cpp + value_functions_p.h target.cpp target_p.cpp target_p.h diff --git a/src/scratch/value.cpp b/src/scratch/value.cpp deleted file mode 100644 index f3d8ad46..00000000 --- a/src/scratch/value.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -using namespace libscratchcpp; - -std::u16string Value::toUtf16() const -{ - return utf8::utf8to16(toString()); -} diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp new file mode 100644 index 00000000..249e6990 --- /dev/null +++ b/src/scratch/value_functions.cpp @@ -0,0 +1,619 @@ +#include +#include +#include + +#include "value_functions_p.h" + +namespace libscratchcpp +{ + +extern "C" +{ + /* free */ + + /*! Frees memory used by the given value if it's a string. */ + void value_free(ValueData *v) + { + if (v->type == ValueType::String) { + assert(v->stringValue); + delete v->stringValue; + v->stringValue = nullptr; + } + } + + /* init */ + + /*! Initializes the given value. */ + void value_init(ValueData *v) + { + v->type = ValueType::Integer; + v->intValue = 0; + } + + /* assign */ + + /*! Assigns number of type 'float' to the given value. */ + void value_assign_float(ValueData *v, float numberValue) + { + value_free(v); + + if (value_isInf(numberValue)) + v->type = ValueType::Infinity; + else if (value_isNegativeInf(numberValue)) + v->type = ValueType::NegativeInfinity; + else if (std::isnan(numberValue)) + v->type = ValueType::NaN; + else { + v->type = ValueType::Double; + v->doubleValue = value_floatToDouble(numberValue); + } + } + + /*! Assigns number of type 'double' to the given value. */ + void value_assign_double(ValueData *v, double numberValue) + { + value_free(v); + + if (value_isInf(numberValue)) + v->type = ValueType::Infinity; + else if (value_isNegativeInf(numberValue)) + v->type = ValueType::NegativeInfinity; + else if (std::isnan(numberValue)) + v->type = ValueType::NaN; + else { + v->type = ValueType::Double; + v->doubleValue = numberValue; + } + } + + /*! Assigns number of type 'int' to the given value. */ + void value_assign_int(ValueData *v, int numberValue) + { + value_free(v); + + v->type = ValueType::Integer; + v->intValue = numberValue; + } + + /*! Assigns number of type 'size_t' to the given value. */ + void value_assign_size_t(ValueData *v, size_t numberValue) + { + value_free(v); + + v->type = ValueType::Integer; + v->intValue = numberValue; + } + + /*! Assigns number of type 'long' to the given value. */ + void value_assign_long(ValueData *v, long numberValue) + { + value_free(v); + + v->type = ValueType::Integer; + v->intValue = numberValue; + } + + /*! Assigns boolean to the given value. */ + void value_assign_bool(ValueData *v, bool boolValue) + { + value_free(v); + + v->type = ValueType::Bool; + v->boolValue = boolValue; + } + + /*! Assigns string to the given value. */ + void value_assign_string(ValueData *v, const std::string &stringValue) + { + if (stringValue == "Infinity") { + value_free(v); + v->type = ValueType::Infinity; + } else if (stringValue == "-Infinity") { + value_free(v); + v->type = ValueType::NegativeInfinity; + } else if (stringValue == "NaN") { + value_free(v); + v->type = ValueType::NaN; + } else { + if (v->type == ValueType::String) + v->stringValue->assign(stringValue); + else { + value_free(v); + v->type = ValueType::String; + v->stringValue = new std::string(stringValue); + } + } + } + + /*! Assigns C string to the given value. */ + void value_assign_cstring(ValueData *v, const char *stringValue) + { + value_assign_string(v, std::string(stringValue)); + } + + /*! Assigns special value to the given value. */ + void value_assign_special(ValueData *v, SpecialValue specialValue) + { + value_free(v); + + if (specialValue == SpecialValue::Infinity) + v->type = ValueType::Infinity; + else if (specialValue == SpecialValue::NegativeInfinity) + v->type = ValueType::NegativeInfinity; + else if (specialValue == SpecialValue::NaN) + v->type = ValueType::NaN; + else { + v->type = ValueType::Integer; + v->intValue = 0; + } + } + + /*! Assigns another value to the given value. */ + void value_assign_copy(ValueData *v, const libscratchcpp::ValueData *another) + { + if (another->type == ValueType::Integer) { + value_free(v); + v->intValue = another->intValue; + } else if (another->type == ValueType::Double) { + value_free(v); + v->doubleValue = another->doubleValue; + } else if (another->type == ValueType::Bool) { + value_free(v); + v->boolValue = another->boolValue; + } else if (another->type == ValueType::String) { + if (v->type == ValueType::String) + v->stringValue->assign(*another->stringValue); + else { + value_free(v); + v->stringValue = new std::string(*another->stringValue); + } + } + + v->type = another->type; + } + + /* type checkers */ + + /*! Returns true if the given value is Infinity. */ + bool value_isInfinity(const libscratchcpp::ValueData *v) + { + switch (v->type) { + case ValueType::Infinity: + return true; + case ValueType::Integer: + return value_isInf(v->intValue); + case ValueType::Double: + return value_isInf(v->doubleValue); + case ValueType::String: + return *v->stringValue == "Infinity"; + default: + return false; + } + } + + /*! Returns true if the given value is -Infinity. */ + bool value_isNegativeInfinity(const libscratchcpp::ValueData *v) + { + switch (v->type) { + case ValueType::NegativeInfinity: + return true; + case ValueType::Integer: + return value_isNegativeInf(-v->intValue); + case ValueType::Double: + return value_isNegativeInf(-v->doubleValue); + case ValueType::String: + return *v->stringValue == "-Infinity"; + default: + return false; + } + } + + /*! Returns true if the given value is NaN. */ + bool value_isNaN(const libscratchcpp::ValueData *v) + { + switch (v->type) { + case ValueType::NaN: + return true; + case ValueType::Double: + assert(!std::isnan(v->doubleValue)); + return std::isnan(v->doubleValue); + case ValueType::String: + return *v->stringValue == "NaN"; + default: + return false; + } + } + + /*! Returns true if the given value is a number. */ + bool value_isNumber(const libscratchcpp::ValueData *v) + { + return v->type == ValueType::Integer || v->type == ValueType::Double; + } + + /*! Returns true if the given value is a number or can be converted to a number. */ + bool value_isValidNumber(const libscratchcpp::ValueData *v) + { + if (value_isNaN(v)) + return false; + + if (value_isInfinity(v) || value_isNegativeInfinity(v)) + return true; + + assert(v->type != ValueType::Infinity && v->type != ValueType::NegativeInfinity); + + switch (v->type) { + case ValueType::Integer: + case ValueType::Double: + case ValueType::Bool: + return true; + case ValueType::String: + return v->stringValue->empty() || value_checkString(*v->stringValue) > 0; + default: + return false; + } + } + + /*! Returns true if the given value represents a round integer. */ + bool value_isInt(const ValueData *v) + { + // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L157-L181 + switch (v->type) { + case ValueType::Integer: + case ValueType::Bool: + case ValueType::Infinity: + case ValueType::NegativeInfinity: + case ValueType::NaN: + return true; + case ValueType::Double: { + double intpart; + std::modf(v->doubleValue, &intpart); + return v->doubleValue == intpart; + } + case ValueType::String: + return v->stringValue->find('.') == std::string::npos; + } + + return false; + } + + /*! Returns true if the given value is a boolean. */ + bool value_isBool(const libscratchcpp::ValueData *v) + { + return v->type == ValueType::Bool; + } + + /*! Returns true if the given value is a string. */ + bool value_isString(const libscratchcpp::ValueData *v) + { + return v->type == ValueType::String; + } + + /* conversion */ + + /*! Returns the long representation of the given value. */ + long value_toLong(const libscratchcpp::ValueData *v) + { + if (v->type == ValueType::Integer) + return v->intValue; + else if (v->type == ValueType::Double) + return v->doubleValue; + else if (v->type == ValueType::Bool) + return v->boolValue; + else if (v->type == ValueType::String) + return value_stringToLong(*v->stringValue); + else + return 0; + } + + /*! Returns the int representation of the given value. */ + int value_toInt(const libscratchcpp::ValueData *v) + { + if (v->type == ValueType::Integer) + return v->intValue; + else if (v->type == ValueType::Double) + return v->doubleValue; + else if (v->type == ValueType::Bool) + return v->boolValue; + else if (v->type == ValueType::String) + return value_stringToLong(*v->stringValue); + else + return 0; + } + + /*! Returns the double representation of the given value. */ + double value_toDouble(const libscratchcpp::ValueData *v) + { + if (v->type == ValueType::Double) + return v->doubleValue; + else if (v->type == ValueType::Integer) + return v->intValue; + else if (v->type == ValueType::Bool) + return v->boolValue; + else if (v->type == ValueType::String) + return value_stringToDouble(*v->stringValue); + else if (v->type == ValueType::Infinity) + return std::numeric_limits::infinity(); + else if (v->type == ValueType::NegativeInfinity) + return -std::numeric_limits::infinity(); + else + return 0; + } + + /*! Returns the boolean representation of the given value. */ + bool value_toBool(const libscratchcpp::ValueData *v) + { + if (v->type == ValueType::Bool) { + return v->boolValue; + } else if (v->type == ValueType::Integer) { + return v->intValue != 0; + } else if (v->type == ValueType::Double) { + return v->doubleValue != 0; + } else if (v->type == ValueType::String) { + return !v->stringValue->empty() && !value_stringsEqual(*v->stringValue, "false") && *v->stringValue != "0"; + } else if (v->type == ValueType::Infinity || v->type == ValueType::NegativeInfinity) { + return true; + } else if (v->type == ValueType::NaN) { + return false; + } else { + return false; + } + } + + /*! Writes the string representation of the given value to dst. */ + void value_toString(const libscratchcpp::ValueData *v, std::string *dst) + { + if (v->type == ValueType::String) + dst->assign(*v->stringValue); + else if (v->type == ValueType::Integer) + dst->assign(std::to_string(v->intValue)); + else if (v->type == ValueType::Double) + dst->assign(value_doubleToString(v->doubleValue)); + else if (v->type == ValueType::Bool) + dst->assign(v->boolValue ? "true" : "false"); + else if (v->type == ValueType::Infinity) + dst->assign("Infinity"); + else if (v->type == ValueType::NegativeInfinity) + dst->assign("-Infinity"); + else if (v->type == ValueType::NaN) + dst->assign("NaN"); + else + dst->clear(); + } + + /*! Writes the UTF-16 representation of the given value to dst. */ + void value_toUtf16(const libscratchcpp::ValueData *v, std::u16string *dst) + { + std::string s; + value_toString(v, &s); + dst->assign(utf8::utf8to16(s)); + } + + /* operations */ + + /*! Adds the given values and writes the result to dst. */ + void value_add(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) + { + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) { + value_assign_long(dst, v1->intValue + v2->intValue); + return; + } else if (v1->type == ValueType::Double && v2->type == ValueType::Double) { + value_assign_double(dst, v1->doubleValue + v2->doubleValue); + return; + } else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) { + value_assign_long(dst, v1->boolValue + v2->boolValue); + return; + } else if ((static_cast(v1->type) < 0) || (static_cast(v2->type) < 0)) { + if ((v1->type == ValueType::Infinity && v2->type == ValueType::NegativeInfinity) || (v1->type == ValueType::NegativeInfinity && v2->type == ValueType::Infinity)) { + value_assign_special(dst, SpecialValue::NaN); + return; + } else if (v1->type == ValueType::Infinity || v2->type == ValueType::Infinity) { + value_assign_special(dst, SpecialValue::Infinity); + return; + } else if (v1->type == ValueType::NegativeInfinity || v2->type == ValueType::NegativeInfinity) { + value_assign_special(dst, SpecialValue::NegativeInfinity); + return; + } + } + + value_assign_double(dst, value_toDouble(v1) + value_toDouble(v2)); + } + + /*! Subtracts the given values and writes the result to dst. */ + void value_subtract(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) + { + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) { + value_assign_long(dst, v1->intValue - v2->intValue); + return; + } else if (v1->type == ValueType::Double && v2->type == ValueType::Double) { + value_assign_double(dst, v1->doubleValue - v2->doubleValue); + return; + } else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) { + value_assign_long(dst, v1->boolValue - v2->boolValue); + return; + } else if ((static_cast(v1->type) < 0) || (static_cast(v2->type) < 0)) { + if ((v1->type == ValueType::Infinity && v2->type == ValueType::Infinity) || (v1->type == ValueType::NegativeInfinity && v2->type == ValueType::NegativeInfinity)) { + value_assign_special(dst, SpecialValue::NaN); + return; + } else if (v1->type == ValueType::Infinity || v2->type == ValueType::NegativeInfinity) { + value_assign_special(dst, SpecialValue::Infinity); + return; + } else if (v1->type == ValueType::NegativeInfinity || v2->type == ValueType::Infinity) { + value_assign_special(dst, SpecialValue::NegativeInfinity); + return; + } + } + + value_assign_double(dst, value_toDouble(v1) - value_toDouble(v2)); + } + + /*! Multiplies the given values and writes the result to dst. */ + void value_multiply(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) + { + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) + value_assign_long(dst, v1->intValue * v2->intValue); + else if (v1->type == ValueType::Double && v2->type == ValueType::Double) + value_assign_double(dst, v1->doubleValue * v2->doubleValue); + else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) + value_assign_long(dst, v1->boolValue * v2->boolValue); + else { + const ValueType t1 = v1->type, t2 = v2->type; + + if ((static_cast(t1) < 0 && t1 != ValueType::NaN) || (static_cast(t2) < 0 && t2 != ValueType::NaN)) { + if (t1 == ValueType::Infinity || t1 == ValueType::NegativeInfinity || t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity) { + bool mode = (t1 == ValueType::Infinity || t2 == ValueType::Infinity); + const ValueData *value; + + if ((t1 == ValueType::Infinity && (t2 == ValueType::Infinity || t2 == ValueType::NegativeInfinity)) || (t2 != ValueType::Infinity && t2 != ValueType::NegativeInfinity)) + value = v2; + else + value = v1; + + if (value_isPositive(value)) + value_assign_special(dst, mode ? SpecialValue::Infinity : SpecialValue::NegativeInfinity); + else if (value_isNegative(value)) + value_assign_special(dst, mode ? SpecialValue::NegativeInfinity : SpecialValue::Infinity); + else + value_assign_special(dst, SpecialValue::NaN); + } + } else + value_assign_double(dst, value_toDouble(v1) * value_toDouble(v2)); + } + } + + /*! Divides the given values and writes the result to dst. */ + void value_divide(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) + { + if (value_isZero(v1) && value_isZero(v2)) + value_assign_special(dst, SpecialValue::NaN); + else if (value_toDouble(v2) == 0) { + if (value_isPositive(v1)) + value_assign_special(dst, SpecialValue::Infinity); + else + value_assign_special(dst, SpecialValue::NegativeInfinity); + } else if ((v1->type == ValueType::Infinity || v1->type == ValueType::NegativeInfinity) && (v2->type == ValueType::Infinity || v2->type == ValueType::NegativeInfinity)) { + value_assign_special(dst, SpecialValue::NaN); + } else if (v1->type == ValueType::Infinity || v1->type == ValueType::NegativeInfinity) { + if (value_toDouble(v2) < 0) { + if (v1->type == ValueType::Infinity) + value_assign_special(dst, SpecialValue::NegativeInfinity); + else + value_assign_special(dst, SpecialValue::Infinity); + } else { + if (v1->type == ValueType::Infinity) + value_assign_special(dst, SpecialValue::Infinity); + else + value_assign_special(dst, SpecialValue::NegativeInfinity); + } + } else if (v2->type == ValueType::Infinity || v2->type == ValueType::NegativeInfinity) { + value_assign_long(dst, 0); + } else + value_assign_double(dst, value_toDouble(v1) / value_toDouble(v2)); + } + + /*! Calculates the modulo the given values and writes the result to dst. */ + void value_mod(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) + { + if ((v2 == 0) || (v1->type == ValueType::Infinity || v1->type == ValueType::NegativeInfinity)) + value_assign_special(dst, SpecialValue::NaN); + else if (v2->type == ValueType::Infinity || v2->type == ValueType::NegativeInfinity) + value_assign_double(dst, value_toDouble(v1)); + else if (value_isNegative(v1) || value_isNegative(v2)) + value_assign_double(dst, fmod(value_toDouble(v2) + fmod(value_toDouble(v1), -value_toDouble(v2)), value_toDouble(v2))); + else + value_assign_double(dst, fmod(value_toDouble(v1), value_toDouble(v2))); + } + + /* comparison */ + + /*! Returns true if the given values are equal. */ + bool value_equals(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2) + { + // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L121-L150 + assert(v1 && v2); + + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) + return v1->intValue == v2->intValue; + else if (v1->type == ValueType::Double && v2->type == ValueType::Double) + return v1->doubleValue == v2->doubleValue; + else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) + return v1->boolValue == v2->boolValue; + + bool ok; + double n1 = value_getNumber(v1, &ok); + double n2; + + if (ok) + n2 = value_getNumber(v2, &ok); + + if (!ok) { + // At least one argument can't be converted to a number + // Scratch compares strings as case insensitive + std::u16string s1, s2; + value_toUtf16(v1, &s1); + value_toUtf16(v2, &s2); + return value_u16StringsEqual(s1, s2); + } + + // Handle the special case of Infinity + if ((static_cast(v1->type) < 0) && (static_cast(v2->type) < 0)) { + assert(v1->type != ValueType::NaN); + assert(v2->type != ValueType::NaN); + return v1->type == v2->type; + } + + // Compare as numbers + return n1 == n2; + } + + /*! Returns true if the first value is greater than the second value. */ + bool value_greater(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2) + { + assert(v1 && v2); + + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) + return v1->intValue > v2->intValue; + else if (v1->type == ValueType::Double && v2->type == ValueType::Double) + return v1->doubleValue > v2->doubleValue; + else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) + return v1->boolValue > v2->boolValue; + else if ((static_cast(v1->type) < 0) || (static_cast(v2->type) < 0)) { + if (v1->type == ValueType::Infinity) { + return v2->type != ValueType::Infinity; + } else if (v1->type == ValueType::NegativeInfinity) + return false; + else if (v2->type == ValueType::Infinity) + return false; + else if (v2->type == ValueType::NegativeInfinity) + return true; + } + + return value_toDouble(v1) > value_toDouble(v2); + } + + /*! Returns true if the first value is lower than the second value. */ + bool value_lower(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2) + { + assert(v1 && v2); + + if (v1->type == ValueType::Integer && v2->type == ValueType::Integer) + return v1->intValue < v2->intValue; + else if (v1->type == ValueType::Double && v2->type == ValueType::Double) + return v1->doubleValue < v2->doubleValue; + else if (v1->type == ValueType::Bool && v2->type == ValueType::Bool) + return v1->boolValue < v2->boolValue; + else if ((static_cast(v1->type) < 0) || (static_cast(v2->type) < 0)) { + if (v1->type == ValueType::Infinity) { + return false; + } else if (v1->type == ValueType::NegativeInfinity) + return v2->type != ValueType::NegativeInfinity; + else if (v2->type == ValueType::Infinity) + return v1->type != ValueType::Infinity; + else if (v2->type == ValueType::NegativeInfinity) + return false; + } + + return value_toDouble(v1) < value_toDouble(v2); + } +} + +} // namespace libscratchcpp diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h new file mode 100644 index 00000000..bc070038 --- /dev/null +++ b/src/scratch/value_functions_p.h @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace libscratchcpp +{ + +template +inline unsigned int value_digitCount(T v) +{ + const T epsilon = 0.0000001; + T intpart; + unsigned int i = 1, j = 0; + + if (std::abs(std::modf(v, &intpart)) >= epsilon) { + T tmp_intpart; + + while (std::abs(std::modf(v * std::pow(10, i), &tmp_intpart)) >= epsilon) + i++; + } + + while (std::abs(intpart / pow(10, j)) >= 1.0) + j++; + + return i + j; +} + +template +inline bool value_isInf(T v) +{ + return v > 0 && std::isinf(v); +} + +template +inline bool value_isNegativeInf(T v) +{ + return v < 0 && std::isinf(v); +} + +extern "C" +{ + + inline bool value_u16StringsEqual(std::u16string s1, std::u16string s2) + { + std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); + std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); + return (s1.compare(s2) == 0); + } + + inline bool value_stringsEqual(std::string s1, std::string s2) + { + std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); + std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); + return (s1.compare(s2) == 0); + } + + inline long value_hexToDec(const std::string &s) + { + static const std::string digits = "0123456789abcdef"; + + for (char c : s) { + if (digits.find(c) == std::string::npos) { + return 0; + } + } + + std::istringstream stream(s); + long ret; + stream >> std::hex >> ret; + return ret; + } + + inline long value_octToDec(const std::string &s) + { + static const std::string digits = "01234567"; + + for (char c : s) { + if (digits.find(c) == std::string::npos) { + return 0; + } + } + + std::istringstream stream(s); + long ret; + stream >> std::oct >> ret; + return ret; + } + + inline double value_binToDec(const std::string &s) + { + static const std::string digits = "01"; + + for (char c : s) { + if (digits.find(c) == std::string::npos) { + return 0; + } + } + + return std::stoi(s, 0, 2); + } + + inline double value_stringToDouble(const std::string &s, bool *ok = nullptr) + { + if (ok) + *ok = false; + + if (s == "Infinity") { + if (ok) + *ok = true; + return std::numeric_limits::infinity(); + } else if (s == "-Infinity") { + if (ok) + *ok = true; + return -std::numeric_limits::infinity(); + } else if (s == "NaN") { + if (ok) + *ok = true; + return 0; + } + + if (s.size() >= 2 && s[0] == '0') { + std::string sub = s.substr(2, s.size() - 2); + std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); + + if (s[1] == 'x' || s[1] == 'X') { + return value_hexToDec(sub); + } else if (s[1] == 'o' || s[1] == 'O') { + return value_octToDec(sub); + } else if (s[1] == 'b' || s[1] == 'B') { + return value_binToDec(sub); + } + } + + static const std::string digits = "0123456789.eE+-"; + const std::string *stringPtr = &s; + bool customStr = false; + + if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) { + std::string *localPtr = new std::string(s); + stringPtr = localPtr; + customStr = true; + + while (!localPtr->empty() && (localPtr->at(0) == ' ')) + localPtr->erase(0, 1); + + while (!localPtr->empty() && (localPtr->back() == ' ')) + localPtr->pop_back(); + } + + for (char c : *stringPtr) { + if (digits.find(c) == std::string::npos) { + return 0; + } + } + + try { + if (ok) + *ok = true; + + // Set locale to C to avoid conversion issues + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "C"); + + double ret = std::stod(*stringPtr); + + // Restore old locale + std::setlocale(LC_NUMERIC, oldLocale.c_str()); + + if (customStr) + delete stringPtr; + + return ret; + } catch (...) { + if (ok) + *ok = false; + return 0; + } + } + + inline long value_stringToLong(const std::string &s, bool *ok = nullptr) + { + if (ok) + *ok = false; + + if (s == "Infinity") { + if (ok) + *ok = true; + return std::numeric_limits::infinity(); + } else if (s == "-Infinity") { + if (ok) + *ok = true; + return -std::numeric_limits::infinity(); + } else if (s == "NaN") { + if (ok) + *ok = true; + return 0; + } + + if (s.size() >= 2 && s[0] == '0') { + std::string sub = s.substr(2, s.size() - 2); + std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); + + if (s[1] == 'x' || s[1] == 'X') { + return value_hexToDec(sub); + } else if (s[1] == 'o' || s[1] == 'O') { + return value_octToDec(sub); + } else if (s[1] == 'b' || s[1] == 'B') { + return value_binToDec(sub); + } + } + + static const std::string digits = "0123456789.eE+-"; + + for (char c : s) { + if (digits.find(c) == std::string::npos) { + return 0; + } + } + + try { + if (ok) + *ok = true; + return std::stol(s); + } catch (...) { + if (ok) + *ok = false; + return 0; + } + } +} + +inline std::string value_doubleToString(double v) +{ + std::stringstream stream; + + if (v != 0) { + const int exponent = std::log10(std::abs(v)); + + if (exponent > 20) + stream << std::scientific << std::setprecision(value_digitCount(v / std::pow(10, exponent + 1)) - 1) << v; + else + stream << std::setprecision(std::max(16u, value_digitCount(v))) << v; + } else + stream << std::setprecision(std::max(16u, value_digitCount(v))) << v; + + std::string s = stream.str(); + std::size_t index; + + for (int i = 0; i < 2; i++) { + if (i == 0) + index = s.find("e+"); + else + index = s.find("e-"); + + if (index != std::string::npos) { + while ((s.size() >= index + 3) && (s[index + 2] == '0')) + s.erase(index + 2, 1); + } + } + + return s; +} + +extern "C" +{ + inline double value_floatToDouble(float v) + { + unsigned int digits = value_digitCount(v); + double f = std::pow(10, digits); + return std::round(v * f) / f; + } + + inline int value_checkString(const std::string &str) + { + bool ok; + + if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) { + value_stringToLong(str, &ok); + return ok ? 1 : 0; + } else { + value_stringToDouble(str, &ok); + return ok ? 2 : 0; + } + } + + inline double value_getNumber(const ValueData *v, bool *ok = nullptr) + { + // Equivalent to JavaScript Number(), *ok == false means NaN + if (ok) + *ok = true; + + // Since functions calling this already prioritize int, double and bool, + // we can optimize by prioritizing the other types here. + if (v->type == ValueType::String) + return value_stringToDouble(*v->stringValue, ok); + else if (v->type == ValueType::Infinity) + return std::numeric_limits::infinity(); + else if (v->type == ValueType::NegativeInfinity) + return -std::numeric_limits::infinity(); + else if (v->type == ValueType::NaN) { + if (ok) + *ok = false; + + return 0; + } else if (v->type == ValueType::Integer) + return v->intValue; + else if (v->type == ValueType::Double) + return v->doubleValue; + else if (v->type == ValueType::Bool) + return v->boolValue; + else { + assert(false); // this should never happen + if (ok) + *ok = false; + + return 0; + } + } + + bool value_isPositive(const ValueData *v) + { + if (v->type == ValueType::Integer) + return v->intValue > 0; + else if (v->type == ValueType::Double) + return v->doubleValue > 0; + else if (v->type == ValueType::Bool) + return v->boolValue; + else if (static_cast(v->type) < 0) { + if (v->type == ValueType::Infinity) + return true; + else + return false; + } + + return value_toDouble(v) > 0; + } + + bool value_isNegative(const ValueData *v) + { + if (v->type == ValueType::Integer) + return v->intValue < 0; + else if (v->type == ValueType::Double) + return v->doubleValue < 0; + else if (v->type == ValueType::Bool) + return false; + else if (static_cast(v->type) < 0) { + if (v->type == ValueType::NegativeInfinity) + return true; + else + return false; + } + + return value_toDouble(v) < 0; + } + + bool value_isZero(const ValueData *v) + { + if (v->type == ValueType::Integer) + return v->intValue == 0; + else if (v->type == ValueType::Double) + return v->doubleValue == 0; + else if (v->type == ValueType::Bool) + return !v->boolValue; + else if (static_cast(v->type) < 0) + return false; + + bool ok; + double n = value_getNumber(v, &ok); + + if (ok) + return n == 0; + else + return false; + } +} + +} // namespace libscratchcpp diff --git a/test/blocks/list_blocks_test.cpp b/test/blocks/list_blocks_test.cpp index 7c02be84..8c3c946e 100644 --- a/test/blocks/list_blocks_test.cpp +++ b/test/blocks/list_blocks_test.cpp @@ -423,7 +423,7 @@ TEST_F(ListBlocksTest, ListContainsItem) // [list2] contains -Infinity auto list2 = std::make_shared("d", "list2"); - auto block2 = createListItemBlock("c", "data_listcontainsitem", list2, Value::SpecialValue::NegativeInfinity); + auto block2 = createListItemBlock("c", "data_listcontainsitem", list2, SpecialValue::NegativeInfinity); compiler.init(); compiler.setBlock(block1); @@ -433,7 +433,7 @@ TEST_F(ListBlocksTest, ListContainsItem) compiler.end(); ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_LIST_CONTAINS, 0, vm::OP_CONST, 1, vm::OP_LIST_CONTAINS, 1, vm::OP_HALT })); - ASSERT_EQ(compiler.constValues(), std::vector({ "hello world", Value(Value::SpecialValue::NegativeInfinity) })); + ASSERT_EQ(compiler.constValues(), std::vector({ "hello world", Value(SpecialValue::NegativeInfinity) })); ASSERT_TRUE(compiler.variables().empty()); ASSERT_EQ( compiler.lists(), diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index f17349cb..01402470 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1463,9 +1463,7 @@ TEST_F(LooksBlocksTest, SwitchCostumeToImpl) static unsigned int bytecode13[] = { vm::OP_START, vm::OP_CONST, 12, vm::OP_EXEC, 0, vm::OP_HALT }; static unsigned int bytecode14[] = { vm::OP_START, vm::OP_CONST, 13, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchCostumeTo }; - static Value constValues[] = { - "costume2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next costume", "previous costume" - }; + static Value constValues[] = { "costume2", 0, 1, 2, 3, "2", "3", SpecialValue::NaN, SpecialValue::Infinity, SpecialValue::NegativeInfinity, "", " ", "next costume", "previous costume" }; Target target; target.addCostume(std::make_shared("costume1", "c1", "svg")); @@ -1781,8 +1779,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToImpl) static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropTo }; static Value constValues[] = { - "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", - "random backdrop" + "backdrop2", 0, 1, 2, 3, "2", "3", SpecialValue::NaN, SpecialValue::Infinity, SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", "random backdrop" }; Target target; @@ -2058,8 +2055,7 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWaitImpl) static unsigned int bytecode15[] = { vm::OP_START, vm::OP_CONST, 14, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &LooksBlocks::switchBackdropToAndWait }; static Value constValues[] = { - "backdrop2", 0, 1, 2, 3, "2", "3", Value::SpecialValue::NaN, Value::SpecialValue::Infinity, Value::SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", - "random backdrop" + "backdrop2", 0, 1, 2, 3, "2", "3", SpecialValue::NaN, SpecialValue::Infinity, SpecialValue::NegativeInfinity, "", " ", "next backdrop", "previous backdrop", "random backdrop" }; Target target; diff --git a/test/blocks/operator_blocks_test.cpp b/test/blocks/operator_blocks_test.cpp index cb5a465e..711b6b52 100644 --- a/test/blocks/operator_blocks_test.cpp +++ b/test/blocks/operator_blocks_test.cpp @@ -837,7 +837,7 @@ TEST_F(OperatorBlocksTest, MathOpLn) vm::OP_HALT }; static BlockFunc functions[] = { &OperatorBlocks::op_ln }; - static Value constValues[] = { Value::SpecialValue::NegativeInfinity, -2.5, 0, Value::SpecialValue::NaN, std::exp(1), 50, Value::SpecialValue::Infinity }; + static Value constValues[] = { SpecialValue::NegativeInfinity, -2.5, 0, SpecialValue::NaN, std::exp(1), 50, SpecialValue::Infinity }; VirtualMachine vm; vm.setBytecode(bytecode); @@ -890,7 +890,7 @@ TEST_F(OperatorBlocksTest, MathOpLog) vm::OP_HALT }; static BlockFunc functions[] = { &OperatorBlocks::op_log }; - static Value constValues[] = { Value::SpecialValue::NegativeInfinity, -2.5, 0, Value::SpecialValue::NaN, 100, 1500, Value::SpecialValue::Infinity }; + static Value constValues[] = { SpecialValue::NegativeInfinity, -2.5, 0, SpecialValue::NaN, 100, 1500, SpecialValue::Infinity }; VirtualMachine vm; vm.setBytecode(bytecode); @@ -943,7 +943,7 @@ TEST_F(OperatorBlocksTest, MathOpExp) vm::OP_HALT }; static BlockFunc functions[] = { &OperatorBlocks::op_eexp }; - static Value constValues[] = { Value::SpecialValue::NegativeInfinity, -3.25, 0, Value::SpecialValue::NaN, 1, 5, Value::SpecialValue::Infinity }; + static Value constValues[] = { SpecialValue::NegativeInfinity, -3.25, 0, SpecialValue::NaN, 1, 5, SpecialValue::Infinity }; VirtualMachine vm; vm.setBytecode(bytecode); @@ -996,7 +996,7 @@ TEST_F(OperatorBlocksTest, MathOp10Exp) vm::OP_HALT }; static BlockFunc functions[] = { &OperatorBlocks::op_10exp }; - static Value constValues[] = { Value::SpecialValue::NegativeInfinity, -2, 0, Value::SpecialValue::NaN, 1, 5.5, Value::SpecialValue::Infinity }; + static Value constValues[] = { SpecialValue::NegativeInfinity, -2, 0, SpecialValue::NaN, 1, 5.5, SpecialValue::Infinity }; VirtualMachine vm; vm.setBytecode(bytecode); diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 695e5da6..0d2384a3 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -397,7 +397,7 @@ TEST(ValueTest, CStringConstructor) TEST(ValueTest, InfinityConstructor) { - Value v(Value::SpecialValue::Infinity); + Value v(SpecialValue::Infinity); ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); @@ -411,7 +411,7 @@ TEST(ValueTest, InfinityConstructor) TEST(ValueTest, NegativeInfinityConstructor) { - Value v(Value::SpecialValue::NegativeInfinity); + Value v(SpecialValue::NegativeInfinity); ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); @@ -425,7 +425,7 @@ TEST(ValueTest, NegativeInfinityConstructor) TEST(ValueTest, NaNConstructor) { - Value v(Value::SpecialValue::NaN); + Value v(SpecialValue::NaN); ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); @@ -496,7 +496,7 @@ TEST(ValueTest, CopyConstructor) } { - Value v1(Value::SpecialValue::NaN); + Value v1(SpecialValue::NaN); Value v2(v1); ASSERT_EQ(v2.toDouble(), 0); ASSERT_EQ(v1.type(), v2.type()); @@ -510,7 +510,7 @@ TEST(ValueTest, CopyConstructor) } { - Value v1(Value::SpecialValue::Infinity); + Value v1(SpecialValue::Infinity); Value v2(v1); ASSERT_EQ(v1.type(), v2.type()); ASSERT_TRUE(v2.isInfinity()); @@ -523,7 +523,7 @@ TEST(ValueTest, CopyConstructor) } { - Value v1(Value::SpecialValue::NegativeInfinity); + Value v1(SpecialValue::NegativeInfinity); Value v2(v1); ASSERT_EQ(v1.type(), v2.type()); ASSERT_FALSE(v2.isInfinity()); @@ -808,7 +808,7 @@ TEST(ValueTest, CStringAssignment) TEST(ValueTest, InfinityAssignment) { Value v; - v = Value::SpecialValue::Infinity; + v = SpecialValue::Infinity; ASSERT_EQ(v.type(), ValueType::Infinity); ASSERT_TRUE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); @@ -822,7 +822,7 @@ TEST(ValueTest, InfinityAssignment) TEST(ValueTest, NegativeInfinityAssignment) { Value v; - v = Value::SpecialValue::NegativeInfinity; + v = SpecialValue::NegativeInfinity; ASSERT_EQ(v.type(), ValueType::NegativeInfinity); ASSERT_FALSE(v.isInfinity()); ASSERT_TRUE(v.isNegativeInfinity()); @@ -836,7 +836,7 @@ TEST(ValueTest, NegativeInfinityAssignment) TEST(ValueTest, NaNAssignment) { Value v; - v = Value::SpecialValue::NaN; + v = SpecialValue::NaN; ASSERT_EQ(v.type(), ValueType::NaN); ASSERT_FALSE(v.isInfinity()); ASSERT_FALSE(v.isNegativeInfinity()); @@ -910,7 +910,7 @@ TEST(ValueTest, CopyAssignment) } { - Value v1(Value::SpecialValue::NaN); + Value v1(SpecialValue::NaN); Value v2; v2 = v1; ASSERT_EQ(v2.toDouble(), 0); @@ -925,7 +925,7 @@ TEST(ValueTest, CopyAssignment) } { - Value v1(Value::SpecialValue::Infinity); + Value v1(SpecialValue::Infinity); Value v2; v2 = v1; ASSERT_EQ(v1.type(), v2.type()); @@ -939,7 +939,7 @@ TEST(ValueTest, CopyAssignment) } { - Value v1(Value::SpecialValue::NegativeInfinity); + Value v1(SpecialValue::NegativeInfinity); Value v2; v2 = v1; ASSERT_EQ(v1.type(), v2.type()); @@ -2270,12 +2270,12 @@ TEST(ValueTest, EqualityOperators) } { - Value v1(Value::SpecialValue::Infinity); - Value v2(Value::SpecialValue::Infinity); - Value v3(Value::SpecialValue::NegativeInfinity); - Value v4(Value::SpecialValue::NegativeInfinity); - Value v5(Value::SpecialValue::NaN); - Value v6(Value::SpecialValue::NaN); + Value v1(SpecialValue::Infinity); + Value v2(SpecialValue::Infinity); + Value v3(SpecialValue::NegativeInfinity); + Value v4(SpecialValue::NegativeInfinity); + Value v5(SpecialValue::NaN); + Value v6(SpecialValue::NaN); ASSERT_TRUE(v1 == v2); ASSERT_FALSE(v1 != v2); @@ -2408,9 +2408,9 @@ TEST(ValueTest, EqualityOperators) { Value v1 = 5; Value v2 = 0; - Value v3(Value::SpecialValue::Infinity); - Value v4(Value::SpecialValue::NegativeInfinity); - Value v5(Value::SpecialValue::NaN); + Value v3(SpecialValue::Infinity); + Value v4(SpecialValue::NegativeInfinity); + Value v5(SpecialValue::NaN); ASSERT_FALSE(v1 == v3); ASSERT_TRUE(v1 != v3); @@ -2512,9 +2512,9 @@ TEST(ValueTest, EqualityOperators) { Value v1 = true; Value v2 = false; - Value v3(Value::SpecialValue::Infinity); - Value v4(Value::SpecialValue::NegativeInfinity); - Value v5(Value::SpecialValue::NaN); + Value v3(SpecialValue::Infinity); + Value v4(SpecialValue::NegativeInfinity); + Value v5(SpecialValue::NaN); ASSERT_FALSE(v1 == v3); ASSERT_TRUE(v1 != v3); @@ -2536,9 +2536,9 @@ TEST(ValueTest, EqualityOperators) Value v4 = "-infinity"; Value v5 = "NaN"; Value v6 = "nan"; - Value v7(Value::SpecialValue::Infinity); - Value v8(Value::SpecialValue::NegativeInfinity); - Value v9(Value::SpecialValue::NaN); + Value v7(SpecialValue::Infinity); + Value v8(SpecialValue::NegativeInfinity); + Value v9(SpecialValue::NaN); // Infinity ASSERT_TRUE(v1 == v7); @@ -2606,9 +2606,9 @@ TEST(ValueTest, EqualityOperators) Value v2 = " "; Value v3 = ""; Value v4 = "0"; - Value v5(Value::SpecialValue::Infinity); - Value v6(Value::SpecialValue::NegativeInfinity); - Value v7(Value::SpecialValue::NaN); + Value v5(SpecialValue::Infinity); + Value v6(SpecialValue::NegativeInfinity); + Value v7(SpecialValue::NaN); // Infinity ASSERT_FALSE(v1 == v5); diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp index 3a91c12c..904174fa 100644 --- a/test/virtual_machine/virtual_machine_test.cpp +++ b/test/virtual_machine/virtual_machine_test.cpp @@ -995,7 +995,7 @@ TEST(VirtualMachineTest, OP_LIST_DEL) 0, OP_CONST, 9, OP_LIST_DEL, 0, OP_READ_LIST, 0, OP_CONST, 10, OP_LIST_DEL, 0, OP_READ_LIST, 0, OP_CONST, 11, OP_LIST_DEL, 0, OP_READ_LIST, 0, OP_CONST, 12, OP_LIST_DEL, 0, OP_READ_LIST, 0, OP_CONST, 13, OP_LIST_DEL, 0, OP_READ_LIST, 0, OP_HALT }; - static Value constValues[] = { 3, 1, "6", 0, 7, -1, 9, Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN, "invalid", "last", "random", "all" }; + static Value constValues[] = { 3, 1, "6", 0, 7, -1, 9, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "invalid", "last", "random", "all" }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1067,7 +1067,7 @@ TEST(VirtualMachineTest, OP_LIST_INSERT) 0, OP_CONST, 0, OP_CONST, 10, OP_LIST_INSERT, 0, OP_READ_LIST, 0, OP_CONST, 0, OP_CONST, 11, OP_LIST_INSERT, 0, OP_READ_LIST, 0, OP_CONST, 0, OP_CONST, 12, OP_LIST_INSERT, 0, OP_READ_LIST, 0, OP_CONST, 0, OP_CONST, 13, OP_LIST_INSERT, 0, OP_READ_LIST, 0, OP_HALT }; - static Value constValues[] = { "new item", "3", 1, 10, 0, 12, -1, 14, Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN, "last", "random", "invalid" }; + static Value constValues[] = { "new item", "3", 1, 10, 0, 12, -1, 14, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid" }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1118,9 +1118,7 @@ TEST(VirtualMachineTest, OP_LIST_REPLACE) 0, OP_CONST, 10, OP_CONST, 0, OP_LIST_REPLACE, 0, OP_READ_LIST, 0, OP_CONST, 11, OP_CONST, 11, OP_LIST_REPLACE, 0, OP_READ_LIST, 0, OP_CONST, 12, OP_CONST, 14, OP_LIST_REPLACE, 0, OP_READ_LIST, 0, OP_CONST, 13, OP_CONST, 0, OP_LIST_REPLACE, 0, OP_READ_LIST, 0, OP_HALT }; - static Value constValues[] = { - "new item", 3, "1", 8, 0, 9, -1, 12, Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN, "last", "random", "invalid", "test" - }; + static Value constValues[] = { "new item", 3, "1", 8, 0, 9, -1, 12, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid", "test" }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1168,7 +1166,7 @@ TEST(VirtualMachineTest, OP_LIST_GET_ITEM) 0, OP_CONST, 5, OP_LIST_GET_ITEM, 0, OP_CONST, 6, OP_LIST_GET_ITEM, 0, OP_CONST, 7, OP_LIST_GET_ITEM, 0, OP_CONST, 8, OP_LIST_GET_ITEM, 0, OP_CONST, 9, OP_LIST_GET_ITEM, 0, OP_CONST, 10, OP_LIST_GET_ITEM, 0, OP_CONST, 11, OP_LIST_GET_ITEM, 0, OP_CONST, 12, OP_LIST_GET_ITEM, 0, OP_HALT }; - static Value constValues[] = { 3, 1, "8", 0, 9, -1, 12, Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN, "last", "random", "invalid" }; + static Value constValues[] = { 3, 1, "8", 0, 9, -1, 12, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid" }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1215,7 +1213,7 @@ TEST(VirtualMachineTest, OP_LIST_INDEX_OF) OP_START, OP_CONST, 0, OP_LIST_INDEX_OF, 0, OP_CONST, 1, OP_LIST_INDEX_OF, 0, OP_CONST, 2, OP_LIST_INDEX_OF, 0, OP_CONST, 3, OP_LIST_INDEX_OF, 0, OP_CONST, 4, OP_LIST_INDEX_OF, 0, OP_CONST, 5, OP_LIST_INDEX_OF, 0, OP_CONST, 6, OP_LIST_INDEX_OF, 0, OP_CONST, 7, OP_LIST_INDEX_OF, 0, OP_HALT }; - static Value constValues[] = { "c", "A", "e", "", "invalid", Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN }; + static Value constValues[] = { "c", "A", "e", "", "invalid", SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1223,7 +1221,7 @@ TEST(VirtualMachineTest, OP_LIST_INDEX_OF) list1.push_back("d"); list1.push_back("e"); list1.push_back(""); - list1.push_back(Value::SpecialValue::Infinity); + list1.push_back(SpecialValue::Infinity); list1.push_back(8); List *lists[] = { &list1 }; @@ -1270,7 +1268,7 @@ TEST(VirtualMachineTest, OP_LIST_CONTAINS) OP_START, OP_CONST, 0, OP_LIST_CONTAINS, 0, OP_CONST, 1, OP_LIST_CONTAINS, 0, OP_CONST, 2, OP_LIST_CONTAINS, 0, OP_CONST, 3, OP_LIST_CONTAINS, 0, OP_CONST, 4, OP_LIST_CONTAINS, 0, OP_CONST, 5, OP_LIST_CONTAINS, 0, OP_CONST, 6, OP_LIST_CONTAINS, 0, OP_CONST, 7, OP_LIST_CONTAINS, 0, OP_HALT }; - static Value constValues[] = { "c", "A", "e", "", "invalid", Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN }; + static Value constValues[] = { "c", "A", "e", "", "invalid", SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN }; List list1("", "list1"); list1.push_back("a"); list1.push_back("b"); @@ -1278,7 +1276,7 @@ TEST(VirtualMachineTest, OP_LIST_CONTAINS) list1.push_back("d"); list1.push_back("e"); list1.push_back(""); - list1.push_back(Value::SpecialValue::Infinity); + list1.push_back(SpecialValue::Infinity); list1.push_back(8); List *lists[] = { &list1 }; @@ -1301,7 +1299,7 @@ TEST(VirtualMachineTest, OP_LIST_CONTAINS) TEST(VirtualMachineTest, OP_STR_CONCAT) { static unsigned int bytecode[] = { OP_START, OP_CONST, 0, OP_CONST, 1, OP_STR_CONCAT, OP_CONST, 2, OP_STR_CONCAT, OP_CONST, 3, OP_STR_CONCAT, OP_HALT }; - static Value constValues[] = { "abc ", "def", " ghi", Value::SpecialValue::NegativeInfinity }; + static Value constValues[] = { "abc ", "def", " ghi", SpecialValue::NegativeInfinity }; VirtualMachine vm; vm.setBytecode(bytecode); @@ -1318,9 +1316,7 @@ TEST(VirtualMachineTest, OP_STR_AT) OP_STR_AT, OP_CONST, 0, OP_CONST, 6, OP_STR_AT, OP_CONST, 0, OP_CONST, 7, OP_STR_AT, OP_CONST, 0, OP_CONST, 8, OP_STR_AT, OP_CONST, 0, OP_CONST, 9, OP_STR_AT, OP_CONST, 0, OP_CONST, 10, OP_STR_AT, OP_CONST, 0, OP_CONST, 11, OP_STR_AT, OP_CONST, 0, OP_CONST, 12, OP_STR_AT, OP_CONST, 0, OP_CONST, 13, OP_STR_AT, OP_HALT }; - static Value constValues[] = { - "abcd efg hijý", 3, 1, 14, 0, 15, -1, 16, Value::SpecialValue::NegativeInfinity, Value::SpecialValue::Infinity, Value::SpecialValue::NaN, "last", "random", "invalid" - }; + static Value constValues[] = { "abcd efg hijý", 3, 1, 14, 0, 15, -1, 16, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid" }; VirtualMachine vm; vm.setBytecode(bytecode); From 9ffdadd05e7a7192a16c7a7a4b4046d49b73a244 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:16:55 +0200 Subject: [PATCH 22/72] Add test with different locale to Value test --- test/scratch_classes/value_test.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 0d2384a3..43da3ac4 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -1161,6 +1161,9 @@ TEST(ValueTest, ToLong) TEST(ValueTest, ToDouble) { + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "sk_SK.UTF-8"); + Value v = 2147483647; ASSERT_EQ(v.toDouble(), 2147483647.0); v = -2147483647; @@ -1270,6 +1273,8 @@ TEST(ValueTest, ToDouble) v = "0b100112001"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } TEST(ValueTest, ToBool) From d6439bb5fca28199c0b5e82725271463568a2e85 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:50:56 +0200 Subject: [PATCH 23/72] Add fast_float library --- .../thirdparty/fast_float/fast_float.h | 3913 +++++++++++++++++ 1 file changed, 3913 insertions(+) create mode 100644 src/scratch/thirdparty/fast_float/fast_float.h diff --git a/src/scratch/thirdparty/fast_float/fast_float.h b/src/scratch/thirdparty/fast_float/fast_float.h new file mode 100644 index 00000000..860001dc --- /dev/null +++ b/src/scratch/thirdparty/fast_float/fast_float.h @@ -0,0 +1,3913 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License or the Boost License. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// +// BOOST License Notice +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ + __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 +#else +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include +#include +#ifdef __has_include +#if __has_include() && (__cplusplus > 202002L || _MSVC_LANG > 202002L) +#include +#endif +#endif + +namespace fast_float { + +#define FASTFLOAT_JSONFMT (1 << 5) +#define FASTFLOAT_FORTRANFMT (1 << 6) + +enum chars_format { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, + fortran = FASTFLOAT_FORTRANFMT | fixed | scientific, + general = fixed | scientific +}; + +template struct from_chars_result_t { + UC const *ptr; + std::errc ec; +}; +using from_chars_result = from_chars_result_t; + +template struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.')) + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; +}; +using parse_options = parse_options_t; + +} // namespace fast_float + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64)) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. +// We can never tell the register width, but the SIZE_MAX is a good +// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FASTFLOAT_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FASTFLOAT_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) \ + { \ + if (!(x)) \ + return false; \ + } + +#define FASTFLOAT_ENABLE_IF(...) \ + typename std::enable_if<(__VA_ARGS__), int>::type + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +template +fastfloat_really_inline constexpr bool is_supported_float_type() { + return std::is_same::value || std::is_same::value +#if __STDCPP_FLOAT32_T__ + || std::is_same::value +#endif +#if __STDCPP_FLOAT64_T__ + || std::is_same::value +#endif + ; +} + +template +fastfloat_really_inline constexpr bool is_supported_char_type() { + return std::is_same::value || std::is_same::value || + std::is_same::value || std::is_same::value; +} + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *input1, UC const *input2, size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; ++i) { + running_diff |= (char(input1[i]) ^ char(input2[i])); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template struct span { + const T *ptr; + size_t length; + constexpr span(const T *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { return length; } + + FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + constexpr value128() : low(0), high(0) {} +}; + +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if (input_num & uint64_t(0xffffffff00000000)) { + input_num >>= 32; + last_bit |= 32; + } + if (input_num & uint64_t(0xffff0000)) { + input_num >>= 16; + last_bit |= 16; + } + if (input_num & uint64_t(0xff00)) { + input_num >>= 8; + last_bit |= 8; + } + if (input_num & uint64_t(0xf0)) { + input_num >>= 4; + last_bit |= 4; + } + if (input_num & uint64_t(0xc)) { + input_num >>= 2; + last_bit |= 2; + } + if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ + last_bit |= 1; + } + return 63 - last_bit; +} + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO +#if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); +#else + return leading_zeroes_generic(input_num); +#endif +#else + return __builtin_clzll(input_num); +#endif +} + +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, + uint64_t cd, + uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || \ + (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + constexpr bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + constexpr bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = + typename std::conditional::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int + min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(int64_t power); + static inline constexpr uint64_t + max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x20000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x20000000000000, + 0x20000000000000 / 5, + 0x20000000000000 / (5 * 5), + 0x20000000000000 / (5 * 5 * 5), + 0x20000000000000 / (5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555), + 0x20000000000000 / (constant_55555 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 63; +} +template <> inline constexpr int binary_format::sign_index() { + return 31; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> inline constexpr int binary_format::smallest_power_of_ten() { + return -64; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F800000; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007FFFFF; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x00800000; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +to_float(bool negative, adjusted_mantissa am, T &value) { + using fastfloat_uint = typename binary_format::equiv_uint; + fastfloat_uint word = (fastfloat_uint)am.mantissa; + word |= fastfloat_uint(am.power2) + << binary_format::mantissa_explicit_bits(); + word |= fastfloat_uint(negative) << binary_format::sign_index(); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default +template struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr bool space_lut::value[]; + +#endif + +inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; } +#endif + +template static constexpr uint64_t int_cmp_zeros() { + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), + "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 + : (sizeof(UC) == 2) + ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | + uint64_t(UC('0')) << 16 | UC('0')) + : (uint64_t(UC('0')) << 32 | UC('0')); +} +template static constexpr int int_cmp_len() { + return sizeof(uint64_t) / sizeof(UC); +} +template static constexpr UC const *str_const_nan() { + return nullptr; +} +template <> constexpr char const *str_const_nan() { return "nan"; } +template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } +template <> constexpr char16_t const *str_const_nan() { + return u"nan"; +} +template <> constexpr char32_t const *str_const_nan() { + return U"nan"; +} +template static constexpr UC const *str_const_inf() { + return nullptr; +} +template <> constexpr char const *str_const_inf() { return "infinity"; } +template <> constexpr wchar_t const *str_const_inf() { + return L"infinity"; +} +template <> constexpr char16_t const *str_const_inf() { + return u"infinity"; +} +template <> constexpr char32_t const *str_const_inf() { + return U"infinity"; +} + +template struct int_luts { + static constexpr uint8_t chdigit[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + + static constexpr size_t maxdigits_u64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + + static constexpr uint64_t min_safe_u64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint8_t int_luts::chdigit[]; + +template constexpr size_t int_luts::maxdigits_u64[]; + +template constexpr uint64_t int_luts::min_safe_u64[]; + +#endif + +template +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + return int_luts<>::chdigit[static_cast(c)]; +} + +fastfloat_really_inline constexpr size_t max_digits_u64(int base) { + return int_luts<>::maxdigits_u64[base - 2]; +} + +// If a u64 is exactly max_digits_u64() in length, this is +// the value below which it has definitely overflowed. +fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { + return int_luts<>::min_safe_u64[base - 2]; +} + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-indepent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point values (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +template ())> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept; +/** + * from_chars for integer types. + */ +template ())> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; + +} // namespace fast_float +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include +#include +#include + + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return std::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return !(c > UC('9') || c < UC('0')); +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +read8_to_u64(const UC *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint64_t val = 0; + for (int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline uint64_t simd_read8_to_u64(const __m128i data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); + return value; +#endif + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + _mm_loadu_si128(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + +fastfloat_really_inline uint64_t simd_read8_to_u64(const uint16x8_t data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + vld1q_u16(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const *) { + return 0; +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +parse_eight_digits_unrolled(UC const *chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline constexpr bool +is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +simd_parse_if_eight_digits_unrolled(const char16_t *chars, + uint64_t &i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 + FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i data = + _mm_loadu_si128(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) + FASTFLOAT_SIMD_DISABLE_WARNINGS + const uint16x8_t data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); + const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { + return 0; +} + +template ::value) = 0> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(const UC *&p, const UC *const pend, uint64_t &i) { + if (!has_simd_opt()) { + return; + } + while ((std::distance(p, pend) >= 8) && + simd_parse_if_eight_digits_unrolled( + p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(const char *&p, const char *const pend, + uint64_t &i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((std::distance(p, pend) >= 8) && + is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + + parse_eight_digits_unrolled(read8_to_u64( + p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +enum class parse_error { + no_error, + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, + // A sign must be followed by an integer or dot. + missing_integer_or_dot_after_sign, + // [JSON-only] The integer part must not have leading zeros. + leading_zeros_in_integer_part, + // [JSON-only] The integer part must have at least one digit. + no_digits_in_integer_part, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + no_digits_in_fractional_part, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +}; + +template struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable + parse_error error{parse_error::no_error}; +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +report_parse_error(UC const *p, parse_error error) { + parsed_number_string_t answer; + answer.valid = false; + answer.lastmatch = p; + answer.error = error; + return answer; +} + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, + parse_options_t options) noexcept { + chars_format const fmt = options.format; + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == UC('-')); +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { +#else + if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here +#endif + ++p; + if (p == pend) { + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + if (fmt & FASTFLOAT_JSONFMT) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return report_parse_error(p, + parse_error::missing_integer_after_sign); + } + } else { + if (!is_integer(*p) && + (*p != + decimal_point)) { // a sign must be followed by an integer or the dot + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + } + } + UC const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - + UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0) { + return report_parse_error(p, parse_error::no_digits_in_integer_part); + } + if ((start_digits[0] == UC('0') && digit_count > 1)) { + return report_parse_error(start_digits, + parse_error::leading_zeros_in_integer_part); + } + } + + int64_t exponent = 0; + const bool has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { + ++p; + UC const *before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return report_parse_error(p, + parse_error::no_digits_in_fractional_part); + } + } else if (digit_count == + 0) { // we must have encountered at least one integer! + return report_parse_error(p, parse_error::no_digits_in_mantissa); + } + int64_t exp_number = 0; // explicit exponential part + if (((fmt & chars_format::scientific) && (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) || + ((fmt & FASTFLOAT_FORTRANFMT) && (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p)))) { + UC const *location_of_e = p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || + (UC('D') == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && + (UC('+') == + *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if (!(fmt & chars_format::fixed)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. + return report_parse_error(p, parse_error::missing_exponential_part); + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if ((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { + return report_parse_error(p, parse_error::missing_exponential_part); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const *start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if (*start == UC('0')) { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_int_string(UC const *p, UC const *pend, T &value, int base) { + from_chars_result_t answer; + + UC const *const first = p; + + bool negative = (*p == UC('-')); + if (!std::is_signed::value && negative) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == UC('-')) || (*p == UC('+'))) { +#else + if (*p == UC('-')) { +#endif + ++p; + } + + UC const *const start_num = p; + + while (p != pend && *p == UC('0')) { + ++p; + } + + const bool has_leading_zeros = p > start_num; + + UC const *const start_digits = p; + + uint64_t i = 0; + if (base == 10) { + loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ch_to_digit(*p); + if (digit >= base) { + break; + } + i = uint64_t(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = size_t(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = max_digits_u64(base); + if (digit_count > max_digits) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < min_safe_u64(base)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + + // check other types overflow + if (!std::is_same::value) { + if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + } + + if (negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + // this weird workaround is required because: + // - converting unsigned to signed when its value is greater than signed max + // is UB pre-C++23. + // - reinterpret_casting (~i + 1) would work, but it is not constexpr + // this is always optimized into a neg instruction (note: T is an integer + // type) + value = T(-std::numeric_limits::max() - + T(i - uint64_t(std::numeric_limits::max()))); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + } else { + value = T(i); + } + + answer.ec = std::errc(); + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template struct powers_template { + + constexpr static int smallest_power_of_five = + binary_format::smallest_power_of_ten(); + constexpr static int largest_power_of_five = + binary_format::largest_power_of_ten(); + constexpr static int number_of_entries = + 2 * (largest_power_of_five - smallest_power_of_five + 1); + // Powers of five from 5^-342 all the way to 5^308 rounded toward one. + constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, + }; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr uint64_t + powers_template::power_of_five_128[number_of_entries]; + +#endif + +using powers = powers_template<>; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit +// words approximating the result, with the "high" part corresponding to the +// most significant bits and the low part corresponding to the least significant +// bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact + // because The line value128 firstproduct = full_multiplication(w, + // power_of_five_128[index]); gives the exact answer. + value128 firstproduct = + full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), + " precision should be in (0,64]"); + constexpr uint64_t precision_mask = + (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if ((firstproduct.high & precision_mask) == + precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + value128 secondproduct = + full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ +constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; +} +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa +compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = + compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + + value128 product = + compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; + + answer.mantissa = product.high >> shift; + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if (-answer.power2 + 1 >= + 64) { // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + ? 0 + : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { return length; } + constexpr bool is_empty() const noexcept { return length == 0; } + constexpr size_t capacity() const noexcept { return size; } + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb *ptr = data + length; + std::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb *first = data + len(); + limb *last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +empty_hi64(bool &truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, bool &truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, bool &truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_add(limb x, limb y, bool &overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_mul(limb x, limb y, limb &carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; +#endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, + size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +small_add(stackvec &vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, + limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, + size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +large_add_from(stackvec &x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, + 5UL, + 25UL, + 125UL, + 625UL, + 3125UL, + 15625UL, + 78125UL, + 390625UL, + 1953125UL, + 9765625UL, + 48828125UL, + 244140625UL, + 1220703125UL, + 6103515625UL, + 30517578125UL, + 152587890625UL, + 762939453125UL, + 3814697265625UL, + 19073486328125UL, + 95367431640625UL, + 476837158203125UL, + 2384185791015625UL, + 11920928955078125UL, + 59604644775390625UL, + 298023223876953125UL, + 1490116119384765625UL, + 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint32_t pow5_tables::large_step; + +template constexpr uint64_t pow5_tables::small_power_of_5[]; + +template constexpr limb pow5_tables::large_power_of_5[]; + +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = + uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(const bigint &other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb *dst = vec.data + n; + const limb *src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY(small_mul( + vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t +scientific_exponent(parsed_number_string_t &num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended(T value) noexcept { + using equiv_uint = typename binary_format::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, + callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < + (uint64_t(1) << binary_format::mantissa_explicit_bits())) + ? 0 + : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= + (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, + callback cb) noexcept { + const uint64_t mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; + const uint64_t halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_down(adjusted_mantissa &am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +skip_zeros(UC const *&first, UC const *last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(UC const *first, UC const *last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +parse_eight_digits(const UC *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +parse_one_digit(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +add_native(bigint &big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +round_up_bigint(bigint &big, size_t &count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 void +parse_mantissa(bigint &result, parsed_number_string_t &num, + size_t max_digits, size_t &digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const *p = num.integer.ptr; + UC const *pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, + [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || + (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( + bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, + [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include +#include +namespace fast_float { + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, + UC const *last, + T &value) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == + UC('-')) { // assume first < last, so dereference without checks; + // C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if (*first == UC('+')) { + ++first; + } +#endif + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() + : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == UC('(')) { + for (UC const *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || + (UC('A') <= *ptr && *ptr <= UC('Z')) || + (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if ((last - first >= 8) && + fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keywoard prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., + // asm). The value does not need to be std::numeric_limits::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static volatile float fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. +// +// Explanation: +// Only when fegetround() == FE_TONEAREST do we have that +// fmin + 1.0f == 1.0f - fmin. +// +// FE_UPWARD: +// fmin + 1.0f > 1 +// 1.0f - fmin == 1 +// +// FE_DOWNWARD or FE_TOWARDZERO: +// fmin + 1.0f == 1 +// 1.0f - fmin < 1 +// +// Note: This may fail to be accurate if fast-math has been +// enabled, as rounding conventions may not apply. +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +// todo: is there a VS warning? +// see +// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return (fmini + 1.0f == 1.0f - fmini); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +} // namespace detail + +template struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced(first, last, value, options); + } +}; + +#if __STDCPP_FLOAT32_T__ == 1 +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float32_t &value, + parse_options_t options) noexcept { + // if std::float32_t is defined, and we are in C++23 mode; macro set for + // float32; set value to float due to equivalence between float and + // float32_t + float val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +#if __STDCPP_FLOAT64_T__ == 1 +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float64_t &value, + parse_options_t options) noexcept { + // if std::float64_t is defined, and we are in C++23 mode; macro set for + // float64; set value as double due to equivalence between double and + // float64_t + double val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(fmt)); +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + + static_assert(is_supported_float_type(), + "only some floating-point types are supported"); + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + if (binary_format::min_exponent_fast_path() <= pns.exponent && + pns.exponent <= binary_format::max_exponent_fast_path() && + !pns.too_many_digits) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(pns.mantissa); + if (pns.exponent < 0) { + value = value / binary_format::exact_power_of_ten(-pns.exponent); + } else { + value = value * binary_format::exact_power_of_ten(pns.exponent); + } + if (pns.negative) { + value = -value; + } + return answer; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + // proposal + if (pns.exponent >= 0 && + pns.mantissa <= + binary_format::max_mantissa_fast_path(pns.exponent)) { +#if defined(__clang__) || defined(FASTFLOAT_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (pns.mantissa == 0) { + value = pns.negative ? T(-0.) : T(0.); + return answer; + } +#endif + value = T(pns.mantissa) * + binary_format::exact_power_of_ten(pns.exponent); + if (pns.negative) { + value = -value; + } + return answer; + } + } + } + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) { + if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = digit_comp(pns, am); + } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } + return answer; +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_float_type(), + "only some floating-point types are supported"); + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = + parse_number_string(first, last, options); + if (!pns.valid) { + if (options.format & chars_format::no_infnan) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value); + } + } + + // call overload that takes parsed_number_string_t directly. + return from_chars_advanced(pns, value); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base) noexcept { + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif + if (first == last || base < 2 || base > 36) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + return parse_int_string(first, last, value, base); +} + +} // namespace fast_float + +#endif + From 1be0fb23e9aaea0a95a94a414df699f57c02d1ed Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:58:03 +0200 Subject: [PATCH 24/72] Optimize string conversion --- src/scratch/value_functions.cpp | 27 +++- src/scratch/value_functions_p.h | 226 ++++++++++++++-------------- test/scratch_classes/value_test.cpp | 205 ++++++++++++++++++++++++- 3 files changed, 341 insertions(+), 117 deletions(-) diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 249e6990..3509a877 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -587,7 +587,19 @@ extern "C" return true; } - return value_toDouble(v1) > value_toDouble(v2); + double n1, n2; + + if (v1->type == ValueType::String) + n1 = value_stringToDouble(*v1->stringValue); + else + n1 = value_toDouble(v1); + + if (v2->type == ValueType::String) + n2 = value_stringToDouble(*v2->stringValue); + else + n2 = value_toDouble(v2); + + return n1 > n2; } /*! Returns true if the first value is lower than the second value. */ @@ -613,6 +625,19 @@ extern "C" } return value_toDouble(v1) < value_toDouble(v2); + double n1, n2; + + if (v1->type == ValueType::String) + n1 = value_stringToDouble(*v1->stringValue); + else + n1 = value_toDouble(v1); + + if (v2->type == ValueType::String) + n2 = value_stringToDouble(*v2->stringValue); + else + n2 = value_toDouble(v2); + + return n1 < n2; } } diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index bc070038..32853c0b 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -7,8 +7,14 @@ #include #include #include +#include #include +#include "thirdparty/fast_float/fast_float.h" + +// Faster than std::isspace() +#define IS_SPACE(x) (x == ' ' || x == '\f' || x == '\n' || x == '\r' || x == '\t' || x == '\v') + namespace libscratchcpp { @@ -61,49 +67,65 @@ extern "C" return (s1.compare(s2) == 0); } - inline long value_hexToDec(const std::string &s) + inline double value_hexToDec(const char *s, int n, bool *ok) { - static const std::string digits = "0123456789abcdef"; + if (ok) + *ok = false; + + // Ignore floats + const char *p = s; - for (char c : s) { - if (digits.find(c) == std::string::npos) { + while (p++ < s + n) { + if (*p == '.') return 0; - } } - std::istringstream stream(s); - long ret; - stream >> std::hex >> ret; - return ret; + double result = 0; + auto [ptr, ec] = std::from_chars(s, s + n, result, std::chars_format::hex); + + if (ec == std::errc{} && ptr == s + n) { + if (ok) + *ok = true; + + return result; + } else + return 0; } - inline long value_octToDec(const std::string &s) + inline long value_octToDec(const char *s, int n, bool *ok) { - static const std::string digits = "01234567"; + if (ok) + *ok = false; - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } + char *err; + const double ret = std::strtol(s, &err, 8); - std::istringstream stream(s); - long ret; - stream >> std::oct >> ret; - return ret; + if (*err != 0 && !std::isspace(*err)) + return 0; + else { + if (ok) + *ok = true; + + return ret; + } } - inline double value_binToDec(const std::string &s) + inline double value_binToDec(const char *s, int n, bool *ok) { - static const std::string digits = "01"; + if (ok) + *ok = false; - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } + char *err; + const double ret = std::strtol(s, &err, 2); + + if (*err != 0 && !std::isspace(*err)) + return 0; + else { + if (ok) + *ok = true; - return std::stoi(s, 0, 2); + return ret; + } } inline double value_stringToDouble(const std::string &s, bool *ok = nullptr) @@ -111,128 +133,110 @@ extern "C" if (ok) *ok = false; - if (s == "Infinity") { - if (ok) - *ok = true; - return std::numeric_limits::infinity(); - } else if (s == "-Infinity") { - if (ok) - *ok = true; - return -std::numeric_limits::infinity(); - } else if (s == "NaN") { + if (s.empty()) { if (ok) *ok = true; + return 0; } - if (s.size() >= 2 && s[0] == '0') { - std::string sub = s.substr(2, s.size() - 2); - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); + const char *begin = s.data(); + const char *strEnd = s.data() + s.size(); + const char *end = strEnd; - if (s[1] == 'x' || s[1] == 'X') { - return value_hexToDec(sub); - } else if (s[1] == 'o' || s[1] == 'O') { - return value_octToDec(sub); - } else if (s[1] == 'b' || s[1] == 'B') { - return value_binToDec(sub); - } - } + // Trim leading spaces + while (begin < end && IS_SPACE(*begin)) + ++begin; - static const std::string digits = "0123456789.eE+-"; - const std::string *stringPtr = &s; - bool customStr = false; + end = begin + 1; - if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) { - std::string *localPtr = new std::string(s); - stringPtr = localPtr; - customStr = true; + // Trim trailing spaces + while (end < strEnd && !IS_SPACE(*(end))) + ++end; - while (!localPtr->empty() && (localPtr->at(0) == ' ')) - localPtr->erase(0, 1); + // Only whitespace can be after the end + const char *p = end; - while (!localPtr->empty() && (localPtr->back() == ' ')) - localPtr->pop_back(); + while (p < strEnd) { + if (!IS_SPACE(*p)) + return 0; + + p++; } - for (char c : *stringPtr) { - if (digits.find(c) == std::string::npos) { - return 0; + if (end - begin <= 0) + return 0; + + if (end - begin > 2 && begin[0] == '0') { + const char prefix = begin[1]; + const char *sub = begin + 2; + + if (prefix == 'x' || prefix == 'X') { + return value_hexToDec(sub, end - begin - 2, ok); + } else if (prefix == 'o' || prefix == 'O') { + return value_octToDec(sub, end - begin - 2, ok); + } else if (prefix == 'b' || prefix == 'B') { + return value_binToDec(sub, end - begin - 2, ok); } } - try { + // Trim leading zeros + while (begin < end && (*begin == '0') && !(begin + 1 < end && begin[1] == '.')) + ++begin; + + if (end - begin <= 0) { if (ok) *ok = true; - // Set locale to C to avoid conversion issues - std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); - std::setlocale(LC_NUMERIC, "C"); - - double ret = std::stod(*stringPtr); + return 0; + } - // Restore old locale - std::setlocale(LC_NUMERIC, oldLocale.c_str()); + double ret = 0; + auto [ptr, ec] = fast_float::from_chars(begin, end, ret, fast_float::chars_format::json); - if (customStr) - delete stringPtr; + if (ec == std::errc{} && ptr == end) { + if (ok) + *ok = true; return ret; - } catch (...) { - if (ok) - *ok = false; + } else return 0; - } - } - - inline long value_stringToLong(const std::string &s, bool *ok = nullptr) - { - if (ok) - *ok = false; + // Special values if (s == "Infinity") { if (ok) *ok = true; - return std::numeric_limits::infinity(); + return std::numeric_limits::infinity(); } else if (s == "-Infinity") { if (ok) *ok = true; - return -std::numeric_limits::infinity(); + return -std::numeric_limits::infinity(); } else if (s == "NaN") { if (ok) *ok = true; - return 0; + return std::numeric_limits::quiet_NaN(); } - if (s.size() >= 2 && s[0] == '0') { - std::string sub = s.substr(2, s.size() - 2); - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); + return 0; + } - if (s[1] == 'x' || s[1] == 'X') { - return value_hexToDec(sub); - } else if (s[1] == 'o' || s[1] == 'O') { - return value_octToDec(sub); - } else if (s[1] == 'b' || s[1] == 'B') { - return value_binToDec(sub); - } - } + inline long value_stringToLong(const std::string &s, bool *ok = nullptr) + { + return value_stringToDouble(s, ok); + } - static const std::string digits = "0123456789.eE+-"; + inline bool value_stringIsInt(const char *s, int n) + { + const char *p = s; - for (char c : s) { - if (digits.find(c) == std::string::npos) { - return 0; - } - } + while (p < s + n) { + if (*s == '.' || *s == 'e' || *s == 'E') + return false; - try { - if (ok) - *ok = true; - return std::stol(s); - } catch (...) { - if (ok) - *ok = false; - return 0; + p++; } + + return true; } } @@ -281,7 +285,7 @@ extern "C" { bool ok; - if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) { + if (value_stringIsInt(str.c_str(), str.size())) { value_stringToLong(str, &ok); return ok ? 1 : 0; } else { diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 43da3ac4..68881024 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -1005,6 +1005,18 @@ TEST(ValueTest, ToInt) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 2814); + v = " 0xafe"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 2814); + + v = "0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 2814); + + v = " 0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 2814); + v = "0x0afe"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 2814); @@ -1021,11 +1033,27 @@ TEST(ValueTest, ToInt) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 0); + v = "0xabf.d"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 0); + // Octal v = "0o506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 326); + v = " 0o506"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 326); + + v = "0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 326); + + v = " 0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 326); + v = "0o0506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 326); @@ -1038,11 +1066,27 @@ TEST(ValueTest, ToInt) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 0); + v = "0o573.2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 0); + // Binary v = "0b101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 45); + v = " 0b101101"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 45); + + v = "0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 45); + + v = " 0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 45); + v = "0b0101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 45); @@ -1054,6 +1098,10 @@ TEST(ValueTest, ToInt) v = "0b100112001"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toInt(), 0); + + v = "0b10011001.1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toInt(), 0); } TEST(ValueTest, ToLong) @@ -1078,11 +1126,6 @@ TEST(ValueTest, ToLong) v = true; ASSERT_EQ(v.toLong(), 1); - v = "999999999999999999"; - ASSERT_EQ(v.toLong(), 999999999999999999L); - v = "-999999999999999999"; - ASSERT_EQ(v.toLong(), -999999999999999999L); - v = "255.625"; ASSERT_EQ(v.toLong(), 255); v = "-255.625"; @@ -1108,6 +1151,18 @@ TEST(ValueTest, ToLong) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 2814); + v = " 0xafe"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 2814); + + v = "0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 2814); + + v = " 0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 2814); + v = "0x0afe"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 2814); @@ -1124,11 +1179,27 @@ TEST(ValueTest, ToLong) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 0); + v = "0xabf.d"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 0); + // Octal v = "0o506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 326); + v = " 0o506"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 326); + + v = "0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 326); + + v = " 0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 326); + v = "0o0506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 326); @@ -1141,11 +1212,27 @@ TEST(ValueTest, ToLong) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 0); + v = "0o573.2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 0); + // Binary v = "0b101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 45); + v = " 0b101101"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 45); + + v = "0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 45); + + v = " 0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 45); + v = "0b0101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 45); @@ -1157,6 +1244,10 @@ TEST(ValueTest, ToLong) v = "0b100112001"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toLong(), 0); + + v = "0b100112001.1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toLong(), 0); } TEST(ValueTest, ToDouble) @@ -1194,6 +1285,11 @@ TEST(ValueTest, ToDouble) v = "-255.625"; ASSERT_EQ(v.toDouble(), -255.625); + v = "0.15"; + ASSERT_EQ(v.toDouble(), 0.15); + v = "-0.15"; + ASSERT_EQ(v.toDouble(), -0.15); + v = "9432.4e-12"; ASSERT_EQ(v.toDouble(), 9.4324e-9); v = "-9432.4e-12"; @@ -1204,6 +1300,9 @@ TEST(ValueTest, ToDouble) v = "-9432.4e+6"; ASSERT_EQ(v.toDouble(), -9.4324e+9); + v = "1 2 3"; + ASSERT_EQ(v.toDouble(), 0); + v = "false"; ASSERT_EQ(v.toDouble(), 0.0); v = "true"; @@ -1224,6 +1323,18 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 2814); + v = " 0xafe"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 2814); + + v = "0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 2814); + + v = " 0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 2814); + v = "0x0afe"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 2814); @@ -1240,11 +1351,27 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "0xabf.d"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + // Octal v = "0o506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 326); + v = " 0o506"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 326); + + v = "0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 326); + + v = " 0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 326); + v = "0o0506"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 326); @@ -1257,11 +1384,27 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "0o573.2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + // Binary v = "0b101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 45); + v = " 0b101101"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 45); + + v = "0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 45); + + v = " 0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 45); + v = "0b0101101"; ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 45); @@ -1274,6 +1417,10 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "0b10011001.1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } @@ -1345,6 +1492,18 @@ TEST(ValueTest, ToBool) ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + v = " 0xafe"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = "0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = " 0xafe "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + v = "0x0afe"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); @@ -1365,11 +1524,27 @@ TEST(ValueTest, ToBool) ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + v = "0xabf.d"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + // Octal v = "0o506"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + v = " 0o506"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = "0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = " 0o506 "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + v = "0o0506"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); @@ -1382,11 +1557,27 @@ TEST(ValueTest, ToBool) ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + v = "0o573.2"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + // Binary v = "0b101101"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + v = " 0b101101"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = "0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + + v = " 0b101101 "; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); + v = "0b0101101"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); @@ -1398,6 +1589,10 @@ TEST(ValueTest, ToBool) v = "0b100112001"; ASSERT_TRUE(v.isString()); ASSERT_TRUE(v.toBool()); + + v = "0b10011001.1"; + ASSERT_TRUE(v.isString()); + ASSERT_TRUE(v.toBool()); } TEST(ValueTest, ToString) From 49a44ed670c2bfb269257ff051a65b51539b92b4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:49:01 +0200 Subject: [PATCH 25/72] Add support for more conversion edge cases --- src/scratch/value_functions_p.h | 95 +++++++++++++++++++++++++++-- test/scratch_classes/value_test.cpp | 62 ++++++++++++++++++- 2 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index 32853c0b..222000a6 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -72,12 +72,14 @@ extern "C" if (ok) *ok = false; - // Ignore floats + // Ignore dots, plus and minus signs const char *p = s; - while (p++ < s + n) { - if (*p == '.') + while (p < s + n) { + if (*p == '.' || *p == '+' || *p == '-') return 0; + + p++; } double result = 0; @@ -97,6 +99,16 @@ extern "C" if (ok) *ok = false; + // Ignore plus and minus signs + const char *p = s; + + while (p < s + n) { + if (*p == '+' || *p == '-') + return 0; + + p++; + } + char *err; const double ret = std::strtol(s, &err, 8); @@ -115,6 +127,16 @@ extern "C" if (ok) *ok = false; + // Ignore plus and minus signs + const char *p = s; + + while (p < s + n) { + if (*p == '+' || *p == '-') + return 0; + + p++; + } + char *err; const double ret = std::strtol(s, &err, 2); @@ -164,10 +186,53 @@ extern "C" p++; } - if (end - begin <= 0) + if (end - begin <= 0 || end > strEnd) { + if (ok) + *ok = true; + + return 0; + } + + char *copy = nullptr; + + // If there's a leading plus sign, copy the string and replace the plus sign with zero (e. g. '+.15' -> '0.15') + // If there's a leading minus sign with a dot, insert zero next to the minus sign (e. g. '-.15' -> '-0.15') + if (begin[0] == '+' || (begin[0] == '-' && end - begin > 1 && begin[1] == '.')) { + copy = new char[end - begin + 2]; + + if (begin[0] == '-') { + copy[0] = '-'; + copy[1] = '0'; + memcpy(copy + 2, begin + 1, end - begin - 1); + copy[end - begin + 1] = '\0'; + end = copy + (end - begin) + 1; + } else { + memcpy(copy, begin, end - begin); + copy[0] = '0'; + copy[end - begin] = '\0'; + end = copy + (end - begin); + } + begin = copy; + } else { + // If there's a leading dot, copy the string and insert zero prior to the dot (e. g. '.15' -> '0.15') + if (begin[0] == '.') { + copy = new char[end - begin + 2]; + copy[0] = '0'; + memcpy(copy + 1, begin, end - begin); + end = copy + (end - begin) + 1; + begin = copy; + } + } + + if (end - begin <= 0) { + if (copy) + delete[] copy; + return 0; + } - if (end - begin > 2 && begin[0] == '0') { + // Handle hex, oct and bin (currently ignored if the string was manipulated above) + if (!copy && end - begin > 2 && begin[0] == '0') { const char prefix = begin[1]; const char *sub = begin + 2; @@ -181,19 +246,37 @@ extern "C" } // Trim leading zeros - while (begin < end && (*begin == '0') && !(begin + 1 < end && begin[1] == '.')) + bool trimmed = false; + + while (begin < end && (*begin == '0') && !(begin + 1 < end && begin[1] == '.')) { + trimmed = true; ++begin; + } if (end - begin <= 0) { if (ok) *ok = true; + if (copy) + delete[] copy; + + return 0; + } + + // Ignore cases like '0+5' or '0-5' + if (trimmed && (begin[0] == '+' || begin[0] == '-')) { + if (copy) + delete[] copy; + return 0; } double ret = 0; auto [ptr, ec] = fast_float::from_chars(begin, end, ret, fast_float::chars_format::json); + if (copy) + delete[] copy; + if (ec == std::errc{} && ptr == end) { if (ok) *ok = true; diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 68881024..54ec3a60 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -299,7 +299,7 @@ TEST(ValueTest, StdStringConstructor) ASSERT_FALSE(v.isNegativeInfinity()); ASSERT_FALSE(v.isNaN()); ASSERT_FALSE(v.isNumber()); - ASSERT_FALSE(v.isValidNumber()); + ASSERT_TRUE(v.isValidNumber()); ASSERT_TRUE(v.isInt()); ASSERT_FALSE(v.isBool()); ASSERT_TRUE(v.isString()); @@ -1290,6 +1290,18 @@ TEST(ValueTest, ToDouble) v = "-0.15"; ASSERT_EQ(v.toDouble(), -0.15); + v = "+.15"; + ASSERT_EQ(v.toDouble(), 0.15); + v = ".15"; + ASSERT_EQ(v.toDouble(), 0.15); + v = "-.15"; + ASSERT_EQ(v.toDouble(), -0.15); + + v = "0+5"; + ASSERT_EQ(v.toDouble(), 0); + v = "0-5"; + ASSERT_EQ(v.toDouble(), 0); + v = "9432.4e-12"; ASSERT_EQ(v.toDouble(), 9.4324e-9); v = "-9432.4e-12"; @@ -1355,6 +1367,22 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "+0xa"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "-0xa"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0x+a"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0x-a"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + // Octal v = "0o506"; ASSERT_TRUE(v.isString()); @@ -1388,6 +1416,22 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "+0o2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "-0o2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0o+2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0o-2"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + // Binary v = "0b101101"; ASSERT_TRUE(v.isString()); @@ -1421,6 +1465,22 @@ TEST(ValueTest, ToDouble) ASSERT_TRUE(v.isString()); ASSERT_EQ(v.toDouble(), 0); + v = "+0b1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "-0b1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0b+1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + + v = "0b-1"; + ASSERT_TRUE(v.isString()); + ASSERT_EQ(v.toDouble(), 0); + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } From 4ae88a2e1a7d6a2e5e4bbc776bf77106b88d95d3 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:32:09 +0200 Subject: [PATCH 26/72] Fix value_stringIsInt() --- src/scratch/value_functions_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index 222000a6..54b303a4 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -313,7 +313,7 @@ extern "C" const char *p = s; while (p < s + n) { - if (*s == '.' || *s == 'e' || *s == 'E') + if (*p == '.' || *p == 'e' || *p == 'E') return false; p++; From ed9e43518a8b234897e47b297cecc168414ee6c0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:38:17 +0200 Subject: [PATCH 27/72] Use value_checkString() to check if string is int --- src/scratch/value_functions.cpp | 2 +- test/scratch_classes/value_test.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 3509a877..fe7bf58a 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -270,7 +270,7 @@ extern "C" return v->doubleValue == intpart; } case ValueType::String: - return v->stringValue->find('.') == std::string::npos; + return value_checkString(*v->stringValue) == 1; } return false; diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 54ec3a60..ff71d0c3 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -227,7 +227,7 @@ TEST(ValueTest, StdStringConstructor) ASSERT_FALSE(v.isNaN()); ASSERT_FALSE(v.isNumber()); ASSERT_FALSE(v.isValidNumber()); - ASSERT_TRUE(v.isInt()); + ASSERT_FALSE(v.isInt()); ASSERT_FALSE(v.isBool()); ASSERT_TRUE(v.isString()); } @@ -270,7 +270,7 @@ TEST(ValueTest, StdStringConstructor) ASSERT_FALSE(v.isNaN()); ASSERT_FALSE(v.isNumber()); ASSERT_FALSE(v.isValidNumber()); - ASSERT_TRUE(v.isInt()); + ASSERT_FALSE(v.isInt()); ASSERT_FALSE(v.isBool()); ASSERT_TRUE(v.isString()); } @@ -353,7 +353,7 @@ TEST(ValueTest, CStringConstructor) ASSERT_FALSE(v.isNaN()); ASSERT_FALSE(v.isNumber()); ASSERT_FALSE(v.isValidNumber()); - ASSERT_TRUE(v.isInt()); + ASSERT_FALSE(v.isInt()); ASSERT_FALSE(v.isBool()); ASSERT_TRUE(v.isString()); } From a1b10add168a747a627c3762f8702e507c49d952 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:39:21 +0200 Subject: [PATCH 28/72] Use C string to store strings in values --- include/scratchcpp/valuedata.h | 3 +- src/scratch/value_functions.cpp | 64 ++++++++++++++-------------- src/scratch/value_functions_p.h | 74 ++++++++++++++++++++++++++------- 3 files changed, 92 insertions(+), 49 deletions(-) diff --git a/include/scratchcpp/valuedata.h b/include/scratchcpp/valuedata.h index aef7881f..58c863cf 100644 --- a/include/scratchcpp/valuedata.h +++ b/include/scratchcpp/valuedata.h @@ -37,10 +37,11 @@ extern "C" long intValue; double doubleValue; bool boolValue; - std::string *stringValue; + char *stringValue; }; ValueType type; + size_t stringSize; // allocated size, not length }; } diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index fe7bf58a..cfa8db4a 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -16,8 +16,9 @@ extern "C" { if (v->type == ValueType::String) { assert(v->stringValue); - delete v->stringValue; + free(v->stringValue); v->stringValue = nullptr; + v->stringSize = 0; } } @@ -105,32 +106,29 @@ extern "C" /*! Assigns string to the given value. */ void value_assign_string(ValueData *v, const std::string &stringValue) { - if (stringValue == "Infinity") { + value_assign_cstring(v, stringValue.c_str()); + } + + /*! Assigns C string to the given value. */ + void value_assign_cstring(ValueData *v, const char *stringValue) + { + if (strcmp(stringValue, "Infinity") == 0) { value_free(v); v->type = ValueType::Infinity; - } else if (stringValue == "-Infinity") { + } else if (strcmp(stringValue, "-Infinity") == 0) { value_free(v); v->type = ValueType::NegativeInfinity; - } else if (stringValue == "NaN") { + } else if (strcmp(stringValue, "NaN") == 0) { value_free(v); v->type = ValueType::NaN; + } else if (v->type == ValueType::String) { + value_replaceStr(v, stringValue); } else { - if (v->type == ValueType::String) - v->stringValue->assign(stringValue); - else { - value_free(v); - v->type = ValueType::String; - v->stringValue = new std::string(stringValue); - } + value_free(v); + value_initStr(v, stringValue); } } - /*! Assigns C string to the given value. */ - void value_assign_cstring(ValueData *v, const char *stringValue) - { - value_assign_string(v, std::string(stringValue)); - } - /*! Assigns special value to the given value. */ void value_assign_special(ValueData *v, SpecialValue specialValue) { @@ -162,10 +160,10 @@ extern "C" v->boolValue = another->boolValue; } else if (another->type == ValueType::String) { if (v->type == ValueType::String) - v->stringValue->assign(*another->stringValue); + value_replaceStr(v, another->stringValue); else { value_free(v); - v->stringValue = new std::string(*another->stringValue); + value_initStr(v, another->stringValue); } } @@ -185,7 +183,7 @@ extern "C" case ValueType::Double: return value_isInf(v->doubleValue); case ValueType::String: - return *v->stringValue == "Infinity"; + return strcmp(v->stringValue, "Infinity") == 0; default: return false; } @@ -202,7 +200,7 @@ extern "C" case ValueType::Double: return value_isNegativeInf(-v->doubleValue); case ValueType::String: - return *v->stringValue == "-Infinity"; + return strcmp(v->stringValue, "-Infinity") == 0; default: return false; } @@ -218,7 +216,7 @@ extern "C" assert(!std::isnan(v->doubleValue)); return std::isnan(v->doubleValue); case ValueType::String: - return *v->stringValue == "NaN"; + return strcmp(v->stringValue, "NaN") == 0; default: return false; } @@ -247,7 +245,7 @@ extern "C" case ValueType::Bool: return true; case ValueType::String: - return v->stringValue->empty() || value_checkString(*v->stringValue) > 0; + return strlen(v->stringValue) == 0 || value_checkString(v->stringValue) > 0; default: return false; } @@ -270,7 +268,7 @@ extern "C" return v->doubleValue == intpart; } case ValueType::String: - return value_checkString(*v->stringValue) == 1; + return value_checkString(v->stringValue) == 1; } return false; @@ -300,7 +298,7 @@ extern "C" else if (v->type == ValueType::Bool) return v->boolValue; else if (v->type == ValueType::String) - return value_stringToLong(*v->stringValue); + return value_stringToLong(v->stringValue); else return 0; } @@ -315,7 +313,7 @@ extern "C" else if (v->type == ValueType::Bool) return v->boolValue; else if (v->type == ValueType::String) - return value_stringToLong(*v->stringValue); + return value_stringToLong(v->stringValue); else return 0; } @@ -330,7 +328,7 @@ extern "C" else if (v->type == ValueType::Bool) return v->boolValue; else if (v->type == ValueType::String) - return value_stringToDouble(*v->stringValue); + return value_stringToDouble(v->stringValue); else if (v->type == ValueType::Infinity) return std::numeric_limits::infinity(); else if (v->type == ValueType::NegativeInfinity) @@ -349,7 +347,7 @@ extern "C" } else if (v->type == ValueType::Double) { return v->doubleValue != 0; } else if (v->type == ValueType::String) { - return !v->stringValue->empty() && !value_stringsEqual(*v->stringValue, "false") && *v->stringValue != "0"; + return strlen(v->stringValue) != 0 && !value_stringsEqual(v->stringValue, "false") && strcmp(v->stringValue, "0") != 0; } else if (v->type == ValueType::Infinity || v->type == ValueType::NegativeInfinity) { return true; } else if (v->type == ValueType::NaN) { @@ -363,7 +361,7 @@ extern "C" void value_toString(const libscratchcpp::ValueData *v, std::string *dst) { if (v->type == ValueType::String) - dst->assign(*v->stringValue); + dst->assign(v->stringValue); else if (v->type == ValueType::Integer) dst->assign(std::to_string(v->intValue)); else if (v->type == ValueType::Double) @@ -590,12 +588,12 @@ extern "C" double n1, n2; if (v1->type == ValueType::String) - n1 = value_stringToDouble(*v1->stringValue); + n1 = value_stringToDouble(v1->stringValue); else n1 = value_toDouble(v1); if (v2->type == ValueType::String) - n2 = value_stringToDouble(*v2->stringValue); + n2 = value_stringToDouble(v2->stringValue); else n2 = value_toDouble(v2); @@ -628,12 +626,12 @@ extern "C" double n1, n2; if (v1->type == ValueType::String) - n1 = value_stringToDouble(*v1->stringValue); + n1 = value_stringToDouble(v1->stringValue); else n1 = value_toDouble(v1); if (v2->type == ValueType::String) - n2 = value_stringToDouble(*v2->stringValue); + n2 = value_stringToDouble(v2->stringValue); else n2 = value_toDouble(v2); diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index 54b303a4..e58e6ed2 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -53,6 +53,46 @@ inline bool value_isNegativeInf(T v) extern "C" { + inline size_t value_getSize(size_t x) + { + if (x == 0) + return 0; + + size_t ret = 1; + + while (ret < x) + ret *= 2; + + return ret; + } + + inline void value_initStr(ValueData *v, const char *s) + { + const size_t len = strlen(s); + v->stringSize = value_getSize(len + 1); + v->type = ValueType::String; + v->stringValue = (char *)malloc((v->stringSize * sizeof(char))); + memcpy(v->stringValue, s, len); + v->stringValue[len] = '\0'; + } + + inline void value_replaceStr(ValueData *v, const char *s) + { + const size_t len = strlen(s); + const size_t size = value_getSize(len + 1); + + if (size == 0) + return; + + if (size > v->stringSize || v->stringSize / size > 3) { + v->stringSize = size; + v->stringValue = (char *)realloc(v->stringValue, v->stringSize * sizeof(char)); + } + + memcpy(v->stringValue, s, len); + v->stringValue[len] = '\0'; + } + inline bool value_u16StringsEqual(std::u16string s1, std::u16string s2) { std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); @@ -60,11 +100,13 @@ extern "C" return (s1.compare(s2) == 0); } - inline bool value_stringsEqual(std::string s1, std::string s2) + inline bool value_stringsEqual(const char *s1, const char *s2) { - std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); - std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); - return (s1.compare(s2) == 0); + std::string str1(s1); + std::string str2(s2); + std::transform(str1.begin(), str1.end(), str1.begin(), ::tolower); + std::transform(str2.begin(), str2.end(), str2.begin(), ::tolower); + return (str1.compare(str2) == 0); } inline double value_hexToDec(const char *s, int n, bool *ok) @@ -150,20 +192,22 @@ extern "C" } } - inline double value_stringToDouble(const std::string &s, bool *ok = nullptr) + inline double value_stringToDouble(const char *s, bool *ok = nullptr) { if (ok) *ok = false; - if (s.empty()) { + const size_t len = strlen(s); + + if (strlen(s) == 0) { if (ok) *ok = true; return 0; } - const char *begin = s.data(); - const char *strEnd = s.data() + s.size(); + const char *begin = s; + const char *strEnd = s + len; const char *end = strEnd; // Trim leading spaces @@ -286,15 +330,15 @@ extern "C" return 0; // Special values - if (s == "Infinity") { + if (strcmp(s, "Infinity") == 0) { if (ok) *ok = true; return std::numeric_limits::infinity(); - } else if (s == "-Infinity") { + } else if (strcmp(s, "-Infinity") == 0) { if (ok) *ok = true; return -std::numeric_limits::infinity(); - } else if (s == "NaN") { + } else if (strcmp(s, "NaN") == 0) { if (ok) *ok = true; return std::numeric_limits::quiet_NaN(); @@ -303,7 +347,7 @@ extern "C" return 0; } - inline long value_stringToLong(const std::string &s, bool *ok = nullptr) + inline long value_stringToLong(const char *s, bool *ok = nullptr) { return value_stringToDouble(s, ok); } @@ -364,11 +408,11 @@ extern "C" return std::round(v * f) / f; } - inline int value_checkString(const std::string &str) + inline int value_checkString(const char *str) { bool ok; - if (value_stringIsInt(str.c_str(), str.size())) { + if (value_stringIsInt(str, strlen(str))) { value_stringToLong(str, &ok); return ok ? 1 : 0; } else { @@ -386,7 +430,7 @@ extern "C" // Since functions calling this already prioritize int, double and bool, // we can optimize by prioritizing the other types here. if (v->type == ValueType::String) - return value_stringToDouble(*v->stringValue, ok); + return value_stringToDouble(v->stringValue, ok); else if (v->type == ValueType::Infinity) return std::numeric_limits::infinity(); else if (v->type == ValueType::NegativeInfinity) From cef78ba388939cbe8fb3032dcd850697f4e03f4d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:02:07 +0200 Subject: [PATCH 29/72] Optimize value_stringToDouble() --- src/scratch/value_functions_p.h | 76 +++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index e58e6ed2..c30869bc 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -53,6 +53,41 @@ inline bool value_isNegativeInf(T v) extern "C" { + inline long value_convert_int_str(const char *str, int n, bool *ok) + { + if (ok) + *ok = false; + + const char *begin = str; + const char *end = begin + n; + + bool isNegative = false; + long ret = 0; + + if (*str == '-' || *str == '+') { + isNegative = (*str == '-'); + ++str; + + if (str == begin + n) + return 0; + } + + while (str < end) { + if (*str < '0' || *str > '9') + return 0; + + ret = ret * 10 + (*str++ - '0'); + } + + if (ok) + *ok = true; + + if (isNegative) + return -ret; + else + return ret; + } + inline size_t value_getSize(size_t x) { if (x == 0) @@ -197,44 +232,40 @@ extern "C" if (ok) *ok = false; - const size_t len = strlen(s); - - if (strlen(s) == 0) { + if (!s || *s == '\0') { if (ok) *ok = true; return 0; } + const size_t len = strlen(s); const char *begin = s; - const char *strEnd = s + len; - const char *end = strEnd; + const char *end = s + len; // Trim leading spaces while (begin < end && IS_SPACE(*begin)) ++begin; - end = begin + 1; - // Trim trailing spaces - while (end < strEnd && !IS_SPACE(*(end))) - ++end; - - // Only whitespace can be after the end - const char *p = end; + while (end > begin && IS_SPACE(*(end - 1))) + --end; - while (p < strEnd) { - if (!IS_SPACE(*p)) - return 0; - - p++; + if (begin == end) { // All spaces case + if (ok) + *ok = true; + return 0.0; } - if (end - begin <= 0 || end > strEnd) { + // Try to convert integer early + bool isInt = false; + double ret = value_convert_int_str(begin, end - begin, &isInt); + + if (isInt) { if (ok) *ok = true; - return 0; + return ret; } char *copy = nullptr; @@ -315,7 +346,6 @@ extern "C" return 0; } - double ret = 0; auto [ptr, ec] = fast_float::from_chars(begin, end, ret, fast_float::chars_format::json); if (copy) @@ -330,15 +360,15 @@ extern "C" return 0; // Special values - if (strcmp(s, "Infinity") == 0) { + if (strncmp(s, "Infinity", len) == 0) { if (ok) *ok = true; return std::numeric_limits::infinity(); - } else if (strcmp(s, "-Infinity") == 0) { + } else if (strncmp(s, "-Infinity", len) == 0) { if (ok) *ok = true; return -std::numeric_limits::infinity(); - } else if (strcmp(s, "NaN") == 0) { + } else if (strncmp(s, "NaN", len) == 0) { if (ok) *ok = true; return std::numeric_limits::quiet_NaN(); From 494fe3a0b83ed208bc84b9801034cf0824fb11f6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:50:47 +0200 Subject: [PATCH 30/72] Add support for converting numbers with a trailing dot --- src/scratch/value_functions_p.h | 20 ++++++++++++++++++++ test/scratch_classes/value_test.cpp | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index c30869bc..051a41aa 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -320,6 +320,26 @@ extern "C" } } + // If there's a trailing dot, copy the string and append zero next to it (e. g. '1.' -> '1.0') + if (end[-1] == '.') { + if (copy) { + char *tmpCopy = new char[end - begin + 2]; + memcpy(tmpCopy, begin, end - begin); + delete[] copy; + copy = tmpCopy; + end = copy + (end - begin) + 1; + begin = copy; + } else { + copy = new char[end - begin + 2]; + memcpy(copy, begin, end - begin); + end = copy + (end - begin) + 1; + begin = copy; + } + + copy[end - begin - 1] = '0'; + copy[end - begin] = '\0'; + } + // Trim leading zeros bool trimmed = false; diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index ff71d0c3..295fd73f 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -1297,6 +1297,13 @@ TEST(ValueTest, ToDouble) v = "-.15"; ASSERT_EQ(v.toDouble(), -0.15); + v = "1."; + ASSERT_EQ(v.toDouble(), 1); + v = "+1."; + ASSERT_EQ(v.toDouble(), 1); + v = "-1."; + ASSERT_EQ(v.toDouble(), -1); + v = "0+5"; ASSERT_EQ(v.toDouble(), 0); v = "0-5"; From ecd5916861f2c447d7bbe118a0c2274cf2e0944b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:54:22 +0200 Subject: [PATCH 31/72] Doxyfile: Ignore signal.h --- Doxyfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index 9b266624..8847319b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -958,7 +958,8 @@ RECURSIVE = YES # run. EXCLUDE = docs/theme \ - include/scratchcpp/spimpl.h + include/scratchcpp/spimpl.h \ + include/scratchcpp/signal.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded From 5b4a90d6a85de1b33cd9200e626812ad6ea8f29e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:59:53 +0200 Subject: [PATCH 32/72] Import veque https://github.com/Shmoopty/veque/blob/master/include/veque.hpp --- Doxyfile | 3 +- include/scratchcpp/veque.h | 1473 ++++++++++++++++++++++++++++++++++++ 2 files changed, 1475 insertions(+), 1 deletion(-) create mode 100644 include/scratchcpp/veque.h diff --git a/Doxyfile b/Doxyfile index 8847319b..c2fcdb0a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -959,7 +959,8 @@ RECURSIVE = YES EXCLUDE = docs/theme \ include/scratchcpp/spimpl.h \ - include/scratchcpp/signal.h + include/scratchcpp/signal.h \ + include/scratchcpp/veque.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/include/scratchcpp/veque.h b/include/scratchcpp/veque.h new file mode 100644 index 00000000..f75877c7 --- /dev/null +++ b/include/scratchcpp/veque.h @@ -0,0 +1,1473 @@ +/* + * veque.hpp + * + * Efficient generic C++ container combining useful features of std::vector and std::deque + * + * Copyright (C) 2019 Drew Dormann + * + */ + +#ifndef VEQUE_HEADER_GUARD +#define VEQUE_HEADER_GUARD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace veque +{ + // Very fast resizing behavior + struct fast_resize_traits + { + // Relative to size(), amount of unused space to reserve when reallocating + using allocation_before_front = std::ratio<1>; + using allocation_after_back = std::ratio<1>; + + // If true, arbitrary insert and erase operations are twice the speed of + // std::vector, but those operations invalidate all iterators + static constexpr auto resize_from_closest_side = true; + }; + + // Match std::vector iterator invalidation rules + struct vector_compatible_resize_traits + { + // Relative to size(), amount of unused space to reserve when reallocating + using allocation_before_front = std::ratio<1>; + using allocation_after_back = std::ratio<1>; + + // If false, veque is a 100% compatible drop-in replacement for + // std::vector including iterator invalidation rules + static constexpr auto resize_from_closest_side = false; + }; + + // Resizing behavior resembling std::vector. Also ideal for queue-like push_back/pop_front behavior. + struct std_vector_traits + { + // Reserve storage only at back, like std::vector + using allocation_before_front = std::ratio<0>; + using allocation_after_back = std::ratio<1>; + + // Same iterator invalidation rules as std::vector + static constexpr auto resize_from_closest_side = false; + }; + + // Never reallocate more storage than is needed + struct no_reserve_traits + { + // Any operation requiring a greater size reserves only that size + using allocation_before_front = std::ratio<0>; + using allocation_after_back = std::ratio<0>; + + // Same iterator invalidation rules as std::vector + static constexpr auto resize_from_closest_side = false; + }; + + template< typename T, typename ResizeTraits = fast_resize_traits, typename Allocator = std::allocator > + class veque + { + public: + + // Types + using allocator_type = Allocator; + using alloc_traits = std::allocator_traits; + using value_type = T; + using reference = T &; + using const_reference = const T &; + using pointer = T *; + using const_pointer = const T *; + using iterator = T *; + using const_iterator = const T *; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using difference_type = std::ptrdiff_t; + using size_type = std::size_t; + using ssize_type = std::ptrdiff_t; + + // Common member functions + veque() noexcept ( noexcept(Allocator()) ) + : veque( Allocator() ) + { + } + + explicit veque( const Allocator& alloc ) noexcept + : _data { 0, alloc } + { + } + + explicit veque( size_type n, const Allocator& alloc = Allocator() ) + : veque( _allocate_uninitialized_tag{}, n, alloc ) + { + _value_construct_range( begin(), end() ); + } + + veque( size_type n, const T &value, const Allocator& alloc = Allocator() ) + : veque( _allocate_uninitialized_tag{}, n, alloc ) + { + _value_construct_range( begin(), end(), value ); + } + + template< typename InputIt, typename ItCat = typename std::iterator_traits::iterator_category > + veque( InputIt b, InputIt e, const Allocator& alloc = Allocator() ) + : veque( b, e, alloc, ItCat{} ) + { + } + + veque( std::initializer_list lst, const Allocator& alloc = Allocator() ) + : veque( _allocate_uninitialized_tag{}, lst.size(), alloc ) + { + _copy_construct_range( lst.begin(), lst.end(), begin() ); + } + + veque( const veque & other ) + : veque( _allocate_uninitialized_tag{}, other.size(), alloc_traits::select_on_container_copy_construction( other._allocator() ) ) + { + _copy_construct_range( other.begin(), other.end(), begin() ); + } + + template< typename OtherResizeTraits > + veque( const veque & other ) + : veque( _allocate_uninitialized_tag{}, other.size(), alloc_traits::select_on_container_copy_construction( other._allocator() ) ) + { + _copy_construct_range( other.begin(), other.end(), begin() ); + } + + template< typename OtherResizeTraits > + veque( const veque & other, const Allocator & alloc ) + : veque( _allocate_uninitialized_tag{}, other.size(), alloc ) + { + _copy_construct_range( other.begin(), other.end(), begin() ); + } + + veque( veque && other ) noexcept + { + _swap_with_allocator( std::move(other) ); + } + + template< typename OtherResizeTraits > + veque( veque && other ) noexcept + { + _swap_with_allocator( std::move(other) ); + } + + template< typename OtherResizeTraits > + veque( veque && other, const Allocator & alloc ) noexcept + : veque( alloc ) + { + if constexpr ( !alloc_traits::is_always_equal::value ) + { + if ( alloc != other._allocator() ) + { + // Incompatible allocators. Allocate new storage. + auto replacement = veque( _allocate_uninitialized_tag{}, other.size(), alloc ); + _nothrow_move_construct_range( other.begin(), other.end(), replacement.begin() ); + _swap_without_allocator( std::move(replacement) ); + return; + } + } + _swap_without_allocator( std::move(other) ); + } + + ~veque() + { + _destroy( begin(), end() ); + } + + veque & operator=( const veque & other ) + { + return _copy_assignment( other ); + } + + template< typename OtherResizeTraits > + veque & operator=( const veque & other ) + { + return _copy_assignment( other ); + } + + veque & operator=( veque && other ) noexcept( + noexcept(alloc_traits::propagate_on_container_move_assignment::value + || alloc_traits::is_always_equal::value) ) + { + return _move_assignment( std::move(other) ); + } + + template< typename OtherResizeTraits > + veque & operator=( veque && other ) noexcept( + noexcept(alloc_traits::propagate_on_container_move_assignment::value + || alloc_traits::is_always_equal::value) ) + { + return _move_assignment( std::move(other) ); + } + + veque & operator=( std::initializer_list lst ) + { + _assign( lst.begin(), lst.end() ); + return *this; + } + + void assign( size_type count, const T &value ) + { + if ( count > capacity_full() ) + { + _swap_without_allocator( veque( count, value, _allocator() ) ); + } + else + { + _reassign_existing_storage( count, value ); + } + } + + template< typename InputIt, typename ItCat = typename std::iterator_traits::iterator_category > + void assign( InputIt b, InputIt e ) + { + _assign( b, e, ItCat{} ); + } + + void assign( std::initializer_list lst ) + { + _assign( lst.begin(), lst.end() ); + } + + allocator_type get_allocator() const + { + return _allocator(); + } + + // Element access + reference at( size_type idx ) + { + if ( idx >= size() ) + { + throw std::out_of_range("veque::at(" + std::to_string(idx) + ") out of range"); + } + return (*this)[idx]; + } + + const_reference at( size_type idx ) const + { + if ( idx >= size() ) + { + throw std::out_of_range("veque::at(" + std::to_string(idx) + ") out of range"); + } + return (*this)[idx]; + } + + reference operator[]( size_type idx ) + { + return *(begin() + idx); + } + + const_reference operator[]( size_type idx ) const + { + return *(begin() + idx); + } + + reference front() + { + return (*this)[0]; + } + + const_reference front() const + { + return (*this)[0]; + } + + reference back() + { + return (*this)[size() - 1]; + } + + const_reference back() const + { + return (*this)[size() - 1]; + } + + T * data() noexcept + { + return begin(); + } + + const T * data() const noexcept + { + return begin(); + } + + // Iterators + const_iterator cbegin() const noexcept + { + return _storage_begin() + _offset; + } + + iterator begin() noexcept + { + return _storage_begin() + _offset; + } + + const_iterator begin() const noexcept + { + return cbegin(); + } + + const_iterator cend() const noexcept + { + return _storage_begin() + _offset + size(); + } + + iterator end() noexcept + { + return _storage_begin() + _offset + size(); + } + + const_iterator end() const noexcept + { + return cend(); + } + + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + // Capacity + [[nodiscard]] bool empty() const noexcept + { + return size() == 0; + } + + size_type size() const noexcept + { + return _size; + } + + ssize_type ssize() const noexcept + { + return _size; + } + + size_type max_size() const noexcept + { + constexpr auto compile_time_limit = std::min( + // The ssize type's ceiling + std::numeric_limits::max() / sizeof(T), + // Ceiling imposed by std::ratio math + std::numeric_limits::max() / _full_realloc::num + ); + + // The allocator's ceiling + auto runtime_limit = alloc_traits::max_size(_allocator() ); + + return std::min( compile_time_limit, runtime_limit ); + } + + // Reserve front and back capacity, in one operation. + void reserve( size_type front, size_type back ) + { + if ( front > capacity_front() || back > capacity_back() ) + { + auto allocated_before_begin = std::max( capacity_front(), front ) - size(); + auto allocated_after_begin = std::max( capacity_back(), back ); + auto new_full_capacity = allocated_before_begin + allocated_after_begin; + + if ( new_full_capacity > max_size() ) + { + throw std::length_error("veque::reserve(" + std::to_string(front) + ", " + std::to_string(back) + ") exceeds max_size()"); + } + _reallocate( new_full_capacity, allocated_before_begin ); + } + } + + void reserve_front( size_type count ) + { + reserve( count, 0 ); + } + + void reserve_back( size_type count ) + { + reserve( 0, count ); + } + + void reserve( size_type count ) + { + reserve( count, count ); + } + + // Returns current size + unused allocated storage before front() + size_type capacity_front() const noexcept + { + return _offset + size(); + } + + // Returns current size + unused allocated storage after back() + size_type capacity_back() const noexcept + { + return capacity_full() - _offset; + } + + // Returns current size + all unused allocated storage + size_type capacity_full() const noexcept + { + return _data._allocated; + } + + // To achieve interface parity with std::vector, capacity() returns capacity_back(); + size_type capacity() const noexcept + { + return capacity_back(); + } + + void shrink_to_fit() + { + if ( size() < capacity_full() ) + { + _reallocate( size(), 0 ); + } + } + + // Modifiers + void clear() noexcept + { + _destroy( begin(), end() ); + _size = 0; + _offset = 0; + if constexpr ( std::ratio_greater_v<_unused_realloc, std::ratio<0>> ) + { + using unused_front_ratio = std::ratio_divide<_front_realloc,_unused_realloc>; + _offset = capacity_full() * unused_front_ratio::num / unused_front_ratio::den; + } + } + + iterator insert( const_iterator it, const T & value ) + { + return emplace( it, value ); + } + + iterator insert( const_iterator it, T && value ) + { + return emplace( it, std::move(value) ); + } + + iterator insert( const_iterator it, size_type count, const T & value ) + { + auto res = _insert_storage( it, count ); + _value_construct_range( res, res + count, value ); + return res; + } + + template< typename InputIt, typename ItCat = typename std::iterator_traits::iterator_category > + iterator insert( const_iterator it, InputIt b, InputIt e ) + { + return _insert( it, b, e, ItCat{} ); + } + + iterator insert( const_iterator it, std::initializer_list lst ) + { + return insert( it, lst.begin(), lst.end() ); + } + + template< typename ...Args > + iterator emplace( const_iterator it, Args && ... args ) + { + auto res = _insert_storage( it, 1 ); + alloc_traits::construct( _allocator(), res, std::forward(args)... ); + return res; + } + + iterator erase( const_iterator it ) + { + return erase( it, std::next(it) ); + } + + iterator erase( const_iterator b, const_iterator e ) + { + auto count = std::distance( b, e ); + if constexpr ( _resize_from_closest_side ) + { + auto elements_before = std::distance( cbegin(), b ); + auto elements_after = std::distance( e, cend( ) ); + if ( elements_before < elements_after ) + { + _shift_back( begin(), b, count ); + _move_begin( count ); + return _mutable_iterator(e); + } + } + _shift_front( e, end(), count ); + _move_end(-count); + return _mutable_iterator(b); + } + + void push_back( const T & value ) + { + emplace_back( value ); + } + + void push_back( T && value ) + { + emplace_back( std::move(value) ); + } + + template< typename ... Args> + reference emplace_back( Args && ...args ) + { + if ( size() == capacity_back() ) + { + _reallocate_space_at_back( size() + 1 ); + } + alloc_traits::construct( _allocator(), end(), std::forward(args)... ); + _move_end( 1 ); + return back(); + } + + void push_front( const T & value ) + { + emplace_front( value ); + } + + void push_front( T && value ) + { + emplace_front( std::move(value) ); + } + + template< typename ... Args> + reference emplace_front( Args && ...args ) + { + if ( size() == capacity_front() ) + { + _reallocate_space_at_front( size() + 1 ); + } + alloc_traits::construct( _allocator(), begin()-1, std::forward(args)... ); + _move_begin( -1 ); + return front(); + } + + void pop_back() + { + alloc_traits::destroy( _allocator(), &back() ); + _move_end( -1 ); + } + + // Move-savvy pop back with strong exception guarantee + T pop_back_element() + { + auto res( _nothrow_construct_move(back()) ); + pop_back(); + return res; + } + + void pop_front() + { + alloc_traits::destroy( _allocator(), &front() ); + _move_begin( 1 ); + } + + // Move-savvy pop front with strong exception guarantee + T pop_front_element() + { + auto res( _nothrow_construct_move(front()) ); + pop_front(); + return res; + } + + // Resizes the veque, by adding or removing from the front. + void resize_front( size_type count ) + { + _resize_front( count ); + } + + void resize_front( size_type count, const T & value ) + { + _resize_front( count, value ); + } + + // Resizes the veque, by adding or removing from the back. + void resize_back( size_type count ) + { + _resize_back( count ); + } + + void resize_back( size_type count, const T & value ) + { + _resize_back( count, value ); + } + + // To achieve interface parity with std::vector, resize() performs resize_back(); + void resize( size_type count ) + { + _resize_back( count ); + } + + void resize( size_type count, const T & value ) + { + _resize_back( count, value ); + } + + template< typename OtherResizeTraits > + void swap( veque & other ) noexcept( + noexcept(alloc_traits::propagate_on_container_swap::value + || alloc_traits::is_always_equal::value)) + { + if constexpr ( alloc_traits::propagate_on_container_swap::value ) + { + _swap_with_allocator( std::move(other) ); + } + else + { + if ( _allocator() == other._allocator() ) + { + _swap_without_allocator( std::move(other) ); + } + else + { + // std::vector would declare this UB. Allocate compatible storage and make it work. + auto new_this = veque( _allocate_uninitialized_tag{}, other.size(), _allocator() ); + _nothrow_move_construct_range( other.begin(), other.end(), new_this.begin() ); + + auto new_other = veque( _allocate_uninitialized_tag{}, size(), other._allocator() ); + _nothrow_move_construct_range( begin(), end(), new_other.begin() ); + + _swap_without_allocator( std::move(new_this) ); + other._swap_without_allocator( std::move(new_other) ); + } + } + } + + private: + + using _front_realloc = typename ResizeTraits::allocation_before_front::type; + using _back_realloc = typename ResizeTraits::allocation_after_back::type; + using _unused_realloc = std::ratio_add< _front_realloc, _back_realloc >; + using _full_realloc = std::ratio_add< std::ratio<1>, _unused_realloc >; + + static constexpr auto _resize_from_closest_side = ResizeTraits::resize_from_closest_side; + + static_assert( _front_realloc::den > 0 ); + static_assert( _back_realloc::den > 0 ); + static_assert( std::ratio_greater_equal_v<_front_realloc,std::ratio<0>>, "Reserving negative space is not well-defined" ); + static_assert( std::ratio_greater_equal_v<_back_realloc,std::ratio<0>>, "Reserving negative space is not well-defined" ); + static_assert( std::ratio_greater_equal_v<_unused_realloc,std::ratio<0>>, "Reserving negative space is not well-defined" ); + + // Confirmation that allocator_traits will only directly call placement new(ptr)T() + static constexpr auto _calls_default_constructor_directly = + std::is_same_v>; + // Confirmation that allocator_traits will only directly call placement new(ptr)T(const T&) + static constexpr auto _calls_copy_constructor_directly = + std::is_same_v>; + // Confirmation that allocator_traits will only directly call ~T() + static constexpr auto _calls_destructor_directly = + std::is_same_v>; + + size_type _size = 0; // Number of elements in use + size_type _offset = 0; // Number of uninitialized elements before begin() + + // Deriving from allocator to leverage empty base optimization + struct Data : Allocator + { + T *_storage = nullptr; + size_type _allocated = 0; + + Data() = default; + Data( size_type size, const Allocator & alloc ) + : Allocator{alloc} + , _storage{size ? std::allocator_traits::allocate( allocator(), size ) : nullptr} + , _allocated{size} + { + } + Data( const Data& ) = delete; + Data( Data && other ) + { + *this = std::move(other); + } + ~Data() + { + if ( _storage ) + { + std::allocator_traits::deallocate( allocator(), _storage, _allocated ); + } + } + Data& operator=( const Data & ) = delete; + Data& operator=( Data && other ) + { + using std::swap; + if constexpr( ! std::is_empty_v ) + { + swap(allocator(), other.allocator()); + } + swap(_allocated, other._allocated); + swap(_storage, other._storage); + return *this; + } + Allocator& allocator() { return *this; } + const Allocator& allocator() const { return *this; } + } _data; + + template< typename InputIt > + veque( InputIt b, InputIt e, const Allocator & alloc, std::input_iterator_tag ) + : veque{alloc} + { + for ( ; b != e; ++b ) + { + push_back( *b ); + } + } + + template< typename InputIt > + veque( InputIt b, InputIt e, const Allocator & alloc, std::forward_iterator_tag ) + : veque( _allocate_uninitialized_tag{}, std::distance( b, e ), alloc ) + { + _copy_construct_range( b, e, begin() ); + } + + // Private tag to indicate initial allocation + struct _allocate_uninitialized_tag {}; + // Private tag to indicate resizing allocation + struct _reallocate_uninitialized_tag {}; + + // Create an uninitialized empty veque, with specified storage params + veque( _allocate_uninitialized_tag, size_type size, size_type allocated, size_type offset, const Allocator & alloc ) + : _size{ size } + , _offset{ offset } + , _data { allocated, alloc } + { + } + + // Create an uninitialized empty veque, with storage for expected size + veque( _allocate_uninitialized_tag, size_type size, const Allocator & alloc ) + : veque( _allocate_uninitialized_tag{}, size, size, 0, alloc ) + { + } + + // Create an uninitialized empty veque, with storage for expected reallocated size + veque( _reallocate_uninitialized_tag, size_type size, const Allocator & alloc ) + : veque( _allocate_uninitialized_tag{}, size, _calc_reallocation(size), _calc_offset(size), alloc ) + { + } + + static constexpr size_type _calc_reallocation( size_type size ) + { + return size * _full_realloc::num / _full_realloc::den; + } + + static constexpr size_type _calc_offset( size_type size ) + { + return size * _front_realloc::num / _front_realloc::den; + } + + // Acquire Allocator + Allocator& _allocator() noexcept + { + return _data.allocator(); + } + + const Allocator& _allocator() const noexcept + { + return _data.allocator(); + } + + // Destroy elements in range + void _destroy( const_iterator b, const_iterator e ) + { + if constexpr ( std::is_trivially_destructible_v && _calls_destructor_directly ) + { + (void)b; (void)e; // Unused + } + else + { + auto start = _mutable_iterator(b); + for ( auto i = start; i != e; ++i ) + { + alloc_traits::destroy( _allocator(), i ); + } + } + } + + template< typename OtherResizeTraits > + veque & _copy_assignment( const veque & other ) + { + if constexpr ( alloc_traits::propagate_on_container_copy_assignment::value ) + { + if constexpr ( !alloc_traits::is_always_equal::value ) + { + if ( other._allocator() != _allocator() || other.size() > capacity_full() ) + { + _swap_with_allocator( veque( other, other._allocator() ) ); + return *this; + } + } + } + if ( other.size() > capacity_full() ) + { + _swap_without_allocator( veque( other, _allocator() ) ); + } + else + { + _reassign_existing_storage( other.begin(), other.end() ); + } + return *this; + } + + template< typename OtherResizeTraits > + veque & _move_assignment( veque && other ) noexcept( + noexcept(alloc_traits::propagate_on_container_move_assignment::value + || alloc_traits::is_always_equal::value) ) + { + if constexpr ( !alloc_traits::is_always_equal::value ) + { + if ( _allocator() != other._allocator() ) + { + if constexpr ( alloc_traits::propagate_on_container_move_assignment::value ) + { + _swap_with_allocator( std::move(other) ); + } + else + { + if ( other.size() > capacity_full() ) + { + _swap_without_allocator( veque( std::move(other), _allocator() ) ); + } + else + { + _reassign_existing_storage( std::move_iterator(other.begin()), std::move_iterator(other.end()) ); + } + } + return *this; + } + } + _swap_without_allocator( std::move(other) ); + return *this; + } + + // Construct elements in range + template< typename ...Args > + void _value_construct_range( const_iterator b, const_iterator e, const Args & ...args ) + { + static_assert( sizeof...(args) <= 1, "This is for default- or copy-constructing" ); + + if constexpr ( std::is_trivially_copy_constructible_v && _calls_default_constructor_directly ) + { + if constexpr ( sizeof...(args) == 0 ) + { + std::memset( _mutable_iterator(b), 0, std::distance( b, e ) * sizeof(T) ); + } + else + { + std::fill( _mutable_iterator(b), _mutable_iterator(e), args...); + } + } + else + { + for ( auto dest = _mutable_iterator(b); dest != e; ++dest ) + { + alloc_traits::construct( _allocator(), dest, args... ); + } + } + } + + template< typename It > + void _copy_construct_range( It b, It e, const_iterator dest ) + { + static_assert( std::is_convertible_v::iterator_category,std::forward_iterator_tag> ); + if constexpr ( std::is_trivially_copy_constructible_v && _calls_copy_constructor_directly ) + { + std::memcpy( _mutable_iterator(dest), b, std::distance( b, e ) * sizeof(T) ); + } + else + { + for ( ; b != e; ++dest, ++b ) + { + alloc_traits::construct( _allocator(), dest, *b ); + } + } + } + + template< typename It > + void _assign( It b, It e ) + { + static_assert( std::is_convertible_v::iterator_category,std::forward_iterator_tag> ); + if ( std::distance( b, e ) > static_cast(capacity_full()) ) + { + _swap_without_allocator( veque( b, e, _allocator() ) ); + } + else + { + _reassign_existing_storage( b, e ); + } + } + + template< typename It > + void _assign( It b, It e, std::forward_iterator_tag ) + { + _assign( b, e ); + } + + template< typename It > + void _assign( It b, It e, std::input_iterator_tag ) + { + // Input Iterators require a single-pass solution + clear(); + for ( ; b != e; ++b ) + { + push_back( *b ); + } + } + + template< typename It > + iterator _insert( const_iterator it, It b, It e ) + { + static_assert( std::is_convertible_v::iterator_category,std::forward_iterator_tag> ); + auto res = _insert_storage( it, std::distance( b, e ) ); + _copy_construct_range( b, e, res ); + return res; + } + + template< typename It > + iterator _insert( const_iterator it, It b, It e, std::forward_iterator_tag ) + { + return _insert( it, b, e ); + } + + template< typename It > + iterator _insert( const_iterator it, It b, It e, std::input_iterator_tag ) + { + // Input Iterators require a single-pass solution + auto allocated = veque( b, e ); + _insert( it, allocated.begin(), allocated.end() ); + } + + template< typename OtherResizeTraits > + void _swap_with_allocator( veque && other ) noexcept + { + // Swap everything + std::swap( _size, other._size ); + std::swap( _offset, other._offset ); + std::swap( _data, other._data ); + } + + template< typename OtherResizeTraits > + void _swap_without_allocator( veque && other ) noexcept + { + // Don't swap _data.allocator(). + std::swap( _size, other._size ); + std::swap( _offset, other._offset ); + std::swap( _data._allocated, other._data._allocated); + std::swap( _data._storage, other._data._storage); + } + + template< typename ...Args > + void _resize_front( size_type count, const Args & ...args ) + { + difference_type delta = count - size(); + if ( delta > 0 ) + { + if ( count > capacity_front() ) + { + _reallocate_space_at_front( count ); + } + _value_construct_range( begin() - delta, begin(), args... ); + } + else + { + _destroy( begin(), begin() - delta ); + } + _move_begin( -delta ); + } + + template< typename ...Args > + void _resize_back( size_type count, const Args & ...args ) + { + difference_type delta = count - size(); + if ( delta > 0 ) + { + if ( count > capacity_back() ) + { + _reallocate_space_at_back( count ); + } + _value_construct_range( end(), end() + delta, args... ); + } + else + { + _destroy( end() + delta, end() ); + } + _move_end( delta ); + } + + // Move veque to new storage, with specified capacity... + // ...and yet-unused space at back of this storage + void _reallocate_space_at_back( size_type count ) + { + auto storage_needed = _calc_reallocation(count); + auto current_capacity = capacity_full(); + auto new_offset = _calc_offset(count); + if ( storage_needed <= current_capacity ) + { + // Shift elements toward front + auto distance = _offset - new_offset; + _shift_front( begin(), end(), distance ); + _move_begin(-distance); + _move_end(-distance); + } + else + { + _reallocate( storage_needed, new_offset ); + } + } + + // ...and yet-unused space at front of this storage + void _reallocate_space_at_front( size_type count ) + { + auto storage_needed = _calc_reallocation(count); + auto current_capacity = capacity_full(); + auto new_offset = count - size() + _calc_offset(count); + if ( storage_needed <= current_capacity ) + { + // Shift elements toward back + auto distance = new_offset - _offset; + _shift_back( begin(), end(), distance ); + _move_begin(distance); + _move_end(distance); + } + else + { + _reallocate( storage_needed, new_offset ); + } + } + + // Move veque to new storage, with specified capacity + void _reallocate( size_type allocated, size_type offset ) + { + auto replacement = veque( _allocate_uninitialized_tag{}, size(), allocated, offset, _allocator() ); + _nothrow_move_construct_range( begin(), end(), replacement.begin() ); + _swap_without_allocator( std::move(replacement) ); + } + + // Insert empty space, choosing the most efficient way to shift existing elements + iterator _insert_storage( const_iterator it, size_type count ) + { + auto required_size = size() + count; + auto can_shift_back = capacity_back() >= required_size; + if constexpr ( std::ratio_greater_v<_front_realloc,std::ratio<0>> ) + { + if ( can_shift_back && it == begin() ) + { + // Don't favor shifting entire contents back + // if realloc will create space + can_shift_back = false; + } + } + + if constexpr ( _resize_from_closest_side ) + { + auto can_shift_front = capacity_front() >= required_size; + if constexpr ( std::ratio_greater_v<_back_realloc,std::ratio<0>> ) + { + if ( can_shift_front && it == end() ) + { + // Don't favor shifting entire contents front + // if realloc will create space + can_shift_front = false; + } + } + + if ( can_shift_back && can_shift_front) + { + // Capacity allows shifting in either direction. + // Remove the choice with the greater operation count. + auto index = std::distance( cbegin(), it ); + if ( index <= ssize() / 2 ) + { + can_shift_back = false; + } + else + { + can_shift_front = false; + } + } + + if ( can_shift_front ) + { + _shift_front( begin(), it, count ); + _move_begin( -count ); + return _mutable_iterator(it) - count; + } + } + if ( can_shift_back ) + { + _shift_back( it, end(), count ); + _move_end( count ); + return _mutable_iterator(it); + } + + // Insufficient capacity. Allocate new storage. + auto replacement = veque( _reallocate_uninitialized_tag{}, required_size, _allocator() ); + auto index = std::distance( cbegin(), it ); + auto insertion_point = begin() + index; + + _nothrow_move_construct_range( begin(), insertion_point, replacement.begin() ); + _nothrow_move_construct_range( insertion_point, end(), replacement.begin() + index + count ); + _swap_with_allocator( std::move(replacement) ); + return begin() + index; + } + + // Moves a valid subrange in the front direction. + // Veque will grow, if range moves past begin(). + // Veque will shrink if range includes end(). + // Returns iterator to beginning of destructed gap + void _shift_front( const_iterator b, const_iterator e, size_type count ) + { + if ( e == begin() ) + { + return; + } + auto element_count = std::distance( b, e ); + auto start = _mutable_iterator(b); + if ( element_count > 0 ) + { + auto dest = start - count; + if constexpr ( std::is_trivially_copyable_v && std::is_trivially_copy_constructible_v && _calls_copy_constructor_directly ) + { + std::memmove( dest, start, element_count * sizeof(T) ); + } + else + { + auto src = start; + auto dest_construct_end = std::min( begin(), _mutable_iterator(e) - count ); + for ( ; dest < dest_construct_end; ++src, ++dest ) + { + _nothrow_move_construct( dest, src ); + } + for ( ; src != e; ++src, ++dest ) + { + _nothrow_move_assign( dest, src ); + } + } + } + _destroy( std::max( cbegin(), e - count ), e ); + } + + // Moves a range towards the back. Veque will grow, if needed. Vacated elements are destructed. + // Moves a valid subrange in the back direction. + // Veque will grow, if range moves past end(). + // Veque will shrink if range includes begin(). + // Returns iterator to beginning of destructed gap + void _shift_back( const_iterator b, const_iterator e, size_type count ) + { + auto start = _mutable_iterator(b); + if ( b == end() ) + { + return; + } + auto element_count = std::distance( b, e ); + if ( element_count > 0 ) + { + if constexpr ( std::is_trivially_copyable_v && std::is_trivially_copy_constructible_v && _calls_copy_constructor_directly ) + { + std::memmove( start + count, start, element_count * sizeof(T) ); + } + else + { + auto src = _mutable_iterator(e-1); + auto dest = src + count; + auto dest_construct_end = std::max( end()-1, dest - element_count ); + for ( ; dest > dest_construct_end; --src, --dest ) + { + // Construct to destinations at or after end() + _nothrow_move_construct( dest, src ); + } + for ( ; src != b-1; --src, --dest ) + { + // Assign to destinations before before end() + _nothrow_move_assign( dest, src ); + } + } + } + _destroy( b, std::min( cend(), b + count ) ); + } + + // Assigns a fitting range of new elements to currently held storage. + // Favors copying over constructing firstly, and positioning the new elements + // at the center of storage secondly + template< typename It > + void _reassign_existing_storage( It b, It e ) + { + static_assert( std::is_convertible_v::iterator_category,std::forward_iterator_tag> ); + + auto count = std::distance( b, e ); + auto size_delta = static_cast( count - size() ); + // The "ideal" begin would put the new data in the center of storage + auto ideal_begin = _storage_begin() + (capacity_full() - count) / 2; + + if ( size() == 0 ) + { + // Existing veque is empty. Construct at the ideal location + _copy_construct_range( b, e, ideal_begin ); + } + else if ( size_delta == 0 ) + { + // Existing veque is the same size. Avoid any construction by copy-assigning everything + std::copy( b, e, begin() ); + return; + } + else if ( size_delta < 0 ) + { + // New size is smaller. Copy-assign everything, placing results as close to center as possible + ideal_begin = std::clamp( ideal_begin, begin(), end() - count ); + + _destroy( begin(), ideal_begin ); + auto ideal_end = std::copy( b, e, ideal_begin ); + _destroy( ideal_end, end() ); + } + else + { + // New size is larger. Copy-assign all existing elements, placing newly + // constructed elements so final store is as close to center as possible + ideal_begin = std::clamp( ideal_begin, end() - count, begin() ); + + auto src = b; + auto copy_src = src + std::distance( ideal_begin, begin() ); + _copy_construct_range( src, copy_src, ideal_begin ); + std::copy( copy_src, copy_src + ssize(), begin() ); + _copy_construct_range( copy_src + ssize(), e, end() ); + } + _move_begin( std::distance( begin(), ideal_begin ) ); + _move_end( std::distance( end(), ideal_begin + count ) ); + } + + void _reassign_existing_storage( size_type count, const T & value ) + { + auto size_delta = static_cast( count - size() ); + auto ideal_begin = _storage_begin(); + // The "ideal" begin would put the new data in the center of storage + if constexpr ( std::ratio_greater_v<_unused_realloc, std::ratio<0>> ) + { + using ideal_begin_ratio = std::ratio_divide<_front_realloc, _unused_realloc >; + ideal_begin += (capacity_full() - count) * ideal_begin_ratio::num / ideal_begin_ratio::den; + } + + if ( size() == 0 ) + { + // Existing veque is empty. Construct at the ideal location + _value_construct_range( ideal_begin, ideal_begin + count, value ); + } + else if ( size_delta == 0 ) + { + // Existing veque is the same size. Avoid any construction by copy-assigning everything + std::fill( begin(), end(), value ); + return; + } + else if ( size_delta < 0 ) + { + // New size is smaller. Copy-assign everything, placing results as close to center as possible + ideal_begin = std::clamp( ideal_begin, begin(), end() - count ); + + _destroy( begin(), ideal_begin ); + std::fill( ideal_begin, ideal_begin + count, value ); + _destroy( ideal_begin + count, end() ); + } + else + { + // New size is larger. Copy-assign all existing elements, placing newly + // constructed elements so final store is as close to center as possible + ideal_begin += _calc_offset(capacity_full() - count) / 2; + _value_construct_range( ideal_begin, begin(), value ); + std::fill( begin(), end(), value ); + _value_construct_range( end(), ideal_begin + count, value ); + } + _move_begin( std::distance( begin(), ideal_begin ) ); + _move_end( std::distance( end(), ideal_begin + count ) ); + } + + // Casts to T&& or T&, depending on whether move construction is noexcept + static decltype(auto) _nothrow_construct_move( T & t ) + { + if constexpr ( std::is_nothrow_move_constructible_v ) + { + return std::move(t); + } + else + { + return t; + } + } + + // Move-constructs if noexcept, copies otherwise + void _nothrow_move_construct( iterator dest, iterator src ) + { + if constexpr ( std::is_trivially_copy_constructible_v && _calls_copy_constructor_directly ) + { + *dest = *src; + } + else + { + alloc_traits::construct( _allocator(), dest, _nothrow_construct_move(*src) ); + } + } + + void _nothrow_move_construct_range( iterator b, iterator e, iterator dest ) + { + auto size = std::distance( b, e ); + if ( size ) + { + if constexpr ( std::is_trivially_copy_constructible_v && _calls_copy_constructor_directly ) + { + std::memcpy( dest, b, size * sizeof(T) ); + } + else + { + for ( ; b != e; ++dest, ++b ) + { + _nothrow_move_construct( dest, b ); + } + } + } + } + + // Move-assigns if noexcept, copies otherwise + static void _nothrow_move_assign( iterator dest, iterator src ) + { + if constexpr ( std::is_nothrow_move_assignable_v ) + { + *dest = std::move(*src); + } + else + { + *dest = *src; + } + } + + static void _nothrow_move_assign_range( iterator b, iterator e, iterator src ) + { + for ( auto dest = b; dest != e; ++dest, ++src ) + { + _nothrow_move_assign( dest, src ); + } + } + + // Adjust begin(), end() iterators + void _move_begin( difference_type count ) noexcept + { + _size -= count; + _offset += count; + } + + void _move_end( difference_type count ) noexcept + { + _size += count; + } + + // Convert a local const_iterator to iterator + iterator _mutable_iterator( const_iterator i ) + { + return begin() + std::distance( cbegin(), i ); + } + + // Retrieves beginning of storage, which may be before begin() + const_iterator _storage_begin() const noexcept + { + return _data._storage; + } + + iterator _storage_begin() noexcept + { + return _data._storage; + } + }; + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator==( const veque &lhs, const veque &rhs ) + { + return std::equal( lhs.begin(), lhs.end(), rhs.begin(), rhs.end() ); + } + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator!=( const veque &lhs, const veque &rhs ) + { + return !( lhs == rhs ); + } + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator<( const veque &lhs, const veque &rhs ) + { + return std::lexicographical_compare( lhs.begin(), lhs.end(), rhs.begin(), rhs.end() ); + } + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator<=( const veque &lhs, const veque &rhs ) + { + return !( rhs < lhs ); + } + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator>( const veque &lhs, const veque &rhs ) + { + return ( rhs < lhs ); + } + + template< typename T, typename LResizeTraits, typename LAlloc, typename RResizeTraits, typename RAlloc > + inline bool operator>=( const veque &lhs, const veque &rhs ) + { + return !( lhs < rhs ); + } + + template< typename T, typename ResizeTraits, typename Alloc > + inline void swap( veque & lhs, veque & rhs ) noexcept(noexcept(lhs.swap(rhs))) + { + lhs.swap(rhs); + } + + // Template deduction guide for iterator pair + template< typename InputIt, + typename Alloc = std::allocator::value_type>> + veque(InputIt, InputIt, Alloc = Alloc()) + -> veque::value_type, fast_resize_traits, Alloc>; + +} + +namespace std +{ + template< typename T, typename ResizeTraits, typename Alloc > + struct hash> + { + size_t operator()( const veque::veque & v ) const + { + size_t hash = 0; + auto hasher = std::hash(); + for ( auto && val : v ) + { + hash ^= hasher(val) + 0x9e3779b9 + (hash<<6) + (hash>>2); + } + return hash; + } + }; +} + +#endif From ecef707e1a70d8354f830e6be05b9634b5abd3b8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 14 Sep 2024 13:00:20 +0200 Subject: [PATCH 33/72] Use veque instead of std::deque for lists --- include/scratchcpp/list.h | 6 +++--- test/scratch_classes/sprite_test.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index 6d63e8b2..ba90fc49 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -3,7 +3,7 @@ #pragma once #include -#include +#include #include "value.h" #include "entity.h" @@ -17,7 +17,7 @@ class ListPrivate; /*! \brief The List class represents a Scratch list. */ class LIBSCRATCHCPP_EXPORT List - : public std::deque + : public veque::veque , public Entity { public: @@ -40,7 +40,7 @@ class LIBSCRATCHCPP_EXPORT List void removeAt(int index) { erase(begin() + index); } /*! Inserts an item at index. */ - void insert(int index, const Value &value) { std::deque::insert(begin() + index, value); } + void insert(int index, const Value &value) { veque::insert(begin() + index, value); } /*! Replaces the item at index. */ void replace(int index, const Value &value) { at(index) = value; } diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp index 9c00e4d7..77f131fd 100644 --- a/test/scratch_classes/sprite_test.cpp +++ b/test/scratch_classes/sprite_test.cpp @@ -112,10 +112,10 @@ TEST(SpriteTest, Clone) ASSERT_NE(clone->lists(), root->lists()); ASSERT_EQ(clone->listAt(0)->id(), "c"); ASSERT_EQ(clone->listAt(0)->name(), "list1"); - ASSERT_EQ(*clone->listAt(0), std::deque({ "item1", "item2" })); + ASSERT_EQ(*clone->listAt(0), veque::veque({ "item1", "item2" })); ASSERT_EQ(clone->listAt(1)->id(), "d"); ASSERT_EQ(clone->listAt(1)->name(), "list2"); - ASSERT_EQ(*clone->listAt(1), std::deque({ "test" })); + ASSERT_EQ(*clone->listAt(1), veque::veque({ "test" })); ASSERT_EQ(clone->listAt(1)->target(), clone); ASSERT_EQ(clone->sounds().size(), 3); From ed0c34239afe5d8f47500e268fa452223b154ca2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 10:48:03 +0200 Subject: [PATCH 34/72] Hide list data in List class --- include/scratchcpp/list.h | 95 ++++++++- include/scratchcpp/value.h | 25 +++ src/engine/virtualmachine_p.cpp | 8 +- src/internal/scratch3reader.cpp | 2 +- src/scratch/list.cpp | 68 ++++--- src/scratch/list_p.h | 3 + test/engine/engine_test.cpp | 34 ++-- test/scratch_classes/list_test.cpp | 188 ++++++++++++------ test/scratch_classes/sprite_test.cpp | 12 +- test/virtual_machine/virtual_machine_test.cpp | 128 ++++++------ 10 files changed, 377 insertions(+), 186 deletions(-) diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index ba90fc49..223bca02 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -16,14 +16,14 @@ class Monitor; class ListPrivate; /*! \brief The List class represents a Scratch list. */ -class LIBSCRATCHCPP_EXPORT List - : public veque::veque - , public Entity +class LIBSCRATCHCPP_EXPORT List : public Entity { public: List(const std::string &id, const std::string &name); List(const List &) = delete; + ~List(); + const std::string &name(); void setName(const std::string &name); @@ -33,17 +33,95 @@ class LIBSCRATCHCPP_EXPORT List Monitor *monitor() const; void setMonitor(Monitor *monitor); - long indexOf(const Value &value) const; - bool contains(const Value &value) const; + /*! Returns the list size. */ + inline size_t size() const { return m_dataPtr->size(); } + + /*! Returns true if the list is empty. */ + inline bool empty() const { return m_dataPtr->empty(); } + + /*! Returns the index of the given item. */ + inline size_t indexOf(const ValueData &value) const + { + for (size_t i = 0; i < m_dataPtr->size(); i++) { + if (value_equals(&m_dataPtr->operator[](i), &value)) + return i; + } + + return -1; + } + + /*! Returns the index of the given item. */ + inline size_t indexOf(const Value &value) const { return indexOf(value.data()); } + + /*! Returns true if the list contains the given item. */ + inline bool contains(const ValueData &value) const { return (indexOf(value) != -1); } + + /*! Returns true if the list contains the given item. */ + inline bool contains(const Value &value) const { return contains(value.data()); } + + /*! Clears the list. */ + inline void clear() + { + for (ValueData &v : *m_dataPtr) + value_free(&v); + + m_dataPtr->clear(); + } + + /*! Appends an item. */ + inline void append(const ValueData &value) + { + m_dataPtr->push_back(ValueData()); + value_init(&m_dataPtr->back()); + value_assign_copy(&m_dataPtr->back(), &value); + } + + /*! Appends an item. */ + inline void append(const Value &value) { append(value.data()); } + + /*! Appends an empty item and returns the reference to it. Can be used for custom initialization. */ + inline ValueData &appendEmpty() + { + m_dataPtr->push_back(ValueData()); + value_init(&m_dataPtr->back()); + return m_dataPtr->back(); + } /*! Removes the item at index. */ - void removeAt(int index) { erase(begin() + index); } + inline void removeAt(size_t index) + { + assert(index >= 0 && index < size()); + value_free(&m_dataPtr->operator[](index)); + m_dataPtr->erase(m_dataPtr->begin() + index); + } + + /*! Inserts an item at index. */ + inline void insert(size_t index, const ValueData &value) + { + assert(index >= 0 && index <= size()); + m_dataPtr->insert(m_dataPtr->begin() + index, ValueData()); + value_init(&m_dataPtr->operator[](index)); + value_assign_copy(&m_dataPtr->operator[](index), &value); + } /*! Inserts an item at index. */ - void insert(int index, const Value &value) { veque::insert(begin() + index, value); } + inline void insert(size_t index, const Value &value) { insert(index, value.data()); } + + /*! Replaces the item at index. */ + inline void replace(size_t index, const ValueData &value) + { + assert(index >= 0 && index < size()); + value_assign_copy(&m_dataPtr->operator[](index), &value); + } /*! Replaces the item at index. */ - void replace(int index, const Value &value) { at(index) = value; } + inline void replace(size_t index, const Value &value) { replace(index, value.data()); } + + inline ValueData &operator[](size_t index) + { + assert(index >= 0 && index < size()); + return m_dataPtr->operator[](index); + } std::string toString() const; @@ -51,6 +129,7 @@ class LIBSCRATCHCPP_EXPORT List private: spimpl::unique_impl_ptr impl; + veque::veque *m_dataPtr = nullptr; // NOTE: accessing through pointer is faster! (from benchmarks) }; } // namespace libscratchcpp diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index 6f425893..df4db60e 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -84,6 +84,13 @@ class LIBSCRATCHCPP_EXPORT Value value_assign_special(&m_data, specialValue); } + /*! Constructs value from ValueData. */ + Value(const ValueData &v) + { + value_init(&m_data); + value_assign_copy(&m_data, &v); + } + Value(const Value &v) { value_init(&m_data); @@ -92,6 +99,18 @@ class LIBSCRATCHCPP_EXPORT Value ~Value() { value_free(&m_data); } + /*! + * Returns a read-only reference to the data. + * \note Valid until the Value object is destroyed. + */ + const ValueData &data() const { return m_data; }; + + /*! + * Returns a read/write reference to the data. + * \note Valid until the Value object is destroyed. + */ + ValueData &data() { return m_data; } + /*! Returns the type of the value. */ ValueType type() const { return m_data.type; } @@ -204,6 +223,12 @@ class LIBSCRATCHCPP_EXPORT Value return *this; } + const Value &operator=(const ValueData &v) + { + value_assign_copy(&m_data, &v); + return *this; + } + const Value &operator=(const Value &v) { value_assign_copy(&m_data, &v.m_data); diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp index 43ac22d2..3b2ab0b2 100644 --- a/src/engine/virtualmachine_p.cpp +++ b/src/engine/virtualmachine_p.cpp @@ -613,7 +613,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) DISPATCH(); OP(LIST_APPEND) : - lists[*++pos]->push_back(*READ_LAST_REG()); + lists[*++pos]->append(*READ_LAST_REG()); FREE_REGS(1); DISPATCH(); @@ -659,7 +659,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) } else { const std::string &str = indexValue->toString(); if (str == "last") { - list->push_back(*READ_REG(0, 2)); + list->append(*READ_REG(0, 2)); index = 0; } else if (str == "random") { size_t size = list->size(); @@ -669,7 +669,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) } if ((index != 0) || list->empty()) { if (list->empty()) - list->push_back(*READ_REG(0, 2)); + list->append(*READ_REG(0, 2)); else list->insert(index - 1, *READ_REG(0, 2)); } @@ -696,7 +696,7 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) index = 0; } if (index != 0) - list->operator[](index - 1) = *READ_REG(1, 2); + list->replace(index - 1, *READ_REG(1, 2)); FREE_REGS(2); DISPATCH(); } diff --git a/src/internal/scratch3reader.cpp b/src/internal/scratch3reader.cpp index 203cf9b6..47c8dfe7 100644 --- a/src/internal/scratch3reader.cpp +++ b/src/internal/scratch3reader.cpp @@ -63,7 +63,7 @@ bool Scratch3Reader::load() auto list = std::make_shared(it.key(), listInfo[0]); auto arr = listInfo[1]; for (auto item : arr) - list->push_back(jsonToValue(item)); + list->append(jsonToValue(item)); target->addList(list); } diff --git a/src/scratch/list.cpp b/src/scratch/list.cpp index 043077ba..cd7a7258 100644 --- a/src/scratch/list.cpp +++ b/src/scratch/list.cpp @@ -12,6 +12,13 @@ List::List(const std::string &id, const std::string &name) : Entity(id), impl(spimpl::make_unique_impl(name)) { + m_dataPtr = &impl->data; +} + +/*! Destroys List. */ +List::~List() +{ + clear(); } /*! Returns the name of the list. */ @@ -50,33 +57,21 @@ void List::setMonitor(Monitor *monitor) impl->monitor = monitor; } -/*! Returns the index of the given item. */ -long List::indexOf(const Value &value) const -{ - auto it = std::find(begin(), end(), value); - - if (it != end()) - return it - begin(); - else - return -1; -} - -/*! Returns true if the list contains the given item. */ -bool List::contains(const Value &value) const -{ - return (indexOf(value) != -1); -} - /*! Joins the list items with spaces or without any separator if there are only digits. */ std::string List::toString() const { std::string ret; + veque::veque strings; + strings.reserve(m_dataPtr->size()); bool digits = true; - for (const auto &item : *this) { - if (item.isValidNumber() && !item.toString().empty()) { - double doubleNum = item.toDouble(); - long num = item.toLong(); + for (const auto &item : *m_dataPtr) { + strings.push_back(std::string()); + value_toString(&item, &strings.back()); + + if (value_isValidNumber(&item) && !strings.back().empty()) { + double doubleNum = value_toDouble(&item); + long num = value_toLong(&item); if (doubleNum != num) { digits = false; @@ -93,13 +88,30 @@ std::string List::toString() const } } + size_t i; + std::string s; + if (digits) { - for (const auto &item : *this) - ret.append(item.toString()); + for (i = 0; i < strings.size(); i++) + ret.append(strings[i]); + + for (; i < m_dataPtr->size(); i++) { + value_toString(&m_dataPtr->operator[](i), &s); + ret.append(s); + } } else { - for (int i = 0; i < size(); i++) { - ret.append(at(i).toString()); - if (i + 1 < size()) + for (i = 0; i < strings.size(); i++) { + ret.append(strings[i]); + + if (i + 1 < m_dataPtr->size()) + ret.push_back(' '); + } + + for (; i < m_dataPtr->size(); i++) { + value_toString(&m_dataPtr->operator[](i), &s); + ret.append(s); + + if (i + 1 < m_dataPtr->size()) ret.push_back(' '); } } @@ -112,8 +124,8 @@ std::shared_ptr List::clone() { auto copy = std::make_shared(id(), impl->name); - for (const Value &item : *this) - copy->push_back(item); + for (const ValueData &item : *m_dataPtr) + copy->append(item); return copy; } diff --git a/src/scratch/list_p.h b/src/scratch/list_p.h index 7fed21d9..9a11a261 100644 --- a/src/scratch/list_p.h +++ b/src/scratch/list_p.h @@ -3,6 +3,8 @@ #pragma once #include +#include +#include namespace libscratchcpp { @@ -17,6 +19,7 @@ struct ListPrivate std::string name; Target *target = nullptr; Monitor *monitor = nullptr; + veque::veque data; }; } // namespace libscratchcpp diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index ca503321..8e3998fe 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -496,19 +496,19 @@ TEST(EngineTest, ExecutionOrder) auto list = GET_LIST(stage, "order"); ASSERT_EQ(list->size(), 13); - ASSERT_EQ((*list)[0].toString(), "Sprite2"); - ASSERT_EQ((*list)[1].toString(), "Sprite3"); - ASSERT_EQ((*list)[2].toString(), "Sprite1"); - ASSERT_EQ((*list)[3].toString(), "Stage"); - ASSERT_EQ((*list)[4].toString(), "Sprite1 1"); - ASSERT_EQ((*list)[5].toString(), "Sprite1 2"); - ASSERT_EQ((*list)[6].toString(), "Sprite1 3"); - ASSERT_EQ((*list)[7].toString(), "Sprite2 msg"); - ASSERT_EQ((*list)[8].toString(), "Sprite3 msg"); - ASSERT_EQ((*list)[9].toString(), "Sprite1 1 msg"); - ASSERT_EQ((*list)[10].toString(), "Sprite1 2 msg"); - ASSERT_EQ((*list)[11].toString(), "Sprite1 3 msg"); - ASSERT_EQ((*list)[12].toString(), "Stage msg"); + ASSERT_EQ(Value((*list)[0]).toString(), "Sprite2"); + ASSERT_EQ(Value((*list)[1]).toString(), "Sprite3"); + ASSERT_EQ(Value((*list)[2]).toString(), "Sprite1"); + ASSERT_EQ(Value((*list)[3]).toString(), "Stage"); + ASSERT_EQ(Value((*list)[4]).toString(), "Sprite1 1"); + ASSERT_EQ(Value((*list)[5]).toString(), "Sprite1 2"); + ASSERT_EQ(Value((*list)[6]).toString(), "Sprite1 3"); + ASSERT_EQ(Value((*list)[7]).toString(), "Sprite2 msg"); + ASSERT_EQ(Value((*list)[8]).toString(), "Sprite3 msg"); + ASSERT_EQ(Value((*list)[9]).toString(), "Sprite1 1 msg"); + ASSERT_EQ(Value((*list)[10]).toString(), "Sprite1 2 msg"); + ASSERT_EQ(Value((*list)[11]).toString(), "Sprite1 3 msg"); + ASSERT_EQ(Value((*list)[12]).toString(), "Stage msg"); } TEST(EngineTest, KeyState) @@ -1859,9 +1859,9 @@ TEST(EngineTest, Clones) for (int i = 0; i < list->size(); i++) { if (i < 10) - ASSERT_EQ((*list)[i].toInt(), 1); + ASSERT_EQ(value_toInt(&(*list)[i]), 1); else - ASSERT_EQ((*list)[i].toInt(), 2); + ASSERT_EQ(value_toInt(&(*list)[i]), 2); } ASSERT_LIST(stage, "log2"); @@ -1869,9 +1869,9 @@ TEST(EngineTest, Clones) for (int i = 0; i < list->size(); i++) { if (i < 10) - ASSERT_EQ((*list)[i].toInt(), 1); + ASSERT_EQ(value_toInt(&(*list)[i]), 1); else - ASSERT_EQ((*list)[i].toString(), "12"); + ASSERT_EQ(Value((*list)[i]).toString(), "12"); } } diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp index 2c01ec9a..9c006424 100644 --- a/test/scratch_classes/list_test.cpp +++ b/test/scratch_classes/list_test.cpp @@ -40,71 +40,125 @@ TEST(ListTest, Monitor) ASSERT_EQ(list.monitor(), &monitor); } +TEST(ListTest, Size) +{ + List list("", "test list"); + ASSERT_EQ(list.size(), 0); + ASSERT_TRUE(list.empty()); + + list.append("Lorem"); + list.append("ipsum"); + ASSERT_EQ(list.size(), 2); + ASSERT_FALSE(list.empty()); + + list.append("dolor"); + ASSERT_EQ(list.size(), 3); + ASSERT_FALSE(list.empty()); + + list.removeAt(0); + ASSERT_EQ(list.size(), 2); + ASSERT_FALSE(list.empty()); +} + TEST(ListTest, IndexOf) { List list("", "test list"); - list.push_back("Lorem"); - list.push_back("ipsum"); - list.push_back("dolor"); - list.push_back("sit"); - list.push_back("amet"); - list.push_back("é čľíá"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + list.append("é čľíá"); ASSERT_EQ(list.indexOf(""), -1); ASSERT_EQ(list.indexOf("test"), -1); ASSERT_EQ(list.indexOf("Lorem"), 0); ASSERT_EQ(list.indexOf("ipsum"), 1); - ASSERT_EQ(list.indexOf("iPsum"), 1); - ASSERT_EQ(list.indexOf("dolor"), 2); + ASSERT_EQ(list.indexOf(Value("iPsum").data()), 1); + ASSERT_EQ(list.indexOf(Value("dolor").data()), 2); ASSERT_EQ(list.indexOf("sit"), 3); ASSERT_EQ(list.indexOf("amet"), 4); ASSERT_EQ(list.indexOf("é čľíá"), 5); list.clear(); - list.push_back(""); + list.append(""); ASSERT_EQ(list.indexOf(""), 0); } TEST(ListTest, Contains) { List list("", "test list"); - list.push_back("Lorem"); - list.push_back("ipsum"); - list.push_back("dolor"); - list.push_back("sit"); - list.push_back("amet"); - list.push_back("é čľíá"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + list.append("é čľíá"); ASSERT_FALSE(list.contains("")); + ASSERT_FALSE(list.contains(Value("test").data())); ASSERT_FALSE(list.contains("test")); ASSERT_TRUE(list.contains("Lorem")); ASSERT_TRUE(list.contains("ipsum")); ASSERT_TRUE(list.contains("iPsum")); ASSERT_TRUE(list.contains("dolor")); - ASSERT_TRUE(list.contains("sit")); + ASSERT_TRUE(list.contains(Value("sit").data())); ASSERT_TRUE(list.contains("amet")); ASSERT_TRUE(list.contains("é čľíá")); list.clear(); - list.push_back(""); + list.append(""); ASSERT_TRUE(list.contains("")); } +TEST(ListTest, Clear) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + list.append("é čľíá"); + + list.clear(); + ASSERT_EQ(list.size(), 0); + ASSERT_TRUE(list.empty()); +} + +TEST(ListTest, Append) +{ + List list("", "test list"); + list.append("Lorem"); + list.append(Value("ipsum").data()); + list.append("dolor"); + list.append(Value("sit").data()); + list.append("amet"); + ASSERT_EQ(list.toString(), "Lorem ipsum dolor sit amet"); + + list.append("consectetur"); + ASSERT_EQ(list.toString(), "Lorem ipsum dolor sit amet consectetur"); + + list.clear(); + list.append("adipiscing"); + ASSERT_EQ(list.toString(), "adipiscing"); +} + TEST(ListTest, RemoveAt) { List list("", "test list"); - list.push_back("Lorem"); - list.push_back("ipsum"); - list.push_back("dolor"); - list.push_back("sit"); - list.push_back("amet"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); list.removeAt(1); ASSERT_EQ(list.toString(), "Lorem dolor sit amet"); list.removeAt(3); ASSERT_EQ(list.toString(), "Lorem dolor sit"); list.removeAt(0); ASSERT_EQ(list.toString(), "dolor sit"); - list.removeAt(3); + list.removeAt(1); ASSERT_EQ(list.toString(), "dolor"); list.removeAt(0); ASSERT_TRUE(list.empty()); @@ -113,13 +167,13 @@ TEST(ListTest, RemoveAt) TEST(ListTest, Insert) { List list("", "test list"); - list.insert(0, "Lorem"); + list.insert(0, Value("Lorem").data()); list.insert(0, "ipsum"); list.insert(0, "dolor"); ASSERT_EQ(list.toString(), "dolor ipsum Lorem"); list.insert(1, "sit"); ASSERT_EQ(list.toString(), "dolor sit ipsum Lorem"); - list.insert(2, "amet"); + list.insert(2, Value("amet").data()); ASSERT_EQ(list.toString(), "dolor sit amet ipsum Lorem"); list.insert(5, "consectetur"); ASSERT_EQ(list.toString(), "dolor sit amet ipsum Lorem consectetur"); @@ -130,77 +184,93 @@ TEST(ListTest, Insert) TEST(ListTest, Replace) { List list("", "test list"); - list.push_back("Lorem"); - list.push_back("ipsum"); - list.push_back("dolor"); - list.push_back("sit"); - list.push_back("amet"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); ASSERT_EQ(list.toString(), "Lorem ipsum dolor sit amet"); list.replace(0, "test1"); ASSERT_EQ(list.toString(), "test1 ipsum dolor sit amet"); - list.replace(4, "test2"); + list.replace(4, Value("test2").data()); ASSERT_EQ(list.toString(), "test1 ipsum dolor sit test2"); - list.replace(2, "test3"); + list.replace(2, Value("test3").data()); ASSERT_EQ(list.toString(), "test1 ipsum test3 sit test2"); } +TEST(ListTest, ArrayIndexOperator) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + ASSERT_EQ(Value(list[0]).toString(), "Lorem"); + ASSERT_EQ(Value(list[1]).toString(), "ipsum"); + ASSERT_EQ(Value(list[2]).toString(), "dolor"); + ASSERT_EQ(Value(list[3]).toString(), "sit"); + ASSERT_EQ(Value(list[4]).toString(), "amet"); +} + TEST(ListTest, ToString) { List list("", "test list"); ASSERT_EQ(list.toString(), ""); - list.push_back(""); + list.append(""); ASSERT_EQ(list.toString(), ""); - list.push_back(""); - list.push_back(""); + list.append(""); + list.append(""); ASSERT_EQ(list.toString(), " "); list.clear(); - list.push_back("item1"); - list.push_back("i t e m 2"); - list.push_back("item 3"); + list.append("item1"); + list.append("i t e m 2"); + list.append("item 3"); ASSERT_EQ(list.toString(), "item1 i t e m 2 item 3"); list.clear(); - list.push_back(" "); - list.push_back("a "); - list.push_back(" b"); - list.push_back(" c "); + list.append(" "); + list.append("a "); + list.append(" b"); + list.append(" c "); ASSERT_EQ(list.toString(), " a b c "); list.clear(); - list.push_back("áä"); - list.push_back("ľ š"); + list.append("áä"); + list.append("ľ š"); ASSERT_EQ(list.toString(), "áä ľ š"); list.clear(); - list.push_back(-2); - list.push_back(5); - list.push_back(8); + list.append(-2); + list.append(5); + list.append(8); ASSERT_EQ(list.toString(), "-2 5 8"); list.clear(); - list.push_back(2); - list.push_back(10); - list.push_back(8); + list.append(2); + list.append(10); + list.append(8); ASSERT_EQ(list.toString(), "2 10 8"); list.clear(); - list.push_back(0); - list.push_back(9); - list.push_back(8); + list.append(0); + list.append(9); + list.append(8); ASSERT_EQ(list.toString(), "098"); } TEST(ListTest, Clone) { List list("abc", "test list"); - list.push_back("Lorem"); - list.push_back("ipsum"); - list.push_back("dolor"); - list.push_back("sit"); - list.push_back("amet"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); std::shared_ptr clone = list.clone(); ASSERT_TRUE(clone); @@ -209,5 +279,5 @@ TEST(ListTest, Clone) ASSERT_EQ(clone->size(), list.size()); for (std::size_t i = 0; i < list.size(); i++) - ASSERT_EQ(list[i], (*clone)[i]); + ASSERT_TRUE(value_equals(&list[i], &(*clone)[i])); } diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp index 77f131fd..23c778d6 100644 --- a/test/scratch_classes/sprite_test.cpp +++ b/test/scratch_classes/sprite_test.cpp @@ -72,10 +72,10 @@ TEST(SpriteTest, Clone) sprite.addSound(s3); auto list1 = std::make_shared("c", "list1"); - list1->push_back("item1"); - list1->push_back("item2"); + list1->append("item1"); + list1->append("item2"); auto list2 = std::make_shared("d", "list2"); - list2->push_back("test"); + list2->append("test"); sprite.addList(list1); sprite.addList(list2); @@ -112,10 +112,12 @@ TEST(SpriteTest, Clone) ASSERT_NE(clone->lists(), root->lists()); ASSERT_EQ(clone->listAt(0)->id(), "c"); ASSERT_EQ(clone->listAt(0)->name(), "list1"); - ASSERT_EQ(*clone->listAt(0), veque::veque({ "item1", "item2" })); + ASSERT_EQ(clone->listAt(0)->size(), 2); + ASSERT_EQ(clone->listAt(0)->toString(), "item1 item2"); ASSERT_EQ(clone->listAt(1)->id(), "d"); ASSERT_EQ(clone->listAt(1)->name(), "list2"); - ASSERT_EQ(*clone->listAt(1), veque::veque({ "test" })); + ASSERT_EQ(clone->listAt(1)->size(), 1); + ASSERT_EQ(clone->listAt(1)->toString(), "test"); ASSERT_EQ(clone->listAt(1)->target(), clone); ASSERT_EQ(clone->sounds().size(), 3); diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp index 904174fa..e7e7744d 100644 --- a/test/virtual_machine/virtual_machine_test.cpp +++ b/test/virtual_machine/virtual_machine_test.cpp @@ -946,12 +946,12 @@ TEST(VirtualMachineTest, OP_READ_LIST) { static unsigned int bytecode[] = { OP_START, OP_READ_LIST, 0, OP_READ_LIST, 1, OP_HALT }; List list1("", "list1"); - list1.push_back(5.3); - list1.push_back("abc"); - list1.push_back(false); + list1.append(5.3); + list1.append("abc"); + list1.append(false); List list2("", "list2"); - list2.push_back("t e s t"); - list2.push_back(true); + list2.append("t e s t"); + list2.append(true); List *lists[] = { &list1, &list2 }; VirtualMachine vm; @@ -978,10 +978,10 @@ TEST(VirtualMachineTest, OP_LIST_APPEND) vm.run(); ASSERT_EQ(list1.size(), 1); - ASSERT_EQ(list1[0], 3.52); + ASSERT_EQ(Value(list1[0]), 3.52); ASSERT_EQ(list2.size(), 1); - ASSERT_EQ(list2[0], "test"); + ASSERT_EQ(Value(list2[0]), "test"); ASSERT_EQ(vm.registerCount(), 0); } @@ -997,14 +997,14 @@ TEST(VirtualMachineTest, OP_LIST_DEL) }; static Value constValues[] = { 3, 1, "6", 0, 7, -1, 9, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "invalid", "last", "random", "all" }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back("f"); - list1.push_back("g"); - list1.push_back("h"); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append("f"); + list1.append("g"); + list1.append("h"); List *lists[] = { &list1 }; RandomGeneratorMock rng; @@ -1041,11 +1041,11 @@ TEST(VirtualMachineTest, OP_LIST_DEL_ALL) { static unsigned int bytecode[] = { OP_START, OP_LIST_DEL_ALL, 0, OP_LIST_DEL_ALL, 1, OP_HALT }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); + list1.append("a"); + list1.append("b"); + list1.append("c"); List list2("", "list1"); - list1.push_back("ab c"); + list1.append("ab c"); List *lists[] = { &list1, &list2 }; VirtualMachine vm; @@ -1069,14 +1069,14 @@ TEST(VirtualMachineTest, OP_LIST_INSERT) }; static Value constValues[] = { "new item", "3", 1, 10, 0, 12, -1, 14, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid" }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back("f"); - list1.push_back("g"); - list1.push_back("h"); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append("f"); + list1.append("g"); + list1.append("h"); List *lists[] = { &list1 }; RandomGeneratorMock rng; @@ -1120,14 +1120,14 @@ TEST(VirtualMachineTest, OP_LIST_REPLACE) }; static Value constValues[] = { "new item", 3, "1", 8, 0, 9, -1, 12, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid", "test" }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back("f"); - list1.push_back("g"); - list1.push_back("h"); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append("f"); + list1.append("g"); + list1.append("h"); List *lists[] = { &list1 }; RandomGeneratorMock rng; @@ -1168,14 +1168,14 @@ TEST(VirtualMachineTest, OP_LIST_GET_ITEM) }; static Value constValues[] = { 3, 1, "8", 0, 9, -1, 12, SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN, "last", "random", "invalid" }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back("f"); - list1.push_back("g"); - list1.push_back("h"); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append("f"); + list1.append("g"); + list1.append("h"); List *lists[] = { &list1 }; RandomGeneratorMock rng; @@ -1215,14 +1215,14 @@ TEST(VirtualMachineTest, OP_LIST_INDEX_OF) }; static Value constValues[] = { "c", "A", "e", "", "invalid", SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back(""); - list1.push_back(SpecialValue::Infinity); - list1.push_back(8); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append(""); + list1.append(SpecialValue::Infinity); + list1.append(8); List *lists[] = { &list1 }; VirtualMachine vm; @@ -1245,12 +1245,12 @@ TEST(VirtualMachineTest, OP_LIST_LENGTH) { static unsigned int bytecode[] = { OP_START, OP_LIST_LENGTH, 0, OP_LIST_LENGTH, 1, OP_HALT }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); List list2("", "list2"); - list2.push_back("a"); + list2.append("a"); List *lists[] = { &list1, &list2 }; VirtualMachine vm; @@ -1270,14 +1270,14 @@ TEST(VirtualMachineTest, OP_LIST_CONTAINS) }; static Value constValues[] = { "c", "A", "e", "", "invalid", SpecialValue::NegativeInfinity, SpecialValue::Infinity, SpecialValue::NaN }; List list1("", "list1"); - list1.push_back("a"); - list1.push_back("b"); - list1.push_back("c"); - list1.push_back("d"); - list1.push_back("e"); - list1.push_back(""); - list1.push_back(SpecialValue::Infinity); - list1.push_back(8); + list1.append("a"); + list1.append("b"); + list1.append("c"); + list1.append("d"); + list1.append("e"); + list1.append(""); + list1.append(SpecialValue::Infinity); + list1.append(8); List *lists[] = { &list1 }; VirtualMachine vm; From 395e14a0bc78ee520c95e8df21c4ec277e128466 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 11:07:48 +0200 Subject: [PATCH 35/72] Optimize List::toString() --- include/scratchcpp/list.h | 68 +++++++++++++++++++++++++++++- src/engine/virtualmachine_p.cpp | 6 ++- src/scratch/list.cpp | 62 --------------------------- test/scratch_classes/list_test.cpp | 17 ++++++++ 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index 223bca02..54618b30 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -123,7 +123,73 @@ class LIBSCRATCHCPP_EXPORT List : public Entity return m_dataPtr->operator[](index); } - std::string toString() const; + /*! Joins the list items with spaces or without any separator if there are only digits and stores the result in dst. */ + inline void toString(std::string &dst) const + { + dst.clear(); + veque::veque strings; + strings.reserve(m_dataPtr->size()); + bool digits = true; + + for (const auto &item : *m_dataPtr) { + strings.push_back(std::string()); + value_toString(&item, &strings.back()); + + if (value_isValidNumber(&item) && !strings.back().empty()) { + double doubleNum = value_toDouble(&item); + long num = value_toLong(&item); + + if (doubleNum != num) { + digits = false; + break; + } + + if (num < 0 || num >= 10) { + digits = false; + break; + } + } else { + digits = false; + break; + } + } + + size_t i; + std::string s; + + if (digits) { + for (i = 0; i < strings.size(); i++) + dst.append(strings[i]); + + for (; i < m_dataPtr->size(); i++) { + value_toString(&m_dataPtr->operator[](i), &s); + dst.append(s); + } + } else { + for (i = 0; i < strings.size(); i++) { + dst.append(strings[i]); + + if (i + 1 < m_dataPtr->size()) + dst.push_back(' '); + } + + for (; i < m_dataPtr->size(); i++) { + value_toString(&m_dataPtr->operator[](i), &s); + dst.append(s); + + if (i + 1 < m_dataPtr->size()) + dst.push_back(' '); + } + } + } + + /*! Same as the other method, but returns the result directly. */ + inline std::string toString() const + { + std::string ret; + toString(ret); + return ret; + } std::shared_ptr clone(); diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp index 3b2ab0b2..18ff56c7 100644 --- a/src/engine/virtualmachine_p.cpp +++ b/src/engine/virtualmachine_p.cpp @@ -609,7 +609,11 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) DISPATCH(); OP(READ_LIST) : - ADD_RET_VALUE(lists[*++pos]->toString()); + { + std::string s; + lists[*++pos]->toString(s); + ADD_RET_VALUE(s); + } DISPATCH(); OP(LIST_APPEND) : diff --git a/src/scratch/list.cpp b/src/scratch/list.cpp index cd7a7258..b9ddf71e 100644 --- a/src/scratch/list.cpp +++ b/src/scratch/list.cpp @@ -57,68 +57,6 @@ void List::setMonitor(Monitor *monitor) impl->monitor = monitor; } -/*! Joins the list items with spaces or without any separator if there are only digits. */ -std::string List::toString() const -{ - std::string ret; - veque::veque strings; - strings.reserve(m_dataPtr->size()); - bool digits = true; - - for (const auto &item : *m_dataPtr) { - strings.push_back(std::string()); - value_toString(&item, &strings.back()); - - if (value_isValidNumber(&item) && !strings.back().empty()) { - double doubleNum = value_toDouble(&item); - long num = value_toLong(&item); - - if (doubleNum != num) { - digits = false; - break; - } - - if (num < 0 || num >= 10) { - digits = false; - break; - } - } else { - digits = false; - break; - } - } - - size_t i; - std::string s; - - if (digits) { - for (i = 0; i < strings.size(); i++) - ret.append(strings[i]); - - for (; i < m_dataPtr->size(); i++) { - value_toString(&m_dataPtr->operator[](i), &s); - ret.append(s); - } - } else { - for (i = 0; i < strings.size(); i++) { - ret.append(strings[i]); - - if (i + 1 < m_dataPtr->size()) - ret.push_back(' '); - } - - for (; i < m_dataPtr->size(); i++) { - value_toString(&m_dataPtr->operator[](i), &s); - ret.append(s); - - if (i + 1 < m_dataPtr->size()) - ret.push_back(' '); - } - } - - return ret; -} - /*! Creates a copy of the list. */ std::shared_ptr List::clone() { diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp index 9c006424..a6574b5d 100644 --- a/test/scratch_classes/list_test.cpp +++ b/test/scratch_classes/list_test.cpp @@ -217,9 +217,14 @@ TEST(ListTest, ArrayIndexOperator) TEST(ListTest, ToString) { List list("", "test list"); + std::string s; + list.toString(s); + ASSERT_EQ(s, ""); ASSERT_EQ(list.toString(), ""); list.append(""); + list.toString(s); + ASSERT_EQ(s, ""); ASSERT_EQ(list.toString(), ""); list.append(""); @@ -230,6 +235,8 @@ TEST(ListTest, ToString) list.append("item1"); list.append("i t e m 2"); list.append("item 3"); + list.toString(s); + ASSERT_EQ(s, "item1 i t e m 2 item 3"); ASSERT_EQ(list.toString(), "item1 i t e m 2 item 3"); list.clear(); @@ -237,29 +244,39 @@ TEST(ListTest, ToString) list.append("a "); list.append(" b"); list.append(" c "); + list.toString(s); + ASSERT_EQ(s, " a b c "); ASSERT_EQ(list.toString(), " a b c "); list.clear(); list.append("áä"); list.append("ľ š"); + list.toString(s); + ASSERT_EQ(s, "áä ľ š"); ASSERT_EQ(list.toString(), "áä ľ š"); list.clear(); list.append(-2); list.append(5); list.append(8); + list.toString(s); + ASSERT_EQ(s, "-2 5 8"); ASSERT_EQ(list.toString(), "-2 5 8"); list.clear(); list.append(2); list.append(10); list.append(8); + list.toString(s); + ASSERT_EQ(s, "2 10 8"); ASSERT_EQ(list.toString(), "2 10 8"); list.clear(); list.append(0); list.append(9); list.append(8); + list.toString(s); + ASSERT_EQ(s, "098"); ASSERT_EQ(list.toString(), "098"); } From 7c0912fcbce120da9e9f69070c33eaf2d216029f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 13:06:38 +0200 Subject: [PATCH 36/72] List: Do not deallocate deleted items --- include/scratchcpp/list.h | 100 +++++++++++++++++++++++--------------- src/scratch/list.cpp | 6 ++- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index 54618b30..e3eb0efa 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -15,7 +15,11 @@ class Target; class Monitor; class ListPrivate; -/*! \brief The List class represents a Scratch list. */ +/*! + * \brief The List class represents a Scratch list. + * + * Due to the high optimization of the methods, indices out of range will result in undefined behavior! + */ class LIBSCRATCHCPP_EXPORT List : public Entity { public: @@ -34,15 +38,15 @@ class LIBSCRATCHCPP_EXPORT List : public Entity void setMonitor(Monitor *monitor); /*! Returns the list size. */ - inline size_t size() const { return m_dataPtr->size(); } + inline size_t size() const { return m_size; } /*! Returns true if the list is empty. */ - inline bool empty() const { return m_dataPtr->empty(); } + inline bool empty() const { return m_size == 0; } /*! Returns the index of the given item. */ inline size_t indexOf(const ValueData &value) const { - for (size_t i = 0; i < m_dataPtr->size(); i++) { + for (size_t i = 0; i < m_size; i++) { if (value_equals(&m_dataPtr->operator[](i), &value)) return i; } @@ -60,47 +64,37 @@ class LIBSCRATCHCPP_EXPORT List : public Entity inline bool contains(const Value &value) const { return contains(value.data()); } /*! Clears the list. */ - inline void clear() - { - for (ValueData &v : *m_dataPtr) - value_free(&v); + inline void clear() { m_size = 0; } - m_dataPtr->clear(); + /*! Appends an empty item and returns the reference to it. Can be used for custom initialization. */ + inline ValueData &appendEmpty() + { + m_size++; + reserve(getAllocSize(m_size)); + return m_dataPtr->operator[](m_size - 1); } /*! Appends an item. */ - inline void append(const ValueData &value) - { - m_dataPtr->push_back(ValueData()); - value_init(&m_dataPtr->back()); - value_assign_copy(&m_dataPtr->back(), &value); - } + inline void append(const ValueData &value) { value_assign_copy(&appendEmpty(), &value); } /*! Appends an item. */ inline void append(const Value &value) { append(value.data()); } - /*! Appends an empty item and returns the reference to it. Can be used for custom initialization. */ - inline ValueData &appendEmpty() - { - m_dataPtr->push_back(ValueData()); - value_init(&m_dataPtr->back()); - return m_dataPtr->back(); - } - /*! Removes the item at index. */ inline void removeAt(size_t index) { assert(index >= 0 && index < size()); - value_free(&m_dataPtr->operator[](index)); - m_dataPtr->erase(m_dataPtr->begin() + index); + std::rotate(m_dataPtr->begin() + index, m_dataPtr->begin() + index + 1, m_dataPtr->begin() + m_size); + m_size--; } /*! Inserts an item at index. */ inline void insert(size_t index, const ValueData &value) { assert(index >= 0 && index <= size()); - m_dataPtr->insert(m_dataPtr->begin() + index, ValueData()); - value_init(&m_dataPtr->operator[](index)); + m_size++; + reserve(getAllocSize(m_size)); + std::rotate(m_dataPtr->rbegin() + m_dataPtr->size() - m_size, m_dataPtr->rbegin() + m_dataPtr->size() - m_size + 1, m_dataPtr->rend() - index); value_assign_copy(&m_dataPtr->operator[](index), &value); } @@ -128,16 +122,18 @@ class LIBSCRATCHCPP_EXPORT List : public Entity { dst.clear(); veque::veque strings; - strings.reserve(m_dataPtr->size()); + strings.reserve(m_size); bool digits = true; + size_t i; - for (const auto &item : *m_dataPtr) { + for (i = 0; i < m_size; i++) { + const ValueData *item = &m_dataPtr->operator[](i); strings.push_back(std::string()); - value_toString(&item, &strings.back()); + value_toString(item, &strings.back()); - if (value_isValidNumber(&item) && !strings.back().empty()) { - double doubleNum = value_toDouble(&item); - long num = value_toLong(&item); + if (value_isValidNumber(item) && !strings.back().empty()) { + double doubleNum = value_toDouble(item); + long num = value_toLong(item); if (doubleNum != num) { digits = false; @@ -154,14 +150,13 @@ class LIBSCRATCHCPP_EXPORT List : public Entity } } - size_t i; std::string s; if (digits) { for (i = 0; i < strings.size(); i++) dst.append(strings[i]); - for (; i < m_dataPtr->size(); i++) { + for (; i < m_size; i++) { value_toString(&m_dataPtr->operator[](i), &s); dst.append(s); } @@ -169,15 +164,15 @@ class LIBSCRATCHCPP_EXPORT List : public Entity for (i = 0; i < strings.size(); i++) { dst.append(strings[i]); - if (i + 1 < m_dataPtr->size()) + if (i + 1 < m_size) dst.push_back(' '); } - for (; i < m_dataPtr->size(); i++) { + for (; i < m_size; i++) { value_toString(&m_dataPtr->operator[](i), &s); dst.append(s); - if (i + 1 < m_dataPtr->size()) + if (i + 1 < m_size) dst.push_back(' '); } } @@ -194,8 +189,37 @@ class LIBSCRATCHCPP_EXPORT List : public Entity std::shared_ptr clone(); private: + inline void reserve(size_t size) + { + assert(size >= m_size); + + while (size > m_dataPtr->size()) { + m_dataPtr->push_back(ValueData()); + value_init(&m_dataPtr->back()); + } + + while (size < m_dataPtr->size()) { + value_free(&m_dataPtr->back()); + m_dataPtr->erase(m_dataPtr->end()); + } + } + + inline size_t getAllocSize(size_t x) + { + if (x == 0) + return 0; + + size_t ret = 1; + + while (ret < x) + ret *= 2; + + return ret; + } + spimpl::unique_impl_ptr impl; veque::veque *m_dataPtr = nullptr; // NOTE: accessing through pointer is faster! (from benchmarks) + size_t m_size = 0; }; } // namespace libscratchcpp diff --git a/src/scratch/list.cpp b/src/scratch/list.cpp index b9ddf71e..668b1491 100644 --- a/src/scratch/list.cpp +++ b/src/scratch/list.cpp @@ -19,6 +19,7 @@ List::List(const std::string &id, const std::string &name) : List::~List() { clear(); + reserve(m_size); } /*! Returns the name of the list. */ @@ -61,9 +62,10 @@ void List::setMonitor(Monitor *monitor) std::shared_ptr List::clone() { auto copy = std::make_shared(id(), impl->name); + copy->reserve(m_size); - for (const ValueData &item : *m_dataPtr) - copy->append(item); + for (size_t i = 0; i < m_size; i++) + copy->append(m_dataPtr->operator[](i)); return copy; } From 53731086cf4122cff79e27eeea349b8dd7dac6b2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 13:38:25 +0200 Subject: [PATCH 37/72] List: Deallocate items in clear() after 200,000 --- include/scratchcpp/list.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index e3eb0efa..8ce30c85 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -64,7 +64,15 @@ class LIBSCRATCHCPP_EXPORT List : public Entity inline bool contains(const Value &value) const { return contains(value.data()); } /*! Clears the list. */ - inline void clear() { m_size = 0; } + inline void clear() + { + // Keep at least 200,000 items allocated if the list has more + constexpr size_t limit = 200000; + m_size = 0; + + if (m_dataPtr->size() > limit) + reserve(limit); + } /*! Appends an empty item and returns the reference to it. Can be used for custom initialization. */ inline ValueData &appendEmpty() From 948195735b0bc72d77fabd29ddc5aef5c6d0073f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:50:17 +0200 Subject: [PATCH 38/72] ScratchConfiguration: Initialize when needed --- include/scratchcpp/scratchconfiguration.h | 3 ++- src/scratchconfiguration.cpp | 24 ++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/include/scratchcpp/scratchconfiguration.h b/include/scratchcpp/scratchconfiguration.h index 991bdcdc..dd58ea33 100644 --- a/include/scratchcpp/scratchconfiguration.h +++ b/include/scratchcpp/scratchconfiguration.h @@ -47,8 +47,9 @@ class LIBSCRATCHCPP_EXPORT ScratchConfiguration private: static const std::vector> getExtensions(); + static std::shared_ptr &getImpl(); - static std::unique_ptr impl; + static inline std::shared_ptr impl; }; } // namespace libscratchcpp diff --git a/src/scratchconfiguration.cpp b/src/scratchconfiguration.cpp index 24411dec..f67706ef 100644 --- a/src/scratchconfiguration.cpp +++ b/src/scratchconfiguration.cpp @@ -9,18 +9,16 @@ using namespace libscratchcpp; -std::unique_ptr ScratchConfiguration::impl = std::make_unique(); - /*! Registers the given extension. */ void ScratchConfiguration::registerExtension(std::shared_ptr extension) { - impl->registerExtension(extension); + getImpl()->registerExtension(extension); } /*! Returns the extension with the given name, or nullptr if it isn't registered. */ IExtension *ScratchConfiguration::getExtension(const std::string &name) { - return impl->getExtension(name); + return getImpl()->getExtension(name); } /*! Registers the given graphics effect. */ @@ -29,21 +27,21 @@ void ScratchConfiguration::registerGraphicsEffect(std::shared_ptrgraphicsEffects[effect->name()] = effect; + getImpl()->graphicsEffects[effect->name()] = effect; } /*! Removes the given graphics effect. */ void ScratchConfiguration::removeGraphicsEffect(const std::string &name) { - impl->graphicsEffects.erase(name); + getImpl()->graphicsEffects.erase(name); } /*! Returns the graphics effect with the given name, or nullptr if it isn't registered. */ IGraphicsEffect *ScratchConfiguration::getGraphicsEffect(const std::string &name) { - auto it = impl->graphicsEffects.find(name); + auto it = getImpl()->graphicsEffects.find(name); - if (it == impl->graphicsEffects.cend()) + if (it == getImpl()->graphicsEffects.cend()) return nullptr; else return it->second.get(); @@ -76,5 +74,13 @@ int ScratchConfiguration::patchVersion() const std::vector> ScratchConfiguration::getExtensions() { - return impl->extensions; + return getImpl()->extensions; +} + +std::shared_ptr &ScratchConfiguration::getImpl() +{ + if (!impl) + impl = std::make_unique(); + + return impl; } From a7eefef66d380291cce8695758d77aaf8c1b66a7 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:51:16 +0200 Subject: [PATCH 39/72] Add Thread class --- CMakeLists.txt | 1 + include/scratchcpp/thread.h | 42 +++++++++++ include/scratchcpp/virtualmachine.h | 4 +- src/engine/CMakeLists.txt | 3 + src/engine/thread.cpp | 75 +++++++++++++++++++ src/engine/thread_p.cpp | 12 +++ src/engine/thread_p.h | 20 +++++ src/engine/virtualmachine.cpp | 12 ++- src/engine/virtualmachine_p.cpp | 5 +- src/engine/virtualmachine_p.h | 4 +- test/CMakeLists.txt | 1 + test/thread/CMakeLists.txt | 14 ++++ test/thread/thread_test.cpp | 26 +++++++ test/virtual_machine/virtual_machine_test.cpp | 2 +- 14 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 include/scratchcpp/thread.h create mode 100644 src/engine/thread.cpp create mode 100644 src/engine/thread_p.cpp create mode 100644 src/engine/thread_p.h create mode 100644 test/thread/CMakeLists.txt create mode 100644 test/thread/thread_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fe84adbf..deeb6780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(scratchcpp include/scratchcpp/iengine.h include/scratchcpp/iextension.h include/scratchcpp/iblocksection.h + include/scratchcpp/thread.h include/scratchcpp/asset.h include/scratchcpp/costume.h include/scratchcpp/sound.h diff --git a/include/scratchcpp/thread.h b/include/scratchcpp/thread.h new file mode 100644 index 00000000..f3f7693e --- /dev/null +++ b/include/scratchcpp/thread.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "global.h" +#include "spimpl.h" + +namespace libscratchcpp +{ + +class VirtualMachine; +class Target; +class IEngine; +class Script; +class ThreadPrivate; + +/*! \brief The Thread class represents a running Scratch script. */ +class LIBSCRATCHCPP_EXPORT Thread +{ + public: + Thread(Target *target, IEngine *engine, Script *script); + Thread(const Thread &) = delete; + + VirtualMachine *vm() const; + Target *target() const; + IEngine *engine() const; + Script *script() const; + + void run(); + void kill(); + void reset(); + + bool isFinished() const; + + void promise(); + void resolvePromise(); + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/virtualmachine.h b/include/scratchcpp/virtualmachine.h index ac642837..e72aef4f 100644 --- a/include/scratchcpp/virtualmachine.h +++ b/include/scratchcpp/virtualmachine.h @@ -88,6 +88,7 @@ enum Opcode class Target; class IEngine; class Script; +class Thread; class List; /*! \brief The VirtualMachine class is a virtual machine for compiled Scratch scripts. */ @@ -95,7 +96,7 @@ class LIBSCRATCHCPP_EXPORT VirtualMachine { public: VirtualMachine(); - VirtualMachine(Target *target, IEngine *engine, Script *script); + VirtualMachine(Target *target, IEngine *engine, Script *script, Thread *thread = nullptr); VirtualMachine(const VirtualMachine &) = delete; void setProcedures(unsigned int **procedures); @@ -121,6 +122,7 @@ class LIBSCRATCHCPP_EXPORT VirtualMachine Target *target() const; IEngine *engine() const; Script *script() const; + Thread *thread() const; const Value *getInput(unsigned int index, unsigned int argCount) const; diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index e7ee439a..66f24e61 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -9,6 +9,9 @@ target_sources(scratchcpp script.cpp script_p.cpp script_p.h + thread.cpp + thread_p.cpp + thread_p.h internal/engine.cpp internal/engine.h internal/clock.cpp diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp new file mode 100644 index 00000000..92a7fa35 --- /dev/null +++ b/src/engine/thread.cpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "thread_p.h" + +using namespace libscratchcpp; + +/*! Constructs Thread. */ +Thread::Thread(Target *target, IEngine *engine, Script *script) : + impl(spimpl::make_unique_impl(target, engine, script)) +{ + impl->vm = std::make_unique(target, engine, script, this); +} + +/*! Returns the virtual machine of this thread. */ +VirtualMachine *Thread::vm() const +{ + return impl->vm.get(); +} + +/*! Returns the Target of the script. */ +Target *Thread::target() const +{ + return impl->target; +} + +/*! Returns the engine of the project. */ +IEngine *Thread::engine() const +{ + return impl->engine; +} + +/*! Returns the Script. */ +Script *Thread::script() const +{ + return impl->script; +} + +/*! Runs the script until it finishes or yields. */ +void Thread::run() +{ + impl->vm->run(); +} + +/*! Stops the script. */ +void Thread::kill() +{ + impl->vm->kill(); +} + +/*! Resets the script to run from the start. */ +void Thread::reset() +{ + impl->vm->reset(); +} + +/*! Returns true if the script is stopped or finished. */ +bool Thread::isFinished() const +{ + return impl->vm->atEnd(); +} + +/*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */ +void Thread::promise() +{ + impl->vm->promise(); +} + +/*! Resolves the promise and resumes the script. */ +void Thread::resolvePromise() +{ + impl->vm->resolvePromise(); +} diff --git a/src/engine/thread_p.cpp b/src/engine/thread_p.cpp new file mode 100644 index 00000000..c634078d --- /dev/null +++ b/src/engine/thread_p.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "thread_p.h" + +using namespace libscratchcpp; + +ThreadPrivate::ThreadPrivate(Target *target, IEngine *engine, Script *script) : + target(target), + engine(engine), + script(script) +{ +} diff --git a/src/engine/thread_p.h b/src/engine/thread_p.h new file mode 100644 index 00000000..f7f9fa82 --- /dev/null +++ b/src/engine/thread_p.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +struct ThreadPrivate +{ + ThreadPrivate(Target *target, IEngine *engine, Script *script); + + std::unique_ptr vm; + Target *target = nullptr; + IEngine *engine = nullptr; + Script *script = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/engine/virtualmachine.cpp b/src/engine/virtualmachine.cpp index 98b12248..7f66b009 100644 --- a/src/engine/virtualmachine.cpp +++ b/src/engine/virtualmachine.cpp @@ -10,13 +10,13 @@ using namespace vm; /*! Constructs VirtualMachine. */ VirtualMachine::VirtualMachine() : - VirtualMachine(nullptr, nullptr, nullptr) + VirtualMachine(nullptr, nullptr, nullptr, nullptr) { } /*! \copydoc VirtualMachine() */ -VirtualMachine::VirtualMachine(Target *target, IEngine *engine, Script *script) : - impl(spimpl::make_unique_impl(this, target, engine, script)) +VirtualMachine::VirtualMachine(Target *target, IEngine *engine, Script *script, Thread *thread) : + impl(spimpl::make_unique_impl(this, target, engine, script, thread)) { } @@ -137,6 +137,12 @@ Script *VirtualMachine::script() const return impl->script; } +/*! Returns the Thread this VM belongs to. */ +Thread *VirtualMachine::thread() const +{ + return impl->thread; +} + /*! Returns the register at the given index with the given argument (register) count. */ const Value *VirtualMachine::getInput(unsigned int index, unsigned int argCount) const { diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp index 18ff56c7..558d7b76 100644 --- a/src/engine/virtualmachine_p.cpp +++ b/src/engine/virtualmachine_p.cpp @@ -110,11 +110,12 @@ const unsigned int VirtualMachinePrivate::instruction_arg_count[] = { 0 // OP_WARP }; -VirtualMachinePrivate::VirtualMachinePrivate(VirtualMachine *vm, Target *target, IEngine *engine, Script *script) : +VirtualMachinePrivate::VirtualMachinePrivate(VirtualMachine *vm, Target *target, IEngine *engine, Script *script, Thread *thread) : vm(vm), target(target), engine(engine), - script(script) + script(script), + thread(thread) { regsVector.reserve(1024); for (int i = 0; i < 1024; i++) diff --git a/src/engine/virtualmachine_p.h b/src/engine/virtualmachine_p.h index 5ee86cf0..3164c730 100644 --- a/src/engine/virtualmachine_p.h +++ b/src/engine/virtualmachine_p.h @@ -13,13 +13,14 @@ class VirtualMachine; class Target; class IEngine; class Script; +class Thread; class Value; class List; class IRandomGenerator; struct VirtualMachinePrivate { - VirtualMachinePrivate(VirtualMachine *vm, Target *target, IEngine *engine, Script *script); + VirtualMachinePrivate(VirtualMachine *vm, Target *target, IEngine *engine, Script *script, Thread *thread); VirtualMachinePrivate(const VirtualMachinePrivate &) = delete; ~VirtualMachinePrivate(); @@ -41,6 +42,7 @@ struct VirtualMachinePrivate Target *target = nullptr; IEngine *engine = nullptr; Script *script = nullptr; + Thread *thread = nullptr; unsigned int *pos = nullptr; unsigned int *checkpoint = nullptr; bool running = false; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cbf80d13..07771920 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory(zip) add_subdirectory(load_project) add_subdirectory(project) add_subdirectory(compiler) +add_subdirectory(thread) add_subdirectory(virtual_machine) add_subdirectory(scratch_classes) add_subdirectory(target_interfaces) diff --git a/test/thread/CMakeLists.txt b/test/thread/CMakeLists.txt new file mode 100644 index 00000000..ae84dc1e --- /dev/null +++ b/test/thread/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable( + thread_test + thread_test.cpp +) + +target_link_libraries( + thread_test + GTest::gtest_main + GTest::gmock_main + scratchcpp + scratchcpp_mocks +) + +gtest_discover_tests(thread_test) diff --git a/test/thread/thread_test.cpp b/test/thread/thread_test.cpp new file mode 100644 index 00000000..fb80cfe4 --- /dev/null +++ b/test/thread/thread_test.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +#include "../common.h" + +using namespace libscratchcpp; + +TEST(ThreadTest, Constructor) +{ + TargetMock target; + EngineMock engine; + Script script(nullptr, nullptr, nullptr); + Thread thread(&target, &engine, &script); + ASSERT_EQ(thread.target(), &target); + ASSERT_EQ(thread.engine(), &engine); + ASSERT_EQ(thread.script(), &script); + ASSERT_TRUE(thread.vm()); + ASSERT_EQ(thread.vm()->target(), &target); + ASSERT_EQ(thread.vm()->engine(), &engine); + ASSERT_EQ(thread.vm()->script(), &script); +} + +// NOTE: Since VirtualMachine is going to be removed, tests for the other methods will be added later diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp index e7e7744d..a6e123f4 100644 --- a/test/virtual_machine/virtual_machine_test.cpp +++ b/test/virtual_machine/virtual_machine_test.cpp @@ -418,7 +418,7 @@ TEST(VirtualMachineTest, OP_RANDOM) static unsigned int bytecode8[] = { OP_START, OP_CONST, 6, OP_CONST, 7, OP_RANDOM, OP_HALT }; static Value constValues[] = { -45, 12, 6.05, -78.686, "-45", "12", "6.05", "-78.686" }; - VirtualMachinePrivate vm(nullptr, nullptr, nullptr, nullptr); + VirtualMachinePrivate vm(nullptr, nullptr, nullptr, nullptr, nullptr); vm.constValues = constValues; RandomGeneratorMock rng; From d3618509c33f7af54f3995136c9d57bd9fba73a3 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 15 Sep 2024 22:13:42 +0200 Subject: [PATCH 40/72] Use Thread instead of VirtualMachine for threads --- include/scratchcpp/iengine.h | 15 +- include/scratchcpp/script.h | 6 +- src/blocks/controlblocks.cpp | 2 +- src/blocks/eventblocks.cpp | 4 +- src/blocks/looksblocks.cpp | 9 +- src/blocks/motionblocks.cpp | 7 +- src/blocks/sensingblocks.cpp | 10 +- src/blocks/soundblocks.cpp | 3 +- src/engine/internal/engine.cpp | 88 ++-- src/engine/internal/engine.h | 42 +- src/engine/script.cpp | 15 +- test/blocks/control_blocks_test.cpp | 14 +- test/blocks/event_blocks_test.cpp | 51 +-- test/blocks/looks_blocks_test.cpp | 598 ++++++++++++++-------------- test/compiler/compiler_test.cpp | 17 +- test/engine/engine_test.cpp | 33 +- test/mocks/enginemock.h | 14 +- test/script/script_test.cpp | 87 ++-- 18 files changed, 519 insertions(+), 496 deletions(-) diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index 3cdd5295..df32aa87 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -23,6 +23,7 @@ class Stage; class Variable; class List; class Script; +class Thread; class ITimer; class KeyEvent; class Monitor; @@ -58,26 +59,26 @@ class LIBSCRATCHCPP_EXPORT IEngine virtual void stop() = 0; /*! Starts a script with the given top level block as the given Target (a sprite or the stage). */ - virtual VirtualMachine *startScript(std::shared_ptr topLevelBlock, Target *) = 0; + virtual Thread *startScript(std::shared_ptr topLevelBlock, Target *) = 0; /*! Starts the scripts of the broadcast with the given index. */ - virtual void broadcast(int index, VirtualMachine *sender) = 0; + virtual void broadcast(int index, Thread *sender) = 0; /*! Starts the scripts of the given broadcast. */ - virtual void broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) = 0; + virtual void broadcastByPtr(Broadcast *broadcast, Thread *sender) = 0; /*! Starts the "when backdrop switches to" scripts for the given backdrop broadcast. */ - virtual void startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) = 0; + virtual void startBackdropScripts(Broadcast *broadcast, Thread *sender) = 0; /*! Stops the given script. */ - virtual void stopScript(VirtualMachine *vm) = 0; + virtual void stopScript(Thread *vm) = 0; /*! * Stops all scripts in the given target. * \param[in] target The Target to stop. * \param[in] exceptScript Sets this parameter to stop all scripts except the given script. */ - virtual void stopTarget(Target *target, VirtualMachine *exceptScript) = 0; + virtual void stopTarget(Target *target, Thread *exceptScript) = 0; /*! Calls the "when I start as a clone" blocks of the given sprite. */ virtual void initClone(std::shared_ptr clone) = 0; @@ -121,7 +122,7 @@ class LIBSCRATCHCPP_EXPORT IEngine virtual sigslot::signal<> &aboutToRender() = 0; /*! Emits when a script is about to stop. */ - virtual sigslot::signal &threadAboutToStop() = 0; + virtual sigslot::signal &threadAboutToStop() = 0; /*! Emits when the project is stopped by calling stop(). */ virtual sigslot::signal<> &stopped() = 0; diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h index b82aaf48..dbf82b51 100644 --- a/include/scratchcpp/script.h +++ b/include/scratchcpp/script.h @@ -15,7 +15,7 @@ class Target; class Block; class IEngine; class Value; -class VirtualMachine; +class Thread; class Variable; class List; class ScriptPrivate; @@ -42,8 +42,8 @@ class LIBSCRATCHCPP_EXPORT Script void setVariables(const std::vector &variables); void setLists(const std::vector &lists); - std::shared_ptr start(); - std::shared_ptr start(Target *target); + std::shared_ptr start(); + std::shared_ptr start(Target *target); private: BlockFunc *getFunctions() const; diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index 0f965123..6ae35901 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -204,7 +204,7 @@ unsigned int ControlBlocks::stopAll(VirtualMachine *vm) unsigned int ControlBlocks::stopOtherScriptsInSprite(VirtualMachine *vm) { - vm->engine()->stopTarget(vm->target(), vm); + vm->engine()->stopTarget(vm->target(), vm->thread()); return 0; } diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 9945e337..53b72f7b 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -181,7 +181,7 @@ unsigned int EventBlocks::broadcast(VirtualMachine *vm) std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); for (int index : broadcasts) - vm->engine()->broadcast(index, vm); + vm->engine()->broadcast(index, vm->thread()); return 1; } @@ -191,7 +191,7 @@ unsigned int EventBlocks::broadcastAndWait(VirtualMachine *vm) std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); for (int index : broadcasts) - vm->engine()->broadcast(index, vm); + vm->engine()->broadcast(index, vm->thread()); vm->promise(); diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 066c368d..7b460c90 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -99,9 +100,9 @@ void LooksBlocks::registerBlocks(IEngine *engine) void LooksBlocks::onInit(IEngine *engine) { - engine->threadAboutToStop().connect([](VirtualMachine *vm) { - m_timeMap.erase(vm); - erase_if(m_waitingBubbles, [vm](const std::pair &pair) { return pair.second == vm; }); + engine->threadAboutToStop().connect([](Thread *thread) { + m_timeMap.erase(thread->vm()); + erase_if(m_waitingBubbles, [thread](const std::pair &pair) { return pair.second == thread->vm(); }); }); engine->stopped().connect([engine]() { @@ -892,7 +893,7 @@ void LooksBlocks::startBackdropScripts(VirtualMachine *vm, bool wait) { if (Stage *stage = vm->engine()->stage()) { if (stage->costumes().size() > 0) - vm->engine()->startBackdropScripts(stage->currentCostume()->broadcast(), vm); + vm->engine()->startBackdropScripts(stage->currentCostume()->broadcast(), vm->thread()); } } diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 549cb18d..fd2991ac 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -73,9 +74,9 @@ void MotionBlocks::registerBlocks(IEngine *engine) void MotionBlocks::onInit(IEngine *engine) { - engine->threadAboutToStop().connect([](VirtualMachine *vm) { - m_timeMap.erase(vm); - m_glideMap.erase(vm); + engine->threadAboutToStop().connect([](Thread *thread) { + m_timeMap.erase(thread->vm()); + m_glideMap.erase(thread->vm()); }); } diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index 0a198a97..b13f9309 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include @@ -99,16 +100,17 @@ void SensingBlocks::registerBlocks(IEngine *engine) void SensingBlocks::onInit(IEngine *engine) { - engine->threadAboutToStop().connect([engine](VirtualMachine *thread) { + engine->threadAboutToStop().connect([engine](Thread *thread) { if (!m_questionList.empty()) { // Abort the question of this thread if it's currently being displayed - if (m_questionList.front()->vm == thread) { + if (m_questionList.front()->vm == thread->vm()) { thread->target()->setBubbleText(""); engine->questionAborted()(); } - m_questionList - .erase(std::remove_if(m_questionList.begin(), m_questionList.end(), [thread](const std::unique_ptr &question) { return question->vm == thread; }), m_questionList.end()); + m_questionList.erase( + std::remove_if(m_questionList.begin(), m_questionList.end(), [thread](const std::unique_ptr &question) { return question->vm == thread->vm(); }), + m_questionList.end()); } }); } diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 4eb311fa..1f34e27f 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "soundblocks.h" @@ -60,7 +61,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) void SoundBlocks::onInit(IEngine *engine) { m_waitingSounds.clear(); - engine->threadAboutToStop().connect([](VirtualMachine *vm) { erase_if(m_waitingSounds, [vm](const std::pair &pair) { return pair.second == vm; }); }); + engine->threadAboutToStop().connect([](Thread *thread) { erase_if(m_waitingSounds, [thread](const std::pair &pair) { return pair.second == thread->vm(); }); }); } bool SoundBlocks::compilePlayCommon(Compiler *compiler, bool untilDone, bool *byIndex) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 098ed2a3..92f61279 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ void Engine::clear() m_monitorRemoved(monitor.get(), monitor->impl->iface); for (auto thread : m_threads) { - if (!thread->atEnd()) + if (!thread->isFinished()) m_threadAboutToStop(thread.get()); } @@ -395,7 +396,7 @@ void Engine::stop() std::remove_if( m_threads.begin(), m_threads.end(), - [](std::shared_ptr thread) { + [](std::shared_ptr thread) { assert(thread); Target *target = thread->target(); assert(target); @@ -414,7 +415,7 @@ void Engine::stop() // If there isn't any active thread, it means the project was stopped from the outside // In this case all threads should be removed and the project should be considered stopped for (auto thread : m_threads) { - if (!thread->atEnd()) + if (!thread->isFinished()) m_threadAboutToStop(thread.get()); } @@ -428,12 +429,12 @@ void Engine::stop() m_stopped(); } -VirtualMachine *Engine::startScript(std::shared_ptr topLevelBlock, Target *target) +Thread *Engine::startScript(std::shared_ptr topLevelBlock, Target *target) { return pushThread(topLevelBlock, target).get(); } -void Engine::broadcast(int index, VirtualMachine *sender) +void Engine::broadcast(int index, Thread *sender) { if (index < 0 || index >= m_broadcasts.size()) return; @@ -441,26 +442,26 @@ void Engine::broadcast(int index, VirtualMachine *sender) broadcastByPtr(m_broadcasts[index].get(), sender); } -void Engine::broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) +void Engine::broadcastByPtr(Broadcast *broadcast, Thread *sender) { startHats(HatType::BroadcastReceived, { { HatField::BroadcastOption, broadcast } }, nullptr); addBroadcastPromise(broadcast, sender); } -void Engine::startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) +void Engine::startBackdropScripts(Broadcast *broadcast, Thread *sender) { startHats(HatType::BackdropChanged, { { HatField::Backdrop, broadcast->name() } }, nullptr); addBroadcastPromise(broadcast, sender); } -void Engine::stopScript(VirtualMachine *vm) +void Engine::stopScript(Thread *vm) { stopThread(vm); } -void Engine::stopTarget(Target *target, VirtualMachine *exceptScript) +void Engine::stopTarget(Target *target, Thread *exceptScript) { - std::vector threads; + std::vector threads; for (auto thread : m_threads) { if ((thread->target() == target) && (thread.get() != exceptScript)) @@ -485,7 +486,7 @@ void Engine::initClone(std::shared_ptr clone) #ifndef NDEBUG // Since we're initializing the clone, it shouldn't have any running scripts for (auto thread : m_threads) - assert(thread->target() != clone.get() || thread->atEnd()); + assert(thread->target() != clone.get() || thread->isFinished()); #endif startHats(HatType::CloneInit, {}, clone.get()); @@ -530,9 +531,9 @@ void Engine::updateMonitors() auto script = monitor->script(); if (script) { - auto vm = script->start(); - vm->run(); - monitor->updateValue(vm.get()); + auto thread = script->start(); + thread->run(); + monitor->updateValue(thread->vm()); } } } @@ -544,7 +545,7 @@ void Engine::step() updateFrameDuration(); // Clean up threads that were told to stop during or since the last step - m_threads.erase(std::remove_if(m_threads.begin(), m_threads.end(), [](std::shared_ptr thread) { return thread->atEnd(); }), m_threads.end()); + m_threads.erase(std::remove_if(m_threads.begin(), m_threads.end(), [](std::shared_ptr thread) { return thread->isFinished(); }), m_threads.end()); // Find all edge-activated hats, and add them to threads to be evaluated for (auto const &[hatType, edgeActivated] : m_hatEdgeActivated) { @@ -607,7 +608,7 @@ void Engine::step() const auto &scripts = (*broadcastMap)[broadcast]; for (auto script : scripts) { - if (std::find_if(m_threads.begin(), m_threads.end(), [script](std::shared_ptr thread) { return thread->script() == script; }) != m_threads.end()) { + if (std::find_if(m_threads.begin(), m_threads.end(), [script](std::shared_ptr thread) { return thread->script() == script; }) != m_threads.end()) { found = true; break; } @@ -615,9 +616,9 @@ void Engine::step() } if (!found) { - VirtualMachine *th = senderThread; + Thread *th = senderThread; - if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr thread) { return thread.get() == th; }) != m_threads.end()) + if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr thread) { return thread.get() == th; }) != m_threads.end()) th->resolvePromise(); resolved.push_back(broadcast); @@ -660,7 +661,7 @@ sigslot::signal<> &Engine::aboutToRender() return m_aboutToRedraw; } -sigslot::signal &Engine::threadAboutToStop() +sigslot::signal &Engine::threadAboutToStop() { return m_threadAboutToStop; } @@ -670,7 +671,7 @@ sigslot::signal<> &Engine::stopped() return m_stopped; } -std::vector> Engine::stepThreads() +std::vector> Engine::stepThreads() { // https://github.com/scratchfoundation/scratch-vm/blob/develop/src/engine/sequencer.js#L70-L173 const double WORK_TIME = 0.75 * m_frameDuration.count(); // 75% of frame duration @@ -678,7 +679,7 @@ std::vector> Engine::stepThreads() auto stepStart = m_clock->currentSteadyTime(); size_t numActiveThreads = 1; // greater than zero - std::vector> doneThreads; + std::vector> doneThreads; auto elapsedTime = [this, &stepStart]() { std::chrono::steady_clock::time_point currentTime = m_clock->currentSteadyTime(); @@ -695,20 +696,20 @@ std::vector> Engine::stepThreads() m_activeThread = m_threads[i]; // Check if the thread is done so it is not executed - if (m_activeThread->atEnd()) { + if (m_activeThread->isFinished()) { // Finished with this thread continue; } stepThread(m_activeThread); - if (!m_activeThread->atEnd()) + if (!m_activeThread->isFinished()) numActiveThreads++; } // Remove threads in m_threadsToStop for (auto thread : m_threadsToStop) { - if (!thread->atEnd()) + if (!thread->isFinished()) m_threadAboutToStop(thread.get()); m_threads.erase(std::remove(m_threads.begin(), m_threads.end(), thread), m_threads.end()); @@ -721,8 +722,8 @@ std::vector> Engine::stepThreads() std::remove_if( m_threads.begin(), m_threads.end(), - [&doneThreads](std::shared_ptr thread) { - if (thread->atEnd()) { + [&doneThreads](std::shared_ptr thread) { + if (thread->isFinished()) { doneThreads.push_back(thread); return true; } else @@ -738,7 +739,7 @@ std::vector> Engine::stepThreads() return doneThreads; } -void Engine::stepThread(std::shared_ptr thread) +void Engine::stepThread(std::shared_ptr thread) { // https://github.com/scratchfoundation/scratch-vm/blob/develop/src/engine/sequencer.js#L179-L276 thread->run(); @@ -1884,12 +1885,12 @@ void Engine::updateFrameDuration() m_frameDuration = std::chrono::milliseconds(static_cast(1000 / m_fps)); } -void Engine::addRunningScript(std::shared_ptr vm) +void Engine::addRunningScript(std::shared_ptr thread) { - m_threads.push_back(vm); + m_threads.push_back(thread); } -void Engine::addBroadcastPromise(Broadcast *broadcast, VirtualMachine *sender) +void Engine::addBroadcastPromise(Broadcast *broadcast, Thread *sender) { assert(broadcast); assert(sender); @@ -1897,13 +1898,13 @@ void Engine::addBroadcastPromise(Broadcast *broadcast, VirtualMachine *sender) // Resolve broadcast promise if it's already running auto it = m_broadcastSenders.find(broadcast); - if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr thread) { return thread.get() == it->second; }) != m_threads.end()) + if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr thread) { return thread.get() == it->second; }) != m_threads.end()) it->second->resolvePromise(); m_broadcastSenders[broadcast] = sender; } -std::shared_ptr Engine::pushThread(std::shared_ptr block, Target *target) +std::shared_ptr Engine::pushThread(std::shared_ptr block, Target *target) { // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L1649-L1661 if (!block) { @@ -1919,30 +1920,30 @@ std::shared_ptr Engine::pushThread(std::shared_ptr block, } auto script = m_scripts[block]; - std::shared_ptr vm = script->start(target); - addRunningScript(vm); - return vm; + std::shared_ptr thread = script->start(target); + addRunningScript(thread); + return thread; } -void Engine::stopThread(VirtualMachine *thread) +void Engine::stopThread(Thread *thread) { // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L1667-L1672 assert(thread); - if (!thread->atEnd()) + if (!thread->isFinished()) m_threadAboutToStop(thread); thread->kill(); } -std::shared_ptr Engine::restartThread(std::shared_ptr thread) +std::shared_ptr Engine::restartThread(std::shared_ptr thread) { // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L1681C30-L1694 - std::shared_ptr newThread = thread->script()->start(thread->target()); + std::shared_ptr newThread = thread->script()->start(thread->target()); auto it = std::find(m_threads.begin(), m_threads.end(), thread); if (it != m_threads.end()) { - if (!thread->atEnd()) + if (!thread->isFinished()) m_threadAboutToStop(thread.get()); auto i = it - m_threads.begin(); @@ -1977,11 +1978,10 @@ void Engine::allScriptsByOpcodeDo(HatType hatType, F &&f, Target *optTarget) delete targetsPtr; } -std::vector> -Engine::startHats(HatType hatType, const std::unordered_map> &optMatchFields, Target *optTarget) +std::vector> Engine::startHats(HatType hatType, const std::unordered_map> &optMatchFields, Target *optTarget) { // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L1818-L1889 - std::vector> newThreads; + std::vector> newThreads; allScriptsByOpcodeDo( hatType, @@ -2036,7 +2036,7 @@ Engine::startHats(HatType hatType, const std::unordered_maptarget() == target && thread->script() == script && !thread->atEnd()) { + if (thread->target() == target && thread->script() == script && !thread->isFinished()) { // Some thread is already running return; } diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 6ffee1da..6c76a758 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -20,6 +20,7 @@ namespace libscratchcpp class Entity; class IClock; class IAudioEngine; +class Thread; class Engine : public IEngine { @@ -34,12 +35,12 @@ class Engine : public IEngine void start() override; void stop() override; - VirtualMachine *startScript(std::shared_ptr topLevelBlock, Target *target) override; - void broadcast(int index, VirtualMachine *sender) override; - void broadcastByPtr(Broadcast *broadcast, VirtualMachine *sender) override; - void startBackdropScripts(Broadcast *broadcast, VirtualMachine *sender) override; - void stopScript(VirtualMachine *vm) override; - void stopTarget(Target *target, VirtualMachine *exceptScript) override; + Thread *startScript(std::shared_ptr topLevelBlock, Target *target) override; + void broadcast(int index, Thread *sender) override; + void broadcastByPtr(Broadcast *broadcast, Thread *sender) override; + void startBackdropScripts(Broadcast *broadcast, Thread *sender) override; + void stopScript(Thread *vm) override; + void stopTarget(Target *target, Thread *exceptScript) override; void initClone(std::shared_ptr clone) override; void deinitClone(std::shared_ptr clone) override; @@ -54,7 +55,7 @@ class Engine : public IEngine void stopEventLoop() override; sigslot::signal<> &aboutToRender() override; - sigslot::signal &threadAboutToStop() override; + sigslot::signal &threadAboutToStop() override; sigslot::signal<> &stopped() override; bool isRunning() const override; @@ -192,8 +193,8 @@ class Engine : public IEngine WhenGreaterThanMenu }; - std::vector> stepThreads(); - void stepThread(std::shared_ptr thread); + std::vector> stepThreads(); + void stepThread(std::shared_ptr thread); void eventLoop(bool untilProjectStops = false); void finalize(); void deleteClones(); @@ -215,19 +216,18 @@ class Engine : public IEngine void updateSpriteLayerOrder(); void updateFrameDuration(); - void addRunningScript(std::shared_ptr vm); + void addRunningScript(std::shared_ptr thread); - void addBroadcastPromise(Broadcast *broadcast, VirtualMachine *sender); + void addBroadcastPromise(Broadcast *broadcast, Thread *sender); - std::shared_ptr pushThread(std::shared_ptr block, Target *target); - void stopThread(VirtualMachine *thread); - std::shared_ptr restartThread(std::shared_ptr thread); + std::shared_ptr pushThread(std::shared_ptr block, Target *target); + void stopThread(Thread *thread); + std::shared_ptr restartThread(std::shared_ptr thread); template void allScriptsByOpcodeDo(HatType hatType, F &&f, Target *optTarget); - std::vector> - startHats(HatType hatType, const std::unordered_map> &optMatchFields, Target *optTarget); + std::vector> startHats(HatType hatType, const std::unordered_map> &optMatchFields, Target *optTarget); static const std::unordered_map m_hatRestartExistingThreads; // used to check whether a hat should restart existing threads static const std::unordered_map m_hatEdgeActivated; // used to check whether a hat is edge-activated (runs when a predicate becomes true) @@ -237,13 +237,13 @@ class Engine : public IEngine std::vector> m_broadcasts; std::unordered_map> m_broadcastMap; std::unordered_map> m_backdropBroadcastMap; - std::unordered_map m_broadcastSenders; // used for resolving broadcast promises + std::unordered_map m_broadcastSenders; // used for resolving broadcast promises std::vector> m_monitors; std::vector m_extensions; std::vector m_executableTargets; // sorted by layer (reverse order of execution) - std::vector> m_threads; - std::vector> m_threadsToStop; - std::shared_ptr m_activeThread; + std::vector> m_threads; + std::vector> m_threadsToStop; + std::shared_ptr m_activeThread; std::unordered_map, std::shared_ptr