Browse Source

Use `std::from_chars` instead of `std::stoi`

Adds a `ParseInt` function which uses `std::from_chars`.

From `std::from_chars` documentation:
> Unlike other parsing functions in C++ and C libraries,
> std::from_chars is locale-independent, non-allocating, and non-throwing.

Co-authored-by: Andrew James <ephphatha@thelettereph.com>
pull/6457/head
Gleb Mazovetskiy 3 years ago committed by Anders Jenbo
parent
commit
53db86ee9a
  1. 10
      Source/control.cpp
  2. 108
      Source/debug.cpp
  3. 36
      Source/diablo.cpp
  4. 17
      Source/pfile.cpp
  5. 51
      Source/utils/parse_int.hpp

10
Source/control.cpp

@ -48,6 +48,7 @@
#include "utils/format_int.hpp"
#include "utils/language.h"
#include "utils/log.hpp"
#include "utils/parse_int.hpp"
#include "utils/sdl_geometry.h"
#include "utils/stdcompat/optional.hpp"
#include "utils/str_case.hpp"
@ -388,9 +389,9 @@ std::string TextCmdArena(const string_view parameter)
return ret;
}
int arenaNumber = atoi(parameter.data());
_setlevels arenaLevel = static_cast<_setlevels>(arenaNumber - 1 + SL_FIRST_ARENA);
if (arenaNumber < 0 || !IsArenaLevel(arenaLevel)) {
const ParseIntResult<int> parsedParam = ParseInt<int>(parameter, /*min=*/0);
const _setlevels arenaLevel = parsedParam.ok() ? static_cast<_setlevels>(parsedParam.value - 1 + SL_FIRST_ARENA) : _setlevels::SL_NONE;
if (!IsArenaLevel(arenaLevel)) {
StrAppend(ret, _("Invalid arena-number. Valid numbers are:"));
AppendArenaOverview(ret);
return ret;
@ -413,10 +414,11 @@ std::string TextCmdArenaPot(const string_view parameter)
StrAppend(ret, _("Arenas are only supported in multiplayer."));
return ret;
}
int numPots = ParseInt<int>(parameter, /*min=*/1).value_or(1);
Player &myPlayer = *MyPlayer;
for (int potNumber = std::max(1, atoi(parameter.data())); potNumber > 0; potNumber--) {
for (int potNumber = numPots; potNumber > 0; potNumber--) {
Item item {};
InitializeItem(item, IDI_ARENAPOT);
GenerateNewSeed(item);

108
Source/debug.cpp

@ -33,6 +33,7 @@
#include "utils/file_util.h"
#include "utils/language.h"
#include "utils/log.hpp"
#include "utils/parse_int.hpp"
#include "utils/str_case.hpp"
#include "utils/str_cat.hpp"
#include "utils/str_split.hpp"
@ -192,8 +193,11 @@ std::string DebugCmdTakeGoldCheat(const string_view parameter)
std::string DebugCmdWarpToLevel(const string_view parameter)
{
Player &myPlayer = *MyPlayer;
auto level = atoi(parameter.data());
if (level < 0 || level > (gbIsHellfire ? 24 : 16))
const ParseIntResult<int> parsedParam = ParseInt<int>(parameter, /*min=*/0);
if (!parsedParam.ok())
return "Specify the level number";
const int level = parsedParam.value;
if (level > (gbIsHellfire ? 24 : 16))
return StrCat("Level ", level, " is not known. Do you want to write a mod?");
if (!setlevel && myPlayer.isOnLevel(level))
return StrCat("I did nothing but fulfilled your wish. You are already at level ", level, ".");
@ -214,7 +218,10 @@ std::string DebugCmdLoadQuestMap(const string_view parameter)
return ret;
}
auto level = atoi(parameter.data());
const ParseIntResult<int> parsedParam = ParseInt<int>(parameter, /*min=*/0);
if (!parsedParam.ok())
return "Specify the level number";
const int level = parsedParam.value;
if (level < 1)
return "Map id must be 1 or higher";
if (setlevel && setlvlnum == level)
@ -245,15 +252,24 @@ std::string DebugCmdLoadMap(const string_view parameter)
case 0:
TestMapPath = StrCat(arg, ".dun");
break;
case 1:
mapType = atoi(std::string(arg).c_str());
break;
case 2:
spawn.x = atoi(std::string(arg).c_str());
break;
case 3:
spawn.y = atoi(std::string(arg).c_str());
break;
case 1: {
const ParseIntResult<int> parsedArg = ParseInt<int>(arg, /*min=*/0);
if (!parsedArg.ok())
return "Failed to parse argument 1 as integer";
mapType = parsedArg.value;
} break;
case 2: {
const ParseIntResult<int> parsedArg = ParseInt<int>(arg);
if (!parsedArg.ok())
return "Failed to parse argument 2 as integer";
spawn.x = parsedArg.value;
} break;
case 3: {
const ParseIntResult<int> parsedArg = ParseInt<int>(arg);
if (!parsedArg.ok())
return "Failed to parse argument 3 as integer";
spawn.y = parsedArg.value;
} break;
}
count++;
}
@ -398,15 +414,23 @@ std::string DebugCmdResetLevel(const string_view parameter)
auto it = args.begin();
if (it == args.end())
return "What level do you want to visit?";
auto level = atoi(std::string(*it).c_str());
if (level < 0 || level > (gbIsHellfire ? 24 : 16))
int level;
{
const ParseIntResult<int> parsedArg = ParseInt<int>(*it, /*min=*/0);
if (!parsedArg.ok())
return "Failed to parse argument 1 as integer";
level = parsedArg.value;
}
if (level > (gbIsHellfire ? 24 : 16))
return StrCat("Level ", level, " is not known. Do you want to write an extension mod?");
myPlayer._pLvlVisited[level] = false;
DeltaClearLevel(level);
if (++it != args.end()) {
const auto seed = static_cast<uint32_t>(std::stoul(std::string(*it)));
glSeedTbl[level] = seed;
const ParseIntResult<uint32_t> parsedArg = ParseInt<uint32_t>(*it);
if (!parsedArg.ok())
return "Failed to parse argument 2 as uint32_t";
glSeedTbl[level] = parsedArg.value;
}
if (myPlayer.isOnLevel(level))
@ -489,7 +513,10 @@ std::string DebugCmdQuest(const string_view parameter)
return "Happy questing";
}
int questId = atoi(parameter.data());
const ParseIntResult<int> parsedArg = ParseInt<int>(parameter, /*min=*/0);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
const int questId = parsedArg.value;
if (questId >= MAXQUESTS)
return StrCat("Quest ", questId, " is not known. Do you want to write a mod?");
@ -506,7 +533,7 @@ std::string DebugCmdQuest(const string_view parameter)
std::string DebugCmdLevelUp(const string_view parameter)
{
int levels = std::max(1, atoi(parameter.data()));
const int levels = ParseInt<int>(parameter, /*min=*/1).value_or(1);
for (int i = 0; i < levels; i++)
NetSendCmd(true, CMD_CHEAT_EXPERIENCE);
return "New experience leads to new insights.";
@ -534,7 +561,10 @@ std::string DebugCmdMinStats(const string_view parameter)
std::string DebugCmdSetSpellsLevel(const string_view parameter)
{
uint8_t level = static_cast<uint8_t>(std::max(0, atoi(parameter.data())));
const ParseIntResult<uint8_t> parsedArg = ParseInt<uint8_t>(parameter);
if (!parsedArg.ok())
return "Failed to parse argument as uint8_t";
const uint8_t level = parsedArg.value;
for (uint8_t i = static_cast<uint8_t>(SpellID::Firebolt); i < MAX_SPELLS; i++) {
if (GetSpellBookLevel(static_cast<SpellID>(i)) != -1) {
NetSendCmdParam2(true, CMD_CHANGE_SPELL_LEVEL, i, level);
@ -562,8 +592,12 @@ std::string DebugCmdChangeHealth(const string_view parameter)
Player &myPlayer = *MyPlayer;
int change = -1;
if (!parameter.empty())
change = atoi(parameter.data());
if (!parameter.empty()) {
const ParseIntResult<int> parsedArg = ParseInt<int>(parameter);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
change = parsedArg.value;
}
if (change == 0)
return "Health hasn't changed.";
@ -581,8 +615,12 @@ std::string DebugCmdChangeMana(const string_view parameter)
Player &myPlayer = *MyPlayer;
int change = -1;
if (!parameter.empty())
change = atoi(parameter.data());
if (!parameter.empty()) {
const ParseIntResult<int> parsedArg = ParseInt<int>(parameter);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
change = parsedArg.value;
}
if (change == 0)
return "Mana hasn't changed.";
@ -659,7 +697,10 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter)
std::string name;
int count = 1;
for (string_view arg : SplitByChar(parameter, ' ')) {
const int num = atoi(std::string(arg).c_str());
const ParseIntResult<int> parsedArg = ParseInt<int>(arg);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
const int num = parsedArg.value;
if (num > 0) {
count = num;
break;
@ -746,7 +787,10 @@ std::string DebugCmdSpawnMonster(const string_view parameter)
std::string name;
int count = 1;
for (string_view arg : SplitByChar(parameter, ' ')) {
const int num = atoi(std::string(arg).c_str());
const ParseIntResult<int> parsedArg = ParseInt<int>(arg);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
const int num = parsedArg.value;
if (num > 0) {
count = num;
break;
@ -920,7 +964,10 @@ std::string DebugCmdQuestInfo(const string_view parameter)
return ret;
}
int questId = atoi(parameter.data());
const ParseIntResult<int> parsedArg = ParseInt<int>(parameter, /*min=*/0);
if (!parsedArg.ok())
return "Failed to parse argument as integer";
const int questId = parsedArg.value;
if (questId >= MAXQUESTS)
return StrCat("Quest ", questId, " is not known. Do you want to write a mod?");
@ -930,7 +977,14 @@ std::string DebugCmdQuestInfo(const string_view parameter)
std::string DebugCmdPlayerInfo(const string_view parameter)
{
int playerId = atoi(parameter.data());
if (parameter.empty()) {
return StrCat("Provide a player ID between 0 and ", Players.size() - 1);
}
const ParseIntResult<size_t> parsedArg = ParseInt<size_t>(parameter);
if (!parsedArg.ok()) {
return "Failed to parse argument as size_t in range";
}
const size_t playerId = parsedArg.value;
if (static_cast<size_t>(playerId) >= Players.size())
return "My friend, we need a valid playerId.";
Player &player = Players[playerId];

36
Source/diablo.cpp

@ -79,6 +79,7 @@
#include "utils/console.h"
#include "utils/display.h"
#include "utils/language.h"
#include "utils/parse_int.hpp"
#include "utils/paths.h"
#include "utils/stdcompat/string_view.hpp"
#include "utils/str_cat.hpp"
@ -949,13 +950,18 @@ void PrintHelpOption(string_view flags, string_view description)
diablo_quit(0);
}
void PrintFlagsRequiresArgument(string_view flag)
void PrintFlagMessage(string_view flag, string_view message)
{
printInConsole(flag);
printInConsole(" requires an argument");
printInConsole(message);
printNewlineInConsole();
}
void PrintFlagRequiresArgument(string_view flag)
{
PrintFlagMessage(flag, " requires an argument");
}
void DiabloParseFlags(int argc, char **argv)
{
#ifdef _DEBUG
@ -980,44 +986,54 @@ void DiabloParseFlags(int argc, char **argv)
diablo_quit(0);
} else if (arg == "--data-dir") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--data-dir");
PrintFlagRequiresArgument("--data-dir");
diablo_quit(64);
}
paths::SetBasePath(argv[++i]);
} else if (arg == "--save-dir") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--save-dir");
PrintFlagRequiresArgument("--save-dir");
diablo_quit(64);
}
paths::SetPrefPath(argv[++i]);
} else if (arg == "--config-dir") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--config-dir");
PrintFlagRequiresArgument("--config-dir");
diablo_quit(64);
}
paths::SetConfigPath(argv[++i]);
} else if (arg == "--lang") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--lang");
PrintFlagRequiresArgument("--lang");
diablo_quit(64);
}
forceLocale = argv[++i];
#ifndef DISABLE_DEMOMODE
} else if (arg == "--demo") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--demo");
PrintFlagRequiresArgument("--demo");
diablo_quit(64);
}
demoNumber = SDL_atoi(argv[++i]);
ParseIntResult<int> parsedParam = ParseInt<int>(argv[++i]);
if (!parsedParam.ok()) {
PrintFlagMessage("--demo", " must be a number");
diablo_quit(64);
}
demoNumber = parsedParam.value;
gbShowIntro = false;
} else if (arg == "--timedemo") {
timedemo = true;
} else if (arg == "--record") {
if (i + 1 == argc) {
PrintFlagsRequiresArgument("--record");
PrintFlagRequiresArgument("--record");
diablo_quit(64);
}
ParseIntResult<int> parsedParam = ParseInt<int>(argv[++i]);
if (!parsedParam.ok()) {
PrintFlagMessage("--record", " must be a number");
diablo_quit(64);
}
recordNumber = SDL_atoi(argv[++i]);
recordNumber = parsedParam.value;
} else if (arg == "--create-reference") {
createDemoReference = true;
#else

17
Source/pfile.cpp

@ -24,6 +24,7 @@
#include "utils/endian.hpp"
#include "utils/file_util.h"
#include "utils/language.h"
#include "utils/parse_int.hpp"
#include "utils/paths.h"
#include "utils/stdcompat/abs.hpp"
#include "utils/stdcompat/string_view.hpp"
@ -284,8 +285,10 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf
auto it = counter.find(counterAsString);
if (it != counter.end())
return it->second;
int countFromMapFile = std::stoi(counterAsString);
return CompareCounter { countFromMapFile, countFromMapFile };
const ParseIntResult<int> countFromMapFile = ParseInt<int>(counterAsString);
if (!countFromMapFile.ok())
app_fatal(StrCat("Failed to parse ", counterAsString, " as int"));
return CompareCounter { countFromMapFile.value, countFromMapFile.value };
};
auto addDiff = [&](const std::string &diffKey) {
auto it = foundDiffs.find(diffKey);
@ -363,7 +366,10 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf
if (command == "R" || command == "LT" || command == "LC" || command == "LC_LE") {
const auto bitsAsString = std::string(*++it);
const auto comment = std::string(*++it);
size_t bytes = static_cast<size_t>(std::stoi(bitsAsString) / 8);
const ParseIntResult<size_t> parsedBytes = ParseInt<size_t>(bitsAsString);
if (!parsedBytes.ok())
app_fatal(StrCat("Failed to parse ", bitsAsString, " as size_t"));
const size_t bytes = static_cast<size_t>(parsedBytes.value / 8);
if (command == "LT") {
int32_t valueReference = read32BitInt(compareInfoReference, false);
@ -389,7 +395,10 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf
string_view comment = *++it;
CompareCounter count = getCounter(countAsString);
size_t bytes = static_cast<size_t>(std::stoi(bitsAsString) / 8);
const ParseIntResult<size_t> parsedBytes = ParseInt<size_t>(bitsAsString);
if (!parsedBytes.ok())
app_fatal(StrCat("Failed to parse ", bitsAsString, " as size_t"));
const size_t bytes = static_cast<size_t>(parsedBytes.value / 8);
for (int i = 0; i < count.max(); i++) {
count.checkIfDataExists(i, compareInfoReference, compareInfoActual);
if (!compareBytes(bytes)) {

51
Source/utils/parse_int.hpp

@ -0,0 +1,51 @@
#pragma once
#if __cplusplus >= 201703L
#include <charconv>
#include <system_error>
#endif
#include "utils/stdcompat/string_view.hpp"
namespace devilution {
enum class ParseIntStatus {
Ok,
ParseError,
OutOfRange
};
template <typename IntT>
struct ParseIntResult {
ParseIntStatus status;
IntT value = 0;
[[nodiscard]] bool ok() const
{
return status == ParseIntStatus::Ok;
}
template <typename T>
[[nodiscard]] IntT value_or(T defaultValue) const // NOLINT(readability-identifier-naming)
{
return ok() ? value : static_cast<IntT>(defaultValue);
}
};
template <typename IntT>
ParseIntResult<IntT> ParseInt(
string_view str, IntT min = std::numeric_limits<IntT>::min(),
IntT max = std::numeric_limits<IntT>::max())
{
IntT value;
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec == std::errc::invalid_argument)
return ParseIntResult<IntT> { ParseIntStatus::ParseError };
if (result.ec == std::errc::result_out_of_range || value < min || value > max)
return ParseIntResult<IntT> { ParseIntStatus::OutOfRange };
if (result.ec != std::errc())
return ParseIntResult<IntT> { ParseIntStatus::ParseError };
return ParseIntResult<IntT> { ParseIntStatus::Ok, value };
}
} // namespace devilution
Loading…
Cancel
Save