1
0

Merge remote-tracking branch 'origin/furnaceparsing'

This commit is contained in:
Mattes D 2014-06-26 17:11:40 +02:00
commit 3216fbabfd
2 changed files with 175 additions and 97 deletions

View File

@ -5,7 +5,8 @@
#include "Item.h"
#include <fstream>
#include <sstream>
#define FURNACE_RECIPE_FILE "furnace.txt"
@ -54,127 +55,78 @@ 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;
}
// TODO: Replace this messy parse with a high-level-structured one (ReadLine / ProcessLine)
bool bSyntaxError = false;
while (f.good())
{
char c;
unsigned int Line = 0;
AString ParsingLine;
//////////////////////////////////////////////////////////////////////////
// comments
f >> c;
f.unget();
if( c == '#' )
while (std::getline(f, ParsingLine))
{
while( f.good() && c != '\n' )
Line++;
ParsingLine.erase(std::remove_if(ParsingLine.begin(), ParsingLine.end(), isspace), ParsingLine.end()); // Remove whitespace
if (ParsingLine.empty())
{
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;
}
f.unget();
//////////////////////////////////////////////////////////////////////////
// Check for fuel
f >> c;
if( c == '!' ) // It's fuel :)
{
// 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
{
f >> IItemHealth;
continue;
}
// Burn time
int BurnTime;
f >> c; if( c != '=' ) { bSyntaxError = true; break; }
f >> BurnTime;
if (ParsingLine[0] == '!') // Fuels start with a bang :)
{
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
)
{
return;
}
// Add to fuel list
Fuel F;
F.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth);
F.BurnTime = BurnTime;
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 CookTime;
f >> c; if( c != '@' ) { bSyntaxError = true; break; }
f >> CookTime;
int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0;
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;
AString::size_type BeginPos = 0; // Begin at start of line
// Optional health
f >> c;
if( c != ':' )
f.unget();
else
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
)
{
f >> OItemHealth;
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 = CookTime;
R.CookTime = IBurnTime;
m_pState->Recipes.push_back(R);
}
if (bSyntaxError)
{
LOGERROR("ERROR: FurnaceRecipe, syntax error" );
}
LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
}
@ -182,6 +134,110 @@ void cFurnaceRecipe::ReloadRecipes(void)
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)
{
PrintParseError(a_Line, a_Begin, a_Delimiter);
return false;
}
}
// 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)))
{
PrintParseError(a_Line, a_Begin, "number");
return false;
}
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);
}
void cFurnaceRecipe::ClearRecipes(void)
{
for (RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr)

View File

@ -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;
};