1
0

Rewrote furnace recipe parser

* Fixes #110
This commit is contained in:
Tiger Wang
2014-06-21 20:33:23 +01:00
parent 35a4aa419c
commit a908f39dde
2 changed files with 185 additions and 97 deletions

View File

@@ -5,7 +5,8 @@
#include "Item.h"
#include <fstream>
#include <sstream>
#define FURNACE_RECIPE_FILE "furnace.txt"
@@ -53,128 +54,193 @@ void cFurnaceRecipe::ReloadRecipes(void)
ClearRecipes();
LOGD("Loading furnace recipes...");
std::ifstream f;
char a_File[] = "furnace.txt";
f.open(a_File, std::ios::in);
if (!f.good())
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);
F.close();
LOG("Could not open the furnace recipes file \"%s\"", FURNACE_RECIPE_FILE);
return;
}
AString SyntaxError;
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())
ASSERT(ParsingLine.empty());
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;
int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0;
AString::size_type BeginPos = 1; // Begin at one after exclamation mark (bang)
// Optional health
f >> c;
if( c != ':' )
f.unget();
else
if (
!ReadMandatoryNumber(BeginPos, ":", ParsingLine, SyntaxError, Line, IItemID) || // Read item ID
!ReadOptionalNumbers(BeginPos, ":", "=", ParsingLine, SyntaxError, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
!ReadMandatoryNumber(BeginPos, "0123456789", ParsingLine, SyntaxError, Line, IBurnTime, true) // Read item burn time - last value
)
{
f >> IItemHealth;
break;
}
// 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);
printf("%i %i %i %i\n", IItemID, IItemCount, IItemHealth, IBurnTime);
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, SyntaxError, Line, IItemID) || // Read item ID
!ReadOptionalNumbers(BeginPos, ":", "@", ParsingLine, SyntaxError, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
!ReadMandatoryNumber(BeginPos, "=", ParsingLine, SyntaxError, Line, IBurnTime) || // Read item burn time
!ReadMandatoryNumber(BeginPos, ":", ParsingLine, SyntaxError, Line, OItemID) || // Read result ID
!ReadOptionalNumbers(BeginPos, ":", "012456789", ParsingLine, SyntaxError, Line, OItemCount, OItemHealth, true) // Read result count (and optionally health) - last value
)
{
break;
}
// 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;
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
{
f >> OItemHealth;
}
// 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)
F.close();
if (!SyntaxError.empty())
{
LOGERROR("ERROR: FurnaceRecipe, syntax error" );
LOGWARN("Error parsing furnace recipes at %s", SyntaxError.c_str());
}
LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
else
{
LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
}
}
void cFurnaceRecipe::SetParseError(AString & a_ErrorString, unsigned int a_Line, size_t a_Position, const AString & a_CharactersMissing)
{
Printf(a_ErrorString, "line %i pos %i: 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, AString & a_ErrorString, unsigned int a_Line, int & a_Value, bool a_IsLastValue)
{
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)
{
SetParseError(a_ErrorString, 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)))
{
SetParseError(a_ErrorString, a_Line, a_Begin, "number");
return false;
}
a_Value = std::stoi(a_Text.substr(a_Begin, End - a_Begin));
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, AString & a_ErrorString, unsigned int a_Line, int & a_ValueOne, int & a_ValueTwo, bool a_IsLastValue)
{
unsigned int 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::stoi(a_Text.substr(Begin, End - Begin));
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)
{
SetParseError(a_ErrorString, 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)))
{
SetParseError(a_ErrorString, a_Line, Begin, "number");
return false;
}
a_ValueTwo = std::stoi(a_Text.substr(Begin, End - Begin));
a_Begin = End + 1; // Jump over delimiter
return true;
}
else
{
return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_ErrorString, a_Line, a_ValueOne, a_IsLastValue);
}
}
return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_ErrorString, a_Line, a_ValueOne, a_IsLastValue);
}
bool cFurnaceRecipe::DoesStringContainOnlyNumbers(const AString & a_String)
{
return std::all_of(a_String.begin(), a_String.end(), isdigit);
}