summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/FurnaceRecipe.cpp240
-rw-r--r--src/FurnaceRecipe.h22
2 files changed, 170 insertions, 92 deletions
diff --git a/src/FurnaceRecipe.cpp b/src/FurnaceRecipe.cpp
index 2cb204ccf..92b52c6e8 100644
--- a/src/FurnaceRecipe.cpp
+++ b/src/FurnaceRecipe.cpp
@@ -5,7 +5,8 @@
#include "Item.h"
#include <fstream>
-#include <sstream>
+
+#define FURNACE_RECIPE_FILE "furnace.txt"
@@ -54,128 +55,183 @@ void cFurnaceRecipe::ReloadRecipes(void)
ClearRecipes();
LOGD("Loading furnace recipes...");
- std::ifstream f;
- char a_File[] = "furnace.txt";
- f.open(a_File, std::ios::in);
-
+ std::ifstream f(FURNACE_RECIPE_FILE, std::ios::in);
if (!f.good())
{
- f.close();
- LOG("Could not open the furnace recipes file \"%s\"", a_File);
+ LOG("Could not open the furnace recipes file \"%s\"", FURNACE_RECIPE_FILE);
return;
}
+
+ unsigned int Line = 0;
+ AString ParsingLine;
- // TODO: Replace this messy parse with a high-level-structured one (ReadLine / ProcessLine)
- bool bSyntaxError = false;
- while (f.good())
+ while (std::getline(f, ParsingLine))
{
- char c;
-
- //////////////////////////////////////////////////////////////////////////
- // comments
- f >> c;
- f.unget();
- if( c == '#' )
+ Line++;
+ ParsingLine.erase(std::remove_if(ParsingLine.begin(), ParsingLine.end(), isspace), ParsingLine.end()); // Remove whitespace
+ if (ParsingLine.empty())
{
- while( f.good() && c != '\n' )
- {
- f.get( c );
- }
continue;
}
-
- //////////////////////////////////////////////////////////////////////////
- // Line breaks
- f.get( c );
- while( f.good() && ( c == '\n' || c == '\r' ) ) { f.get( c ); }
- if (f.eof())
+ // Comments
+ if (ParsingLine[0] == '#')
{
- break;
+ continue;
}
- f.unget();
- //////////////////////////////////////////////////////////////////////////
- // Check for fuel
- f >> c;
- if( c == '!' ) // It's fuel :)
+ if (ParsingLine[0] == '!') // Fuels start with a bang :)
{
- // Read item
- int IItemID = 0, IItemCount = 0, IItemHealth = 0;
- f >> IItemID;
- f >> c; if( c != ':' ) { bSyntaxError = true; break; }
- f >> IItemCount;
-
- // Optional health
- f >> c;
- if( c != ':' )
- f.unget();
- else
+ int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0;
+ AString::size_type BeginPos = 1; // Begin at one after exclamation mark (bang)
+
+ if (
+ !ReadMandatoryNumber(BeginPos, ":", ParsingLine, Line, IItemID) || // Read item ID
+ !ReadOptionalNumbers(BeginPos, ":", "=", ParsingLine, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
+ !ReadMandatoryNumber(BeginPos, "0123456789", ParsingLine, Line, IBurnTime, true) // Read item burn time - last value
+ )
{
- f >> IItemHealth;
+ return;
}
- // Burn time
- int BurnTime;
- f >> c; if( c != '=' ) { bSyntaxError = true; break; }
- f >> BurnTime;
-
// Add to fuel list
Fuel F;
- F.In = new cItem( (ENUM_ITEM_ID) IItemID, (char)IItemCount, (short)IItemHealth );
- F.BurnTime = BurnTime;
- m_pState->Fuel.push_back( F );
+ F.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth);
+ F.BurnTime = IBurnTime;
+ m_pState->Fuel.push_back(F);
continue;
}
- f.unget();
-
- //////////////////////////////////////////////////////////////////////////
- // Read items
- int IItemID = 0, IItemCount = 0, IItemHealth = 0;
- f >> IItemID;
- f >> c; if( c != ':' ) { bSyntaxError = true; break; }
- f >> IItemCount;
-
- // Optional health
- f >> c;
- if( c != ':' )
- f.unget();
- else
+ else if (isdigit(ParsingLine[0])) // Recipes start with a numeral :)
{
- f >> IItemHealth;
+ int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0;
+ int OItemID = 0, OItemCount = 0, OItemHealth = 0;
+ AString::size_type BeginPos = 0; // Begin at start of line
+
+ if (
+ !ReadMandatoryNumber(BeginPos, ":", ParsingLine, Line, IItemID) || // Read item ID
+ !ReadOptionalNumbers(BeginPos, ":", "@", ParsingLine, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
+ !ReadMandatoryNumber(BeginPos, "=", ParsingLine, Line, IBurnTime) || // Read item burn time
+ !ReadMandatoryNumber(BeginPos, ":", ParsingLine, Line, OItemID) || // Read result ID
+ !ReadOptionalNumbers(BeginPos, ":", "012456789", ParsingLine, Line, OItemCount, OItemHealth, true) // Read result count (and optionally health) - last value
+ )
+ {
+ return;
+ }
+
+ // Add to recipe list
+ Recipe R;
+ R.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth);
+ R.Out = new cItem((ENUM_ITEM_ID)OItemID, (char)OItemCount, (short)OItemHealth);
+ R.CookTime = IBurnTime;
+ m_pState->Recipes.push_back(R);
}
+ }
- int CookTime;
- f >> c; if( c != '@' ) { bSyntaxError = true; break; }
- f >> CookTime;
+ LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
+}
- int OItemID = 0, OItemCount = 0, OItemHealth = 0;
- f >> c; if( c != '=' ) { bSyntaxError = true; break; }
- f >> OItemID;
- f >> c; if( c != ':' ) { bSyntaxError = true; break; }
- f >> OItemCount;
- // Optional health
- f >> c;
- if( c != ':' )
- f.unget();
- else
+
+
+
+void cFurnaceRecipe::PrintParseError(unsigned int a_Line, size_t a_Position, const AString & a_CharactersMissing)
+{
+ LOGWARN("Error parsing furnace recipes at line %i pos " SIZE_T_FMT ": missing '%s'", a_Line, a_Position, a_CharactersMissing.c_str());
+}
+
+
+
+
+
+bool cFurnaceRecipe::ReadMandatoryNumber(AString::size_type & a_Begin, const AString & a_Delimiter, const AString & a_Text, unsigned int a_Line, int & a_Value, bool a_IsLastValue)
+{
+ // TODO: replace atoi with std::stoi
+ AString::size_type End;
+ if (a_IsLastValue)
+ {
+ End = a_Text.find_first_not_of(a_Delimiter, a_Begin);
+ }
+ else
+ {
+ End = a_Text.find_first_of(a_Delimiter, a_Begin);
+ if (End == AString::npos)
{
- f >> OItemHealth;
+ PrintParseError(a_Line, a_Begin, a_Delimiter);
+ return false;
}
-
- // Add to recipe list
- Recipe R;
- R.In = new cItem( (ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth );
- R.Out = new cItem( (ENUM_ITEM_ID)OItemID, (char)OItemCount, (short)OItemHealth );
- R.CookTime = CookTime;
- m_pState->Recipes.push_back( R );
}
- if (bSyntaxError)
+
+ // stoi won't throw an exception if the string is alphanumeric, we should check for this
+ if (!DoesStringContainOnlyNumbers(a_Text.substr(a_Begin, End - a_Begin)))
{
- LOGERROR("ERROR: FurnaceRecipe, syntax error" );
+ PrintParseError(a_Line, a_Begin, "number");
+ return false;
}
- LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
+ a_Value = atoi(a_Text.substr(a_Begin, End - a_Begin).c_str());
+
+ a_Begin = End + 1; // Jump over delimiter
+ return true;
+}
+
+
+
+
+
+bool cFurnaceRecipe::ReadOptionalNumbers(AString::size_type & a_Begin, const AString & a_DelimiterOne, const AString & a_DelimiterTwo, const AString & a_Text, unsigned int a_Line, int & a_ValueOne, int & a_ValueTwo, bool a_IsLastValue)
+{
+ // TODO: replace atoi with std::stoi
+ AString::size_type End, Begin = a_Begin;
+
+ End = a_Text.find_first_of(a_DelimiterOne, Begin);
+ if (End != AString::npos)
+ {
+ if (DoesStringContainOnlyNumbers(a_Text.substr(Begin, End - Begin)))
+ {
+ a_ValueOne = std::atoi(a_Text.substr(Begin, End - Begin).c_str());
+ Begin = End + 1;
+
+ if (a_IsLastValue)
+ {
+ End = a_Text.find_first_not_of(a_DelimiterTwo, Begin);
+ }
+ else
+ {
+ End = a_Text.find_first_of(a_DelimiterTwo, Begin);
+ if (End == AString::npos)
+ {
+ PrintParseError(a_Line, Begin, a_DelimiterTwo);
+ return false;
+ }
+ }
+
+ // stoi won't throw an exception if the string is alphanumeric, we should check for this
+ if (!DoesStringContainOnlyNumbers(a_Text.substr(Begin, End - Begin)))
+ {
+ PrintParseError(a_Line, Begin, "number");
+ return false;
+ }
+ a_ValueTwo = atoi(a_Text.substr(Begin, End - Begin).c_str());
+
+ a_Begin = End + 1; // Jump over delimiter
+ return true;
+ }
+ else
+ {
+ return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_Line, a_ValueOne, a_IsLastValue);
+ }
+ }
+
+ return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_Line, a_ValueOne, a_IsLastValue);
+}
+
+
+
+
+
+bool cFurnaceRecipe::DoesStringContainOnlyNumbers(const AString & a_String)
+{
+ // TODO: replace this with std::all_of(a_String.begin(), a_String.end(), isdigit)
+ return (a_String.find_first_not_of("0123456789") == AString::npos);
}
diff --git a/src/FurnaceRecipe.h b/src/FurnaceRecipe.h
index 2f91e9bcb..ab78b6051 100644
--- a/src/FurnaceRecipe.h
+++ b/src/FurnaceRecipe.h
@@ -41,6 +41,28 @@ public:
private:
void ClearRecipes(void);
+ /** Calls LOGWARN with the line, position, and error */
+ static void PrintParseError(unsigned int a_Line, size_t a_Position, const AString & a_CharactersMissing);
+
+ /** Reads a number from a string given, starting at a given position and ending at a delimiter given
+ Updates beginning position to the delimiter found + 1, and updates the value to the one read
+ If it encounters a substring that is not fully numeric, it will call SetParseError() and return false; the caller should abort processing
+ Otherwise, the function will return true
+ */
+ static bool ReadMandatoryNumber(AString::size_type & a_Begin, const AString & a_Delimiter, const AString & a_Text, unsigned int a_Line, int & a_Value, bool a_IsLastValue = false);
+
+ /** Reads two numbers from a string given, starting at a given position and ending at the first delimiter given, then again (with an updated position) until the second delimiter given
+ Updates beginning position to the second delimiter found + 1, and updates the values to the ones read
+ If it encounters a substring that is not fully numeric whilst reading the second value, it will call SetParseError() and return false; the caller should abort processing
+ If this happens whilst reading the first value, it will call ReadMandatoryNumber() with the appropriate position, as this may legitimately occur with the optional value and AString::find_first_of finding the incorrect delimiter. It will return the result of ReadMandatoryNumber()
+ True will be returned definitively for an optional value that is valid
+ */
+ static bool ReadOptionalNumbers(AString::size_type & a_Begin, const AString & a_DelimiterOne, const AString & a_DelimiterTwo, const AString & a_Text, unsigned int a_Line, int & a_ValueOne, int & a_ValueTwo, bool a_IsLastValue = false);
+
+ /** Uses std::all_of to determine if a string contains only digits */
+ static bool DoesStringContainOnlyNumbers(const AString & a_String);
+
+
struct sFurnaceRecipeState;
sFurnaceRecipeState * m_pState;
};